Source code for phenotypic.enhance._subtract_gaussian

from __future__ import annotations

from typing import TYPE_CHECKING, Annotated

if TYPE_CHECKING:
    from phenotypic._core._image import Image

import numpy as np
from pydantic import Field
from skimage.filters import gaussian

from phenotypic.abc_ import BackgroundSubtraction
from phenotypic.sdk_.typing_ import TuneSpec


[docs] class SubtractGaussian(BackgroundSubtraction): """Remove background from ``detect_mat`` by subtracting a Gaussian-blurred estimate. Estimates a smooth background by blurring the image with a wide Gaussian kernel and subtracts it, removing gradual illumination gradients such as vignetting, agar thickness variation, and scanner shading while retaining sharp colony features. The result is clipped to [0, 1] and improves downstream thresholding and edge detection. For algorithm details, see :doc:`/explanation/what_enhancement_does`. Best For: - Correcting uneven illumination gradients across the scan bed or plate. - Flattening background intensity so bright colonies or bright colony features stand out against local background. - Normalizing batches captured with varying scanner exposure or lamp profiles. - Plates where illumination varies smoothly and a Gaussian is a reasonable background model. Consider Also: - :class:`SubtractRollingBall` for parabolic background estimation that handles non-Gaussian intensity ramps and sharp gradients more accurately. - :class:`SubtractOpening` for OpenCV-accelerated morphological background subtraction in high-throughput pipelines. - :class:`FlattenIllumination` for homomorphic filtering that separates illumination from reflectance in the frequency domain. - :class:`ImageInverter` before this operation when colonies are dark on bright agar and should be made bright explicitly. Args: sigma: Standard deviation of the Gaussian background kernel in pixels. Set larger than the typical colony diameter so that colonies are blurred into the background estimate rather than surviving it; too small a sigma subtracts colony signal along with the background. Typical range: 20--100 for standard plate images. A reasonable starting point is a value somewhat larger than the widest colony present. Default: 50.0. mode: Border-handling strategy for the Gaussian convolution. Accepted values: ``'reflect'`` (default), ``'constant'``, ``'nearest'``, ``'mirror'``, ``'wrap'``. cval: Constant fill value used when ``mode='constant'``. Has no effect for other border modes. Default: 0.0. truncate: Number of standard deviations at which the Gaussian kernel is truncated. Larger values are more accurate but increase compute time. Default: 4.0. preserve_range: Preserve the input pixel value range during Gaussian filtering. Default: ``True``. n_iter: Number of successive background-subtraction passes. Additional passes remove residual gradients left after the first subtraction. Typical range: 1--3. Default: 1. Returns: Image: Input image with ``detect_mat`` background-subtracted and clipped to [0, 1]. ``rgb`` and ``gray`` are unchanged. Raises: ValueError: If ``n_iter`` is less than 1. See Also: :doc:`/tutorials/notebooks/03_enhancing_before_detection` for a visual walkthrough of background subtraction on plate images. :doc:`/explanation/what_enhancement_does` for background on illumination correction strategies. """ # TODO: review bound (unverified vs literature) sigma: Annotated[float, TuneSpec(20.0, 100.0)] = 50.0 mode: str = "reflect" cval: Annotated[float, TuneSpec(tunable=False)] = 0.0 truncate: Annotated[float, TuneSpec(tunable=False)] = 4.0 preserve_range: bool = True n_iter: Annotated[int, TuneSpec(1, 3)] = Field(1, ge=1) def _operate(self, image: Image) -> Image: for _ in range(self.n_iter): background = gaussian( image=image.detect_mat[:], sigma=self.sigma, mode=self.mode, cval=self.cval, truncate=self.truncate, preserve_range=self.preserve_range, ) image.detect_mat[:] = np.clip((image.detect_mat[:].copy() - background), a_min=0.0, a_max=1.0) return image