phenotypic.abc_.GridOperation#

class phenotypic.abc_.GridOperation[source]#

Bases: ImageOperation, ABC

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

GridOperation is a marker abstract base class that enforces type safety for operations designed to work exclusively with GridImage objects. It’s a lightweight subclass of ImageOperation that overrides the apply() method to require a GridImage input instead of a generic Image.

What is GridOperation?

GridOperation exists to distinguish between two categories of image operations:

  • ImageOperation: Works on single, unaligned Image objects. The image may or may not have grid information. Used for general-purpose preprocessing, detection, and measurement. Examples: GaussianBlur, OtsuDetector, MeasureColorComposition.

  • GridOperation: Works only on GridImage objects that have grid structure information (row/column layout of wells on an agar plate). The operation assumes grid information is present and available. Used for grid-aware operations where well-level analysis or grid alignment is required. Examples: GridObjectDetector, GridCorrector, GridRefiner.

Why GridOperation exists

GridOperation provides three benefits:

  1. Type Safety: The apply() method signature requires a GridImage argument, catching misuse at runtime if someone tries to apply a grid operation to a plain Image.

  2. Intent Clarity: Developers can immediately see which operations require grid information, making the design space clear: “Use ImageOperation for general image ops, GridOperation for plate-specific grid-aware ops.”

  3. Documentation: Allows documentation and tutorials to clearly distinguish operations by their input type requirements.

What is GridImage?

GridImage is a specialized Image subclass that adds grid structure information:

  • Inherits from Image: All standard image capabilities (RGB, grayscale, color spaces, object detection results, etc.) are available.

  • Adds grid field: Contains a grid attribute (GridInfo object) storing the detected or specified grid layout (row/column positions, cell dimensions, rotation angle).

  • Arrayed plate context: Represents images of agar plates with samples arranged in regular grids (96-well, 384-well, 1536-well formats). Typical nrows=8, ncols=12 for 96-well plates.

  • Grid accessors: Via image.grid, provides row/column counts, well positions, and grid-related metadata.

GridOperation subclasses

Most concrete grid operations inherit from BOTH a specific operation ABC (like ObjectDetector) AND GridOperation to create specialized grid-aware variants:

GridOperation (marker ABC)
├── GridObjectDetector (inherits ObjectDetector + GridOperation)
│   ├── GridInstanceDetector, GridThresholdDetector, GridCannyDetector, ...
│   └── Use for: well-level colony detection on gridded plates
│
├── GridCorrector (inherits ImageCorrector + GridOperation)
│   ├── GridAligner, ...
│   └── Use for: grid alignment, rotation, color correction per-well
│
└── GridObjectRefiner (inherits ObjectRefiner + GridOperation)
    ├── GridSizeRefiner, ...
    └── Use for: per-well mask refinement, filtering by well location

When to use GridOperation vs ImageOperation

  • ImageOperation: Input is a plain Image with unknown grid state. Typical use: preprocessing (blur, contrast), general-purpose detection, color measurements that don’t depend on grid layout.

  • GridOperation: Input is a GridImage with detected/specified grid structure. Typical use: well-level analysis, grid-based refinement, operations that reference well positions or grid-aligned regions.

  • Overlap: Some operations work on both. E.g., a ColorComposition measurement can apply to an Image, but a GridColorComposition can specialize to per-well measurements on a GridImage.

When to subclass GridOperation

Subclass GridOperation when your operation:

  1. Requires grid information: Needs to access image.grid to get well positions, row/column structure, or grid-aligned regions.

  2. Operates on well-level data: Processes colonies at the well level rather than globally on the image (e.g., per-well filtering, well-based alignment).

  3. Makes assumptions about grid structure: Your algorithm assumes a regular grid layout and would fail or produce nonsensical results on an image without grid info.

Otherwise, subclass ImageOperation instead. GridOperation operations are more specialized and less broadly applicable.

Multiple inheritance pattern

Most GridOperation subclasses use multiple inheritance:

class GridObjectDetector(ObjectDetector, GridOperation, ABC):
    '''Detects objects using grid structure.'''
    def apply(self, image: GridImage, inplace=False) -> GridImage:
        if not isinstance(image, GridImage):
            raise GridImageInputError
        return super().apply(image=image, inplace=inplace)

This combines:

  • ObjectDetector behavior: Sets image.objmask and image.objmap, with integrity checks.

  • GridOperation type safety: Requires GridImage input, enforced at runtime.

  • ABC pattern: Subclasses implement _operate() with grid-aware logic.

The key insight: GridOperation is just a type annotation layer over ImageOperation that makes the grid requirement explicit in the method signature.

Notes

  • GridOperation is a marker class with no implementation. It only overrides apply() to specify the GridImage type and enforce input validation.

  • GridImage inherits all Image functionality. Grid information is accessed via the grid accessor: image.grid.nrows, image.grid.ncols, etc.

  • If you’re unsure whether your operation needs GridOperation, ask: “Does this algorithm fundamentally depend on grid structure?” If yes, use GridOperation. If it works equally well on plain Images, use ImageOperation.

  • GridImage is typically created with ImageGridHandler or GridFinder operations that detect grid structure. GridFinder is an ImageOperation, but the result is a GridImage suitable for downstream GridOperation subclasses.

Examples

Using a GridOperation subclass
from phenotypic import GridImage
from phenotypic.detect import GridObjectDetector

# Load plate image (96-well)
grid_image = GridImage('plate_scan.jpg', nrows=8, ncols=12)

# Apply a grid-aware detector (subclass of GridObjectDetector)
# This operation requires GridImage and uses well structure
detector = GridObjectDetector()  # Concrete subclass
detected = detector.apply(grid_image)  # Type-safe: GridImage -> GridImage

# Access detected colonies per well
for well_row in range(grid_image.nrows):
    for well_col in range(grid_image.ncols):
        # Per-well analysis available because operation is grid-aware
        pass
Understanding the type safety benefit
from phenotypic import Image, GridImage
from phenotypic.enhance import GaussianBlur
from phenotypic.detect import GridObjectDetector

image = Image.from_image_path('generic.jpg')  # Plain Image
grid_image = GridImage('plate.jpg')           # GridImage

# ImageOperation (GaussianBlur) accepts both
enhancer = GaussianBlur(sigma=2)
result1 = enhancer.apply(image)       # OK: Image -> Image
result2 = enhancer.apply(grid_image)  # OK: GridImage -> GridImage

# GridOperation requires GridImage
detector = GridObjectDetector()  # Subclass of GridOperation
result3 = detector.apply(grid_image)  # OK: GridImage -> GridImage
# result4 = detector.apply(image)  # ERROR: raises GridImageInputError

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: GridImage, inplace: bool = False) GridImage[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.