Source code for phenotypic.refine._mask_eroder
from __future__ import annotations
from typing import Literal, TYPE_CHECKING
if TYPE_CHECKING:
from phenotypic._core._image import Image
from phenotypic.abc_ import ObjectRefiner
from phenotypic.tools_.mixin import FootprintMixin
import numpy as np
from skimage.morphology import erosion
[docs]
class MaskEroder(ObjectRefiner, FootprintMixin):
"""Shrink colony masks inward to remove thin protrusions and noise pixels.
Removes outer boundary pixels from all objects, eliminating thin
whiskers, isolated specks, and uncertain boundary pixels from soft
edges. Leaves the core colony structure intact.
Args:
shape: Structuring element. ``'auto'``, ``'disk'``, ``'square'``,
``'diamond'``, or custom ndarray. Default: ``None``.
width: Footprint width in pixels. Default: 3.
n_iter: Number of erosion iterations. Default: 1.
Returns:
Image: Input image with ``objmask`` and ``objmap`` eroded.
Best For:
- Removing thin protrusions or whiskers from colony edges.
- Eliminating noise specks that survived previous cleanup.
- Excluding uncertain boundary pixels for higher-precision measurements.
Consider Also:
- :class:`MaskDilator` for the opposite effect — expanding masks
outward.
- :class:`MaskOpener` for erosion-then-dilation that removes thin
features without permanently shrinking colonies.
- :class:`SmallObjectRemover` for removing small objects by area
rather than shrinking all objects.
See Also:
:doc:`/explanation/refinement_strategies` for the recommended
refinement sequence.
"""
[docs]
def __init__(
self,
shape: Literal[
"auto", "square", "diamond", "disk"] | np.ndarray | None = None,
width: int = 3,
n_iter: int = 1,
):
"""Initialize the eroder.
Args:
shape (Literal["auto", "square", "diamond", "disk"] | np.ndarray | None):
Structuring element for erosion. Use:
- "auto" to select a disk shape scaled to image size
(larger plates → slightly larger width),
- a NumPy array to pass a custom shape,
- one of the named shapes ("disk", "square", "diamond") with
a specified width,
- or ``None`` to use the library default.
Larger widths erode more aggressively, removing larger features
but risking elimination of small colonies and over-shrinkage
of area measurements.
width (int): Footprint width in pixels when using named shapes
or auto-scaling. Default: 3 pixels (moderate erosion).
n_iter (int): Number of times to apply erosion. Repeated erosion
with a small element produces smoother results than a single
pass with a larger element. Default: 1.
"""
super().__init__()
self.shape = shape
self.width = width
self.n_iter = n_iter
def _operate(self, image: Image) -> Image:
if self.shape == "auto":
footprint = FootprintMixin._make_footprint(
"disk", width=max(2, round(np.min(image.shape) * 0.003))
)
elif isinstance(self.shape, np.ndarray):
footprint = self.shape
elif self.shape in self._footprint_shapes:
footprint = FootprintMixin._make_footprint(self.shape, width=self.width)
elif not self.shape:
footprint = None
else:
raise AttributeError("Invalid shape type")
for _ in range(self.n_iter):
image.objmask[:] = erosion(image.objmask[:], footprint=footprint)
return image