Plugin Integration Guide

pyCyto integrates with several external tools via a standard wrapper pattern. This page describes how each integration works and how to add new ones.

Integration Patterns

1. Python bindings (baremetal)

The tool is available as a Python package and called directly:

import cellpose.models as cp_models

model = cp_models.CellposeModel(gpu=True, model_type="cpsam")
masks, _, _ = model.eval(image, diameter=50)

Used by: cyto.segmentation.cellpose, cyto.segmentation.stardist, cyto.preprocessing.register_denoise

2. JVM bridge (PyImageJ)

Fiji/TrackMate runs inside a JVM managed by PyImageJ. The imagej pixi environment includes PyImageJ and OpenJDK 11:

import imagej

# Auto-download mode (recommended — caches in ~/.jgo)
ij = imagej.init('sc.fiji:fiji', headless=True)

# Pre-installed Fiji
ij = imagej.init('/opt/Fiji.app', headless=True)

Java classes are imported via scyjava:

import scyjava
Model = scyjava.jimport('fiji.plugin.trackmate.Model')

Used by: cyto.tracking.trackmate, cyto.tracking.trackmate_in_mem

Warm-up on shared NFS: Run once on the login node so all compute nodes reuse the cache:

pixi run -e imagej python -c "import imagej; imagej.init('sc.fiji:fiji')"

3. GPU kernel (pyclesperanto)

pyclesperanto uses OpenCL to run GPU kernels directly from Python. The gpu pixi environment includes it:

import pyclesperanto_prototype as cle

cle.select_device("GPU")
labels = cle.voronoi_otsu_labeling(image, spot_sigma=5)

Used by: cyto.postprocessing.contact

4. CLI subprocess

For tools with no Python API, invoke via subprocess and exchange data through temporary files:

import subprocess, tempfile, numpy as np

with tempfile.TemporaryDirectory() as tmpdir:
    in_path = f"{tmpdir}/input.npy"
    out_path = f"{tmpdir}/output.npy"
    np.save(in_path, data["image"])
    subprocess.run(["external_tool", in_path, out_path], check=True)
    result = np.load(out_path)

5. Container-wrapped tool

For tools with conflicting environment requirements (e.g. conflicting CUDA versions), wrap in a container. See Docker → Apptainer.


Adding a New Algorithm Class

Step 1 — Write the class

Follow the standard compute node contract: __init__ takes algorithm params, __call__ takes a dict and returns a dict. Use CellPose in cyto/segmentation/cellpose.py as the canonical example:

# cyto/segmentation/my_segmenter.py
try:
    import my_segmenter_lib
except ImportError:
    my_segmenter_lib = None

from tqdm import tqdm


class MySegmenter:
    def __init__(self, threshold=0.5, verbose=True):
        self.name      = "MySegmenter"
        self.threshold = threshold
        self.verbose   = verbose

    def __call__(self, data: dict) -> dict:
        image = data["image"]
        if self.verbose:
            tqdm.write(f"[{self.name}] segmenting {image.shape}")
        label = my_segmenter_lib.segment(image, thresh=self.threshold)
        return {"label": label}

Step 2 — Register in main.py

Open main.py and add the import alongside existing segmentation imports:

from cyto.segmentation.my_segmenter import MySegmenter

The YAML dispatcher resolves name: MySegmenter to the class using the convention:

cyto.<stage>.<module_file>.<ClassName>

where <stage> is one of preprocessing, segmentation, tracking, postprocessing and <module_file> is the Python filename (without .py). The name: field in the pipeline YAML must match the class name exactly.

Step 3 — Wire into a pipeline YAML

pipeline:
  segmentation:
    - name: MySegmenter
      tag: MySegmenter_TCell
      channels: [TCell]
      input_type: image
      output_type: label
      args:
        threshold: 0.4
      output: true

Step 4 — Add the dependency to pixi.toml

[feature.myseg.dependencies]
my-segmenter-lib = ">=1.0"

Then install: pixi install -e myseg

Step 5 — Mock in Sphinx conf.py

Add to autodoc_mock_imports in doc/source/conf.py so API docs build without the optional dep:

autodoc_mock_imports = [
    ...
    "my_segmenter_lib",
]

Step 6 — Test in isolation

import numpy as np
from cyto.segmentation.my_segmenter import MySegmenter

seg = MySegmenter(threshold=0.4, verbose=False)
result = seg({"image": np.random.rand(5, 128, 128).astype("float32")})
assert "label" in result

Step 7 — Package for container execution (optional)

If the tool has conflicting environment requirements, wrap it in an Apptainer container. No changes to the Python class are needed — reference the .sif path in the resource YAML:

# configs/distributed/pipeline-resources.yaml
pipeline:
  segmentation:
    MySegmenter_TCell:
      partition: gpu_short
      gres: gpu:1
      mem: 32G
      container: containers/images/my-segmenter.sif

See Docker → Apptainer for building the .sif file.


Adding a New External Tool

  1. Create cyto/<stage>/my_tool.py with a class that wraps the tool.

  2. Guard the import so the default environment does not fail:

    try:
        import my_tool
    except ImportError:
        my_tool = None
    
  3. Add the import to autodoc_mock_imports in doc/source/conf.py so Sphinx docs build without the tool installed.

  4. Add the dependency to the correct [feature.<env>] block in pixi.toml.

  5. Test with pixi run -e <env> pytest tests/test_<stage>.py.