The _operate() Contract#

Every PhenoTypic operation implements a single method: _operate(). This page specifies the full contract that implementations must follow.

Signature#

def _operate(self, image: Image) -> Image:

Rules#

  1. Return the image. Always return the image argument, even if you modified it in place. The pipeline relies on the return value.

  2. Access data through accessors. Use image.detect_mat[:], image.objmask[:], etc. Do not access internal _data attributes directly.

  3. Respect the read/write contract. Each ABC type specifies which accessors it may read and write. An enhancer that writes to objmap is violating its contract.

  4. Store parameters on self. The constructor should assign all configuration to instance attributes. The _operate() method accesses them via self.param_name.

  5. Be deterministic. Given the same image and parameters, the operation should produce the same result. Use fixed random seeds if randomness is involved.

  6. Preserve GridImage type. If the input is a GridImage, the output must also be a GridImage with grid state preserved. The base class handles this automatically when you return the same image object.

Common Patterns#

Read-Transform-Write (Enhancer)#

def _operate(self, image):
    mat = image.detect_mat[:]
    transformed = some_function(mat, self.param)
    image.detect_mat[:] = transformed
    return image

Detect-Label-Write (Detector)#

def _operate(self, image):
    mat = image.detect_mat[:]
    mask = mat > self.threshold
    labeled = label(mask).astype(np.uint16)
    image.objmask[:] = mask
    image.objmap[:] = labeled
    return image

Read-Measure-Return (Measurement)#

def _operate(self, image):
    objmap = image.objmap[:]
    rows = []
    for prop in regionprops(objmap):
        rows.append({"ObjectLabel": prop.label, "MyMetric": ...})
    return pd.DataFrame(rows)