Write Your First Custom Operation#
PhenoTypic is designed to be extended. In this tutorial you will create a custom ImageEnhancer from scratch, implement its _operate() method, and use it inside an ImagePipeline.
What you will learn:
Subclass
ImageEnhancerImplement
_operate(self, image) -> imageAdd your custom operation to a pipeline
[1]:
import numpy as np
import phenotypic as pht
from phenotypic.abc_ import ImageEnhancer
from phenotypic.data import load_yeast_plate
from phenotypic.detect import OtsuDetector
Step 1: Define the Class#
The simplest operation to start with is an enhancer, since it only modifies detect_mat. You don’t need to worry about objmask, objmap, or grid state.
Let’s create an enhancer that inverts the detection matrix — making dark colonies bright and bright background dark. This is useful when your colonies are darker than the agar.
[2]:
class InvertDetectMat(ImageEnhancer):
"""Invert the detection matrix so dark features become bright."""
def _operate(self, image):
# Read the current detection matrix
mat = image.detect_mat[:]
# Invert: subtract from the maximum value
inverted = mat.max() - mat
# Write back
image.detect_mat[:] = inverted
return image
That’s the entire implementation. The _operate() contract is simple:
Receive an
Image(orGridImage)Read from and write to accessors
Return the modified image
For an enhancer, you read detect_mat, transform it, and write it back. Never modify rgb or gray — those belong to the original image data.
Step 2: Test It#
[3]:
plate = load_yeast_plate()
plate.detect_mat.dash()
Data type cannot be displayed: application/vnd.plotly.v1+json
[4]:
inverter = InvertDetectMat()
plate = inverter.apply(plate)
plate.detect_mat.dash()
Data type cannot be displayed: application/vnd.plotly.v1+json
The detection matrix is now inverted — dark regions became bright.
Step 3: Use It in a Pipeline#
Custom operations work seamlessly with ImagePipeline — just include them in the ops list like any built-in operation.
[5]:
pipeline = pht.ImagePipeline(
ops=[InvertDetectMat(), OtsuDetector()],
)
plate = load_yeast_plate()
result = pipeline.apply(plate)
result.dash(overlay=True)
Data type cannot be displayed: application/vnd.plotly.v1+json
Adding Parameters#
Most operations need configurable parameters. Define them in __init__ and access them via self in _operate().
[6]:
class ScaleDetectMat(ImageEnhancer):
"""Scale the detection matrix by a constant factor."""
def __init__(self, factor: float = 2.0):
self.factor = factor
def _operate(self, image):
image.detect_mat[:] = np.clip(
image.detect_mat[:] * self.factor, 0, 1
)
return image
# Use it
scaler = ScaleDetectMat(factor=1.5)
print(f"Factor: {scaler.factor}")
Factor: 1.5
Summary#
You have written a custom ImageEnhancer and used it in a pipeline. The key points:
Subclass the appropriate ABC (
ImageEnhancer,ObjectDetector,ObjectRefiner,MeasureFeatures, etc.)Implement
_operate(self, image) -> imageAccess image data through accessors (
image.detect_mat[:],image.objmask[:], etc.)Store parameters as instance attributes in
__init__Your operation works automatically with
ImagePipeline, JSON serialization, and the CLI
For more on each ABC type, see the how-to guides for custom detectors, custom refiners, and custom measurements.