Source code for phenotypic.abc_._grid_measure

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from phenotypic._core._grid_image import GridImage

import pandas as pd

from phenotypic.abc_ import MeasureFeatures
from phenotypic.sdk_.exceptions_ import GridImageInputError, OutputValueError
from phenotypic.sdk_.funcs_ import validate_measure_integrity
from abc import ABC


[docs] class GridMeasureFeatures(MeasureFeatures, ABC): """Extract feature measurements from detected colonies in GridImage objects. GridMeasureFeatures is a type-safe wrapper around MeasureFeatures that enforces GridImage input type. It is to MeasureFeatures what GridOperation is to ImageOperation: a specialization for grid-aware (arrayed plate) analysis. **What is GridMeasureFeatures?** GridMeasureFeatures enables measurements that leverage well structure in arrayed plate images: - **Type-safe wrapper:** Enforces GridImage input (guaranteed valid grid structure with rows, cols, well positions). - **Grid-aware measurements:** Access per-well positions, row/column layout via image.grid.info(), enabling well-normalized and position-aware metrics. - **Per-well metrics:** Compute measurements per colony but with awareness of which well it belongs to, well size, and row/column position. - **Relationship to MeasureFeatures:** Inherits all methods from MeasureFeatures (parent class). Only difference is stricter input type validation (GridImage required) plus guaranteed access to grid information. - **Output format:** Returns pandas.DataFrame with one row per detected colony, matching image.objmap labels. Can easily map back to grid layout. - **Well-normalized analysis:** Enables computing metrics relative to well size/position (e.g., colony area as fraction of well area, normalized by well position on plate). **Purpose** Use GridMeasureFeatures when implementing measurement operations that extract quantitative metrics from colonies in grid-structured agar plate images. Like MeasureFeatures, it returns pandas DataFrames with one row per detected colony. The only difference is that it requires GridImage input, making explicit that your measurement may leverage grid structure (well positions, row/column layout) if desired. **GridImage vs Image** - **Image:** Generic image with optional, unvalidated grid information. - **GridImage:** Specialized Image subclass with validated grid structure (row/column layout, well positions, grid alignment). Suitable for 96-well, 384-well, or other arrayed plate formats. **Quick Decision Guide** Choose GridMeasureFeatures vs MeasureFeatures: - **GridMeasureFeatures:** Measurement depends on or benefits from well structure. Examples: per-well metrics, well-normalized values, measurements filtered by well position, edge well exclusion. - **MeasureFeatures:** Measurement works equally well on any Image. Examples: colony size, color, morphology computed globally without well awareness or position dependency. - **Type safety:** GridMeasureFeatures enforces GridImage; MeasureFeatures accepts any Image (grid optional). - **Well-level data:** Use GridMeasureFeatures when you need image.grid.info() for per-well analysis and when grid structure is essential to your measurement. - **Multi-well experiments:** GridMeasureFeatures simplifies tracking which well each colony belongs to, row/column position, and inter-well comparisons. - **Subclass reference:** Both GridMeasureSize and GridMeasureShape inherit from GridMeasureFeatures. - **Performance:** GridMeasureFeatures has minimal overhead vs MeasureFeatures; use it when grid structure matters. - **Flexibility:** If uncertain whether you need grid structure, start with MeasureFeatures; upgrade to GridMeasureFeatures if well-aware logic becomes necessary. **Implementation Pattern** Inherit from GridMeasureFeatures and implement ``_operate()`` as an instance method: .. code-block:: python from phenotypic.abc_ import GridMeasureFeatures from phenotypic import GridImage from phenotypic.schema import OBJECT import pandas as pd class GridMeasureWellOccupancy(GridMeasureFeatures): '''Measure fraction of well area occupied by colonies.''' normalize: bool = True # Annotated class-level field def _operate(self, image: GridImage) -> pd.DataFrame: # image is guaranteed to be GridImage with validated grid structure grid_info = image.grid.info() # Access well positions and layout # grid_info contains: 'grid_shape', 'well_centers', 'well_size', etc. nrows, ncols = grid_info['grid_shape'] well_size = grid_info['well_size'] # Calculate area occupied by colonies using MeasureFeatures methods area = self._calculate_sum(image.objmask[:], image.objmap[:]) # Optional: normalize by well size for grid-aware analysis if self.normalize and well_size > 0: area = area / (well_size ** 2) # Build results DataFrame with colony labels (required by contract) results = pd.DataFrame({'WellArea': area}) results.insert(0, OBJECT.LABEL, image.objects.labels2series()) return results **Typical Use Cases** - **Per-well phenotypic analysis:** Extract growth metrics where well position and size matter for normalization (e.g., center wells may grow differently from edge wells due to evaporation). - **Grid-based filtering:** Measure only colonies in specific well regions (e.g., measure only center wells in high-variance experiments, exclude border wells due to contamination risk). - **Well-normalized metrics:** Compute colony area relative to well size, colony density within well boundaries, or occupancy rates per well in a 96-well or 384-well plate. - **Multi-well experiments:** Track which well each colony occupies via grid.info(), enabling growth curve fitting per well and inter-well statistical comparisons. - **Quality control filtering:** Exclude edge wells from analysis where plate handling artifacts are common, or normalize measurements by well position. **Helper Methods Available** Inherit all measurement methods from MeasureFeatures parent class: - ``_calculate_sum()`` - Total measurement per object (e.g., total area) - ``_calculate_mean()`` - Average measurement per object (e.g., mean intensity) - ``_calculate_median()`` - Median measurement per object - ``_calculate_std()`` - Standard deviation per object - ``_calculate_percentile()`` - Percentile-based measurements (e.g., 95th percentile size) - ``_extract_properties_per_object()`` - Extract properties from regionprops or similar **Grid Information Available** Access grid structure via ``image.grid.info()`` in _operate(): - ``'grid_shape'`` - Tuple (nrows, ncols) of well layout - ``'well_centers'`` - List of (row_px, col_px) well center coordinates in pixel space - ``'well_size'`` - Typical well size in pixels (varies by format: 96-well vs 384-well) - ``'rotation_angle'`` - Current grid rotation angle in degrees - ``'alignment'`` - Alignment state/quality metrics **Notes** - The ``measure()`` method is inherited from MeasureFeatures; the only difference is input type validation (GridImage required). - Returns pandas.DataFrame with one row per detected object, first column is OBJECT.LABEL (matching image.objmap labels). - GridImage must have valid grid structure set before measuring. Typically set by GridFinder or GridCorrector operations in the pipeline. - All helper methods from MeasureFeatures (mean, median, sum, etc.) are available. - DataFrame returned can be easily mapped back to grid layout using image.grid.info(). **Relationship to MeasureFeatures and GridOperation** - **MeasureFeatures:** Parent class providing all measurement logic. GridMeasureFeatures enforces GridImage input and guarantees grid structure is available via image.grid.info(). - **GridOperation:** Base class for all grid-aware operations. GridMeasureFeatures is a specialized subclass combining measurement functionality with grid awareness. - **Pipeline integration:** Typical order: detect_colonies (ObjectDetector) → measure_features (GridMeasureFeatures) → analyze_growth (e.g., growth curve fitting per well). **Well-to-Colony Mapping Patterns** Common workflows for mapping measurements back to grid: - **Per-well aggregation:** Group colonies by well position; compute statistics per well (mean, std, count). - **Well-position filtering:** Exclude edge wells, measure only center wells, or apply position-dependent normalization. - **Row/column analysis:** Group by row or column; detect spatial patterns or plate gradients. - **Multi-well comparisons:** Compare growth metrics across wells for identification of outliers or resistance. **Output DataFrame Structure** Standard format returned by GridMeasureFeatures._operate(): - **First column:** OBJECT.LABEL (int) - Matches image.objmap labels, one row per colony - **Measurement columns:** Your custom measurements (Area, Intensity, Circularity, etc.) - **Mapping to grid:** Use image.grid.info()['well_centers'] + label → pixel position → well assignment **Validation Checklist** Before shipping GridMeasureFeatures subclass: - Verify grid.info() call succeeds and returns expected keys - Test on GridImage with known grid (use load_synth_yeast_plate()) - Ensure DataFrame output matches expected column structure (OBJECT.LABEL first, then measurements) - Check that measurements make sense (e.g., area > 0, intensity in expected range) - Validate on both small (96-well) and large (384-well) plates if possible **Known Implementations** Reference implementations in the PhenoTypic framework: - **GridMeasureSize:** Measures colony size and morphological features per detected object in GridImage. Extends GridMeasureFeatures with size-specific methods and grid-aware normalization. - **GridMeasureShape:** Measures colony shape/circularity/symmetry features. Demonstrates how grid structure can influence shape interpretation (e.g., elongation along grid rows). - **MeasureSize (MeasureFeatures):** Parent class implementation. GridMeasureSize extends this with grid awareness. **Testing GridMeasureFeatures Implementations** Best practices for testing new GridMeasureFeatures subclasses: - Use ``load_synth_yeast_plate()`` from phenotypic.data (creates GridImage with detected colonies). - Verify DataFrame output structure: first column OBJECT.LABEL, subsequent columns are measurements. - Test with edge cases: image with no colonies detected, single large colony, multiple overlapping colonies. - Check well-mapping: ensure measurements map correctly to grid positions via image.grid.info(). - Validate row/column filtering logic if using position-aware filtering (center wells, edge exclusion). Examples: Grid-aware measurement of colony size per well: >>> from phenotypic import GridImage >>> from phenotypic.abc_ import GridMeasureFeatures >>> from phenotypic.schema import OBJECT >>> import pandas as pd >>> class MeasureWellOccupancy(GridMeasureFeatures): ... '''Measure total area occupied in each well.''' ... ... def _operate(self, image: GridImage) -> pd.DataFrame: ... # Use grid accessor to calculate per-well metrics ... area = self._calculate_sum(image.objmask[:], image.objmap[:]) ... well_info = image.grid.info() # Get well assignments ... # Combine area with well location ... results = pd.DataFrame({ ... 'WellArea': area, ... }) ... results.insert(0, OBJECT.LABEL, image.objects.labels2series()) ... return results >>> # Usage >>> from phenotypic import Image >>> from phenotypic.detect import OtsuDetector >>> image = Image('plate.jpg') >>> image = OtsuDetector().operate(image) >>> grid_image = GridImage(image) >>> grid_image.detect_grid() # Establish grid structure >>> measurer = MeasureWellOccupancy() >>> df = measurer.measure(grid_image) # Returns grid-aware measurements Grid-aware filtering: measure only center wells: >>> from phenotypic.abc_ import GridMeasureFeatures >>> from phenotypic import GridImage >>> import pandas as pd >>> class MeasureCenterWells(GridMeasureFeatures): ... '''Measure size only for colonies in center wells (exclude edge wells).''' ... ... def _operate(self, image: GridImage) -> pd.DataFrame: ... grid_info = image.grid.info() ... nrows, ncols = grid_info['grid_shape'] ... # Get all measurements first ... all_areas = self._calculate_sum(image.objmask[:], image.objmap[:]) ... labels = image.objects.labels2series() ... # Filter: keep only colonies in center wells ... center_mask = self._get_center_well_mask(grid_info, nrows, ncols) ... # Return measurements for center-well colonies only ... results = pd.DataFrame({'Area': all_areas[center_mask]}) ... results.insert(0, 'OBJECT.LABEL', labels[center_mask]) ... return results """
[docs] @validate_measure_integrity() def measure(self, image: GridImage) -> pd.DataFrame: from phenotypic import GridImage if not isinstance(image, GridImage): raise GridImageInputError() output = super().measure(image) if not isinstance(output, pd.DataFrame): raise OutputValueError("pandas.DataFrame") return output