Skip to main content

VollSeg

PyPI version Python versions License

Hierarchical, composable segmentation for biological image data — PyTorch + PyTorch Lightning + CAREamics.

pip install kapoorlabs-vollseg            # SDK
pip install kapoorlabs-vollseg[napari]    # SDK + napari dock plugin
pip install kapoorlabs-vollseg[all]       # everything

Quick start

from tifffile import imread
from kapoorlabs_vollseg import (
    StarDistSegmenter, MaskUNetSegmenter, VollSeg, predict_timelapse,
)

star = StarDistSegmenter.from_folder("models/xenopus_stardist/")
roi  = MaskUNetSegmenter.from_folder("models/xenopus_maskunet/")

# Compose: ROI → StarDist (the production pipeline for embryo timelapses).
pipe = VollSeg.from_models(stardist=star, roi_unet=roi)

# Single volume.
labels = pipe.predict(imread("frame.tif")).labels

# Timelapse sharded across all visible GPUs.
out = predict_timelapse(pipe, imread("timelapse.tif"),
                        devices=-1, strategy="ddp")
labels_tzyx = out["labels"]

from_folder reads a Lightning .ckpt plus a training_config.json sidecar that records architecture knobs (conv_dims, unet_depth, n_rays, anisotropy, tuned prob_thresh / nms_thresh). StarDist rays are regenerated deterministically from (conv_dims, n_rays, anisotropy); no rays.npy sidecar is needed.

Architecture

Three orthogonal layers. Composition, not inheritance.

Layer 3   VollSeg.from_models / VollCellSeg.from_models           smart factories
Layer 2   ROIPipeline · UNetStarDistPipeline · DenoisedPipeline   composites
          NucleiSeededCellPosePipeline · Chunked
Layer 1   CAREDenoiser · UNetSegmenter · MaskUNetSegmenter        singletons
          StarDistSegmenter · CellPoseSegmenter

All singletons + composites implement the same protocol:

class Pipeline(Protocol):
    def predict(self, image: np.ndarray, **kwargs) -> Result: ...
Singleton Output (Result.*)
CAREDenoiser denoised
UNetSegmenter labels, semantic, probability
MaskUNetSegmenter labels, semantic, probability
StarDistSegmenter labels, probability
CellPoseSegmenter labels

2D vs 3D is dispatched on image.ndim inside each singleton — no parallel class trees.

Composite Wraps Adds
DenoisedPipeline any downstream CARE denoise → downstream sees the denoised image
ROIPipeline any downstream Mask-UNet ROI bbox → downstream on the crop, paste back
UNetStarDistPipeline stardist (+ optional unet) Side-by-side or seed-pool watershed fusion
NucleiSeededCellPosePipeline nuclei pipe + cellpose Nuclei labels seed a CellPose-gated membrane watershed
Chunked any downstream Overlapping tiles → predict → label-safe stitch

Composition order

VollSeg.from_models(...) always nests in the same order — only the stages whose models you supply appear in the chain:

          ┌─────────┐    ┌──────────┐    ┌─────────┐    ┌──────────────┐    ┌────────┐
image ──▶ │ Chunked │──▶ │ Denoised │──▶ │   ROI   │──▶ │ segmentation │──▶ │ Result │
          │ (chunk) │    │  (care)  │    │(roi_unet│    │ core         │    └────────┘
          └─────────┘    └──────────┘    └─────────┘    │ (stardist /  │
                                                       │  unet / fused│
                                                       └──────────────┘

Each box is optional and only appears when its model / chunk shape is supplied. Image always flows left → right; every stage downstream of CARE sees the denoised image, not the raw one.

Predict-time flow:

  1. Chunked (optional) — split big volumes into overlapping tiles.
  2. DenoisedPipeline (optional) — CARE denoises the chunk; every downstream stage sees the denoised image, never the raw one.
  3. ROIPipeline (optional) — Mask-UNet predicts an ROI mask on the denoised image; downstream runs on the bounding-box crop and the labels are pasted back into the full-shape array.
  4. Segmentation core — StarDist alone, U-Net alone, or the UNetStarDistPipeline composite (side-by-side or fused).

Segmentation core: how the toggles map to a pipeline

                          unet supplied?
                          ┌─────────┴─────────┐
                         yes                  no
                          │                   │
                  ┌───────┴────────┐  ┌───────┴────────┐
                  │ stardist + unet│  │ stardist only  │
                  └───────┬────────┘  └───────┬────────┘
                          │                   │
              seedpool? ──┤                   ├── seedpool?
                          │                   │
             ┌──── T ─────┤                   ├──── T ────┐
             │            │                   │           │
             │     ┌──── F                    F ────┐     │
             │     │                                │     │
             ▼     ▼                                ▼     ▼
   ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────────┐
   │ UNet+StarDist    │  │ UNet+StarDist    │  │ UNetStarDist:        │
   │ + watershed fuse │  │ side-by-side     │  │ Otsu seed pool +     │
   │ (classic VollSeg)│  │ (no fusion)      │  │ watershed fuse       │
   └──────────────────┘  └──────────────────┘  └──────────────────────┘
                                               ┌──────────────────────┐
                                               │ bare StarDist        │
                                               │ (seedpool ignored)   │
                                               └──────────────────────┘

Result fields per scenario

Result.* fields are populated only when the corresponding model runs — otherwise they stay None. The factory's job is to pick a pipeline shape that produces the maximal set of fields for the supplied models.

care roi_unet unet stardist seedpool Pipeline composition (outer → inner) Result fields populated
T Denoised( ROI( UNetStarDist(unet, stardist, seedpool=T) ) ) labels = vollseg_labels, stardist_labels, unet_labels, semantic, denoised, roi, polys
T UNetStarDist(unet, stardist, seedpool=T) labels = vollseg_labels, stardist_labels, unet_labels, semantic, polys
T Denoised( UNetStarDist(unet=None, stardist, seedpool=T) ) — Otsu threshold seed pool labels = vollseg_labels, stardist_labels, semantic (Otsu), denoised, polys
F UNetStarDist(unet, stardist, seedpool=F) — side by side labels = stardist_labels, stardist_labels, unet_labels, semantic, polys
F Denoised( ROI( stardist ) ) — bare StarDist on denoised ROI crop labels, denoised, roi, polys
F Denoised( stardist ) — denoise then StarDist labels, denoised, polys
any Denoised( unet ) — denoise then U-Net labels, semantic, probability, denoised
any bare U-Net singleton (seedpool ignored, no stardist to fuse) labels, semantic, probability
any bare StarDist singleton (seedpool ignored — no unet and no care to source a mask) labels, probability, polys
any bare CARE singleton — "denoise as the whole pipeline" denoised
any bare Mask-UNet singleton — its output is the ROI mask itself labels, semantic, probability
any ValueError — no model to do anything with

Permissive rules — the only failure mode is "no model supplied":

  • seedpool=True is silently ignored when its prerequisites aren't met (no stardist to fuse; or no unet AND no care to source the mask). The factory falls back to the next-best shape.
  • Any single-model configuration returns the bare singleton.
  • Composition only kicks in when there's actually something to compose.
# Production pipeline for embryo timelapses — denoise, ROI-gate, segment.
pipe = VollSeg.from_models(
    care=care, roi_unet=roi, stardist=star, unet=unet,
    seedpool=True,                  # auto-ignored if prerequisites missing
    chunk=(64, 256, 256),           # optional → wraps in Chunked
)
result = pipe.predict(image)
result.vollseg_labels   # watershed-fused instance labels (canonical = labels)
result.stardist_labels  # raw StarDist instances
result.unet_labels      # CC labels of U-Net mask
result.semantic         # U-Net binary mask
result.denoised         # CARE output
result.roi              # ROI gating mask

StarDist — PyTorch port

End-to-end PyTorch reimplementation matching upstream stardist at the file level (Schmidt & Weigert, MICCAI 2020). See src/kapoorlabs_vollseg/stardist/README.md for the algorithmic notes (rays, CSBDeep tile iterator port, kernel + convex-hull short-circuit polyhedron rasterizer, paint rule, anisotropy handling). The vendored upstream C++ kernel sources live under src/kapoorlabs_vollseg/stardist/_lib/ for diffing and optional native compilation; zero runtime dependency on the stardist package in the PyTorch path.

For embryo timelapses with early-stage mostly-empty frames, wrap StarDist in ROIPipeline(roi_unet=mask_unet, downstream=star) — the Mask-UNet bbox prevents the whole-volume percentile-normalisation saturation that otherwise inflates polyhedra on near-empty frames. Validated against the legacy keras reference: mean cell volume / radius / surface area within ~2 % across every developmental stage.

Prediction

Every singleton + the composites accept the same from_folder(path) constructor. Predict scripts in scripts/model_prediction/ are Hydra-driven and accept either a local log_path (wins when present) or an hf_repo_id (HuggingFace fallback).

from kapoorlabs_vollseg import StarDistSegmenter, ensure_model, predict_timelapse

# Pull from HF if not on disk yet, then load.
folder = ensure_model("./local_models", "xenopus-stardist-pytorch",
                      repo_id="KapoorLabs/xenopus-stardist-pytorch")
star = StarDistSegmenter.from_folder(folder)

# Timelapse on all visible GPUs.
out = predict_timelapse(star, imread("timelapse.tif"),
                        devices=-1, strategy="ddp")

predict_timelapse wraps any Pipeline in a TimelapsePredictor LightningModule, dispatches via Trainer.predict with a DistributedSampler over the T axis, gathers per-rank outputs onto rank 0, dedupes against sampler padding, and returns one stacked (T, …) array per Result field.

Pretrained models live under KapoorLabs/ on HuggingFace:

KapoorLabs/xenopus-stardist-pytorch
KapoorLabs/xenopus-unet-pytorch
KapoorLabs/xenopus-maskunet-pytorch
KapoorLabs/xenopus-care-pytorch

Curvature & force profiles

After segmentation, kapoorlabs_vollseg.curvature computes per-label sliding-window curvature profiles along boundaries (2D) or surfaces (3D), plus optional Young-Laplace pressure and Helfrich bending-energy columns when material constants are supplied.

from kapoorlabs_vollseg.curvature import compute_curvature

profiles = compute_curvature(
    labels,
    spacing=(2.0, 0.6918, 0.6918),  # (dz, dy, dx) μm
    n_window=21, stride=5,
    geodesic=True,                   # mesh-aware neighbours in 3D
    surface_tension=1e-3,            # N/m  — optional, adds Young-Laplace pressure
    bending_modulus=2e-20,           # J    — optional, adds Helfrich f
)

Algorithm: 2D → find_contours → Kasa algebraic circle fit per window; 3D → marching_cubes → geodesic-neighbour Coope sphere fit per vertex. Anisotropic spacing is first-class — pass μm in, get 1/μm out.

Repository layout

KapoorLabs-VollSeg/
├── src/kapoorlabs_vollseg/
│   ├── models/             Layer-1 singletons
│   ├── pipelines/          composites + factories (factory.py, cellseg_factory.py)
│   ├── stardist/           PyTorch StarDist (rays, model, losses, inference, _tiling, _lib/)
│   ├── _backbones/         careamics / stardist / maskunet wrappers + _config.py loader
│   ├── _lightning/         CareModule, dataset, stitch, transforms
│   ├── care_lightning/     vendored CARE Lightning module + signal-handling Trainer
│   ├── training/           TrainingPipeline — Hydra-friendly Lightning fit loop
│   ├── curvature/          per-label curvature + force profiles
│   ├── data/               file IO, label morphology, SmartPatches H5 generator
│   ├── eval/               matching metrics, threshold optimisation primitives
│   ├── fusion.py           watershed_fuse, cellpose_watershed_fuse
│   ├── hub.py              HuggingFace ensure_model + pretrained registry
│   └── seedpool.py         SeedPool / UnetStarMask geometry
├── plugins/
│   ├── napari-vollseg/     segmentation dock plugin
│   └── napari-curvature/   curvature dock plugin
├── scripts/                Hydra CLI for training, prediction, comparison, HF upload
├── docs/                   per-module deep-dives (care, unet, stardist)
└── tests/                  pytest suite — PyTorch path

Documentation

Legacy code & pretrained models

The keras / csbdeep / stardist .h5 Xenopus zoo, the *Keras singleton siblings, and the original 01_*.py driver scripts (the pre-rewrite VollSeg workflow) all live inside this repo — see docs/legacy.md for the full index (registry of HF model repos, driver scripts, upload helper, when to fall back to it). Use it only if you have already-trained .h5 weights you can't retrain. Everything else — new training, new prediction, the napari plugins, the KapoorLabs/ HuggingFace zoo — goes through the PyTorch path documented above.

Development

git clone https://github.com/Kapoorlabs-CAPED/KapoorLabs-VollSeg
cd KapoorLabs-VollSeg
pip install -e ".[testing]"
pre-commit install
pytest tests/ -v

Pre-commit runs pyupgrade (py39+), black, flake8, autoflake, plus a local update_version.py hook that syncs src/kapoorlabs_vollseg/_version.py from the most recent git tag.

License

BSD-3-Clause — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kapoorlabs_vollseg-1.0.5.tar.gz (25.3 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kapoorlabs_vollseg-1.0.5-py3-none-any.whl (236.2 kB view details)

Uploaded Python 3

File details

Details for the file kapoorlabs_vollseg-1.0.5.tar.gz.

File metadata

  • Download URL: kapoorlabs_vollseg-1.0.5.tar.gz
  • Upload date:
  • Size: 25.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for kapoorlabs_vollseg-1.0.5.tar.gz
Algorithm Hash digest
SHA256 032718a6ecb6f7e6345d5e8b21670b2e3e9decbfe6b0b4e8c84b2dd4146a0ce8
MD5 ce19c866bb9ff4e6b182c31522964080
BLAKE2b-256 0cef590a83ffc4827b7cded12a26a45327a9325ecd6ee50cb1feab6e02812e30

See more details on using hashes here.

File details

Details for the file kapoorlabs_vollseg-1.0.5-py3-none-any.whl.

File metadata

File hashes

Hashes for kapoorlabs_vollseg-1.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 6ce13c10894a82db7ab659cc2b64ff51fa3da8ab29ab737ca00fd7d84caf8509
MD5 4f8478237e56b7e93fcd416d352c1367
BLAKE2b-256 b78f06a2048238b0be5be3b2515c2020abaa50e64e7a0ccc9a8c5c4376c2676a

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.0.5 This release

2 files

1.0.4

2 files

1.0.3

2 files

1.0.2

2 files

1.0.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page