Source code for phenotypic.refine._thinning
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from phenotypic._core._image import Image
from skimage.morphology import thin
from phenotypic.abc_ import ObjectRefiner
[docs]
class Thinning(ObjectRefiner):
"""Progressively thin object masks by iteratively removing outer pixels while preserving connectivity.
Strips away boundary pixels one layer at a time, gradually reducing object
width toward single-pixel structures. Unlike skeletonization, thinning
offers explicit iteration control, making it useful for gentle boundary
cleanup (few iterations) or full skeleton extraction (convergence).
Args:
max_num_iter: Maximum thinning iterations. ``None`` iterates until
convergence (full skeleton). A small value (1--3) provides
gentle boundary cleanup; a large value (10--50) thins
aggressively. Default: None.
Returns:
Image: Input image with ``objmask`` thinned by the specified number
of iterations.
Raises:
ValueError: If ``max_num_iter`` is negative.
Best For:
- Gradually separating touching or overlapping colonies via controlled
pixel removal.
- Clarifying diffuse colony boundaries before morphological
measurements.
- Preparing masks for graph-based analysis by converting to
single-pixel skeletons.
- De-noising colony edges while preserving filamentous structures.
Consider Also:
- :class:`Skeletonize` for direct medial-axis extraction without
iterative control.
- :class:`MaskEroder` for uniform inward shrinking with a
configurable structuring element.
- :class:`SeparateObjects` for watershed-based separation of
touching colonies.
See Also:
:doc:`/how_to/notebooks/refine_noisy_boundaries` for thinning-based
boundary cleanup workflows.
:doc:`/explanation/refinement_strategies` for a comparison of
morphological refinement methods.
"""
[docs]
def __init__(self, max_num_iter: int | None = None):
"""Initialize the thinner.
Args:
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.
"""
super().__init__()
self.max_num_iter: int | None = max_num_iter
def _operate(self, image: Image) -> Image:
image.objmask[:] = thin(image.objmask[:], max_num_iter=self.max_num_iter)
return image