phenotypic.refine#

Mask refinement for detected fungal colonies.

Post-detection operations that clean up binary masks by removing artifacts, fixing gaps, and normalizing colony footprints across grid cells. Tools cover circularity checks, size filtering, border exclusion, center deviation reduction, hole filling, morphological opening, tophat-based brightening, oversized-object capping, residual-based outlier removal, skeletonization, and thinning.

Classes

LowCircularityRemover

Remove objects with circularity below a specified cutoff.

SmallObjectRemover

Remove small, likely spurious objects from a labeled object map.

BorderObjectRemover

Remove objects that touch the image border within a configurable margin.

CenterDeviationReducer

Keep the object closest to center and remove off-center detections.

MaskFill

Fill holes inside binary object masks to produce solid colonies.

MaskOpener

Morphologically open binary masks to remove thin connections and specks.

WhiteTophatModifier

Suppress small bright structures in the mask using white tophat.

GridOversizedObjectRemover

Remove objects that are larger than their grid cell allows.

MinResidualErrorReducer

Reduce multi-detections per grid cell by keeping objects closest to a linear-regression prediction of expected positions.

ResidualOutlierRemover

Remove objects with large regression residuals in noisy grid rows/columns.

Skeletonize

Reduce object masks to single-pixel-wide skeletons using medial axis thinning.

Thinning

Progressively thin object masks to single-pixel-wide structures via morphological erosion.

class phenotypic.refine.BorderObjectRemover(border_size: int | float | None = 1)[source]#

Bases: ObjectRefiner

Remove objects that touch the image border within a configurable margin.

Intuition:

Colonies that intersect the plate boundary or image crop edge are often partial, poorly segmented, and bias size/shape measurements. This operation zeroes any labeled objects in image.objmap whose pixels fall within a user-defined border band, ensuring only fully contained colonies are analyzed.

Why this is useful for agar-plate imaging:

Plate crops or grid layouts frequently clip edge colonies. Removing border-touching objects stabilizes downstream phenotyping (area, circularity, intensity) and prevents partial colonies from contaminating statistics or training data.

Use cases:
  • Single-plate captures where the plate rim truncates colonies.

  • Grid assays where wells or positions near the frame boundary are partially visible.

  • Automated crops that shift slightly between frames, cutting off colonies at the margins.

Caveats:
  • A large border may remove valid edge colonies (lower recall). Too small a border may retain partial objects.

  • With very tight crops (little background), even modest margins can eliminate many colonies.

border_size#

Width of the exclusion border around the image.

Type:

int

Examples

Remove objects that touch the image border within a margin
>>> from phenotypic.refine import BorderObjectRemover
>>> op = BorderObjectRemover(border_size=15)
>>> image = op.apply(image, inplace=True)  
>>> # All colonies intersecting a 15-pixel frame margin are removed
Raises:

TypeError – If an invalid border_size type is provided (raised during operation when parameters are validated).

Parameters:

border_size (Optional[Union[int, float]])

__del__()#

Automatically stop tracemalloc when the object is deleted.

__getstate__()#

Prepare the object for pickling by disposing of any widgets.

This ensures that UI components (which may contain unpickleable objects like input functions or thread locks) are cleaned up before serialization.

Note

This method modifies the object state by calling dispose_widgets(). Any active widgets will be detached from the object.

__init__(border_size: int | float | None = 1)[source]#

Initialize the remover.

Parameters:

border_size (int | float | None) –

Width of the exclusion border around the image. - None: Use a default margin equal to 1% of the smaller

image dimension.

  • float in (0, 1): Interpret as a fraction of the minimum image dimension, producing a resolution-adaptive margin.

  • int or float ≥ 1: Interpret as an absolute number of pixels.

Notes

Larger margins remove more edge-touching colonies and are useful when crops are loose or the plate rim intrudes. Smaller margins preserve edge colonies but risk including partial objects.

apply(image, inplace=False)#

Applies the operation to an image, either in-place or on a copy.

Parameters:
  • image (Image) – The arr image to apply the operation on.

  • inplace (bool) – If True, modifies the image in place; otherwise, operates on a copy of the image.

Returns:

The modified image after applying the operation.

Return type:

Image

dispose_widgets() None#

Drop references to the UI widgets.

Return type:

None

sync_widgets_from_state() None#

Push internal state into widgets.

Return type:

None

widget(image: Image | None = None, show: bool = False) Widget#

Return (and optionally display) the root widget.

Parameters:
  • image (Image | None) – Optional image to visualize. If provided, visualization controls will be added to the widget.

  • show (bool) – Whether to display the widget immediately. Defaults to False.

Returns:

The root widget.

Return type:

ipywidgets.Widget

Raises:

ImportError – If ipywidgets or IPython are not installed.

class phenotypic.refine.CenterDeviationReducer[source]#

Bases: ObjectRefiner

Keep the object closest to center and remove off-center detections.

Intuition:

For isolated-colony images (e.g., single-spot captures or per-grid-cell crops), the true colony is typically near the center. Spurious blobs from glare, dust, or agar texture may appear off-center. This operation keeps only the object whose centroid is closest to the image center, removing all others.

Why this is useful for agar plates:

When imaging a grid of pinned colonies, per-cell crops may contain extra detections (ringing, condensation, halo). Selecting the most centered object stabilizes downstream phenotyping by focusing on the intended colony in each crop.

Use cases:
  • Single-colony crops from a grid plate where occasional debris is picked up near edges.

  • Automated pipelines that assume one colony per field-of-view.

Caveats:
  • If the true colony is notably off-center (misalignment, drift), this method can remove it and keep a distractor.

  • Not suitable for multi-colony fields; it will drop all but one object.

(No public attributes)

Examples

>>> from phenotypic.refine import CenterDeviationReducer
>>> op = CenterDeviationReducer()
>>> image = op.apply(image, inplace=True)  
__del__()#

Automatically stop tracemalloc when the object is deleted.

__getstate__()#

Prepare the object for pickling by disposing of any widgets.

This ensures that UI components (which may contain unpickleable objects like input functions or thread locks) are cleaned up before serialization.

Note

This method modifies the object state by calling dispose_widgets(). Any active widgets will be detached from the object.

apply(image, inplace=False)#

Applies the operation to an image, either in-place or on a copy.

Parameters:
  • image (Image) – The arr image to apply the operation on.

  • inplace (bool) – If True, modifies the image in place; otherwise, operates on a copy of the image.

Returns:

The modified image after applying the operation.

Return type:

Image

dispose_widgets() None#

Drop references to the UI widgets.

Return type:

None

sync_widgets_from_state() None#

Push internal state into widgets.

Return type:

None

widget(image: Image | None = None, show: bool = False) Widget#

Return (and optionally display) the root widget.

Parameters:
  • image (Image | None) – Optional image to visualize. If provided, visualization controls will be added to the widget.

  • show (bool) – Whether to display the widget immediately. Defaults to False.

Returns:

The root widget.

Return type:

ipywidgets.Widget

Raises:

ImportError – If ipywidgets or IPython are not installed.

class phenotypic.refine.GridOversizedObjectRemover[source]#

Bases: GridObjectRefiner

Remove objects that are larger than their grid cell allows.

Intuition:

In pinned colony grids, each cell should contain at most one colony of limited extent. Objects spanning nearly an entire cell width/height are often merged colonies, agar edges, or segmentation spillover. Removing these oversized objects improves the reliability of per-cell analysis.

Why this is useful for agar plates:

Grid-based assays assume spatial confinement. Oversized detections disrupt row/column statistics and bias growth comparisons. Filtering them stabilizes downstream measurements.

Use cases:
  • Dropping merged blobs that span adjacent positions.

  • Removing strong edge artifacts near the plate rim that intrude into the grid.

Caveats:
  • If genuine colonies expand to fill the cell (late time points), this remover may exclude real growth.

  • Highly irregular grids or mis-registered edges can cause over- removal; ensure grid metadata is accurate.

(No public attributes)

Examples

Remove objects larger than their grid cell allows
>>> from phenotypic.refine import GridOversizedObjectRemover
>>> op = GridOversizedObjectRemover()
>>> image = op.apply(image, inplace=True)  
__del__()#

Automatically stop tracemalloc when the object is deleted.

__getstate__()#

Prepare the object for pickling by disposing of any widgets.

This ensures that UI components (which may contain unpickleable objects like input functions or thread locks) are cleaned up before serialization.

Note

This method modifies the object state by calling dispose_widgets(). Any active widgets will be detached from the object.

apply(image, inplace=False)#

Applies the operation to an image, either in-place or on a copy.

Parameters:
  • image (Image) – The arr image to apply the operation on.

  • inplace (bool) – If True, modifies the image in place; otherwise, operates on a copy of the image.

Returns:

The modified image after applying the operation.

Return type:

Image

dispose_widgets() None#

Drop references to the UI widgets.

Return type:

None

sync_widgets_from_state() None#

Push internal state into widgets.

Return type:

None

widget(image: Image | None = None, show: bool = False) Widget#

Return (and optionally display) the root widget.

Parameters:
  • image (Image | None) – Optional image to visualize. If provided, visualization controls will be added to the widget.

  • show (bool) – Whether to display the widget immediately. Defaults to False.

Returns:

The root widget.

Return type:

ipywidgets.Widget

Raises:

ImportError – If ipywidgets or IPython are not installed.

class phenotypic.refine.LowCircularityRemover(cutoff: float = 0.785)[source]#

Bases: ObjectRefiner

Remove objects with circularity below a specified cutoff.

Intuition:

Single bacterial/fungal colonies on agar often appear approximately round. Irregular, elongated, or fragmented shapes can indicate merged colonies, scratches, agar texture, or segmentation errors. Filtering by circularity keeps well-formed colonies and removes unlikely shapes.

Why this is useful for agar plates:

Circular colonies produce more reliable area and intensity measurements. Removing low-circularity detections reduces bias from streaks, debris, or incomplete segmentation near plate edges and grid borders.

Use cases:
  • Post-threshold cleanup to exclude elongated artifacts or merged colonies before counting or phenotyping.

  • Enforcing morphology consistency in high-throughput grid assays.

Caveats:
  • Some colonies are intrinsically irregular (wrinkled, spreading, filamentous). A high cutoff may incorrectly remove these phenotypes.

  • Perimeter estimates on low-resolution masks can be noisy, slightly biasing the circularity calculation.

Parameters:

cutoff (float)

cutoff#

Minimum Polsby–Popper circularity required to keep an object, in [0, 1]. Higher values retain only near-circular shapes (sharper shape constraints) and can improve edge sharpness in the kept set but may reduce recall for irregular colonies.

Type:

float

Examples

>>> from phenotypic.refine import LowCircularityRemover
>>> op = LowCircularityRemover(cutoff=0.8)
>>> image = op.apply(image, inplace=True)  
__del__()#

Automatically stop tracemalloc when the object is deleted.

__getstate__()#

Prepare the object for pickling by disposing of any widgets.

This ensures that UI components (which may contain unpickleable objects like input functions or thread locks) are cleaned up before serialization.

Note

This method modifies the object state by calling dispose_widgets(). Any active widgets will be detached from the object.

__init__(cutoff: float = 0.785)[source]#

Initialize the remover.

Parameters:

cutoff (float) – Minimum allowed circularity in [0, 1]. Increasing the cutoff favors compact, round objects (often cleaner masks), whereas lowering it retains irregular colonies but may keep more debris or merged objects.

Raises:

ValueError – If cutoff is outside [0, 1].

apply(image, inplace=False)#

Applies the operation to an image, either in-place or on a copy.

Parameters:
  • image (Image) – The arr image to apply the operation on.

  • inplace (bool) – If True, modifies the image in place; otherwise, operates on a copy of the image.

Returns:

The modified image after applying the operation.

Return type:

Image

dispose_widgets() None#

Drop references to the UI widgets.

Return type:

None

sync_widgets_from_state() None#

Push internal state into widgets.

Return type:

None

widget(image: Image | None = None, show: bool = False) Widget#

Return (and optionally display) the root widget.

Parameters:
  • image (Image | None) – Optional image to visualize. If provided, visualization controls will be added to the widget.

  • show (bool) – Whether to display the widget immediately. Defaults to False.

Returns:

The root widget.

Return type:

ipywidgets.Widget

Raises:

ImportError – If ipywidgets or IPython are not installed.

class phenotypic.refine.MaskFill(structure: numpy.ndarray | None = None, origin: int = 0)[source]#

Bases: ObjectRefiner

Fill holes inside binary object masks to produce solid colonies.

Intuition:

Thresholding on agar-plate images can leave small voids inside colony masks due to illumination gradients, pigment heterogeneity, or glare. Filling these holes produces contiguous masks that better match the true colony footprint and improve area-based phenotypes.

Why this is useful for agar plates:

Many colonies exhibit darker centers or radial texture. Hole filling mitigates these within-colony gaps so that downstream measurements (area, perimeter, intensity summaries) are less biased.

Use cases:
  • After global or adaptive thresholding where donut-like masks appear.

  • Prior to morphological measurements that assume simply connected shapes.

Caveats:
  • Over-aggressive filling with a large structuring element can bridge adjacent colonies through narrow gaps, hurting separation.

  • If masks contain genuine cavities that should remain (e.g., hollow artifacts), filling may misrepresent structure.

Parameters:
  • structure (Optional[np.ndarray])

  • origin (int)

structure#

Structuring element used to define the neighborhood for filling. A larger or denser structure tends to close larger holes but can also smooth away fine boundaries.

Type:

Optional[np.ndarray]

origin#

Center offset for the structuring element. Adjusting the origin subtly shifts how neighborhoods are evaluated, which can influence edge behavior at colony boundaries.

Type:

int

Examples

Fill holes in colony masks to produce solid shapes
>>> from phenotypic.refine import MaskFill
>>> op = MaskFill()
>>> image = op.apply(image, inplace=True)  
__del__()#

Automatically stop tracemalloc when the object is deleted.

__getstate__()#

Prepare the object for pickling by disposing of any widgets.

This ensures that UI components (which may contain unpickleable objects like input functions or thread locks) are cleaned up before serialization.

Note

This method modifies the object state by calling dispose_widgets(). Any active widgets will be detached from the object.

__init__(structure: numpy.ndarray | None = None, origin: int = 0)[source]#

Initialize the filler and validate inputs.

Parameters:
  • structure (Optional[np.ndarray]) – Binary structuring element. Larger or more connected structures fill bigger holes and may reduce small-scale texture within colony masks. If provided, must be a binary array; otherwise a ValueError is raised.

  • origin (int) – Origin offset for the structuring element. Typically left at 0; changing it slightly alters how neighborhoods are centered, which may affect edge sharpness at boundaries.

Raises:

ValueError – If structure is provided and is not a binary mask.

apply(image, inplace=False)#

Applies the operation to an image, either in-place or on a copy.

Parameters:
  • image (Image) – The arr image to apply the operation on.

  • inplace (bool) – If True, modifies the image in place; otherwise, operates on a copy of the image.

Returns:

The modified image after applying the operation.

Return type:

Image

dispose_widgets() None#

Drop references to the UI widgets.

Return type:

None

sync_widgets_from_state() None#

Push internal state into widgets.

Return type:

None

widget(image: Image | None = None, show: bool = False) Widget#

Return (and optionally display) the root widget.

Parameters:
  • image (Image | None) – Optional image to visualize. If provided, visualization controls will be added to the widget.

  • show (bool) – Whether to display the widget immediately. Defaults to False.

Returns:

The root widget.

Return type:

ipywidgets.Widget

Raises:

ImportError – If ipywidgets or IPython are not installed.

class phenotypic.refine.MaskOpener(footprint: Literal['auto'] | numpy.ndarray | int | None = None)[source]#

Bases: ObjectRefiner

Morphologically open binary masks to remove thin connections and specks.

Intuition:

Binary opening (erosion followed by dilation) removes small isolated pixels and breaks narrow bridges between objects. On agar plates, this helps separate touching colonies and suppresses tiny artifacts from dust or condensation without overly shrinking well-formed colonies.

Why this is useful for agar plates:

Colonies may develop halos or be linked by faint film on the agar. A gentle opening step can restore separated masks, improving count and phenotype accuracy.

Use cases:
  • After thresholding, to split colonies connected by 1–2-pixel bridges.

  • To remove tiny noise specks before measuring morphology.

Caveats:
  • Too large a footprint erodes small colonies or weakly-stained edges, lowering recall and edge sharpness.

  • Opening can remove thin filaments that are biologically meaningful in spreading/filamentous phenotypes.

footprint#

Structuring element used for opening. A larger or denser footprint removes more thin connections and specks but risks eroding colony boundaries.

Type:

Literal[“auto”] | np.ndarray | int | None

Examples

Morphologically open masks to separate touching colonies
>>> from phenotypic.refine import MaskOpener
>>> op = MaskOpener(footprint='auto')
>>> image = op.apply(image, inplace=True)  
Raises:

AttributeError – If an invalid footprint type is provided (checked during operation).

Parameters:

footprint (Literal['auto'] | np.ndarray | int | None)

__del__()#

Automatically stop tracemalloc when the object is deleted.

__getstate__()#

Prepare the object for pickling by disposing of any widgets.

This ensures that UI components (which may contain unpickleable objects like input functions or thread locks) are cleaned up before serialization.

Note

This method modifies the object state by calling dispose_widgets(). Any active widgets will be detached from the object.

__init__(footprint: Literal['auto'] | numpy.ndarray | int | None = None)[source]#

Initialize the opener.

Parameters:

footprint (Literal["auto"] | np.ndarray | int | None) –

Structuring element for opening. Use: - “auto” to select a diamond footprint scaled to image size

(larger plates → slightly larger radius),

  • a NumPy array to pass a custom footprint,

  • an int radius to build a diamond footprint of that size,

  • or None to use the library default.

Larger radii disconnect wider bridges and suppress more speckles, but erode edges and can remove small colonies.

apply(image, inplace=False)#

Applies the operation to an image, either in-place or on a copy.

Parameters:
  • image (Image) – The arr image to apply the operation on.

  • inplace (bool) – If True, modifies the image in place; otherwise, operates on a copy of the image.

Returns:

The modified image after applying the operation.

Return type:

Image

dispose_widgets() None#

Drop references to the UI widgets.

Return type:

None

sync_widgets_from_state() None#

Push internal state into widgets.

Return type:

None

widget(image: Image | None = None, show: bool = False) Widget#

Return (and optionally display) the root widget.

Parameters:
  • image (Image | None) – Optional image to visualize. If provided, visualization controls will be added to the widget.

  • show (bool) – Whether to display the widget immediately. Defaults to False.

Returns:

The root widget.

Return type:

ipywidgets.Widget

Raises:

ImportError – If ipywidgets or IPython are not installed.

class phenotypic.refine.MinResidualErrorReducer[source]#

Bases: GridObjectRefiner

Reduce multi-detections per grid cell by keeping objects closest to a linear-regression prediction of expected positions.

Intuition:

In grid assays, some cells contain multiple detections due to halos, debris, or over-segmentation. By modeling the expected colony position in each row/column with linear regression, we can retain the object with the smallest residual error (closest to the predicted location) and remove the rest. This iterates across the grid until each cell is simplified.

Why this is useful for agar plates:

Pinned arrays assume consistent spatial layout. Selecting the object nearest the learned grid trend eliminates off-grid artifacts while preserving the most plausible colony per cell.

Use cases:
  • Over-segmentation yields several blobs per grid position.

  • Condensation or glare introduces extra detections near a colony.

Caveats:
  • If the grid fit is inaccurate (bad registration, warped plates), the closest-to-trend object may not be the true colony.

  • Relatively slow due to repeated measurement and iteration over cells.

(No public attributes)

Examples

Reduce multi-detections per grid cell using residual error
>>> from phenotypic.refine import MinResidualErrorReducer
>>> op = MinResidualErrorReducer()
>>> image = op.apply(image, inplace=True)  
__del__()#

Automatically stop tracemalloc when the object is deleted.

__getstate__()#

Prepare the object for pickling by disposing of any widgets.

This ensures that UI components (which may contain unpickleable objects like input functions or thread locks) are cleaned up before serialization.

Note

This method modifies the object state by calling dispose_widgets(). Any active widgets will be detached from the object.

apply(image, inplace=False)#

Applies the operation to an image, either in-place or on a copy.

Parameters:
  • image (Image) – The arr image to apply the operation on.

  • inplace (bool) – If True, modifies the image in place; otherwise, operates on a copy of the image.

Returns:

The modified image after applying the operation.

Return type:

Image

dispose_widgets() None#

Drop references to the UI widgets.

Return type:

None

sync_widgets_from_state() None#

Push internal state into widgets.

Return type:

None

widget(image: Image | None = None, show: bool = False) Widget#

Return (and optionally display) the root widget.

Parameters:
  • image (Image | None) – Optional image to visualize. If provided, visualization controls will be added to the widget.

  • show (bool) – Whether to display the widget immediately. Defaults to False.

Returns:

The root widget.

Return type:

ipywidgets.Widget

Raises:

ImportError – If ipywidgets or IPython are not installed.

class phenotypic.refine.ResidualOutlierRemover(axis: int | None = None, stddev_multiplier=1.5, max_coeff_variance: int = 1)[source]#

Bases: GridObjectRefiner

Remove objects with large regression residuals in noisy grid rows/columns.

Intuition:

In grid assays, colony centroids should align along near-linear trends within each row/column. Rows or columns with high variability suggest mis-detections or artifacts. Within such noisy lines, this operation removes objects whose positional residuals exceed a robust cutoff.

Why this is useful for agar plates:

Condensation, glare, and debris can produce off-grid detections that inflate row/column variance and break gridding assumptions. Pruning residual outliers restores alignment and improves subsequent measures.

Use cases:
  • Cleaning rows with multiple off-line blobs before measuring growth.

  • Stabilizing grid registration when a subset of positions is noisy.

Caveats:
  • If true colonies deviate due to warping or growth spreading, strict cutoffs may remove real data.

  • Depends on reasonable initial grid fit; with severe misregistration it may prune valid colonies.

Parameters:
  • axis (Optional[int])

  • max_coeff_variance (int)

axis#

Axis to analyze for outliers. None analyzes both rows and columns; 0 analyzes rows; 1 analyzes columns. Restricting the axis can speed up processing or focus on suspected directions of error.

Type:

Optional[int]

cutoff_multiplier#

Multiplier applied to a robust dispersion estimate (IQR-based in implementation) to set the outlier cutoff. Higher values are more permissive (fewer removals) and preserve edge cases; lower values prune more aggressively.

Type:

float

max_coeff_variance#

Maximum coefficient of variance (std/mean) allowed for a row/column before it is considered for outlier pruning. Smaller values trigger cleaning sooner; larger values only clean severely noisy lines.

Type:

int

Examples

Remove objects with large regression residuals
>>> from phenotypic.refine import ResidualOutlierRemover
>>> op = ResidualOutlierRemover(axis=None, stddev_multiplier=1.5, max_coeff_variance=1)
>>> image = op.apply(image, inplace=True)  
__del__()#

Automatically stop tracemalloc when the object is deleted.

__getstate__()#

Prepare the object for pickling by disposing of any widgets.

This ensures that UI components (which may contain unpickleable objects like input functions or thread locks) are cleaned up before serialization.

Note

This method modifies the object state by calling dispose_widgets(). Any active widgets will be detached from the object.

__init__(axis: int | None = None, stddev_multiplier=1.5, max_coeff_variance: int = 1)[source]#

Initialize the remover.

Parameters:
  • axis (Optional[int]) – Axis selection for analysis. None runs both directions; 0 rows; 1 columns. Limiting the axis reduces runtime and targets known problem directions.

  • stddev_multiplier (float) – Robust residual cutoff multiplier. Lower values remove more outliers (stronger cleanup) but risk dropping valid off-center colonies; higher values are conservative.

  • max_coeff_variance (int) – Threshold for row/column variability (std/mean) to trigger outlier analysis. Lower values clean more lines; higher values only address extremely noisy lines.

Raises:

ValueError – If parameters are not consistent with the operation (e.g., invalid types). Errors may arise during execution when measuring grid statistics.

apply(image, inplace=False)#

Applies the operation to an image, either in-place or on a copy.

Parameters:
  • image (Image) – The arr image to apply the operation on.

  • inplace (bool) – If True, modifies the image in place; otherwise, operates on a copy of the image.

Returns:

The modified image after applying the operation.

Return type:

Image

dispose_widgets() None#

Drop references to the UI widgets.

Return type:

None

sync_widgets_from_state() None#

Push internal state into widgets.

Return type:

None

widget(image: Image | None = None, show: bool = False) Widget#

Return (and optionally display) the root widget.

Parameters:
  • image (Image | None) – Optional image to visualize. If provided, visualization controls will be added to the widget.

  • show (bool) – Whether to display the widget immediately. Defaults to False.

Returns:

The root widget.

Return type:

ipywidgets.Widget

Raises:

ImportError – If ipywidgets or IPython are not installed.

class phenotypic.refine.Skeletonize(method: Literal['zhang', 'lee'] | None = None)[source]#

Bases: ObjectRefiner

Reduce object masks to single-pixel-wide skeletons using medial axis thinning.

Intuition:

Skeletonization compresses object regions to their medial axes (centerlines), preserving topological structure while reducing to 1-pixel width. On agar plates, this distills colony morphology to its core branching structure, useful for analyzing filamentous or spreading phenotypes without boundary noise. The method efficiently extracts the ‘backbone’ of colony shape.

Why this is useful for agar plates:

Colonies may have ragged edges, uneven staining, or noise that obscures their true spreading pattern. Skeletons expose the essential branching or directional growth, enabling more robust morphological features (e.g., branch count, elongation) and simplifying hyphae or filament tracking in fungal cultures.

Use cases:
  • Extracting colony centerlines for elongation or orientation analysis.

  • Analyzing branching patterns in spreading/filamentous phenotypes.

  • Simplifying masks for spatial graph analysis or filament tracking.

  • Reducing noise in masks before measuring advanced morphological features.

Caveats:
  • Skeletonization destroys width information; use only if you need topology, not colony boundary details.

  • Thin or poorly-defined colonies may produce fragmented or spurious skeleton branches.

  • Method choice (Zhang vs. Lee) can affect branch detection; Zhang is optimized for clean 2D images, Lee is more robust but may produce slightly thicker structures.

  • Isolated noise pixels may create spurious skeleton branches; apply cleanup (e.g., SmallObjectRemover) before skeletonizing.

method#

Thinning algorithm to use. - “zhang”: Fast, optimized for 2D images with clean topology. May produce

thin artifacts on noisy images.

  • “lee”: Works on 2D/3D, more robust to noise and irregular boundaries. Slightly slower than Zhang.

  • None: Auto-select based on image dimensionality (Zhang for 2D, Lee for 3D).

Type:

Literal[“zhang”, “lee”] | None

Examples

Reduce filamentous colony to medial axis skeleton
>>> from phenotypic.refine import Skeletonize
>>> op = Skeletonize(method="zhang")
>>> image = op.apply(image, inplace=True)  
Raises:

ValueError – If an invalid method is provided (checked during operation).

Parameters:

method (Literal['zhang', 'lee'] | None)

__del__()#

Automatically stop tracemalloc when the object is deleted.

__getstate__()#

Prepare the object for pickling by disposing of any widgets.

This ensures that UI components (which may contain unpickleable objects like input functions or thread locks) are cleaned up before serialization.

Note

This method modifies the object state by calling dispose_widgets(). Any active widgets will be detached from the object.

__init__(method: Literal['zhang', 'lee'] | None = None)[source]#

Initialize the skeletonizer.

Parameters:

method (Literal["zhang", "lee"] | None) –

Algorithm for skeletonization. - “zhang”: Optimized for 2D images; fast, produces thin skeletons.

Best for well-defined colony boundaries.

  • ”lee”: Works on 2D/3D; more robust to noisy or irregular boundaries. Slightly slower but preserves topology better on challenging images.

  • None: Automatically selects Zhang for 2D and Lee for 3D.

Choosing the right method depends on image quality: clean, binary masks benefit from Zhang; noisier masks or fungal hyphae benefit from Lee.

apply(image, inplace=False)#

Applies the operation to an image, either in-place or on a copy.

Parameters:
  • image (Image) – The arr image to apply the operation on.

  • inplace (bool) – If True, modifies the image in place; otherwise, operates on a copy of the image.

Returns:

The modified image after applying the operation.

Return type:

Image

dispose_widgets() None#

Drop references to the UI widgets.

Return type:

None

sync_widgets_from_state() None#

Push internal state into widgets.

Return type:

None

widget(image: Image | None = None, show: bool = False) Widget#

Return (and optionally display) the root widget.

Parameters:
  • image (Image | None) – Optional image to visualize. If provided, visualization controls will be added to the widget.

  • show (bool) – Whether to display the widget immediately. Defaults to False.

Returns:

The root widget.

Return type:

ipywidgets.Widget

Raises:

ImportError – If ipywidgets or IPython are not installed.

class phenotypic.refine.SmallObjectRemover(min_size=64)[source]#

Bases: ObjectRefiner

Remove small, likely spurious objects from a labeled object map.

Intuition:

After thresholding/segmentation of agar-plate images, tiny specks from dust, condensation, camera noise, or over-segmentation can appear as separate labeled objects. Removing these below a minimum size reduces false positives and stabilizes downstream phenotyping.

Use cases (agar plates):
  • Clean up salt-and-pepper detections before measuring colony size or shape.

  • Suppress fragmented debris around large colonies that may bias counts or area statistics.

  • Post-processing step after aggressive enhancement/thresholding.

Tuning and effects:
  • min_size: Sets the minimum object area (in pixels). Increasing this value removes more small fragments, typically improving mask quality and background suppression, but may also delete legitimate micro- colonies when colonies are extremely small or underexposed.

Caveats:
  • Setting min_size too high can remove small but real colonies or early-time-point growth, reducing recall.

  • The optimal threshold depends on resolution; what is “small” at high-resolution imaging may be substantial at low resolution.

(No public attributes)

Examples

Remove small spurious objects below a minimum size
>>> from phenotypic.refine import SmallObjectRemover
>>> op = SmallObjectRemover(min_size=100)
>>> image = op.apply(image, inplace=True)  
__del__()#

Automatically stop tracemalloc when the object is deleted.

__getstate__()#

Prepare the object for pickling by disposing of any widgets.

This ensures that UI components (which may contain unpickleable objects like input functions or thread locks) are cleaned up before serialization.

Note

This method modifies the object state by calling dispose_widgets(). Any active widgets will be detached from the object.

__init__(min_size=64)[source]#

Initialize the remover.

Parameters:

min_size (int) – Minimum object area (in pixels) to keep. Higher values remove more small artifacts and fragmented edges, generally improving mask cleanliness but risking loss of tiny colonies.

apply(image, inplace=False)#

Applies the operation to an image, either in-place or on a copy.

Parameters:
  • image (Image) – The arr image to apply the operation on.

  • inplace (bool) – If True, modifies the image in place; otherwise, operates on a copy of the image.

Returns:

The modified image after applying the operation.

Return type:

Image

dispose_widgets() None#

Drop references to the UI widgets.

Return type:

None

sync_widgets_from_state() None#

Push internal state into widgets.

Return type:

None

widget(image: Image | None = None, show: bool = False) Widget#

Return (and optionally display) the root widget.

Parameters:
  • image (Image | None) – Optional image to visualize. If provided, visualization controls will be added to the widget.

  • show (bool) – Whether to display the widget immediately. Defaults to False.

Returns:

The root widget.

Return type:

ipywidgets.Widget

Raises:

ImportError – If ipywidgets or IPython are not installed.

class phenotypic.refine.Thinning(max_num_iter: int | None = None)[source]#

Bases: ObjectRefiner

Progressively thin object masks to single-pixel-wide structures via morphological erosion.

Intuition:

Thinning iteratively removes outer pixels while preserving connectivity and 2x2 blocks, gradually reducing object width to 1 pixel. Unlike skeletonization, thinning does not require solving a medial axis problem, making it simpler and more predictable. On agar plates, thinning can clarify colony boundaries, separate overlapping regions, or prepare masks for filament analysis by stripping away outer noise layers step-by-step.

Why this is useful for agar plates:

Colonies touching or with diffuse edges can be gradually separated by removing outer pixels without aggressive erosion. Thinning is gentler than opening and can preserve thin filamentous structures better than binary erosion, making it ideal for multi-pass boundary cleaning before advanced morphological measurements.

Use cases:
  • Gradually separating touching or overlapping colonies via controlled pixel removal.

  • Clarifying colony boundaries by removing diffuse outer layers.

  • Preparing masks for graph-based analysis (converting to 1-pixel skeletons).

  • De-noising edges while preserving internal structure (e.g., before branching analysis).

Caveats:
  • Each iteration removes pixels; too many iterations will obliterate small features or break thin filaments.

  • Thinning alone does not always separate touching colonies; combine with opening or seed-based separation for better results.

  • The algorithm preserves 2x2 blocks, which can leave small “square” artifacts in very compact colonies.

  • On highly fragmented or noise-laden masks, iterations may not converge smoothly; pre-clean with SmallObjectRemover.

max_num_iter#

Maximum number of thinning iterations. Each iteration removes outer-layer pixels while maintaining topology. - None (default): Iterate until convergence (no changes detected). - Positive int: Stop after N iterations regardless of convergence.

Larger max_num_iter values progressively thin more aggressively. For separating touching colonies, try 1-3 iterations; for full skeletonization, set max_num_iter higher (10-50 depending on colony size).

Type:

int | None

Examples

Gradually thin colonies to clarify boundaries
>>> from phenotypic.refine import Thinning
>>> op = Thinning(max_num_iter=2)
>>> image = op.apply(image, inplace=True)  
Thin to convergence (full skeleton)
>>> from phenotypic.refine import Thinning
>>> op = Thinning()  # Iterate until no change
>>> image = op.apply(image, inplace=True)  
Raises:

ValueError – If max_num_iter is negative (checked during operation).

Parameters:

max_num_iter (int | None)

__del__()#

Automatically stop tracemalloc when the object is deleted.

__getstate__()#

Prepare the object for pickling by disposing of any widgets.

This ensures that UI components (which may contain unpickleable objects like input functions or thread locks) are cleaned up before serialization.

Note

This method modifies the object state by calling dispose_widgets(). Any active widgets will be detached from the object.

__init__(max_num_iter: int | None = None)[source]#

Initialize the thinner.

Parameters:

max_num_iter (int | None) –

Upper limit on iterations. Use: - None (default) to iterate until convergence, yielding a full skeleton. - A small int (e.g., 1-3) for gentle boundary cleanup while preserving

colony bulk.

  • A large int (e.g., 10-50) for aggressive thinning to single-pixel structures.

Choosing max_num_iter is a trade-off: few iterations preserve colony size/robustness but may leave overlaps; many iterations separate more aggressively but risk removing small filaments or creating fragmentation.

apply(image, inplace=False)#

Applies the operation to an image, either in-place or on a copy.

Parameters:
  • image (Image) – The arr image to apply the operation on.

  • inplace (bool) – If True, modifies the image in place; otherwise, operates on a copy of the image.

Returns:

The modified image after applying the operation.

Return type:

Image

dispose_widgets() None#

Drop references to the UI widgets.

Return type:

None

sync_widgets_from_state() None#

Push internal state into widgets.

Return type:

None

widget(image: Image | None = None, show: bool = False) Widget#

Return (and optionally display) the root widget.

Parameters:
  • image (Image | None) – Optional image to visualize. If provided, visualization controls will be added to the widget.

  • show (bool) – Whether to display the widget immediately. Defaults to False.

Returns:

The root widget.

Return type:

ipywidgets.Widget

Raises:

ImportError – If ipywidgets or IPython are not installed.

class phenotypic.refine.WhiteTophatModifier(footprint_shape='disk', footprint_radius: int | None = None)[source]#

Bases: ObjectRefiner

Suppress small bright structures in the mask using white tophat.

Intuition:

White tophat highlights small, bright features relative to their local background. On agar plates, glare, dust, or bright halos can create thin connections or speckles that pollute colony masks. This modifier detects those bright micro-structures and subtracts them from the binary mask to improve separation and mask quality.

Why this is useful for agar plates:

Bright artifacts can bridge adjacent colonies or inflate perimeters. Removing those tiny bright elements yields cleaner, more compact masks that better match colony boundaries under uneven illumination.

Use cases:
  • Reducing glare-induced bridges between neighboring colonies.

  • Removing bright speckles/dust that become embedded in masks after thresholding.

Caveats:
  • Large footprints may remove real bright edges of colonies (e.g., highly reflective rims), slightly eroding edge sharpness.

  • If the footprint is too small, bright artifacts may remain.

Parameters:

footprint_radius (int)

footprint_shape#

Shape for the footprint used in the tophat transform. Supported: ‘disk’, ‘square’. Disk tends to preserve round features, while square can be more aggressive along axes.

Type:

str

footprint_radius#

Radius of the footprint. Larger values remove broader bright features but risk shrinking thin colony appendages. None auto-scales with image size.

Type:

int | None

Examples

Suppress small bright structures in the mask using white tophat
>>> from phenotypic.refine import WhiteTophatModifier
>>> op = WhiteTophatModifier(shape='disk', radius=5)
>>> image = op.apply(image, inplace=True)  
__del__()#

Automatically stop tracemalloc when the object is deleted.

__getstate__()#

Prepare the object for pickling by disposing of any widgets.

This ensures that UI components (which may contain unpickleable objects like input functions or thread locks) are cleaned up before serialization.

Note

This method modifies the object state by calling dispose_widgets(). Any active widgets will be detached from the object.

__init__(footprint_shape='disk', footprint_radius: int | None = None)[source]#

Initialize the modifier.

Parameters:
  • footprint_shape (str) –

    Footprint geometry for white tophat. - ‘disk’: Balanced in all directions; gentle on round colonies. - ‘square’: Slightly stronger along rows/columns; may remove

    more rectilinear glare or sensor artifacts.

  • footprint_radius (int | None) – Radius in pixels. Increasing removes larger bright structures and can improve background suppression, but may thin colony edges. None auto-selects ~0.4% of the smaller image dimension.

Raises:

ValueError – If shape is not one of the supported values (raised during operation).

apply(image, inplace=False)#

Applies the operation to an image, either in-place or on a copy.

Parameters:
  • image (Image) – The arr image to apply the operation on.

  • inplace (bool) – If True, modifies the image in place; otherwise, operates on a copy of the image.

Returns:

The modified image after applying the operation.

Return type:

Image

dispose_widgets() None#

Drop references to the UI widgets.

Return type:

None

sync_widgets_from_state() None#

Push internal state into widgets.

Return type:

None

widget(image: Image | None = None, show: bool = False) Widget#

Return (and optionally display) the root widget.

Parameters:
  • image (Image | None) – Optional image to visualize. If provided, visualization controls will be added to the widget.

  • show (bool) – Whether to display the widget immediately. Defaults to False.

Returns:

The root widget.

Return type:

ipywidgets.Widget

Raises:

ImportError – If ipywidgets or IPython are not installed.