Source code for phenotypic.enhance._visushrink_enhancer

from __future__ import annotations

from typing import TYPE_CHECKING, Literal

if TYPE_CHECKING:
    from phenotypic._core._image import Image

from skimage.restoration import denoise_wavelet

from ..abc_ import ImageEnhancer


[docs] class VisuShrinkEnhancer(ImageEnhancer): """Denoise ``detect_mat`` with universal VisuShrink wavelet thresholding. Applies wavelet-domain denoising with a single universal threshold across all subbands, designed to remove all Gaussian noise with high probability. Faster than :class:`BayesShrinkEnhancer` but may over-smooth regions with low noise. Preserves colony edges better than Gaussian blur. For algorithm details, see :doc:`/explanation/what_enhancement_does`. Args: sigma: Noise standard deviation in [0, 1] scale. ``None`` (default) auto-estimates via MAD. Typical range: 0.01--0.05 for moderate scanner/camera noise. Too high causes over-smoothing. wavelet: Wavelet family. ``'db2'`` (default) balances smoothness and locality; ``'db4'`` captures more detail. Must be orthogonal. mode: Thresholding mode. ``'soft'`` (default) produces smoother results for additive noise; ``'hard'`` preserves edges more. wavelet_levels: Decomposition depth. ``None`` (default) uses max-3 automatically. Higher values give finer denoising. clip: Clip output to [0, 1]. Default: ``True``. Set to ``False`` when using with variance-stabilizing transforms (e.g., GAT). Returns: Image: Input image with ``detect_mat`` denoised via universal wavelet thresholding. ``rgb`` and ``gray`` are unchanged. Best For: - Scanner banding and flatbed scanner noise removal. - High-ISO camera images where colony boundaries must remain sharp. - Agar granularity and condensation speckle suppression before detection. - Pre-filtering before edge detection to avoid noise amplification. Consider Also: - :class:`BayesShrinkEnhancer` for adaptive thresholding that preserves more detail in regions with varying noise levels. - :class:`BM3DDenoiser` for state-of-the-art structured noise removal at higher computational cost. - :class:`BilateralDenoise` for edge-preserving smoothing without wavelet decomposition. References: [1] D. L. Donoho and I. M. Johnstone, "Ideal spatial adaptation by wavelet shrinkage," *Biometrika*, vol. 81, no. 3, pp. 425--455, Sep. 1994. See Also: :doc:`/tutorials/notebooks/03_enhancing_before_detection` for a visual walkthrough of denoising pipelines on plate images. :doc:`/explanation/what_enhancement_does` for background on wavelet denoising and threshold selection strategies. """
[docs] def __init__( self, sigma: float | None = None, wavelet: str = "db2", mode: Literal["soft", "hard"] = "soft", wavelet_levels: int | None = None, clip: bool = True, ): """Initialize VisuShrink wavelet denoiser. Parameters: sigma (float | None): Noise standard deviation in [0, 1] scale. None (default) auto-estimates via median absolute deviation (MAD). For reference: 8-bit noise σ=10/255 ≈ 0.04 in normalized scale. Typical values: 0.01-0.05 for moderate scanner/camera noise. Start with auto-estimation, then tune if needed. wavelet (str): Wavelet type from PyWavelets. 'db2' (default) is a good general choice. 'db4' for more detail, 'sym2' for symmetry. Must be orthogonal (db*, sym*) for proper noise handling. mode (Literal['soft', 'hard']): Threshold type. 'soft' (default) produces smoother results for additive noise. 'hard' preserves edges more but may leave noise artifacts. wavelet_levels (int | None): Decomposition depth. None (default) uses max-3 automatically. Higher = finer denoising, slower. clip (bool): Whether to clip output to [0, 1] range. Default True. Set to False when using with variance-stabilizing transforms (e.g., GAT) that require preserving the original scale. """ self.sigma = sigma self.wavelet = wavelet self.mode = mode self.wavelet_levels = wavelet_levels self.clip = clip
def _operate(self, image: Image) -> Image: """Apply VisuShrink wavelet denoising to detection matrix. Returns: Modified Image with denoised detect_mat """ denoised = denoise_wavelet( image=image.detect_mat[:], sigma=self.sigma, wavelet=self.wavelet, mode=self.mode, wavelet_levels=self.wavelet_levels, method="VisuShrink", channel_axis=None, rescale_sigma=True, ) if self.clip: denoised = denoised.clip(0.0, 1.0) image.detect_mat[:] = denoised return image