Source code for phenotypic.enhance._subtract_white_tophat

from __future__ import annotations

from typing import Annotated, 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 MorphologicalFiltering
from phenotypic.sdk_.typing_ import TuneSpec


[docs] class SubtractWhiteTophat(MorphologicalFiltering): """Suppress small bright artifacts in ``detect_mat`` by subtracting the white top-hat. Computes the white top-hat (original minus morphological opening) and subtracts it from the image, removing small bright blobs such as dust specks, glare highlights, and condensation artifacts while preserving larger colony structures intact. For algorithm details, see :doc:`/explanation/what_enhancement_does`. Best For: - Removing small bright artifacts that could be mistaken for tiny colonies during thresholding. - Reducing glare highlights on shiny agar plates before colony detection. - Cleaning up dust and condensation artifacts that confuse downstream segmentation. Consider Also: - :class:`WhiteTophatEnhance` when the goal is to isolate small bright structures rather than suppress them. - :class:`GrayOpening` for morphological smoothing that removes small bright features without explicit subtraction. - :class:`RankMedianEnhancer` for impulsive noise removal via median filtering when artifacts are single-pixel in scale. Args: shape: Footprint geometry for the structuring element. Accepted values: ``'diamond'`` (default) and ``'disk'`` provide isotropic behavior suited to round artifacts; ``'square'`` can align with sensor grid patterns. width: Maximum bright-artifact size in pixels targeted for removal. Set smaller than the smallest genuine colony diameter to preserve colonies. ``None`` (default) auto-derives a small value as approximately 0.4 % of the shorter image dimension. Returns: Image: Input image with ``detect_mat`` artifact-suppressed by subtracting the white top-hat. ``rgb`` and ``gray`` are unchanged. Raises: ValueError: If an unsupported footprint shape is provided. See Also: :doc:`/tutorials/notebooks/03_enhancing_before_detection` for a visual walkthrough of artifact removal on plate images. :doc:`/explanation/what_enhancement_does` for background on top-hat transforms and artifact suppression strategies. """ shape: str = "diamond" width: Annotated[int | None, TuneSpec(3, 15, step=2)] = None def _operate(self, image: Image) -> Image: white_tophat_results = white_tophat( image.detect_mat[:], footprint=self._get_footprint( self._get_footprint_width(detection_matrix=image.detect_mat[:]), ), ) image.detect_mat[:] = image.detect_mat[:] - white_tophat_results return image def _get_footprint_width(self, detection_matrix: np.ndarray) -> int: if self.width is None: return int(np.min(detection_matrix.shape) * 0.004) else: return self.width def _get_footprint(self, width: int) -> np.ndarray: match self.shape: # Use shared ImageEnhancer utility for common 2D shapes case "disk" | "square" | "diamond": return self._make_footprint(shape=self.shape, width=width) case _: raise ValueError(f"Unsupported footprint shape: {self.shape}")