phenotypic.measure.MeasureSymmetricZones#
- class phenotypic.measure.MeasureSymmetricZones(n_annuli: int = 100, pelt_penalty: float = 5.0, symmetry_threshold: float = 0.6666666666666666, n_angular_bins: int = 6, smoothing_window: int = 3, method: Literal['distance', 'intensity'] = 'distance', tau_core: float = 0.9, tau_sparse: float = 0.5, bright_intensity_fraction: float = 0.5, intensity_source: Literal['gray', 'detect_mat'] = 'gray')[source]
Bases:
MeasureFeaturesMeasure colony radial expansion and angular symmetry from the object mask alone.
Quantifies each colony by four scalars derived directly from its binary mask and distance-from-inoculum map — no skeletonization, no branch tracing, no runner outlier flagging. The headline output is
SymmetricRadius, the first radius past the inoculum core at which the per-annulus circular mean resultant length of mask-boundary pixels drops below a tunable symmetry threshold.CoreRadius(PELT changepoint on the radial density profile, identical algorithm toMeasureRadialExpansion) anchors the measurement;MeanExpansionandMaxExpansionsummarise how far growth reached past that core.- Args:
- n_annuli: Number of equal-area annuli used for the radial density
profile and angular analysis. Defaults to 100.
- pelt_penalty: PELT penalty controlling changepoint sensitivity for
core detection. Defaults to 5.0.
- symmetry_threshold: Minimum angular coverage (fraction of angular
bins occupied) for growth to be considered symmetric. Defaults to 4/6 (~0.667); with 6 angular bins this means at least 4 of 6 60-degree sectors must contain mask pixels.
- n_angular_bins: Number of angular bins used to compute the
per-annulus angular coverage diagnostic. Defaults to 36 (10 degree resolution).
- smoothing_window: Moving-average window (in annuli) applied to the
angular R̄ profile before the threshold test. Defaults to 3.
- method: Inoculum centre estimator —
"distance"uses the peak of the Euclidean distance transform,
"intensity"uses the intensity-weighted centroid. Defaults to"distance".- tau_core: Minimum bright-pixel fraction required for an annulus
to be classified as inoculum core in the per-angle outward walk. Defaults to 0.9.
- tau_sparse: Minimum bright-pixel fraction required for an annulus
to be classified as dense branching (the outer edge of the dense zone). Defaults to 0.5.
- bright_intensity_fraction: Fraction of the core’s median intensity
used as the bright/background threshold for zone segmentation. Defaults to 0.5.
- intensity_source: Image array used for the brightness comparison
–
"gray"uses the grayscale,"detect_mat"uses the detection matrix. Defaults to"gray".
- Returns:
pd.DataFrame: Object-level radial symmetry measurements with columns:
ObjectLabel: unique object identifier.
SymmetricRadius_CoreRadius: inoculum core radius (pixels).
SymmetricRadius_SymmetricRadius: first radius past the core where R̄ exceeds the symmetry threshold (pixels).
SymmetricRadius_MeanExpansion: mean boundary-pixel distance beyond the core (pixels, clamped at 0).
SymmetricRadius_MaxExpansion: maximum mask-pixel distance beyond the core (pixels, clamped at 0).
SymmetricRadius_CoreEndRadius: mean per-angle core boundary radius from the bright-fraction outward walk (pixels).
SymmetricRadius_DenseEndRadius: mean per-angle outer radius of the dense branching zone (pixels).
SymmetricRadius_SparseEndRadius: mean per-angle outer radius of the sparse branching zone, capped at the symmetric envelope (pixels).
SymmetricRadius_CoreArea: pixel^2 area of the inoculum core zone integrated across the 360-sector polar polygon.
SymmetricRadius_DenseArea: pixel^2 area of the dense branching zone (annular region between core and dense boundaries).
SymmetricRadius_SparseArea: pixel^2 area of the sparse branching zone (annular region between dense and outer boundaries).
- Best For:
Summarising colony-level radial growth with a single symmetry figure of merit.
Distinguishing uniformly-expanding colonies from those with sectors, lopsided growth, or directional bias.
Comparing wild-type versus mutant expansion phenotypes when the biological question is about the colony envelope, not individual hyphae.
Time-course assays where runner counts are noisy but colony extent is informative.
- Consider Also:
MeasureRadialExpansionwhen you need per-branch statistics (branch counts, outlier runner detection) rather than a single symmetry scalar.MeasureShapefor general morphological descriptors (circularity, eccentricity) that do not require radial analysis.MeasureBoundsfor lightweight bounding-box data without any radial pipeline.
- See Also:
Tutorial 7: Measuring and Exporting for a walkthrough of measuring and exporting colony data.
Category: SYMMETRIC_RADIUS# Name
Description
CoreRadiusRadius of the dense inoculum core, determined by PELT changepoint detection on the radial mask-density profile centered on the inoculum. Growth measurements are reported relative to this boundary.
SymmetricRadiusRadial distance from the inoculum centroid at which colony growth ceases to be angularly uniform. Computed as the first radius past the core where the smoothed per-annulus circular mean resultant length of mask-boundary pixels exceeds the symmetry threshold. Equals the colony outer envelope when growth remains symmetric throughout.
MeanExpansionMean distance of mask-boundary pixels from the inoculum centroid, measured from the core boundary outward. Captures the typical radial extent of growth past the inoculum, averaged over all angular directions.
MaxExpansionMaximum distance of any mask pixel from the inoculum centroid, measured from the core boundary outward. Captures the farthest extent of growth past the inoculum.
CoreEndRadiusMean radius of the inoculum core boundary derived from the per-angle bright/background ratio walk. Each of 360 1° angular sectors finds the outer edge of the contiguous core run (bright fraction >= tau_core); the reported value is the mean across sectors. Compare with CoreRadius (the global PELT changepoint) — close agreement indicates a well-formed core.
DenseEndRadiusMean outer radius of the dense branching zone, where mask-bright pixels dominate (bright fraction >= tau_sparse). Per-angle radii are capped at the SymmetricRadius and angularly median-smoothed before averaging.
SparseEndRadiusMean outer radius of the sparse branching zone (= colony envelope inside the symmetric growth front). Equals min(objmask outer envelope, SymmetricRadius) per angle, averaged across 360 sectors.
CoreAreaPixel^2 area of the inoculum core zone, integrated across the 360-sector polar polygon defined by the per-angle core radii.
DenseAreaPixel^2 area of the dense branching zone, the annular region between the per-angle core boundary and dense-branching boundary.
SparseAreaPixel^2 area of the sparse branching zone, the annular region between the per-angle dense boundary and the symmetric-envelope outer boundary.
Methods
__init__Plate-level diagnostic overlay for symmetric-radius measurement.
Execute the measurement operation on a detected-object image.
- Parameters:
- inspect(image: Image | None = None, base_layer: Literal['rgb', 'gray', 'detect_mat'] = 'gray')[source]
Plate-level diagnostic overlay for symmetric-radius measurement.
- Parameters:
image (Image | None) – Detected Image with objmap/objmask. If None, the image cached by the most recent
measure()call is reused.base_layer (Literal['rgb', 'gray', 'detect_mat']) – Which image array to use as the plotly background.
- Returns:
Panel Column layout with a single zoomable plotly figure containing toggleable overlay layers.
- __del__()
Automatically stop tracemalloc when the object is deleted.
- measure(image, include_meta=False)
Execute the measurement operation on a detected-object image.
This is the main public API method for extracting measurements. It handles: input validation, parameter extraction via introspection, calling the subclass-specific _operate() method, optional metadata merging, and exception handling.
How it works (for users):
Pass your processed Image (with detected objects) to measure()
The method calls your subclass’s _operate() implementation
Results are validated and returned as a pandas DataFrame
If include_meta=True, image metadata (filename, grid info) is merged in
How it works (for developers):
When you subclass MeasureFeatures, you only implement _operate(). This measure() method automatically:
Extracts __init__ parameters from your instance (introspection)
Passes matched parameters to _operate() as keyword arguments
Validates the Image has detected objects (objmap)
Wraps exceptions in OperationFailedError with context
Merges grid/object metadata if requested
- Parameters:
image (Image) – A PhenoTypic Image object with detected objects (must have non-empty objmap from a prior detection operation).
include_meta (bool, optional) – If True, merge image metadata columns (filename, grid position, etc.) into the results DataFrame. Defaults to False.
- Returns:
Measurement results with structure:
First column: OBJECT.LABEL (integer IDs from image.objmap[:])
Remaining columns: Measurement values (float, int, or string)
One row per detected object
If include_meta=True, additional metadata columns are prepended before OBJECT.LABEL (e.g., Filename, GridRow, GridCol).
- Return type:
pd.DataFrame
- Raises:
OperationFailedError – If _operate() raises any exception, it is caught and re-raised as OperationFailedError with details including the original exception type, message, image name, and operation class. This provides consistent error handling across all measurers.
Notes
This method is the main entry point; do not override in subclasses
Subclasses implement _operate() only, not this method
Automatic memory profiling is available via logging configuration
Image must have detected objects (image.objmap should be non-empty)
Examples
Basic measurement extraction:
>>> from phenotypic import Image >>> from phenotypic.measure import MeasureSize >>> from phenotypic.detect import OtsuDetector >>> # Load and detect >>> image = Image('plate.jpg') >>> image = OtsuDetector().operate(image) >>> # Extract measurements >>> measurer = MeasureSize() >>> df = measurer.measure(image) >>> print(df.head())
Include metadata in measurements:
>>> # With image metadata (filename, grid info) >>> df_with_meta = measurer.measure(image, include_meta=True) >>> print(df_with_meta.columns) # Output: ['Filename', 'GridRow', 'GridCol', 'OBJECT.LABEL', # 'Area', 'IntegratedIntensity', ...]