Source code for phenotypic.refine._mask_fill

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from phenotypic 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 binary object masks to produce solid colonies. Intuition: Thresholding on agar-plate images can leave small voids inside colony masks due to illumination gradients, pigment heterogeneity, or glare. Filling these holes produces contiguous masks that better match the true colony footprint and improve area-based phenotypes. Why this is useful for agar plates: Many colonies exhibit darker centers or radial texture. Hole filling mitigates these within-colony gaps so that downstream measurements (area, perimeter, intensity summaries) are less biased. Use cases: - After global or adaptive thresholding where donut-like masks appear. - Prior to morphological measurements that assume simply connected shapes. Caveats: - Over-aggressive filling with a large structuring element can bridge adjacent colonies through narrow gaps, hurting separation. - If masks contain genuine cavities that should remain (e.g., hollow artifacts), filling may misrepresent structure. Attributes: structure (Optional[np.ndarray]): Structuring element used to define the neighborhood for filling. A larger or denser structure tends to close larger holes but can also smooth away fine boundaries. origin (int): Center offset for the structuring element. Adjusting the origin subtly shifts how neighborhoods are evaluated, which can influence edge behavior at colony boundaries. Examples: .. dropdown:: Fill holes in colony masks to produce solid shapes >>> from phenotypic.refine import MaskFill >>> op = MaskFill() >>> image = op.apply(image, inplace=True) # doctest: +SKIP """
[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