Source code for phenotypic.detect._manual_detector
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from phenotypic._core._image import Image
from skimage.segmentation import clear_border
from ..abc_ import ThresholdDetector
[docs]
class ManualDetector(ThresholdDetector):
"""Detect colonies by applying a user-specified intensity threshold.
Apply a fixed intensity cutoff to the plate detection matrix, producing a
binary colony mask without any automatic threshold computation. This
gives explicit control over detection sensitivity and is the preferred
approach when empirical testing has identified an optimal threshold for a
specific imaging setup. For a full comparison see
:doc:`/explanation/detection_strategies_compared`.
Args:
threshold: Intensity cutoff for binary segmentation. Pixels with
intensity >= *threshold* become colony (True), others become
background (False). For 8-bit images the valid range is
0--255; for 16-bit images 0--65535; for float images 0.0--1.0.
Higher values are more conservative (fewer colonies detected);
lower values are more sensitive (more colonies, more noise).
Default 0.5. Start by inspecting the image histogram to find
the valley between background and colony peaks.
ignore_zeros: If True (default), exclude zero-intensity pixels from
processing. Enable for plates with black borders or masked
regions.
ignore_borders: If True (default), remove colonies touching image
edges via ``clear_border()``. Recommended for grid-based colony
counting.
Returns:
Image: Input image with ``objmask`` set to a binary colony mask
(True = colony, False = background). ``objmap`` is not modified.
Raises:
ValueError: If *threshold* is negative.
Best For:
* Standardised imaging setups where the optimal threshold has been
determined empirically and remains stable across plates.
* Overriding automatic methods (Otsu, triangle, etc.) that
consistently over- or under-segment on a particular plate type.
* High-contrast plates where colonies are uniformly bright or dark
relative to background and a single cutoff cleanly separates
foreground from background.
* Reproducibility-critical workflows where a fixed numeric threshold
eliminates variability introduced by automatic selection.
Consider Also:
* :class:`OtsuDetector` when an automatic, parameter-free threshold
is preferred and the histogram is bimodal.
* :class:`HysteresisDetector` when colony intensity varies across
the plate and a single threshold cannot capture all colonies.
* :class:`TriangleDetector` when colonies are sparse and the
histogram is skewed toward background.
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,
threshold: float = 0.5,
ignore_zeros: bool = False,
ignore_borders: bool = True
):
self.threshold = threshold
self.ignore_zeros = ignore_zeros
self.ignore_borders = ignore_borders
def _operate(self, image: Image) -> Image:
"""Apply manual binary thresholding to the detection matrix image.
This function modifies the input image by applying a user-specified threshold
to its enhanced matrix (``detect_mat``). Pixels with intensity >= threshold
become foreground (True in the binary mask), pixels < threshold become
background (False). The resulting binary mask is stored in the image's
``objmask`` attribute.
Args:
image: The input image object. Must have an ``detect_mat`` attribute
(detection matrix for processing). Optionally uses
``bit_depth`` to validate threshold range.
Returns:
Image: The input image with its ``objmask`` attribute updated to the
computed binary mask.
Raises:
ValueError: If threshold is negative or exceeds the image's intensity
range (inferred from bit depth if available).
"""
enh_matrix = image.detect_mat[:]
# Validate threshold range
if self.threshold < 0:
raise ValueError(f"Threshold must be non-negative, got {self.threshold}")
# Apply threshold
mask = enh_matrix >= self.threshold
# Optionally clear borders
mask = clear_border(mask) if self.ignore_borders else mask
# Set objmask
image.objmask = mask
return image
# Set the docstring so that it appears in the sphinx documentation
ManualDetector.apply.__doc__ = ManualDetector._operate.__doc__