Source code for phenotypic.detect._isodata_detector

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from phenotypic._core._image import Image
from skimage.filters import threshold_isodata
from skimage.segmentation import clear_border

from ..abc_ import ThresholdDetector


[docs] class IsodataDetector(ThresholdDetector): """Detect colonies by iterative ISODATA clustering of the intensity histogram. Iteratively partition pixels into foreground and background classes by computing class means, then refine the threshold until the estimate converges. The resulting binary mask separates colony pixels from agar background and works best when both pixel populations have similar variance and roughly balanced counts. For a full comparison of detection strategies see :doc:`/explanation/detection_strategies_compared`. Best For: - Plates where colony and background pixel counts are roughly balanced. - Medium-contrast images where iterative refinement improves on a single-pass threshold. - Standardised imaging setups producing consistently symmetric histograms across plates. Consider Also: - :class:`OtsuDetector` for a faster single-pass threshold when the histogram is clearly bimodal. - :class:`LiDetector` when the histogram is skewed or noise dominates one side of the distribution. - :class:`HysteresisDetector` when colony brightness varies across the plate and a single threshold under-segments faint regions. Args: ignore_zeros: Exclude zero-intensity pixels from the histogram before computing the threshold. Enable for plates with black borders or masked regions. Default: False. ignore_borders: Remove colonies touching image edges via ``clear_border()``. Recommended for grid-based colony counting to eliminate partial colonies at plate boundaries. Default: True. Returns: Image: Input image with ``objmask`` set to the binary colony mask and ``objmap`` set to labeled connected components. Raises: ValueError: If threshold computation fails due to a degenerate histogram with insufficient intensity variation. References: [1] T. W. Ridler and S. Calvard, "Picture thresholding using an iterative selection method," *IEEE Trans. Syst., Man, Cybern.*, vol. 8, no. 8, pp. 630--632, 1978. See Also: :doc:`/tutorials/notebooks/02_detecting_colonies` for a step-by-step tutorial on basic colony detection. :doc:`/how_to/notebooks/choose_detection_algorithm` for guidance on selecting the right detector for your plate images. :doc:`/explanation/detection_strategies_compared` for an in-depth comparison of all thresholding strategies. """ ignore_zeros: bool = False ignore_borders: bool = True def _operate(self, image: Image) -> Image: """Binarizes the given image matrix using the ISODATA threshold method. This function modifies the arr image by applying a binary mask to its enhanced matrix (`detect_mat`). The binarization threshold is automatically determined using ISODATA method. The resulting binary mask is stored in the image's `objmask` attribute. Args: image (Image): The arr image object. It must have an `detect_mat` attribute, which is used as the basis for creating the binary mask. Returns: Image: The arr image object with its `objmask` attribute updated to the computed binary mask other_image. """ enh_matrix = image.detect_mat[:] nbins = 2**image.bit_depth mask = image.detect_mat[:] >= threshold_isodata( enh_matrix[enh_matrix != 0] if self.ignore_zeros else enh_matrix, nbins=nbins, ) mask = clear_border(mask) if self.ignore_borders else mask image.objmask = mask return image
# Set the docstring so that it appears in the sphinx documentation IsodataDetector.apply.__doc__ = IsodataDetector._operate.__doc__