Source code for phenotypic.enhance._contrast_streching

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from phenotypic._core._image import Image

import numpy as np
from skimage.exposure import rescale_intensity

from ..abc_ import ImageEnhancer


[docs] class ContrastStretching(ImageEnhancer): """Stretch the intensity range of detect_mat to fill the full dynamic range. Rescales pixel values based on lower and upper percentiles, compressing outliers (specular highlights, deep shadows) while expanding the range where colony intensities reside. Simpler and faster than CLAHE, with no local tile artifacts. Args: lower_percentile: Dark clipping point. Pixels below this percentile are mapped to the minimum. Typical range: 1--5. Default: 2. upper_percentile: Bright clipping point. Pixels above this percentile are mapped to the maximum. Typical range: 95--99. Default: 98. Returns: Image: Input image with ``detect_mat`` rescaled to the full dynamic range. ``rgb`` and ``gray`` are unchanged. Best For: - Plates with narrow histograms (under-exposed or low-contrast). - Normalizing exposure across different imaging sessions. - Quick preprocessing before global thresholding (Otsu, Triangle). Consider Also: - :class:`CLAHE` when illumination varies spatially across the plate. - :class:`HomomorphicFilter` when the primary issue is a brightness gradient rather than narrow dynamic range. See Also: :doc:`/how_to/notebooks/enhance_low_contrast` for a comparison of contrast enhancement methods. :doc:`/explanation/what_enhancement_does` for how enhancement fits into the pipeline model. """
[docs] def __init__(self, lower_percentile: int = 2, upper_percentile: int = 98): """ Parameters: lower_percentile (int): Dark clipping point. Increase to suppress deep shadows/edge artifacts; too high may remove meaningful dark background structure. Typical range: 1–5. upper_percentile (int): Bright clipping point. Decrease to suppress glare/highlights; too low may flatten bright colonies. Typical range: 95–99. """ self.lower_percentile = lower_percentile self.upper_percentile = upper_percentile
def _operate(self, image: Image) -> Image: p_lower, p_upper = np.percentile( image.detect_mat[:], (self.lower_percentile, self.upper_percentile) ) image.detect_mat[:] = rescale_intensity( image=image.detect_mat[:], in_range=(p_lower, p_upper), out_range=(0, 1), ) return image