Source code for phenotypic.abc_._prefab_pipeline

from __future__ import annotations

from pathlib import Path
from typing import Union

from .._core._image_pipeline import ImagePipeline


[docs] class PrefabPipeline(ImagePipeline): """Marker class for pre-built, validated image processing pipelines from the PhenoTypic team. PrefabPipeline is a specialized subclass of ImagePipeline that distinguishes "official" pre-built pipelines maintained by the PhenoTypic development team from user-created custom pipelines. It serves as a marker class (no additional functionality) that signals "this pipeline is validated, documented, and recommended for specific use cases in microbe colony phenotyping." **What is PrefabPipeline?** PrefabPipeline is NOT an operation ABC and does NOT inherit from BaseOperation. Instead, it's a subclass of ImagePipeline that: - **Is a marker class:** Inherits all ImagePipeline functionality unchanged; no new methods. - **Indicates official status:** Subclasses of PrefabPipeline are pre-built, validated pipelines with documented performance, parameter settings, and recommended use cases. - **Enables classification:** Code can distinguish official pipelines (``isinstance(obj, PrefabPipeline)``) from user-defined pipelines for documentation, discovery, or defaulting. - **Provides templates:** Each PrefabPipeline subclass is a complete processing workflow (enhancement, detection, refinement, measurement) ready to use out-of-the-box. **Quick Decision Guide: PrefabPipeline vs Custom ImagePipeline** - Use PrefabPipeline for standard colony phenotyping on agar plates with validated workflows - Use custom ImagePipeline for novel imaging scenarios, optimization experiments, or specialized workflows - Start with PrefabPipeline to understand the pipeline structure, then customize via parameter tuning - Clone and modify a PrefabPipeline subclass when you need significant algorithm changes - Combine multiple PrefabPipelines sequentially for complex multi-stage workflows **Available PrefabPipeline Subclasses** The PhenoTypic team maintains several pre-built pipelines optimized for different imaging scenarios: 1. **[HeavyOtsuPipeline](src/phenotypic/prefab/_heavy_otsu_pipeline.py):** Multi-layer Otsu detection with aggressive refinement. Robust detection on challenging images (uneven lighting, varied sizes). Computationally expensive. 2. **[HeavyWatershedPipeline](src/phenotypic/prefab/_heavy_watershed_pipeline.py):** Watershed segmentation for separated colonies. Handles closely-spaced or merged colonies. Very expensive; for small batches or deep analysis. 3. **[RoundPeaksPipeline](src/phenotypic/prefab/_round_peaks_pipeline.py):** Peak detection for circular, well-separated colonies. Fast and suitable for high-throughput screening of early-time-point growth. 4. **[GridSectionPipeline](src/phenotypic/prefab/_grid_section_pipeline.py):** Per-well section extraction and fine-grained analysis. Moderate cost; enables per-well quality control and segmentation. 5. **[FilamentousFungiPipeline](src/phenotypic/prefab/_filamentous_fungi_pipeline.py):** Two-stage filamentous fungi detection with optional Dijkstra branch reconnection. For irregular spreading colonies. **When to use PrefabPipeline vs Custom ImagePipeline** - **Use PrefabPipeline if:** - You're analyzing colony growth on agar plates (the intended use case). - You want an immediately usable, tested workflow without configuration. - You want to reproduce results matching published benchmarks or team documentation. - You need a baseline for custom extensions (subclass or copy and modify). - **Create a custom ImagePipeline if:** - Your imaging scenario is novel (unusual plate format, different organisms, special preparation). - You want to experiment with different detector/refiner/measurement combinations. - You have labeled ground truth and want to optimize parameters for your specific images. - You need pipeline extensions (custom operations not in standard library). **Using a PrefabPipeline** PrefabPipeline subclasses are used exactly like ImagePipeline: .. code-block:: python from phenotypic import Image, GridImage from phenotypic.prefab import HeavyOtsuPipeline # Load image(s) image = GridImage.imread('plate.jpg', nrows=8, ncols=12) # Instantiate and apply pipeline pipeline = HeavyOtsuPipeline() result = pipeline.apply(image) # or .operate([image]) # Access results colonies = result.objects measurements = result.measurements print(f"Detected: {len(colonies)} colonies") print(f"Measurements shape: {measurements.shape}") **Customizing a PrefabPipeline** PrefabPipelines accept tunable parameters in ``__init__()`` to adapt to your images without rebuilding the pipeline structure: .. code-block:: python from phenotypic.prefab import HeavyOtsuPipeline # Use defaults (recommended for most cases) pipeline1 = HeavyOtsuPipeline() # Tune for noisier images pipeline2 = HeavyOtsuPipeline( gaussian_sigma=7, # Stronger blur small_object_min_size=150, # More aggressive noise removal border_remover_size=2 # Remove more edge objects ) # Parameters are typically named after the algorithm or parameter they control. # See pipeline docstring for available parameters and typical values. **When Parameters Fail: Creating a Custom Pipeline** If PrefabPipeline parameter tuning doesn't solve your problem: 1. **Analyze failures:** Which step fails (detection, refinement, measurement)? - Use ``pipeline.benchmark=True, verbose=True`` to trace execution. - Visually inspect intermediate results (detection masks, refined masks). 2. **Create a custom pipeline:** .. code-block:: python from phenotypic import ImagePipeline from phenotypic.enhance import GaussianBlur, CLAHE from phenotypic.detect import CannyDetector # Different detector from phenotypic.refine import SmallObjectRemover, MaskFill from phenotypic.measure import MeasureShape, MeasureColor # Custom pipeline for your specific use case custom = ImagePipeline() custom.add(GaussianBlur(sigma=3)) custom.add(CLAHE()) custom.add(CannyDetector(sigma=1.5, low_threshold=0.1, high_threshold=0.4)) custom.add(SmallObjectRemover(min_size=100)) custom.add(MaskFill()) custom.add(MeasureShape()) custom.add(MeasureColor()) # Test and iterate result = custom.operate([image]) 3. **Share successful custom pipelines:** If you develop a successful custom pipeline for a new imaging scenario, consider contributing it as a PrefabPipeline subclass to the project. **Pipeline Composition Pattern** Combine multiple PrefabPipelinesor mix PrefabPipeline with custom operations: .. code-block:: python from phenotypic import ImagePipeline from phenotypic.prefab import HeavyOtsuPipeline, RoundPeaksPipeline from phenotypic.refine import SmallObjectRemover from phenotypic.measure import MeasureIntensity # Combine different detection strategies with shared refinement pipeline = ImagePipeline() pipeline.add(HeavyOtsuPipeline()) # First detection attempt pipeline.add(SmallObjectRemover(min_size=100)) # Noise removal pipeline.add(MeasureIntensity()) # Measurement # Apply to image result = pipeline.apply(image) **Pipeline Serialization Pattern** Save and load pipelines for reproducible batch processing: .. code-block:: python from phenotypic.prefab import HeavyOtsuPipeline # Create, configure, and save pipeline = HeavyOtsuPipeline(gaussian_sigma=2.0, small_object_min_size=150) pipeline.to_json('my_colony_pipeline.json') # Save configuration # pipeline.to_yaml('my_colony_pipeline.yaml') # Alternative format # Load for batch processing (reproducible results) loaded = HeavyOtsuPipeline.from_json('my_colony_pipeline.json') results = loaded.operate([image1, image2, image3]) **Extending PrefabPipeline** To create a new official PrefabPipeline subclass: .. code-block:: python from phenotypic.abc_ import PrefabPipeline from phenotypic.enhance import GaussianBlur, CLAHE from phenotypic.detect import OtsuDetector from phenotypic.refine import SmallObjectRemover from phenotypic.measure import MeasureShape class MyCustomPrefabPipeline(PrefabPipeline): '''Brief description of when to use this pipeline.''' def __init__(self, param1: int = 100, param2: float = 1.5, benchmark: bool = False, verbose: bool = False): '''Initialize with tunable parameters.''' pipe_cfgs = [ GaussianBlur(sigma=param2), CLAHE(), OtsuDetector(), SmallObjectRemover(min_size=param1), ] meas = [MeasureShape()] super().__init__(pipe_cfgs=pipe_cfgs, meas=meas, benchmark=benchmark, verbose=verbose) Notes: - **Is a marker, not an operation:** PrefabPipeline does not inherit from BaseOperation. It's a convenient subclass of ImagePipeline for classification and discovery. - **Inheritance of ImagePipeline features:** PrefabPipeline inherits all ImagePipeline functionality: sequential operation chaining, benchmarking, verbose logging, batch processing via ``.operate()``, and serialization via ``.to_yaml()`` / ``.from_yaml()``. - **Parameter tuning via __init__():** Most PrefabPipeline subclasses expose key algorithm parameters in ``__init__()`` (e.g., detection threshold, smoothing sigma, refinement shape). Adjust these for your specific images before scaling to large batches. - **Benchmarking for profiling:** Set ``benchmark=True`` when instantiating to track execution time and memory usage per operation. Useful for identifying bottlenecks in large batch runs. - **Documentation and examples:** Each PrefabPipeline subclass is documented with use cases, typical parameters, performance characteristics, and example code. Check the subclass docstring for guidance. - **Not for operations:** Use PrefabPipeline only for complete pipelines. For individual operations (detection, enhancement, measurement), use operation ABCs directly. Examples: Quick start: Detect colonies with HeavyOtsuPipeline: >>> from phenotypic import GridImage >>> from phenotypic.prefab import HeavyOtsuPipeline >>> # Load a 96-well plate image >>> image = GridImage.imread('agar_plate.jpg', nrows=8, ncols=12) >>> # Use the pre-built, validated pipeline >>> pipeline = HeavyOtsuPipeline() >>> result = pipeline.apply(image) >>> # Access results >>> print(f"Detected {len(result.objects)} colonies") >>> print(f"Measurements: {result.measurements.columns.tolist()}") Batch processing multiple plates with a PrefabPipeline: >>> from phenotypic import GridImage >>> from phenotypic.prefab import HeavyOtsuPipeline >>> import glob >>> # Load multiple plate images >>> image_paths = glob.glob('batch_*.jpg') >>> images = [GridImage.imread(p, nrows=8, ncols=12) ... for p in image_paths] >>> # Create pipeline (reusable for all images) >>> pipeline = HeavyOtsuPipeline(benchmark=True) >>> # Batch process >>> results = pipeline.operate(images) >>> # Collect results >>> for i, result in enumerate(results): ... print(f"Image {i}: {len(result.objects)} colonies") ... print(f"Measurements shape: {result.measurements.shape}") Customizing pipeline parameters for difficult images: >>> from phenotypic import GridImage >>> from phenotypic.prefab import HeavyOtsuPipeline >>> image = GridImage.imread('noisy_plate.jpg', nrows=8, ncols=12) >>> # Increase smoothing and noise removal for difficult images >>> pipeline = HeavyOtsuPipeline( ... gaussian_sigma=8, # Stronger blur ... small_object_min_size=200, # Aggressive noise removal ... border_remover_size=2 # More border filtering ... ) >>> result = pipeline.apply(image) >>> print(f"Robust detection: {len(result.objects)} colonies") Comparing PrefabPipeline vs custom pipeline: >>> from phenotypic import GridImage, ImagePipeline >>> from phenotypic.prefab import HeavyOtsuPipeline >>> from phenotypic.detect import CannyDetector >>> from phenotypic.refine import SmallObjectRemover >>> image = GridImage.imread('plate.jpg', nrows=8, ncols=12) >>> # Option 1: Use pre-built validated pipeline >>> prefab = HeavyOtsuPipeline() >>> result1 = prefab.apply(image) >>> # Option 2: Create custom pipeline for comparison >>> custom = ImagePipeline() >>> from phenotypic.enhance import GaussianBlur >>> custom.add(GaussianBlur(sigma=2)) >>> custom.add(CannyDetector(sigma=1.5, low_threshold=0.1, high_threshold=0.4)) >>> custom.add(SmallObjectRemover(min_size=100)) >>> result2 = custom.apply(image) >>> # Compare results >>> print(f"Prefab: {len(result1.objects)}, Custom: {len(result2.objects)}") """
[docs] @classmethod def from_json( cls, json_data: Union[str, Path, dict], benchmark: bool = False, verbose: bool = False, ) -> PrefabPipeline: """Deserialize a PrefabPipeline from JSON. PrefabPipeline subclasses build their ops/meas inside ``__init__``, so the base ``from_json`` (which passes ``ops=`` directly) would conflict. This override deserializes via ``ImagePipeline`` and re-tags the instance as the correct PrefabPipeline subclass. Args: json_data: A JSON string, path to a JSON file, or a pre-parsed dict. benchmark: Whether to enable benchmarking for the pipeline. verbose: Whether to enable verbose output. Returns: A PrefabPipeline (or subclass) instance with the loaded configuration. """ instance = ImagePipeline.from_json( json_data, benchmark=benchmark, verbose=verbose, ) instance.__class__ = cls return instance # type: ignore[return-value]