Source code for phenotypic.refine._small_object_remover

from __future__ import annotations

from typing import TYPE_CHECKING, Annotated

if TYPE_CHECKING:
    from phenotypic._core._image import Image

from skimage.morphology import remove_small_objects

from ..abc_ import ObjectRefiner
from ..sdk_.typing_ import TuneSpec


[docs] class SmallObjectRemover(ObjectRefiner): """Remove detected objects below a minimum pixel area from the detection mask. Eliminates dust particles, condensation specks, agar debris, and noise fragments that appear as small labeled regions after thresholding or edge detection. Reduces false-positive colony counts and stabilizes downstream measurements. For guidance on choosing the right refinement sequence, see :doc:`/explanation/refinement_strategies`. Best For: - Cleaning up salt-and-pepper artefacts left after aggressive thresholding or Frangi filtering. - Removing fragmented debris and micro-condensation specks that cluster around genuine colonies. - Post-processing dense detection masks where noise fragments inflate colony counts. - Stabilizing counts before grid assignment or size measurement. Consider Also: - :class:`RemoveBorderObjects` for removing partial colonies at image edges regardless of their size. - :class:`RemoveLowCircularity` for discarding non-circular artefacts regardless of area. - :class:`SmallToLargeMerger` when small fragments should be absorbed into a nearby large colony rather than discarded. Args: min_size: Minimum object area in pixels to retain. Objects whose pixel area falls strictly below this value are removed. Scale with image resolution, since colony area grows roughly with the square of dpi; as a rough starting point, try 20--100 px near 300 dpi and 100--500 px near 1200 dpi. Default: 64. Returns: Image: Input image with ``objmap`` updated to exclude objects smaller than ``min_size``. ``objmask`` is updated to match. See Also: :doc:`/how_to/notebooks/refine_noisy_boundaries` for a walkthrough of refinement operations on real plate images. :doc:`/explanation/refinement_strategies` for choosing the right refinement sequence. """ min_size: Annotated[int, TuneSpec(20, 500, log=True)] = 64 def _operate(self, image: Image) -> Image: objmap = image.objmap[:] # remove_small_objects warns when given a label image with a single # non-zero label (treats it as ambiguous binary vs labeled). Pass a # boolean array in that case; re-label afterwards if needed. if objmap.max() <= 1: cleaned = remove_small_objects( objmap.astype(bool), min_size=self.min_size ) image.objmap[:] = cleaned.astype(objmap.dtype) else: image.objmap[:] = remove_small_objects(objmap, min_size=self.min_size) return image