Source code for phenotypic.refine._residual_outlier_remover
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from phenotypic._core._grid_image import GridImage
import numpy as np
from typing import Optional
from phenotypic.abc_ import GridObjectRefiner
from phenotypic.measure import MeasureGridLinRegStats
from phenotypic.tools_.measurement_info_ import GRID_LINREG_STATS, GRID
[docs]
class ResidualOutlierRemover(GridObjectRefiner):
"""Remove objects with large positional residuals in noisy grid rows or columns.
Fits linear-regression trends to colony centroids along each row and
column, identifies rows/columns with high variability (coefficient of
variance above threshold), and removes objects whose residual error
exceeds an IQR-based cutoff within those noisy lines.
Args:
axis: Axis to analyze. ``None`` analyzes both rows and columns,
``0`` rows only, ``1`` columns only. Restricting the axis
speeds processing and targets known problem directions.
Default: None.
stddev_multiplier: IQR-based cutoff multiplier for outlier removal.
Lower values prune more aggressively; higher values are
conservative. Typical range: 1.0--3.0. Default: 1.5.
max_coeff_variance: Maximum coefficient of variance (std/mean)
allowed before a row/column is considered noisy and eligible
for outlier pruning. Typical range: 1--5. Default: 1.
Returns:
Image: Input image with ``objmap`` and ``objmask`` updated to exclude
positional outliers from noisy grid rows/columns.
Best For:
- Cleaning rows or columns with off-grid detections from condensation,
glare, or debris before measuring growth.
- Stabilizing grid registration when a subset of positions is noisy.
- Plates where most grid lines are well-aligned but a few contain
spurious artifacts.
Consider Also:
- :class:`ReduceMultipleGridObjects` for reducing multi-detections to
one per cell rather than pruning outliers within rows/columns.
- :class:`GridAlignmentRefiner` for full grid-aware filtering using
dominant-object-per-cell selection.
- :class:`GridOversizedObjectRemover` when the problem is oversized
detections rather than positional outliers.
See Also:
:doc:`/how_to/notebooks/refine_noisy_boundaries` for grid-based
outlier removal workflows.
:doc:`/explanation/refinement_strategies` for a comparison of
grid refinement approaches.
"""
[docs]
def __init__(
self,
axis: Optional[int] = None,
stddev_multiplier=1.5,
max_coeff_variance: int = 1,
):
"""Initialize the remover.
Args:
axis (Optional[int]): Axis selection for analysis. ``None`` runs
both directions; ``0`` rows; ``1`` columns. Limiting the axis
reduces runtime and targets known problem directions.
stddev_multiplier (float): Robust residual cutoff multiplier. Lower
values remove more outliers (stronger cleanup) but risk dropping
valid off-center colonies; higher values are conservative.
max_coeff_variance (int): Threshold for row/column variability
(std/mean) to trigger outlier analysis. Lower values clean more
lines; higher values only address extremely noisy lines.
Raises:
ValueError: If parameters are not consistent with the operation
(e.g., invalid types). Errors may arise during execution when
measuring grid statistics.
"""
self.axis = axis # Either none for both axis, 0 for row, or 1 for column
self.cutoff_multiplier = stddev_multiplier
self.max_coeff_variance = max_coeff_variance
def _operate(self, image: GridImage) -> GridImage:
"""Identify and remove residual outliers per noisy row/column.
Args:
image (GridImage): Grid image with object map and grid metadata.
Returns:
GridImage: Modified grid image with outlier objects removed.
Raises:
ValueError: If parameters are misconfigured in a way that prevents
computation (propagated from measurement utilities).
"""
# Generate cached version of grid_info
linreg_stat_extractor = MeasureGridLinRegStats()
grid_info = linreg_stat_extractor.measure(image)
# Create container to hold the id of objects to be removed
outlier_obj_ids = []
# Row-wise residual outlier discovery
if self.axis is None or self.axis == 0:
# Calculate the coefficient of variance (std/mean)
# Collect the standard deviation
row_variance = grid_info.groupby(str(GRID.ROW_NUM))[
str(GRID_LINREG_STATS.RESIDUAL_ERR)
].std()
# Divide standard deviation by mean
row_variance = (
row_variance
/ grid_info.groupby(str(GRID.ROW_NUM))[
str(GRID_LINREG_STATS.RESIDUAL_ERR)
].mean()
)
over_limit_row_variance = row_variance.loc[
row_variance > self.max_coeff_variance
]
# Collect outlier objects in the nrows with a variance over the maximum
for row_idx in over_limit_row_variance.index:
row_err = grid_info.loc[
grid_info.loc[:, str(GRID.ROW_NUM)] == row_idx,
str(GRID_LINREG_STATS.RESIDUAL_ERR),
]
row_err_mean = row_err.mean()
row_q3, row_q1 = row_err.quantile([0.75, 0.25])
row_iqr = row_q3 - row_q1
# row_stddev = row_err.std()
# upper_row_cutoff = row_err_mean + row_stddev * self.cutoff_multiplier
upper_row_cutoff = row_err_mean + row_iqr * self.cutoff_multiplier
outlier_obj_ids += row_err.loc[
row_err >= upper_row_cutoff
].index.tolist()
# Column-wise residual outlier discovery
if self.axis is None or self.axis == 1:
# Calculate the coefficient of variance (std/mean)
# Collect the standard deviation
col_variance = grid_info.groupby(str(GRID.COL_NUM))[
str(GRID_LINREG_STATS.RESIDUAL_ERR)
].std()
# Divide standard deviation by mean
col_variance = (
col_variance
/ grid_info.groupby(str(GRID.COL_NUM))[
str(GRID_LINREG_STATS.RESIDUAL_ERR)
].mean()
)
over_limit_col_variance = col_variance.loc[
col_variance > self.max_coeff_variance
]
# Collect outlier objects in the columns with a variance over the maximum
for col_idx in over_limit_col_variance.index:
col_err = grid_info.loc[
grid_info.loc[:, str(GRID.COL_NUM)] == col_idx,
str(GRID_LINREG_STATS.RESIDUAL_ERR),
]
col_err_mean = col_err.mean()
col_q3, col_q1 = col_err.quantile([0.75, 0.25])
col_iqr = col_q3 - col_q1
# col_stddev = col_err.std()
upper_col_cutoff = col_err_mean + col_iqr * self.cutoff_multiplier
outlier_obj_ids += col_err.loc[
col_err >= upper_col_cutoff
].index.tolist()
# Remove objects from obj map
image.objmap[np.isin(image.objmap[:], outlier_obj_ids)] = 0
return image