Source code for phenotypic.refine._mask_white_tophat

from __future__ import annotations

from typing import Annotated, Literal, TYPE_CHECKING

if TYPE_CHECKING:
    from phenotypic._core._image import Image

import numpy as np
from skimage.morphology import white_tophat

from phenotypic.abc_ import ObjectRefiner
from phenotypic.sdk_.mixin import FootprintMixin
from phenotypic.sdk_.typing_ import NdArrayField, TuneSpec


[docs] class MaskWhiteTophat(ObjectRefiner, FootprintMixin): """Remove bright protrusions and thin bridges from the detection mask using white tophat subtraction. Computes the white tophat transform of the binary mask (mask minus its morphological opening) and subtracts the result, isolating and eliminating small bright structures that are narrower than the structuring element. Glare-induced bridges, dust speckles, and thin connections are suppressed while the main colony body is preserved. For an overview of morphological refinement strategies, see :doc:`/explanation/refinement_strategies`. Best For: - Plates with specular glare that creates bright bridges between adjacent colony masks after thresholding. - Detection masks with small bright dust or condensation speckles that survived the detector. - Colonies whose perimeter measurements are inflated by thin protruding artefacts. - Images where illumination hot-spots produce narrow mask connections narrower than the structuring element width. Consider Also: - :class:`MaskOpening` for general morphological opening that removes thin protrusions and breaks bridges without the tophat detection step. - :class:`SmallObjectRemover` when small artefacts are fully disconnected objects better targeted by an area threshold. - :class:`ExtractColonyCore` for intensity-based core extraction when diffuse halos rather than thin bridges are the primary artefact. Args: shape: Structuring element geometry. ``"disk"`` (default) best preserves round colony boundaries; ``"square"`` is more aggressive along cardinal axes; ``"diamond"`` is a compromise. A NumPy array supplies a custom footprint. Default: ``"disk"``. width: Footprint radius in pixels. Structures narrower than this value are candidates for removal. Larger values eliminate broader protrusions but risk eroding genuine thin appendages. ``None`` auto-scales to approximately 0.4% of the smaller image dimension. Typical range: 3--10. Default: ``None``. Returns: Image: Input image with ``objmask`` updated by subtracting the white tophat result. Assigning ``objmask`` rebuilds ``objmap`` from the binary mask; ``rgb``, ``gray``, and ``detect_mat`` are unchanged. See Also: :doc:`/how_to/notebooks/refine_noisy_boundaries` for tophat-based cleanup workflows on real plate images. :doc:`/explanation/refinement_strategies` for a comparison of morphological refinement methods. """ shape: Literal["disk", "square", "diamond"] | NdArrayField = "disk" width: Annotated[int | None, TuneSpec(3, 10)] = None def _operate(self, image: Image) -> Image: white_tophat_results = white_tophat( image.objmask[:], footprint=FootprintMixin._make_footprint( shape=self.shape, width=self._get_footprint_width(array=image.objmask[:]), ), ) image.objmask[:] = image.objmask[:] & ~white_tophat_results return image def _get_footprint_width(self, array: np.ndarray) -> int: if self.width is None: return int(np.min(array.shape) * 0.004) else: return self.width