Source code for phenotypic.refine._remove_border_objects
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from phenotypic._core._image import Image
import numpy as np
from phenotypic.abc_ import ObjectRefiner
[docs]
class RemoveBorderObjects(ObjectRefiner):
"""Remove colonies that touch or overlap the image border within a configurable margin.
Identifies all labeled objects whose pixels fall within the exclusion
band along any edge, then zeros those labels from ``objmap``. Colonies
that are fully interior are unaffected. Partial border colonies bias
area, perimeter, and shape measurements and should be excluded before
phenotyping.
For an overview of refinement strategies, see
:doc:`/explanation/refinement_strategies`.
Best For:
- Plates where the plate rim or scanner shadow truncates edge
colonies, making size measurements unreliable.
- Grid assays where border wells are only partially visible within
the image crop.
- Time-lapse or batch workflows where automated crops may shift
between frames, intermittently clipping the same edge colonies.
Consider Also:
- :class:`SmallObjectRemover` when the target artefacts are small
noise fragments scattered across the plate, not limited to the
border region.
- :class:`GridOversizedObjectRemover` when the problem is objects
that span multiple grid sections rather than partial border
colonies.
Args:
border_size: Width of the exclusion band along each image edge.
``None`` defaults to 1% of the smaller image dimension.
A float in ``(0, 1)`` is interpreted as a fraction of the
smaller image dimension. An integer or float ``>= 1`` is
treated as an absolute pixel count. Typical range: 1--30 px.
Default: 1.
Returns:
Image: Input image with ``objmap`` and ``objmask`` updated to
exclude all labeled objects that touch the exclusion border.
``rgb``, ``gray``, and ``detect_mat`` are unchanged.
Raises:
TypeError: If ``border_size`` is not an integer, float, or ``None``.
See Also:
:doc:`/how_to/notebooks/refine_noisy_boundaries` for a walkthrough
of border and edge refinement on real plate images.
:doc:`/explanation/refinement_strategies` for choosing the right
refinement sequence.
"""
border_size: int | float | None = 1
def _operate(self, image: Image) -> Image:
if self.border_size is None:
edge_size = int(np.min(image.shape[[1, 2]]) * 0.01)
elif type(self.border_size) is float and 0.0 < self.border_size < 1.0:
edge_size = int(np.min(image.shape) * self.border_size)
elif isinstance(self.border_size, (int, float)):
edge_size = self.border_size
else:
raise TypeError(
"Invalid edge size. Should be int, float, or None to use default edge size."
)
obj_map = image.objmap[:]
edges = [
obj_map[: edge_size - 1, :].ravel(),
obj_map[-edge_size:, :].ravel(),
obj_map[:, : edge_size - 1].ravel(),
obj_map[:, -edge_size:].ravel(),
]
edge_labels = np.unique(np.concatenate(edges))
for label in edge_labels:
obj_map[obj_map == label] = 0
image.objmap = obj_map
return image