Source code for phenotypic.refine._mask_fill
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from phenotypic._core._image import Image
import numpy as np
from scipy.ndimage import binary_fill_holes
from typing import Optional
from phenotypic.abc_ import ObjectRefiner
from phenotypic.tools_.funcs_ import is_binary_mask
[docs]
class MaskFill(ObjectRefiner):
"""Fill holes inside colony masks to produce solid, contiguous regions.
Uses binary flood fill to close voids left by illumination gradients,
pigment heterogeneity, or glare within colonies. Produces masks that
better match the true colony footprint for area and shape measurements.
Args:
structure: Binary structuring element defining the fill neighborhood.
``None`` uses the default cross-shaped element. Default: ``None``.
origin: Center offset for the structuring element. Default: 0.
Returns:
Image: Input image with ``objmask`` and ``objmap`` updated with
filled holes.
Best For:
- Donut-like masks from global thresholding on colonies with dark centers.
- Colonies with radial pigment texture that creates interior gaps.
- Pre-measurement cleanup to ensure simply connected shapes.
Consider Also:
- :class:`MaskCloser` for bridging narrow gaps *between* fragments
rather than filling holes *within* objects.
- :class:`MaskOpener` for the opposite effect — removing thin
connections between objects.
See Also:
:doc:`/how_to/notebooks/refine_noisy_boundaries` for a walkthrough
of refinement operations.
"""
[docs]
def __init__(self, structure: Optional[np.ndarray] = None, origin: int = 0):
"""Initialize the filler and validate inputs.
Args:
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.
"""
if structure is not None:
if not is_binary_mask(structure):
raise ValueError("arr object array must be a binary array")
self.structure = structure
self.origin = origin
def _operate(self, image: Image) -> Image:
image.objmask[:] = binary_fill_holes(
input=image.objmask[:], structure=self.structure, origin=self.origin
)
return image