"""Grid-aligned object refinement - keeps only grid-aligned colonies from detection results.
Refines detected colonies by filtering to keep only the dominant object in each grid cell,
useful for removing off-grid artifacts and enforcing grid structure on detection results.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Literal
if TYPE_CHECKING:
from phenotypic._core._image import Image
import gc
import numpy as np
from phenotypic.abc_ import ObjectRefiner
from phenotypic.tools_.mixin import GridInferenceMixin
from phenotypic.tools_.funcs_ import validate_operation_integrity
[docs]
class GridAlignmentRefiner(GridInferenceMixin, ObjectRefiner):
"""Retain only grid-aligned colonies by keeping the dominant object per cell.
Infers or reads grid geometry, partitions the image into cells, and keeps
one object per cell according to the chosen selection strategy. Off-grid
artifacts, dust, and spurious detections are removed, enforcing regular
grid structure on colony detection results.
Args:
smoothing_sigma: Gaussian smoothing sigma for row/column intensity
profiles during grid inference. Typical range: 0.5--5.0.
Higher values smooth noise but may merge adjacent peaks.
Default: 2.0.
min_peak_distance: Minimum pixel distance between detected grid
peaks. ``None`` auto-estimates as half the expected colony
spacing. Default: None.
peak_prominence: Minimum prominence for peak detection. ``None``
auto-calculates as 10% of signal range. Default: None.
edge_refinement: Refine grid edges using local intensity minima.
Improves accuracy for unevenly lit plates. Default: True.
selection_mode: Strategy for choosing one object per cell.
``"dominant"`` keeps the largest by pixel count, ``"centered"``
keeps the most centered, ``"regularized"`` fits a global
regular-grid model then re-selects. Default: ``"dominant"``.
Returns:
Image: Input image with ``objmap`` filtered to grid-aligned objects
and ``objmask`` updated to match.
Raises:
ValueError: If grid inference fails or image lacks detection results.
Best For:
- High-throughput arrayed plates (96-well, 384-well, pinned cultures)
where colonies should align with known well positions.
- Post-detection cleanup when detections contain off-grid artifacts.
- Explicit grid enforcement when used with GridImage and known grid
coordinates.
Consider Also:
- :class:`SineAlignmentRefiner` when colony intensities are
heterogeneous and rank-based correlation improves grid estimation.
- :class:`GridSectionLargest` for a simpler largest-per-cell strategy
on GridImage inputs.
- :class:`ReduceMultipleGridObjects` for regression-based multi-
detection reduction within grid cells.
See Also:
:doc:`/how_to/notebooks/refine_noisy_boundaries` for grid-based
cleanup workflows.
:doc:`/explanation/refinement_strategies` for a comparison of
grid refinement approaches.
"""
[docs]
def __init__(
self,
smoothing_sigma: float = 2.0,
min_peak_distance: int | None = None,
peak_prominence: float | None = None,
edge_refinement: bool = True,
selection_mode: Literal["dominant", "centered", "regularized"] = "dominant",
split_merged: bool = False,
):
"""Initialize GridAlignmentRefiner with grid inference parameters.
Args:
smoothing_sigma: Gaussian smoothing sigma for intensity profiles.
min_peak_distance: Minimum distance between grid peaks.
peak_prominence: Minimum prominence for peak detection.
edge_refinement: Enable edge refinement via local intensity minima.
selection_mode: Strategy for choosing the object per grid cell.
'dominant' (default) keeps the largest, 'centered' keeps
the most centred, 'regularized' uses a global fit.
split_merged: If True, pre-split merged colonies that span
multiple grid cells using EDT watershed before assignment.
Default False for refiners (splitting is more useful during
initial detection).
"""
super().__init__()
self.smoothing_sigma = smoothing_sigma
self.min_peak_distance = min_peak_distance
self.peak_prominence = peak_prominence
self.edge_refinement = edge_refinement
self.selection_mode = selection_mode
self.split_merged = split_merged
[docs]
@validate_operation_integrity("image.rgb", "image.gray", "image.detect_mat")
def apply(self, image: Image, inplace: bool = False) -> Image:
return super().apply(image=image, inplace=inplace)
def _operate(self, image: Image) -> Image:
"""Refine detected objects to grid-aligned colonies.
This method filters the object map to keep only the dominant object within each
grid cell. Objects are reassigned new labels (1, 2, 3, ...) to ensure contiguous
labeling after filtering.
Returns:
Image: Modified image with filtered objmap and updated objmask.
"""
from phenotypic import GridImage
# Get existing objmap
objmap = image.objmap[:]
# Determine grid edges (GridImage or infer)
if isinstance(image, GridImage):
row_edges = np.round(image.grid.get_row_edges()).astype(int)
col_edges = np.round(image.grid.get_col_edges()).astype(int)
else:
objmask = image.objmask[:]
# Use inherited mixin methods (call via class since _operate is static)
nrows, ncols = GridAlignmentRefiner._infer_grid_shape(objmask)
row_edges = GridAlignmentRefiner._estimate_edges(
objmask,
axis=0,
n_bins=nrows,
smoothing_sigma=self.smoothing_sigma,
min_peak_distance=self.min_peak_distance,
peak_prominence=self.peak_prominence,
)
col_edges = GridAlignmentRefiner._estimate_edges(
objmask,
axis=1,
n_bins=ncols,
smoothing_sigma=self.smoothing_sigma,
min_peak_distance=self.min_peak_distance,
peak_prominence=self.peak_prominence,
)
if self.edge_refinement:
row_edges = GridAlignmentRefiner._refine_edges(objmask, row_edges,
axis=0)
col_edges = GridAlignmentRefiner._refine_edges(objmask, col_edges,
axis=1)
# Clip and unique edges
row_edges = np.clip(np.unique(row_edges), 0, objmap.shape[0])
col_edges = np.clip(np.unique(col_edges), 0, objmap.shape[1])
# Assign objects per grid cell using selection strategy
refined_map = self._assign_grid_objects(
objmap, row_edges, col_edges, self.selection_mode, image._OBJMAP_DTYPE,
intensity=image.detect_mat[:], split_merged=self.split_merged,
)
# Update image with refined map
image.objmap[:] = refined_map
image.objmap.relabel(connectivity=1)
gc.collect()
return image