phenotypic.tools_.branch_pathfinding package#
Multi-source Dijkstra branch pathfinding over image cost surfaces.
General-purpose machinery extracted from
FilamentousFungiDetector. Composes a composite
cost surface from orientation/texture features, prescreens candidate
regions, runs seeded multi-source Dijkstra, extracts minimum-cost paths
back to the source regions, and (optionally) filters paths by structure
quality.
Cost surfaces are the caller’s responsibility — this subpackage is
algorithm-agnostic and does not know about phase congruency, skeletons,
or any other domain concept. Callers (e.g. the fungi detector or
MeasureRadialExpansion) assemble their own
cost surface from whatever image features they have on hand.
- class phenotypic.tools_.branch_pathfinding.CalibrationData(median_cost_values: numpy.ndarray, max_window_cost_values: numpy.ndarray, band_variance_values: numpy.ndarray, pct_energy_median_values: numpy.ndarray, gray_snr_values: numpy.ndarray)[source]#
Bases:
objectCalibration metric arrays collected from known-good colony skeleton branches.
- Parameters:
median_cost_values (numpy.ndarray)
max_window_cost_values (numpy.ndarray)
band_variance_values (numpy.ndarray)
pct_energy_median_values (numpy.ndarray)
gray_snr_values (numpy.ndarray)
- median_cost_values#
Median raw cost for each calibration branch.
- Type:
- max_window_cost_values#
Max windowed cost for each calibration branch.
- Type:
- band_variance_values#
Band cost variance for each calibration branch.
- Type:
- pct_energy_median_values#
PCT energy band median for each branch.
- Type:
- gray_snr_values#
Local grayscale SNR for each branch.
- Type:
- class phenotypic.tools_.branch_pathfinding.DijkstraResult(cost_distance: numpy.ndarray, colony_id: numpy.ndarray, predecessor: numpy.ndarray, colony_centroids: dict[int, tuple[float, float]])[source]#
Bases:
objectOutput of multi-source Dijkstra propagation.
- Parameters:
cost_distance (numpy.ndarray)
colony_id (numpy.ndarray)
predecessor (numpy.ndarray)
- cost_distance#
Float64 array (H, W). Accumulated travel cost from the nearest colony boundary pixel. 0 inside colonies, inf for unreached pixels.
- Type:
- colony_id#
Int32 array (H, W). Colony label that owns each pixel. -1 for unreached pixels.
- Type:
- predecessor#
Int32 array (H, W). Flattened index of the preceding pixel on the shortest path back to the colony. -1 for colony pixels and unreached pixels.
- Type:
- colony_centroids#
Dict mapping colony_id to (row, col) centroid.
- class phenotypic.tools_.branch_pathfinding.FilterResult(passed_ids: set[int], rejected_ids: set[int], per_filter_rejections: dict[str, set[int]], metrics: dict[int, PathMetrics], thresholds: FilterThresholds)[source]#
Bases:
objectOutput of the metric-based path filter.
- Parameters:
- per_filter_rejections#
Dict mapping filter name to the set of fragment IDs rejected by that specific filter.
- metrics#
Dict mapping fragment_id to its computed PathMetrics.
- thresholds#
The FilterThresholds applied during filtering.
- metrics: dict[int, PathMetrics]#
- thresholds: FilterThresholds#
- class phenotypic.tools_.branch_pathfinding.FilterThresholds(tau_median_cost: float, tau_window_cost: float, tau_band_variance: float, tau_pct_energy_median: float, tau_gray_snr: float, k_iqr: float)[source]#
Bases:
objectThreshold values for each path quality filter.
- Parameters:
- tau_pct_energy_median#
Minimum allowed PCT energy band median. Paths below this lack sufficient PCT response.
- Type:
- tau_gray_snr#
Minimum allowed local grayscale SNR. Paths below this don’t stand out from background.
- Type:
- class phenotypic.tools_.branch_pathfinding.FragmentAssignment(fragment_id: int, colony_id: int, is_bridge: bool, majority_fraction: float, mean_cost: float)[source]#
Bases:
objectColony assignment for a single fragment.
- Parameters:
- is_bridge#
True if minority colony fraction exceeds bridge_threshold, indicating the fragment spans a colony boundary.
- Type:
- majority_fraction#
Fraction of fragment pixels assigned to the majority colony. 1.0 means unambiguous assignment.
- Type:
- class phenotypic.tools_.branch_pathfinding.FragmentPath(fragment_id: int, colony_id: int, coords: numpy.ndarray, cost_profile: numpy.ndarray, total_cost: float, path_length: int)[source]#
Bases:
objectPath from a fragment back to its assigned colony.
- Parameters:
fragment_id (int)
colony_id (int)
coords (numpy.ndarray)
cost_profile (numpy.ndarray)
total_cost (float)
path_length (int)
- coords#
(N, 2) int32 array of (row, col) path coordinates, ordered from the fragment seed pixel to the colony boundary.
- Type:
- cost_profile#
(N,) float64 array of per-pixel cost-distance values along the path.
- Type:
- class phenotypic.tools_.branch_pathfinding.PathMetrics(median_raw_cost: float, max_window_cost: float, band_cost_variance: float, pct_energy_band_median: float, gray_band_snr: float)[source]#
Bases:
objectPer-path structure-based quality metrics for filtering.
- Parameters:
- median_raw_cost#
Median of raw cost surface values along the path. Low values indicate the path follows real structure; high values indicate the path crosses background.
- Type:
- max_window_cost#
Maximum of windowed median raw cost along the path. Catches paths that are mostly on structure but have one segment crossing background.
- Type:
- band_cost_variance#
Variance of cost values in a dilated band around the path. Low values indicate uniform cost (real structure); high values indicate lucky low-cost pixels surrounded by high-cost noise.
- Type:
- pct_energy_band_median#
Median PCT energy in the dilated band. High values indicate real structure with strong PCT response across full width; low values indicate the path threads through a weak-PCT region.
- Type:
- gray_band_snr#
Local grayscale SNR using PCT-masked double-dilation background. High values indicate the path stands out from unstructured surroundings; low values indicate the path intensity matches background noise.
- Type:
- class phenotypic.tools_.branch_pathfinding.PrescreenResult(screened_fragment_labels: numpy.ndarray, passed_ids: set[int], rejected_ids: set[int], threshold_used: float)[source]#
Bases:
objectOutput of the fragment pre-screening stage.
- Parameters:
- screened_fragment_labels#
Int32 label array (H, W) with rejected fragments zeroed out.
- Type:
- phenotypic.tools_.branch_pathfinding.apply_border_penalty(cost: numpy.ndarray, edge_margin: int = 50) numpy.ndarray[source]#
Apply a linear ramp penalty near image borders to prevent edge-routing.
- Parameters:
cost (numpy.ndarray) – Float32 (H, W) cost surface.
edge_margin (int) – Width in pixels of the penalized border zone.
- Returns:
Cost surface with border penalty applied. A copy is returned.
- Return type:
- phenotypic.tools_.branch_pathfinding.apply_distance_gap_penalty(cost: numpy.ndarray, pct_energy: numpy.ndarray, colony_labels: numpy.ndarray, alpha: float = 5.0) numpy.ndarray[source]#
Penalize gap-crossing proportionally to distance from colonies.
Near colonies, gaps in PCT energy are cheap (branch crossover is common). Far from colonies, gaps are expensive (growth is radial, gaps indicate noise).
- Parameters:
cost (numpy.ndarray) – Float32 (H, W) cost surface.
pct_energy (numpy.ndarray) – Float (H, W) PCT energy map, range [0, 1].
colony_labels (numpy.ndarray) – Int32 (H, W) labeled colony mask. 0 is background.
alpha (float) – Penalty strength. Higher values impose stronger distance gating.
- Returns:
Cost surface with distance-weighted gap penalty applied. A copy is returned.
- Return type:
- phenotypic.tools_.branch_pathfinding.apply_filter_cascade(paths: dict[int, Any], cost_surface: np.ndarray, thresholds: FilterThresholds, window_cost: int = 30, dilation_radius: int = 2, pct_energy: np.ndarray | None = None, grayscale: np.ndarray | None = None, snr_margin: int = 3, pct_noise_ceil: float = 0.0) FilterResult[source]#
Apply the five-stage filter cascade to candidate paths.
- Parameters:
paths (dict[int, Any]) – Dict mapping fragment_id to a path object (typically
FragmentPath). Each path must have a.coordsattribute.cost_surface (np.ndarray) – Float32 (H, W) raw cost surface.
thresholds (FilterThresholds) – Calibrated
FilterThresholdsfromcalibrate_thresholds.window_cost (int) – Window size for the windowed cost metric.
dilation_radius (int) – Radius for the dilated band sampling.
pct_energy (np.ndarray | None) – Float (H, W) PCT energy map for F4. None to skip.
grayscale (np.ndarray | None) – Float (H, W) grayscale image for F5. None to skip.
snr_margin (int) – Extra radius beyond dilation_radius for the SNR background ring.
pct_noise_ceil (float) – PCT energy threshold for F5 background masking.
- Returns:
FilterResult with per-filter breakdown, passed/rejected ID sets, computed metrics for every path, and the thresholds applied.
- Return type:
- Longer description:
Filters are applied sequentially: each filter only considers paths that passed all previous filters, making the cascade monotonically reducing. The five stages are:
F1 median raw cost (high is bad)
F2 max windowed cost (high is bad)
F3 band cost variance (high is bad)
F4 PCT energy band median (low is bad)
F5 local grayscale SNR (low is bad)
- phenotypic.tools_.branch_pathfinding.apply_structure_mask(cost: numpy.ndarray, colony_mask: numpy.ndarray, eps_free: float = 1e-06) numpy.ndarray[source]#
Set cost to near-zero for pixels belonging to known colony structure.
Pixels already identified as part of a colony (e.g. by a preceding detection step) are given minimal traversal cost so that Dijkstra paths pass through established structure for free.
- Parameters:
cost (numpy.ndarray) – Composite cost surface from
assemble_composite_cost().colony_mask (numpy.ndarray) – Binary or labeled mask where positive values indicate known colony pixels.
eps_free (float) – Cost assigned to known-structure pixels. Should be small but positive to keep the cost surface strictly positive.
- Returns:
Modified cost surface as float32. A copy is returned; cost is not modified in place.
- Return type:
- phenotypic.tools_.branch_pathfinding.assemble_composite_cost(pct_energy: numpy.ndarray, anisotropy: numpy.ndarray, orientation_coherence: numpy.ndarray, mad_map: numpy.ndarray, beta: float = 2.0, gamma: float = 1.0, eps: float = 1e-06) numpy.ndarray[source]#
Assemble the composite cost surface from feature maps.
Combines phase congruency energy, anisotropy, orientation coherence, and local MAD into a single scalar cost field. Pixels with strong, coherent, anisotropic structure (likely hyphae) receive low cost; featureless agar receives high cost. The MAD term in the numerator penalises noisy regions that might otherwise appear low-cost.
The formula is:
C = (1 + gamma * MAD) / (P * A^beta * O_coh + eps)
- Parameters:
pct_energy (numpy.ndarray) – Normalised phase congruency energy (pc_sum from phasecong3).
anisotropy (numpy.ndarray) – Fractional anisotropy from
compute_anisotropy().orientation_coherence (numpy.ndarray) – Coherence from
compute_orientation_coherence().mad_map (numpy.ndarray) – Local MAD from
compute_local_mad_map().beta (float) – Exponent on the anisotropy term. Higher values sharpen the preference for strongly oriented features.
gamma (float) – Weight of the MAD penalty in the numerator.
eps (float) – Small constant to avoid division by zero.
- Returns:
Composite cost surface as float32. Lower values indicate likely hyphal structure.
- Return type:
- phenotypic.tools_.branch_pathfinding.assemble_connected_mask(colony_labels: numpy.ndarray, fragment_labels: numpy.ndarray, assignments: dict[int, FragmentAssignment], paths: dict[int, FragmentPath]) numpy.ndarray[source]#
Paint connected fragments and their paths into the colony mask.
Each connected fragment and its path pixels receive the colony label of the assigned colony, extending the colony territory.
- Parameters:
colony_labels (numpy.ndarray) – Int32 (H, W) original colony label mask.
fragment_labels (numpy.ndarray) – Int32 (H, W) fragment label mask.
assignments (dict[int, FragmentAssignment]) – Fragment-to-colony assignments.
paths (dict[int, FragmentPath]) – Fragment-to-colony paths.
- Returns:
Int32 (H, W) extended label mask with colonies, connected fragments, and path pixels labeled by colony ID.
- Return type:
- phenotypic.tools_.branch_pathfinding.assign_fragments_to_colonies(fragment_labels: numpy.ndarray, colony_id_map: numpy.ndarray, cost_distance: numpy.ndarray, bridge_threshold: float = 0.2) dict[int, FragmentAssignment][source]#
Assign each fragment to a colony by majority vote of colony_id.
For each fragment, samples the colony_id_map at all fragment pixels. The majority colony wins. If the second-most-common colony exceeds bridge_threshold fraction, the fragment is flagged as a bridge.
- Parameters:
fragment_labels (numpy.ndarray) – Int32 (H, W) labeled fragment mask.
colony_id_map (numpy.ndarray) – Int32 (H, W) colony ownership from Dijkstra.
cost_distance (numpy.ndarray) – Float64 (H, W) cost-distance from Dijkstra.
bridge_threshold (float) – Minority fraction above which a fragment is flagged as a bridge. Default 0.20 (20%).
- Returns:
Dict mapping fragment_id to FragmentAssignment.
- Return type:
- phenotypic.tools_.branch_pathfinding.backtrack_path(seed_row: int, seed_col: int, predecessor: numpy.ndarray, cost_distance: numpy.ndarray, cost_surface: numpy.ndarray) tuple[numpy.ndarray, numpy.ndarray] | None[source]#
Follow predecessor pointers from seed to colony (cost_distance==0).
- Parameters:
seed_row (int) – Starting row (typically the min-cost pixel within a fragment).
seed_col (int) – Starting column.
predecessor (numpy.ndarray) – Int32 (H, W) flat-index predecessor map.
cost_distance (numpy.ndarray) – Float64 (H, W) cost-distance map.
cost_surface (numpy.ndarray) – Float32 (H, W) for recording cost profile.
- Returns:
Tuple of (coords, cost_profile) where coords is (N, 2) int32 array of (row, col) and cost_profile is (N,) float64 array, or None if backtracking fails (e.g., seed is unreached).
- Return type:
tuple[numpy.ndarray, numpy.ndarray] | None
- phenotypic.tools_.branch_pathfinding.calibrate_screening_threshold(cost_surface: np.ndarray, colony_branch_mask: np.ndarray, r_screen: int = 30, percentile: float = 99.0, min_cost_envelope: np.ndarray | None = None) tuple[float, np.ndarray][source]#
Derive the screening threshold from known-good branch endpoints.
Identifies pixels at the outer boundary of connected colonies in the inoculum+branch mask and computes their local minimum cost environment. The threshold is set at the specified percentile of this distribution, ensuring that fragments in environments comparable to known-good branches are retained.
This should be run once per image (or per imaging session if conditions are stable) before calling
prescreen_fragments().- Parameters:
cost_surface (np.ndarray) – Composite cost array, shape
(H, W).colony_branch_mask (np.ndarray) – Inoculum+branch mask, shape
(H, W). Nonzero values indicate colony pixels.r_screen (int) – Screening radius, same value as will be used in
prescreen_fragments().percentile (float) – Threshold percentile. Default 99.0 (very permissive: only the worst 1% of known-good environments would be rejected).
min_cost_envelope (np.ndarray | None) – Pre-computed minimum cost envelope from
_compute_screening_envelope(). IfNone, computed internally.
- Returns:
Tuple of
(tau_screen, calibration_values)where tau_screen is the derived threshold and calibration_values is the array of per-boundary-pixel minimum costs used to compute it. The calibration_values can be passed toprescreen_fragments()or used for diagnostic plotting.- Raises:
ValueError – If no boundary pixels are found in colony_branch_mask.
- Return type:
- phenotypic.tools_.branch_pathfinding.calibrate_thresholds(calibration: CalibrationData, k: float = 3.0) FilterThresholds[source]#
Derive filter thresholds from calibration branch metrics via IQR.
F1-F3 use upper thresholds (median + k * IQR, high is bad). F4, F5 use lower thresholds (median - k * IQR, low is bad).
- Parameters:
calibration (CalibrationData) – Metric arrays from
extract_calibration_branches.k (float) – IQR multiplier. Higher values are more permissive.
- Returns:
FilterThresholds with calibrated cutoffs for each metric.
- Return type:
- phenotypic.tools_.branch_pathfinding.compute_anisotropy(M: numpy.ndarray, m: numpy.ndarray) numpy.ndarray[source]#
Compute fractional anisotropy from phase congruency moment maps.
- Parameters:
M (numpy.ndarray) – Maximum moment of phase congruency covariance (edge strength).
m (numpy.ndarray) – Minimum moment of phase congruency covariance (corner strength).
- Returns:
Fractional anisotropy in [0, 1] as float32. Values near 1 indicate strongly oriented structure (e.g. a hypha), values near 0 indicate isotropic texture (e.g. uniform agar background).
- Return type:
- phenotypic.tools_.branch_pathfinding.compute_local_mad_map(image_ced: numpy.ndarray, window_size: int = 7) numpy.ndarray[source]#
Compute a local median absolute deviation (MAD) map.
Two-pass median filter implementation: the first pass computes the local median, then absolute deviations from that median are themselves median-filtered to yield a robust local spread estimate. High MAD indicates textured regions (e.g. hyphal networks); low MAD indicates smooth agar background.
- Parameters:
image_ced (numpy.ndarray) – 2-D image array, typically coherence-enhanced diffusion output.
window_size (int) – Side length of the square median filter kernel. Must be odd.
- Returns:
Local MAD map as float32.
- Raises:
ValueError – If image_ced is not 2-D or window_size is even.
- Return type:
- phenotypic.tools_.branch_pathfinding.compute_min_cost_envelope(cost_surface: numpy.ndarray, r_screen: int) numpy.ndarray[source]#
Precompute local minimum cost within r_screen of every pixel.
Applies a minimum filter with kernel size
2 * r_screen + 1to the cost surface. The output value at each pixel is the minimum cost found within an r_screen-pixel square neighbourhood.- Parameters:
cost_surface (numpy.ndarray) – Composite cost array, shape
(H, W), values > 0.r_screen (int) – Screening radius in pixels. Should match or slightly exceed the maximum biologically plausible gap distance. Typical range: 25–40 px.
- Returns:
Minimum-filtered cost array, same shape as input. Value at
(y, x)is the minimum cost within r_screen pixels.- Return type:
- phenotypic.tools_.branch_pathfinding.compute_orientation_coherence(theta: numpy.ndarray, r_coh: int = 12) numpy.ndarray[source]#
Compute local orientation coherence from phase congruency orientations.
Measures the circular mean resultant length of doubled orientations over a square neighbourhood. High coherence indicates consistently oriented structure such as a hypha traversing the window; low coherence indicates disorganised texture or bare agar.
- Parameters:
theta (numpy.ndarray) – Orientation map in radians from phase congruency.
r_coh (int) – Radius of the square averaging kernel. The kernel side length is
2 * r_coh + 1.
- Returns:
Orientation coherence in [0, 1] as float32. Values near 1 indicate strong local alignment of features.
- Return type:
- phenotypic.tools_.branch_pathfinding.compute_path_metrics(path: Any, cost_surface: np.ndarray, window_cost: int = 30, dilation_radius: int = 2, pct_energy: np.ndarray | None = None, grayscale: np.ndarray | None = None, snr_margin: int = 3, pct_noise_ceil: float = 0.0, band_offsets: np.ndarray | None = None, outer_offsets: np.ndarray | None = None) PathMetrics[source]#
Compute structure-based quality metrics for a single path.
- Parameters:
path (Any) – A duck-typed path object with
.coordsattribute ((N, 2) int array of (row, col) coordinates). Typically aFragmentPathor a calibration proxy.cost_surface (np.ndarray) – Float32 (H, W) raw cost surface (before colony masking).
window_cost (int) – Window size (pixels) for the windowed cost metric.
dilation_radius (int) – Radius for the dilated band sampling (F3/F4/F5).
pct_energy (np.ndarray | None) – Float (H, W) PCT energy map for F4. None to skip.
grayscale (np.ndarray | None) – Float (H, W) grayscale image for F5. None to skip.
snr_margin (int) – Extra radius beyond dilation_radius for the SNR background ring.
pct_noise_ceil (float) – PCT energy threshold for F5 background masking.
band_offsets (np.ndarray | None)
outer_offsets (np.ndarray | None)
- Returns:
PathMetrics with all five metric values.
- Return type:
- phenotypic.tools_.branch_pathfinding.connectivity_correct_labels(voronoi_labels: numpy.ndarray, mask: numpy.ndarray, markers: numpy.ndarray) numpy.ndarray[source]#
Correct Voronoi misassignments using fragment-based iterative fill.
Separates seed pixels from unseeded fragments, then iteratively assigns each fragment the mode label of its neighborhood until convergence.
- Parameters:
voronoi_labels (numpy.ndarray) – Label map from
euclidean_voronoi_assign.mask (numpy.ndarray) – Binary foreground mask (same as used for Voronoi).
markers (numpy.ndarray) – Seed marker array (same as used for Voronoi).
- Returns:
Corrected label map with fragment reassignments applied.
- Return type:
- phenotypic.tools_.branch_pathfinding.euclidean_voronoi_assign(markers: numpy.ndarray, mask: numpy.ndarray, restrict_to_seeded_cc: bool = True) numpy.ndarray[source]#
Assign masked pixels to nearest marker via Euclidean Voronoi partition.
- Parameters:
markers (numpy.ndarray) – 2D int32 array with seed labels at marker positions.
mask (numpy.ndarray) – Binary mask restricting the assignment region.
restrict_to_seeded_cc (bool) – When True (default), only mask connected components that contain at least one seed are labeled; disconnected mask regions with no seed remain 0. When False, seeds are used globally — every mask pixel gets the label of its nearest seed regardless of connectivity.
- Returns:
2D int32 labeled array. Each masked pixel has the label of its nearest marker by Euclidean distance. Pixels outside mask are 0. When restrict_to_seeded_cc is True, pixels in seedless connected components are also 0.
- Return type:
- phenotypic.tools_.branch_pathfinding.extract_calibration_branches(colony_labels: np.ndarray, unmasked_cost_surface: np.ndarray, min_branch_length: int = 10, window_cost: int = 30, dilation_radius: int = 2, pct_energy: np.ndarray | None = None, grayscale: np.ndarray | None = None, snr_margin: int = 3, pct_noise_ceil: float = 0.0) CalibrationData[source]#
Extract quality metrics from known-good colony skeleton branches.
- Parameters:
colony_labels (np.ndarray) – Int32 array (H, W) of labeled colonies. 0 is background.
unmasked_cost_surface (np.ndarray) – Float32 array (H, W) of raw cost values before colony pixels were masked to epsilon.
min_branch_length (int) – Minimum pixel count for a branch to be included in calibration.
window_cost (int) – Window size for the windowed cost metric.
dilation_radius (int) – Radius for the dilated band sampling (F3/F4/F5).
pct_energy (np.ndarray | None) – Float (H, W) PCT energy map for F4. None to skip.
grayscale (np.ndarray | None) – Float (H, W) grayscale image for F5. None to skip.
snr_margin (int) – Extra radius beyond dilation_radius for the SNR background ring.
pct_noise_ceil (float) – PCT energy threshold for F5 background masking.
- Returns:
- CalibrationData with metric arrays from all qualifying branches.
Arrays may be empty if no branches meet min_branch_length.
- Return type:
- Longer description:
Skeletonizes the colony mask, identifies branch points (pixels with more than two skeleton neighbors), removes them to split the skeleton into linear segments, then traces each segment and computes the five quality metrics. The resulting distributions define what “normal” intra-colony paths look like.
- phenotypic.tools_.branch_pathfinding.extract_fragment_paths(fragment_labels: numpy.ndarray, assignments: dict[int, FragmentAssignment], dijkstra: DijkstraResult, cost_surface: numpy.ndarray) tuple[dict[int, FragmentPath], list[int]][source]#
Extract minimum-cost paths from each fragment back to its colony.
For each assigned fragment, finds the pixel with minimum cost-distance and backtracks to the colony boundary.
- Parameters:
fragment_labels (numpy.ndarray) – Int32 (H, W) labeled fragment mask.
assignments (dict[int, FragmentAssignment]) – Fragment-to-colony assignments.
dijkstra (DijkstraResult) – Dijkstra propagation result.
cost_surface (numpy.ndarray) – Float32 (H, W) cost map.
- Returns:
Tuple of (paths_dict, unconnected_ids) where paths_dict maps fragment_id to FragmentPath and unconnected_ids lists fragments that failed path extraction.
- Return type:
- phenotypic.tools_.branch_pathfinding.filter_paths(paths: dict[int, Any], colony_labels: np.ndarray, unmasked_cost_surface: np.ndarray, k: float = 3.0, window_cost: int = 30, dilation_radius: int = 2, thresholds_override: FilterThresholds | None = None, pct_energy: np.ndarray | None = None, grayscale: np.ndarray | None = None, snr_margin: int = 3) FilterResult[source]#
Run the full path quality filtering pipeline.
Extract calibration metrics from colony skeleton branches
Derive thresholds as median +/- k * IQR
Apply five-stage filter cascade to candidate paths
- Parameters:
paths (dict[int, Any]) – Dict mapping fragment_id to a path object from Stage 3.
colony_labels (np.ndarray) – Int32 (H, W) labeled colony mask. 0 is background.
unmasked_cost_surface (np.ndarray) – Float32 (H, W) raw cost surface (before colony masking).
k (float) – IQR multiplier for calibration. Higher is more permissive.
window_cost (int) – Window size for the windowed cost metric.
dilation_radius (int) – Radius for the dilated band sampling.
thresholds_override (FilterThresholds | None) – If provided, skip calibration and use these thresholds directly.
pct_energy (np.ndarray | None) – Float (H, W) PCT energy map for F4/F5. None to skip.
grayscale (np.ndarray | None) – Float (H, W) grayscale image for F5. None to skip.
snr_margin (int) – Extra radius beyond dilation_radius for the SNR background ring.
- Returns:
FilterResult with passed/rejected IDs, per-filter breakdown, metrics, and thresholds.
- Return type:
- phenotypic.tools_.branch_pathfinding.paths_metrics_dataframe(paths: dict[int, FragmentPath] | list[FragmentPath], metrics: dict[int, PathMetrics] | None = None, filter_result: FilterResult | None = None) pandas.DataFrame[source]#
Summarize paths as a per-path DataFrame.
Columns (always): path_id, colony_id, n_pixels, total_cost, mean_cost_profile, max_cost_profile, min_cost_profile.
Additional columns when
metricsis given: median_raw_cost, max_window_cost, band_cost_variance, pct_energy_band_median, gray_band_snr.Additional column when
filter_resultis given: passed (bool).- Parameters:
paths (dict[int, FragmentPath] | list[FragmentPath]) – Fragment-to-colony paths, either as a dict keyed by fragment id or a list of
FragmentPath.metrics (dict[int, PathMetrics] | None) – Optional mapping
fragment_id -> PathMetrics(e.g. themetricsfield of aFilterResult). When provided, per-path structure-quality columns are appended.filter_result (FilterResult | None) – Optional
FilterResult. When provided, apassedboolean column is appended (True if the fragment id is infilter_result.passed_ids).
- Returns:
A
pandas.DataFramewith one row per input path.- Return type:
- phenotypic.tools_.branch_pathfinding.plot_cost_distance_heatmap(dijkstra: DijkstraResult, *, colony_labels: np.ndarray | None = None, log_scale: bool = True, colorscale: str = 'Viridis', title: str = 'Cost-distance map')[source]#
Render the Dijkstra cost-distance map as a heatmap.
Unreached pixels (
cost_distance == inf) are masked to NaN so plotly colours them transparently. Whenlog_scaleis True the values arenp.log1p-transformed for visibility (colour-bar ticks are left in the transformed scale — document this in the annotation). Whencolony_labelsis given, inner colony boundaries are overlaid.- Parameters:
dijkstra (DijkstraResult) –
DijkstraResultproduced byrun_multisource_dijkstra().colony_labels (np.ndarray | None) – Optional (H, W) integer label array. When given, inner boundaries are overlaid in Okabe-Ito sky blue.
log_scale (bool) – If True, apply
np.log1pto the finite cost values before plotting (recommended — raw Dijkstra cost ranges are typically long-tailed).colorscale (str) – Plotly colorscale name (default
"Viridis").title (str) – Figure title.
- Returns:
A
plotly.graph_objects.Figurecontaining the heatmap and optional colony-boundary overlay.- Raises:
ImportError – If plotly is not installed.
- phenotypic.tools_.branch_pathfinding.plot_paths_over_image(background: np.ndarray, paths: dict[int, FragmentPath] | list[FragmentPath], *, colony_labels: np.ndarray | None = None, title: str = 'Dijkstra paths', path_color: str = '#E69F00', colony_boundary_color: str = '#56B4E9', show_fragment_seeds: bool = True, fig=None)[source]#
Overlay Dijkstra paths on a background image.
backgroundis rendered viago.Imageif it is an RGB array (H, W, 3) and viago.Heatmapif it is scalar (H, W). Paths are drawn as a single NaN-separatedgo.Scattergltrace (keeps the browser responsive at 100+ paths). Whencolony_labelsis given, inner colony boundaries are overlaid incolony_boundary_color. Whenshow_fragment_seedsis True, the first coord of each path (the fragment seed) is marked.- Parameters:
background (np.ndarray) – Either an (H, W, 3) RGB uint8 array or an (H, W) scalar array (e.g. grayscale, PCT energy, cost surface).
paths (dict[int, FragmentPath] | list[FragmentPath]) – Fragment-to-colony paths, either as a dict keyed by fragment id or a list of
FragmentPath.colony_labels (np.ndarray | None) – Optional (H, W) integer label array. When given, inner colony boundaries are drawn.
title (str) – Figure title.
path_color (str) – Hex colour for path lines (default Okabe-Ito orange).
colony_boundary_color (str) – Hex colour for colony boundary markers (default Okabe-Ito sky blue).
show_fragment_seeds (bool) – If True, mark the first coord of every path (the fragment seed) with a distinct marker.
fig – Optional existing
plotly.graph_objects.Figureto add the traces to. WhenNonea new figure is created.
- Returns:
A
plotly.graph_objects.Figurewith the background, path scatter, optional colony boundary, and optional fragment-seed markers.- Raises:
ImportError – If plotly is not installed.
ValueError – If
backgroundis neither (H, W) nor (H, W, 3).
- phenotypic.tools_.branch_pathfinding.prescreen_fragments(cost_surface: np.ndarray, fragment_labels: np.ndarray, r_screen: int = 30, tau_screen: float | None = None, calibration_cost_values: np.ndarray | None = None, calibration_percentile: float = 99.0, colony_branch_mask: np.ndarray | None = None, min_cost_envelope: np.ndarray | None = None) PrescreenResult[source]#
Pre-screen fragments against the local cost environment.
Rejects fragments surrounded entirely by high-cost regions before Dijkstra propagation. Uses a minimum filter over the cost surface to efficiently check whether any low-cost corridor exists within r_screen pixels of each fragment boundary.
The threshold tau_screen can be set explicitly or derived from calibration data (known-good fragment boundary cost values). If calibration data is provided, the threshold is set at the specified percentile, making screening very permissive (only reject clearly hopeless cases).
- Parameters:
cost_surface (np.ndarray) – Composite cost array, shape
(H, W), values > 0. Output of cost surface construction, with known objects already masked to epsilon_free.fragment_labels (np.ndarray) – Labelled fragment array, shape
(H, W). 0 = background, positive integers = fragment IDs. Fragments already connected to colonies (in the inoculum+branch mask) should not be included here.r_screen (int) – Screening radius in pixels. Maximum gap distance considered biologically plausible. Typical: 25–40 px.
tau_screen (float | None) – Cost threshold for rejection. Fragments whose minimum environmental cost exceeds this are rejected. If
None, calibration_cost_values must be provided.calibration_cost_values (np.ndarray | None) – Array of minimum environmental cost values from known-good fragments (those already in the inoculum+branch mask). Used to derive tau_screen at the specified percentile. Ignored if tau_screen is set.
calibration_percentile (float) – Percentile of calibration distribution to use as threshold. Default 99.0 (very permissive).
colony_branch_mask (np.ndarray | None) – If provided, the inoculum+branch mask (any nonzero value = colony). Used to exclude colony pixels from the minimum-filtered cost map so that fragment screening reflects the gap environment, not the free-traversal zone inside colonies. Shape
(H, W).min_cost_envelope (np.ndarray | None) – Pre-computed minimum cost envelope from
_compute_screening_envelope(). IfNone, computed internally. Passing a pre-computed envelope avoids a duplicateminimum_filterpass when bothcalibrate_screening_threshold()and this function are called on the same cost surface.
- Returns:
PrescreenResultcontaining the filtered fragment labels, sets of passed/rejected IDs, and the threshold used.- Raises:
ValueError – If neither tau_screen nor calibration_cost_values is provided, if fragment_labels contains no fragments, or if the shapes of cost_surface and fragment_labels do not match.
- Return type:
- phenotypic.tools_.branch_pathfinding.run_multisource_dijkstra(cost_surface: numpy.ndarray, colony_labels: numpy.ndarray, delta: float = 1.0) DijkstraResult[source]#
Seeded multi-source Dijkstra with radial progress penalty.
All colony boundary pixels are seeded simultaneously at cost 0. Wavefronts expand outward; when a step moves closer to its own colony centroid (retreating), the step cost is penalized by factor (1 + delta). This encourages outward radial expansion consistent with filamentous fungal growth.
Squared distances are compared to avoid sqrt in the inner loop.
- Parameters:
cost_surface (numpy.ndarray) – Float32 (H, W) composite cost map. Colony pixels should already be set to epsilon (~1e-6).
colony_labels (numpy.ndarray) – Int32 (H, W) labeled colony mask. 0 is background.
delta (float) – Radial penalty factor. When a step retreats toward the colony centroid, step_cost is multiplied by (1 + delta). Default 1.0 doubles the cost of retreating steps.
- Returns:
DijkstraResult with cost_distance, colony_id, predecessor maps and colony centroid lookup.
- Return type: