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: MeasureFeatures

Measure 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 to MeasureRadialExpansion) anchors the measurement; MeanExpansion and MaxExpansion summarise 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:
  • MeasureRadialExpansion when you need per-branch statistics (branch counts, outlier runner detection) rather than a single symmetry scalar.

  • MeasureShape for general morphological descriptors (circularity, eccentricity) that do not require radial analysis.

  • MeasureBounds for 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

CoreRadius

Radius 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.

SymmetricRadius

Radial 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.

MeanExpansion

Mean 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.

MaxExpansion

Maximum distance of any mask pixel from the inoculum centroid, measured from the core boundary outward. Captures the farthest extent of growth past the inoculum.

CoreEndRadius

Mean 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.

DenseEndRadius

Mean 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.

SparseEndRadius

Mean 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.

CoreArea

Pixel^2 area of the inoculum core zone, integrated across the 360-sector polar polygon defined by the per-angle core radii.

DenseArea

Pixel^2 area of the dense branching zone, the annular region between the per-angle core boundary and dense-branching boundary.

SparseArea

Pixel^2 area of the sparse branching zone, the annular region between the per-angle dense boundary and the symmetric-envelope outer boundary.

Methods

__init__

inspect

Plate-level diagnostic overlay for symmetric-radius measurement.

measure

Execute the measurement operation on a detected-object image.

Parameters:
  • n_annuli (int)

  • pelt_penalty (float)

  • symmetry_threshold (float)

  • n_angular_bins (int)

  • smoothing_window (int)

  • method (Literal['distance', 'intensity'])

  • tau_core (float)

  • tau_sparse (float)

  • bright_intensity_fraction (float)

  • intensity_source (Literal['gray', 'detect_mat'])

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):

  1. Pass your processed Image (with detected objects) to measure()

  2. The method calls your subclass’s _operate() implementation

  3. Results are validated and returned as a pandas DataFrame

  4. 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', ...]