Source code for phenotypic.refine._mask_dilator

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 dilation


[docs] class MaskDilator(ObjectRefiner, FootprintMixin): """Expand colony masks outward using morphological dilation. Adds pixels around object boundaries, bridging small gaps between nearby fragments and recovering faint halos excluded by strict thresholding. Dilation inflates area; follow with erosion (closing) if area accuracy is critical. Args: shape: Structuring element. ``'auto'``, ``'disk'``, ``'square'``, ``'diamond'``, or custom ndarray. Default: ``None``. width: Footprint width in pixels. Default: 3. Returns: Image: Input image with ``objmask`` and ``objmap`` dilated. Best For: - Bridging thin gaps between fragments of the same colony. - Recovering faint colony halos near detection boundaries. - Preprocessing before merge-based refinement operations. Consider Also: - :class:`MaskCloser` for dilation-then-erosion that bridges gaps without inflating colony size. - :class:`MaskEroder` for the opposite effect — shrinking masks to remove thin protrusions. See Also: :doc:`/explanation/refinement_strategies` for the recommended refinement sequence. """
[docs] def __init__( self, shape: Literal["auto", "square", "diamond", "disk"] | np.ndarray[int] | None = None, width: int = 3, n_iter: int = 1, ): """Initialize the dilator. Args: shape (Literal["auto", "square", "diamond", "disk"] | np.ndarray | None): Structuring element for dilation. 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 expand objects more and bridge wider gaps, but risk merging distinct colonies and inflating size measurements beyond recovery. width (int): Footprint width in pixels when using named shapes or auto-scaling. Default: 3 pixels (moderate expansion). n_iter (int): Number of times to apply dilation. Repeated dilation 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( shape="diamond", 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(shape=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[:] = dilation(image.objmask[:], footprint=footprint) return image