Source code for phenotypic.enhance._subtract_rolling_ball

from __future__ import annotations

from typing import TYPE_CHECKING, Annotated

if TYPE_CHECKING:
    from phenotypic._core._image import Image

from skimage.restoration import rolling_ball

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


[docs] class SubtractRollingBall(BackgroundSubtraction): """Remove background from ``detect_mat`` with ImageJ-style rolling-ball subtraction. Models the background as the surface traced by rolling a parabolic ball under the image intensity landscape, then subtracts it. Removes slow illumination gradients and agar shading while preserving colony structures, and handles non-Gaussian intensity ramps better than Gaussian-based subtraction. For algorithm details, see :doc:`/explanation/what_enhancement_does`. Best For: - Scanner vignetting, lid glare, or agar thickness variations that produce non-Gaussian illumination gradients. - Flattening backgrounds so bright colonies or bright colony features stand out against local background. - Images where Gaussian subtraction leaves residual background near plate edges or bright corners. Consider Also: - :class:`SubtractGaussian` for Gaussian-based subtraction with continuous sigma control when the background gradient is smooth and Gaussian-like. - :class:`SubtractOpening` for OpenCV-accelerated morphological background removal in high-throughput pipelines. - :class:`WhiteTophatEnhance` for isolating small bright structures rather than producing a corrected background-free image. - :class:`ImageInverter` before this operation when colonies are dark on bright agar and should be made bright explicitly. Args: radius: Rolling-ball radius in pixels. Must exceed the largest colony diameter so that colony pixels do not contribute to the background estimate. Typical range: 50--200. A reasonable starting point is a value larger than the widest colony on the plate. Default: 100. kernel: Optional custom ball or paraboloid array overriding the default parabolic shape. When provided, ``radius`` is ignored. Default: ``None``. nansafe: Treat NaN-valued pixels as missing data during background estimation, preventing NaN propagation in masked or padded images. Default: ``False``. Returns: Image: Input image with ``detect_mat`` background-subtracted. ``rgb`` and ``gray`` are unchanged. 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 rolling-ball and other illumination correction strategies. """ # TODO: review bound (unverified vs literature) radius: Annotated[int, TuneSpec(50, 200, log=True)] = 100 kernel: NdArrayField | None = None nansafe: bool = False def _operate(self, image: Image): image.detect_mat[:] = image.detect_mat[:] - rolling_ball( image=image.detect_mat[:], radius=self.radius, kernel=self.kernel, nansafe=self.nansafe, ) return image