Source code for phenotypic.enhance._white_tophat_enhancer

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from phenotypic import Image

import numpy as np
from skimage.morphology import white_tophat, cube, ball

from phenotypic.abc_ import ImageEnhancer


[docs] class WhiteTophatEnhancer(ImageEnhancer): """ White top-hat transform to suppress small bright structures. Computes the white top-hat (original minus opening) using a structuring element, then subtracts it from the image here (i.e., remove small bright blobs). In agar plate colony images, this helps reduce bright specks from dust, glare, or condensation, making colonies stand out against a smoother background. Use cases (agar plates): - Remove small bright artifacts that can be mistaken for tiny colonies. - Reduce glare highlights on shiny plates before thresholding. Tuning and effects: - shape: The morphology footprint geometry. 'diamond' or 'disk' are good isotropic choices on plates; 'square' can align with pixel grids. - radius: Sets the maximum size of bright features to remove. Choose slightly smaller than the minimum colony radius so real colonies are preserved. Caveats: - If radius is too large, real small colonies will be attenuated. - Operates on bright features; for dark colonies on bright agar, it primarily removes bright noise rather than enhancing the colonies themselves. Attributes: shape (str): Footprint shape: 'diamond', 'disk', 'square', 'sphere', 'cube'. radius (int | None): Footprint radius in pixels; if None, a small default is derived from the image size. """
[docs] def __init__(self, shape: str = "diamond", radius: int = None): """ Parameters: shape (str): Footprint geometry controlling which bright features are removed. 'diamond' or 'disk' provide isotropic behavior on plates; 'square' can align with sensor grid artifacts. Advanced: 'sphere' or 'cube' for volumetric data. radius (int | None): Maximum bright-object size (in pixels) targeted for removal. Set slightly smaller than the smallest colonies to avoid suppressing real colonies. None picks a small default based on image dimensions. """ self.shape = shape self.radius = radius
def _operate(self, image: Image) -> Image: white_tophat_results = white_tophat( image.enh_gray[:], footprint=self._get_footprint( self._get_footprint_radius(detection_matrix=image.enh_gray[:]), ), ) image.enh_gray[:] = image.enh_gray[:] - white_tophat_results return image def _get_footprint_radius(self, detection_matrix: np.ndarray) -> int: if self.radius is None: return int(np.min(detection_matrix.shape) * 0.004) else: return self.radius def _get_footprint(self, radius: 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, radius=radius) # Preserve volumetric alternatives case "sphere": return ball(radius) case "cube": return cube(radius * 2)