Source code for phenotypic.refine._thinning

from __future__ import annotations

from typing import TYPE_CHECKING, Annotated

if TYPE_CHECKING:
    from phenotypic._core._image import Image

from skimage.morphology import thin

from phenotypic.abc_ import ObjectRefiner
from phenotypic.sdk_.typing_ import TuneSpec


[docs] class Thinning(ObjectRefiner): """Progressively thin object masks by iteratively removing outer boundary pixels. Strips boundary pixels one layer at a time while preserving topological connectivity, gradually reducing each object toward single-pixel-wide structures. Explicit iteration control makes it suitable for anything from gentle edge cleanup to full centerline extraction. For a comparison of morphological refinement methods, see :doc:`/explanation/refinement_strategies`. Best For: - Gently cleaning diffuse or ragged colony boundaries before morphological measurements with a small number of iterations. - Gradually separating lightly touching colonies via controlled pixel removal without applying a full watershed. - Preparing masks for graph-based analysis by iterating to single-pixel-wide structures. - Preserving the overall shape of filamentous colonies while reducing spurious boundary protrusions. Consider Also: - :class:`Skeletonize` for direct medial-axis extraction without per-iteration control. - :class:`MaskErosion` for uniform inward shrinking governed by a configurable structuring element rather than iterative peeling. - :class:`SeparateObjects` for watershed-based separation when colonies are firmly merged rather than lightly touching. Args: max_num_iter: Maximum number of thinning iterations. ``None`` runs until convergence, yielding a fully thinned single-pixel-wide skeleton (Guo--Hall thinning, distinct from the Zhang--Suen/Lee algorithms in :class:`Skeletonize`, so the output is similar but not identical). Small values (1--5) provide gentle boundary cleanup; larger values thin more aggressively. Default: None. Returns: Image: Input image with ``objmask`` thinned by up to ``max_num_iter`` iterations. Assigning ``objmask`` rebuilds ``objmap`` from the thinned mask. See Also: :doc:`/how_to/notebooks/refine_noisy_boundaries` for thinning-based boundary cleanup workflows on real plate images. :doc:`/explanation/refinement_strategies` for a comparison of morphological refinement methods. """ max_num_iter: Annotated[int | None, TuneSpec(1, 50, log=True)] = None def _operate(self, image: Image) -> Image: image.objmask[:] = thin(image.objmask[:], max_num_iter=self.max_num_iter) return image