phenotypic.abc_.GridOperation#

class phenotypic.abc_.GridOperation(*args, **kwargs)[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.

Quick Decision Guide

Use GridOperation when: - Your operation requires grid structure information (well positions, row/column layout) - You’re processing arrayed plate images with regular grid layouts (96-well, 384-well) - Your algorithm needs per-well analysis or grid-aligned regions - You want to enforce that input must be GridImage (type safety)

Use ImageOperation when: - Your operation works on general Image objects regardless of grid state - You’re doing global preprocessing, detection, or measurement - Your algorithm doesn’t depend on well structure or grid alignment - Your operation should accept both Image and GridImage inputs

Combining GridOperation with ImageOperation: - GridOperation is typically paired with other ABCs (ObjectDetector, ImageCorrector, etc.) - Use multiple inheritance: class GridObjectDetector(ObjectDetector, GridOperation, ABC) - GridOperation adds type safety without changing algorithm implementation - Most grid operations inherit from both a specific ABC and GridOperation

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 key 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:

  • GridObjectDetector: Detects objects using grid structure. Subclasses implement well-level colony detection on gridded plates.

  • GridCorrector: Corrects grid alignment, rotation, and per-well color correction. Improves grid positioning and well-level alignment.

  • GridObjectRefiner: Refines detection masks at the well level. Filters and adjusts masks based on well location and size constraints.

  • GridMeasureFeatures: Extracts per-well measurements. Computes features organized by grid coordinates rather than globally.

  • GridFinder: Detects grid structure from object positions. Assigns detected objects to grid cells and determines well locations.

Multiple Inheritance Pattern

Most GridOperation subclasses use multiple inheritance to combine operation behavior with grid type safety:

  • Combine with ObjectDetector: class GridObjectDetector(ObjectDetector, GridOperation, ABC)

  • Combine with ImageCorrector: class GridCorrector(ImageCorrector, GridOperation, ABC)

  • Combine with any operation ABC: class CustomGridOp(SomeABC, GridOperation, ABC)

The inheritance order matters: specific ABC first, then GridOperation.

Example of multiple inheritance pattern:

>>> from phenotypic.abc_ import ImageOperation, GridOperation
>>> from phenotypic import GridImage, Image
>>> # Concrete implementation combining ObjectDetector + GridOperation
>>> # class GridObjectDetector(ObjectDetector, GridOperation, ABC):
>>> #     def _operate(self, image: GridImage) -> GridImage:
>>> #         # Implementation uses grid structure from image.grid
>>> #         return image

This combines:

  • Operation 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.

Type Safety Example

GridOperation enforces type checking at apply() time to catch errors early:

>>> from phenotypic import Image, GridImage
>>> from phenotypic.abc_ import GridOperation
>>> # When a GridOperation is called with wrong type:
>>> # detector = SomeGridOperation()  # subclass of GridOperation
>>> # result = detector.apply(Image('plain.jpg'))  # Raises GridImageInputError
>>> # result = detector.apply(GridImage('plate.jpg', nrows=8, ncols=12))  # OK

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.

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 GridFinder operations that detect grid structure. GridFinder detects grid positions and creates a GridImage suitable for downstream GridOperation subclasses.

Examples

Using a GridOperation subclass with GridImage:

>>> from phenotypic import GridImage
>>> from phenotypic.data import load_synth_yeast_plate
>>> from phenotypic.detect import GridObjectDetector
>>> # Load plate image with grid info
>>> image = load_synth_yeast_plate()  # GridImage with detected colonies
>>> grid_image = image
>>> # Apply a grid-aware detector (subclass of GridObjectDetector)
>>> # GridImage is required - type-safe operation
>>> # detector = GridObjectDetector()  # Concrete subclass in practice
>>> # detected = detector.apply(grid_image)

Type safety: GridOperation prevents misuse:

>>> from phenotypic import Image, GridImage
>>> from phenotypic.enhance import GaussianBlur
>>> from phenotypic.data import load_synth_yeast_plate
>>> image = Image('generic.jpg')  # Plain Image
>>> grid_image = load_synth_yeast_plate()  # 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 only
>>> # detector = SomeGridOperation()  # 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.

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.

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.