Source code for phenotypic.refine._keep_nearest_center

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from phenotypic._core._image import Image

from scipy.spatial.distance import euclidean

from phenotypic.abc_ import ObjectRefiner
from phenotypic.schema import BBOX


[docs] class KeepNearestCenter(ObjectRefiner): """Retain only the object whose bounding-box center lies closest to the image center. Computes the Euclidean distance from each detected object's bounding-box center to the image center and discards all objects except the single nearest. The operation produces an ``objmap`` with at most one labeled region. It is most effective on per-cell crops where the intended colony occupies the center of the field and peripheral detections are debris or bleed-through from adjacent wells. For an overview of refinement approaches, see :doc:`/explanation/refinement_strategies`. Best For: - Single-colony crops produced by grid-based workflows where debris and condensation appear near the crop boundary. - Automated pipelines that assume exactly one colony per field of view and need a robust single-object guarantee. - Post-crop cleanup on images extracted from 96-well or 384-well grids where bleed-through from neighbouring cells is visible at the edges. Consider Also: - :class:`KeepSectionLargest` when the largest object per grid cell is a more reliable proxy for the true colony than spatial centrality. - :class:`SmallObjectRemover` when peripheral artefacts are consistently smaller than the genuine colony and size is the better discriminant. - :class:`ReduceSectionsByLine` for grid-aware multi-detection reduction using expected positional regression rather than centroid distance. Returns: Image: Input image with ``objmap`` reduced to the single object whose bounding-box center is closest to the image center; all other objects are set to background. See Also: :doc:`/how_to/notebooks/refine_noisy_boundaries` for single-object cleanup workflows on cropped plate images. """ def _operate(self, image: Image): img_center_cc = image.shape[1] // 2 img_center_rr = image.shape[0] // 2 bound_info = image.objects.info() # Add a column to the bound info for center deviation bound_info.loc[:, "Measurement_CenterDeviation"] = bound_info.apply( lambda row: euclidean( u=[row[str(BBOX.CENTER_CC)], row[str(BBOX.CENTER_RR)]], v=[img_center_cc, img_center_rr], ), axis=1, ) # Get the label of the obj w/ the least deviation obj_to_keep = bound_info.loc[:, "Measurement_CenterDeviation"].idxmin() # Get a working copy of the object map objmap = image.objmap[:] # Set Image object map to new other_image image.objmap[objmap != obj_to_keep] = 0 return image