phenotypic.abc_.GridObjectRefiner#

class phenotypic.abc_.GridObjectRefiner[source]#

Bases: ObjectRefiner, GridOperation, ABC

Abstract base class for post-detection refinement operations on grid-aligned plate images.

GridObjectRefiner is the grid-aware variant of ObjectRefiner, combining object mask refinement with grid structure awareness. It refines detected objects (colony masks and labeled maps) while respecting well boundaries and grid-aligned regions in arrayed plate images (96-well, 384-well, etc.). Like ObjectRefiner, it protects original image data (RGB, grayscale, enhanced grayscale) and modifies only detection results.

What is GridObjectRefiner?

GridObjectRefiner is the specialized version of ObjectRefiner for GridImage objects:

  • GridImage requirement: Accepts only GridImage input (with detected grid structure), enforced at runtime via GridImageInputError.

  • Grid-aware refinement: Can access well positions, grid cell boundaries, and row/column structure via image.grid to make refinement decisions (e.g., remove colonies that exceed well boundaries, filter by grid position).

  • Detection-only modification: Like ObjectRefiner, modifies only image.objmask[:] and image.objmap[:]. Original image components are protected via @validate_operation_integrity.

When to use GridObjectRefiner vs ObjectRefiner

  • ObjectRefiner: Use when refining detections on a plain Image without grid structure. Examples: general-purpose size filtering, morphological cleanup, shape filtering (applies globally regardless of position).

  • GridObjectRefiner: Use when refining detections on a GridImage where well structure matters. Examples: removing objects larger than their grid cell (GridOversizedObjectRemover), per-well filtering, grid-aligned edge removal. The grid structure enables position-aware refinement that improves array phenotyping accuracy.

Typical Use Cases

GridObjectRefiner is useful for addressing grid-specific artifacts:

  • Oversized colonies: Objects spanning nearly an entire well (merged colonies, agar edges, segmentation spillover). Filtering improves per-well consistency.

  • Inter-well artifacts: Detections touching or bridging grid cell boundaries from uneven lighting or thresholding errors.

  • Boundary contamination: Colonies near plate edges that are incomplete or distorted. Grid structure allows identifying and filtering boundary-adjacent objects.

  • Grid registration errors: When grid detection is imperfect, some objects may be mis-assigned to wells; grid-aware refinement can filter or relocate based on position.

Implementing a Custom GridObjectRefiner

Subclass GridObjectRefiner and implement _operate():

from phenotypic.abc_ import GridObjectRefiner
from phenotypic import GridImage
import numpy as np

class MyGridRefiner(GridObjectRefiner):
    def __init__(self, max_width_fraction: float = 0.9):
        super().__init__()
        self.max_width_fraction = max_width_fraction

    @staticmethod
    def _operate(image: GridImage, max_width_fraction: float = 0.9) -> GridImage:
        # Get grid info
        col_edges = image.grid.get_col_edges()
        max_cell_width = (col_edges[1:] - col_edges[:-1]).max()

        # Measure object widths
        objmap = image.objmap[:]
        from skimage.measure import regionprops_table

        props = regionprops_table(objmap, properties=['label', 'bbox'])
        # ... compute widths and filter ...

        return image

Key Rules

  1. _operate() must be static (for parallel execution).

  2. All parameters except image must exist as instance attributes.

  3. Only modify image.objmask[:] and image.objmap[:].

  4. Access grid via image.grid (row/column edges, well positions, metadata).

  5. Return the modified GridImage.

Grid Access Patterns

Within _operate(), access grid information via the GridImage accessor:

# Grid structure
nrows, ncols = image.grid.nrows, image.grid.ncols
row_edges = image.grid.get_row_edges()          # Row boundary positions (y-coordinates)
col_edges = image.grid.get_col_edges()          # Col boundary positions (x-coordinates)
cell_info = image.grid.info()                   # DataFrame with per-object grid info

# Per-object grid metadata (label, row, col, boundary flags)
grid_data = image.grid.info()  # pd.DataFrame with object properties

Notes

  • GridImage input required: apply() enforces GridImage type at runtime. Passing a plain Image raises GridImageInputError.

  • Protected components: The @validate_operation_integrity decorator ensures image.rgb, image.gray, image.enh_gray cannot be modified. Only image.objmask and image.objmap can be refined.

  • Immutability by default: apply(image) returns a modified copy. Set inplace=True for memory-efficient in-place modification.

  • Grid structure assumption: Your algorithm should assume a valid, registered grid. If grid metadata is unreliable, refinement may fail or produce wrong results.

  • Static _operate() requirement: Must be static for parallel execution in pipelines.

  • Parameter matching: All _operate() parameters except image must exist as instance attributes for automatic parameter matching.

Examples

Remove objects larger than their grid cell width
from phenotypic.abc_ import GridObjectRefiner
from phenotypic import GridImage
import numpy as np

class OversizedObjectRemover(GridObjectRefiner):
    '''Remove objects exceeding cell dimensions.'''

    def __init__(self):
        super().__init__()

    @staticmethod
    def _operate(image: GridImage) -> GridImage:
        # Get grid boundaries
        col_edges = image.grid.get_col_edges()
        row_edges = image.grid.get_row_edges()
        max_width = (col_edges[1:] - col_edges[:-1]).max()
        max_height = (row_edges[1:] - row_edges[:-1]).max()

        # Measure objects
        objmap = image.objmap[:]
        from skimage.measure import regionprops_table
        props = regionprops_table(objmap, properties=['label', 'bbox'])

        # Filter oversized
        import pandas as pd
        df = pd.DataFrame(props)
        df['width'] = df['bbox-2'] - df['bbox-0']
        df['height'] = df['bbox-3'] - df['bbox-1']
        keep = df[(df['width'] < max_width) &
                  (df['height'] < max_height)]['label'].values

        # Refine map
        refined = np.where(np.isin(objmap, keep), objmap, 0)
        image.objmap[:] = refined
        return image

# Usage on gridded plate image
from phenotypic.detect import OtsuDetector
image = GridImage.from_image_path('plate.jpg', nrows=8, ncols=12)
detected = OtsuDetector().apply(image)
cleaned = OversizedObjectRemover().apply(detected)
Chaining grid and non-grid refinements
from phenotypic import GridImage, ImagePipeline
from phenotypic.detect import OtsuDetector
from phenotypic.refine import SmallObjectRemover, GridOversizedObjectRemover

# Create detection pipeline with mixed refinements
pipeline = ImagePipeline()
pipeline.add(OtsuDetector())                        # Detect colonies
pipeline.add(SmallObjectRemover(min_size=100))      # Global size filter
pipeline.add(GridOversizedObjectRemover())          # Grid-aware filter

# Apply to gridded plate
image = GridImage.from_image_path('plate.jpg', nrows=8, ncols=12)
results = pipeline.operate([image])
refined_image = results[0]

print(f"Refined: {refined_image.objmap[:].max()} colonies")

Methods

__init__

apply

Applies the operation to an image, either in-place or on a copy.

dispose_widgets

Drop references to the UI widgets.

sync_widgets_from_state

Push internal state into widgets.

widget

Return (and optionally display) the root widget.

apply(image, inplace=False)[source]#

Applies the operation to an image, either in-place or on a copy.

Parameters:
  • image (Image) – The arr image to apply the operation on.

  • inplace (bool) – If True, modifies the image in place; otherwise, operates on a copy of the image.

Returns:

The modified image after applying the operation.

Return type:

Image

__del__()#

Automatically stop tracemalloc when the object is deleted.

__getstate__()#

Prepare the object for pickling by disposing of any widgets.

This ensures that UI components (which may contain unpickleable objects like input functions or thread locks) are cleaned up before serialization.

Note

This method modifies the object state by calling dispose_widgets(). Any active widgets will be detached from the object.

dispose_widgets() None#

Drop references to the UI widgets.

Return type:

None

sync_widgets_from_state() None#

Push internal state into widgets.

Return type:

None

widget(image: Image | None = None, show: bool = False) Widget#

Return (and optionally display) the root widget.

Parameters:
  • image (Image | None) – Optional image to visualize. If provided, visualization controls will be added to the widget.

  • show (bool) – Whether to display the widget immediately. Defaults to False.

Returns:

The root widget.

Return type:

ipywidgets.Widget

Raises:

ImportError – If ipywidgets or IPython are not installed.