phenotypic.abc_.GridOperation#
- class phenotypic.abc_.GridOperation[source]#
Bases:
ImageOperation,ABCAbstract 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:
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.
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.”
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
gridattribute (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 locationWhen 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:
Requires grid information: Needs to access
image.gridto get well positions, row/column structure, or grid-aligned regions.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).
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
gridaccessor: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__Applies the operation to an image, either in-place or on a copy.
Drop references to the UI widgets.
Push internal state into widgets.
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.
- __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:
- Returns:
The root widget.
- Return type:
ipywidgets.Widget
- Raises:
ImportError – If ipywidgets or IPython are not installed.