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 convergence. The resulting binary mask separates colony pixels from agar background. Works best when both classes have similar variance and roughly balanced pixel counts. For a full comparison see :doc:`/explanation/detection_strategies_compared`. Args: ignore_zeros: Exclude zero-intensity pixels from threshold computation. Enable for plates with black borders or masked regions; disable only when zero is a meaningful intensity value. Default: True. 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 binary mask and ``objmap`` set to labeled connected components. Raises: ValueError: If threshold computation fails (e.g., degenerate histogram with insufficient intensity variation). 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 where the histogram is approximately symmetric around the threshold. 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 and a single threshold under-segments faint regions. 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` Step-by-step tutorial for basic colony detection. :doc:`/how_to/notebooks/choose_detection_algorithm` Guide for selecting the right detector for your plate images. :doc:`/explanation/detection_strategies_compared` In-depth comparison of all detection strategies. """ def __init__(self, ignore_zeros: bool = False, ignore_borders: bool = True): self.ignore_zeros = ignore_zeros self.ignore_borders = ignore_borders 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__