Skip to content

Commit 31703b2

Browse files
authored
Merge pull request #499 from danifranco/feature/add-sam3-encoder-example
Feature/add sam3 encoder example
2 parents 0576b2a + 68a2123 commit 31703b2

9 files changed

Lines changed: 870 additions & 12 deletions

File tree

Lines changed: 185 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,190 @@
1-
Here are the scripts used to create a bioimage.io export of the Cellpose-SAM model.
1+
# Cellpose-SAM
22

3-
1. `cellpose_original.py` Run original cellpose model and save an analog input and output for bioimageio tests
4-
1. `bioimageio_export` Create a bioimage.io model description, then export and test it.
5-
1. `analyze_export.py` Compare cellpose results to bioimage.io results in depth.
3+
A generalist deep-learning model for 2D cellular instance segmentation. Cellpose-SAM adapts the pretrained Vision Transformer backbone of the Segment Anything Model (SAM) into the Cellpose framework, producing a foundation model that segments cells and nuclei across a very broad range of imaging modalities, cell types and organisms without retraining.
4+
5+
This repository packages the original Cellpose-SAM weights (`cpsam`) as a [BioImage Model Zoo](https://bioimage.io) model so it can be run directly from BioImage.IO-compatible software.
6+
7+
# Table of Contents
8+
9+
- [Model Details](#model-details)
10+
- [Uses](#uses)
11+
- [Task Details](#task-details)
12+
- [Bias, Risks, and Limitations](#bias-risks-and-limitations)
13+
- [Training Details](#training-details)
14+
- [Evaluation](#evaluation)
15+
- [Technical Specifications](#technical-specifications)
16+
- [How to Get Started with the Model](#how-to-get-started-with-the-model)
17+
- [Reproducing this BioImage.IO export](#reproducing-this-bioimageio-export)
18+
19+
# Model Details
20+
21+
## Model Description
22+
23+
Cellpose-SAM replaces the convolutional backbone used in earlier Cellpose releases with the ViT-L transformer encoder from Meta's Segment Anything Model, fine-tuned inside the Cellpose spatial-flow segmentation framework. The network predicts spatial flow fields and a cell-probability map; these are converted into a labelled instance mask by the Cellpose flow-dynamics post-processing. The authors report that the model substantially exceeds inter-human annotation agreement and approaches the human-consensus bound, and that it is explicitly trained to be robust to channel shuffling, cell size, shot noise, downsampling, and isotropic/anisotropic blur.
24+
25+
- **Developed by:** Marius Pachitariu, Michael Rariden and Carsen Stringer (Stringer & Pachitariu labs, HHMI Janelia Research Campus)
26+
- **Shared by:** Packaged for the BioImage Model Zoo by Daniel Franco-Barranco
27+
- **Model type:** Vision Transformer (SAM ViT-L backbone) within the Cellpose segmentation framework
28+
- **Modality:** 2D light microscopy — fluorescence, brightfield, phase contrast, and other cellular imaging
29+
- **License:** [BSD-3-Clause](https://github.com/MouseLand/cellpose/blob/main/LICENSE) (Cellpose); SAM backbone originally released under Apache-2.0
30+
- **Finetuned from model:** Segment Anything Model (SAM), ViT-L image encoder
31+
32+
## Model Sources
33+
34+
- **Repository:** https://github.com/MouseLand/cellpose
35+
- **Paper:** Pachitariu, Rariden & Stringer, *Cellpose-SAM: superhuman generalization for cellular segmentation*, bioRxiv 2025. DOI: [10.1101/2025.04.28.651001](https://doi.org/10.1101/2025.04.28.651001)
36+
- **Weights:** https://huggingface.co/mouseland/cellpose-sam
37+
- **Documentation:** https://cellpose.readthedocs.io
38+
39+
# Uses
40+
41+
## Direct Use
42+
43+
The model is a **generalist segmenter** intended to be used out of the box, without fine-tuning, on 2D microscopy images of cells or nuclei. It accepts up to three input channels (e.g. cytoplasm + nucleus, RGB histology, or a single grayscale channel replicated across channels) and returns an instance-label image where `0` is background and each connected object receives a unique integer ID.
44+
45+
Because it was trained with heavy channel and appearance augmentation, it generalizes across many microscope types, stains and organisms without the user needing to specify a "cytoplasm" vs "nucleus" channel order.
46+
47+
## Downstream Use
48+
49+
Cellpose-SAM fits into the wider Cellpose ecosystem and can be:
50+
51+
- fine-tuned or trained human-in-the-loop on domain-specific data (see the Cellpose GUI and `cellpose` training API),
52+
- combined with Cellpose image-restoration models,
53+
- used as the 2D engine for 3D segmentation via slice-wise stitching.
54+
55+
## Out-of-Scope Use
56+
57+
- **Not for clinical or diagnostic use.** The model is a research tool and has not been validated for medical decision-making.
58+
- Not validated for imaging modalities far outside the training distribution (e.g. raw electron microscopy volumes) — validate before relying on it.
59+
- Native inference here is **2D**; true 3D volumetric segmentation requires the Cellpose 3D pipeline rather than this packaged tile-based model.
60+
- Always verify results on your own data before drawing quantitative conclusions.
61+
62+
# Task Details
63+
64+
- **Task type:** Instance segmentation
65+
- **Input modality:** 2D light microscopy (fluorescence, brightfield, phase contrast, histology)
66+
- **Target structures:** Cells and nuclei
67+
- **Imaging technique:** Broadly modality-agnostic by design
68+
- **Spatial resolution:** No fixed pixel size; the model is trained to be robust to cell size and to down-sampling. Very small or very large objects relative to the tile may benefit from rescaling.
69+
70+
# Bias, Risks, and Limitations
71+
72+
## Known Biases
73+
74+
- Training data, while diverse, is dominated by 2D light-microscopy of common model systems; rare or highly unusual morphologies may be under-represented.
75+
- Performance still depends on image quality; extreme noise, artefacts or out-of-focus regions can degrade results.
76+
77+
## Limitations
78+
79+
- Objects that are heavily touching, overlapping or transparent remain challenging.
80+
- The packaged model runs on fixed-size tiles (256×256) with a 16-pixel halo; very large images are processed tile-by-tile and objects spanning tile borders rely on the halo for continuity.
81+
- Results depend on the flow-dynamics thresholds (`flow_threshold`, `cellprob_threshold`, `min_size`), which are fixed in this package but can be tuned in the native Cellpose API.
82+
83+
## Recommendations
84+
85+
- Validate on a representative subset of your own images.
86+
- Where accuracy is critical, use manual verification or human-in-the-loop correction.
87+
- For non-standard data, consider fine-tuning within Cellpose rather than relying on the generalist weights alone.
88+
89+
# Training Details
90+
91+
Full training methodology, datasets and hyperparameters are described in the original paper and repository. In brief:
92+
93+
- **Backbone:** SAM ViT-L image encoder, adapted into the Cellpose flow-field framework.
94+
- **Augmentations (key contribution):** channel shuffling, cell-size variation, shot noise, down-sampling, and isotropic/anisotropic blur, to maximize generalization.
95+
- **Objective:** Cellpose spatial-flow + cell-probability loss.
96+
- **Framework:** PyTorch.
97+
98+
For exact dataset composition, splits, epochs and optimizer settings, refer to the [paper](https://doi.org/10.1101/2025.04.28.651001) and the [Cellpose repository](https://github.com/MouseLand/cellpose).
99+
100+
# Evaluation
101+
102+
The original authors evaluate Cellpose-SAM against inter-human annotation agreement and report that it **exceeds inter-human agreement and approaches the human-consensus segmentation bound** across a diverse benchmark of cellular images, outperforming previous Cellpose generations. Quantitative benchmarks (Average Precision at IoU thresholds and comparisons to prior models) are reported in the paper.
103+
104+
This BioImage.IO package is additionally checked for numerical reproducibility: the exported model's label output is compared against the reference Cellpose inference on the bundled sample image, within the tolerance declared in the model description (see `bioimageio_export.py`).
105+
106+
# Technical Specifications
107+
108+
## Model Architecture and Objective
109+
110+
- **Architecture:** SAM ViT-L transformer encoder within the Cellpose segmentation head (`CPSAM`); loaded here through the BioImage.IO-compatible subclass `CPnetBioImageIO`.
111+
- **Objective:** Predicts spatial flow fields + cell-probability map, decoded into instances by Cellpose flow dynamics.
112+
113+
### Input specification
114+
115+
| Property | Value |
116+
|----------|-------|
117+
| Axes | `batch`, `channel`, `y`, `x` |
118+
| Channels | 3 (`channel0`, `channel1`, `channel2`) |
119+
| Tile size | 256 × 256 |
120+
| Data type | float32 |
121+
| Padding | constant, value `0` |
122+
| Preprocessing | percentile scale-range normalization (1st–99th percentile over `batch, y, x`, `eps=1e-8`) |
123+
124+
### Output specification
125+
126+
| Property | Value |
127+
|----------|-------|
128+
| Tensor | `labels` — instance label image, `0` = background |
129+
| Axes | `batch`, `channel`, `y`, `x` (16-pixel halo on `y` and `x`) |
130+
| Postprocessing | Cellpose flow dynamics (`cellprob_threshold=0.0`, `flow_threshold=0.4`, `do_3D=False`, `min_size=15`) |
131+
132+
## Compute Infrastructure
133+
134+
### Hardware Requirements
135+
136+
- **Inference:** Runs on CPU; a CUDA GPU is strongly recommended for reasonable speed on large images.
137+
- **Storage:** The `cpsam` weights are several hundred MB.
138+
139+
### Software Dependencies
140+
141+
- **Framework:** PyTorch (tested with 2.10.0)
142+
- **Libraries:** `cellpose==4.2.1.1` (pinned in the packaged `environment.yaml`), `bioimageio.core`
143+
- **BioImage.IO compatibility:** loadable via `bioimageio.core`; the weights are provided as a PyTorch state dict with a bundled architecture file, so no upstream Cellpose patch is required.
144+
145+
# How to Get Started with the Model
146+
147+
Using the BioImage.IO Python API:
148+
149+
```python
150+
from bioimageio.core import load_description, predict
151+
import numpy as np
152+
153+
model = load_description("cpsam_bioimageio.zip") # or the bioimage.io model id
154+
155+
# image: (batch, channel=3, y, x) float array; single-channel data can be
156+
# broadcast across the three channels.
157+
image = np.load("test_input.npy")
158+
labels = predict(model=model, inputs={"input": image})
159+
```
160+
161+
To run the model natively (full Cellpose feature set, tuning, 3D, training), install Cellpose and follow the [official documentation](https://cellpose.readthedocs.io):
162+
163+
```bash
164+
pip install "cellpose==4.2.1.1"
165+
```
166+
167+
# Reproducing this BioImage.IO export
168+
169+
The scripts in this folder build and validate the BioImage.IO package from the original Cellpose-SAM weights:
170+
171+
1. `cellpose_original.py` — runs the original Cellpose model and saves matching input/output tensors used as BioImage.IO test tensors.
172+
2. `bioimageio_export.py` — builds the `ModelDescr`, then exports and tests the packaged model.
173+
3. `analyze_export.py` — compares Cellpose results against the BioImage.IO results in depth.
174+
4. `cellpose_vit_bioimageio.py` — the bundled `CPnetBioImageIO` architecture that lets the exported model run against a stock, unmodified Cellpose install.
6175

7176
```console
8177
python cellpose_original.py
9178
python bioimageio_export.py
10-
python analzse_export.py
11-
````
179+
python analyze_export.py
180+
```
181+
182+
# Citation
183+
184+
If you use this model, please cite the original work:
185+
186+
> Pachitariu, M., Rariden, M., & Stringer, C. (2025). *Cellpose-SAM: superhuman generalization for cellular segmentation.* bioRxiv. https://doi.org/10.1101/2025.04.28.651001
187+
188+
# License
189+
190+
This model is distributed under the [BSD-3-Clause license](https://github.com/MouseLand/cellpose/blob/main/LICENSE) of the Cellpose project. The SAM backbone from which it is fine-tuned was originally released under Apache-2.0.

‎example/export_cellpose_model/bioimageio_export.py‎

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import hashlib
12
import os
23
from pathlib import Path
34

@@ -6,7 +7,7 @@
67
from bioimageio.core import test_model
78
from bioimageio.spec import save_bioimageio_package
89
from bioimageio.spec.model.v0_5 import (
9-
ArchitectureFromLibraryDescr,
10+
ArchitectureFromFileDescr,
1011
AxisId,
1112
BatchAxis,
1213
BioimageioConfig,
@@ -34,11 +35,53 @@
3435
WeightsDescr,
3536
)
3637

38+
# Cellpose version installed in this environment (`pip show cellpose`). Pinned so
39+
# the exported model resolves `cellpose.vit.CPnetBioImageIO` against the same code.
40+
CELLPOSE_VERSION = "4.2.1.1"
41+
42+
43+
def create_environment_file_for_model(building_dir: Path) -> Path:
44+
"""Write a conda environment.yaml pinning the deps needed to run the model.
45+
46+
Mirrors BiaPy's `create_environment_file_for_model` so the bioimage.io
47+
`PytorchStateDictWeightsDescr.dependencies` field points at a reproducible env.
48+
"""
49+
env_yaml = (
50+
"name: cellpose-sam\n"
51+
"channels:\n"
52+
" - conda-forge\n"
53+
" - nodefaults\n"
54+
"dependencies:\n"
55+
" - python>=3.11\n"
56+
" - pip\n"
57+
" - pip:\n"
58+
f" - cellpose=={CELLPOSE_VERSION}\n"
59+
)
60+
building_dir.mkdir(parents=True, exist_ok=True)
61+
env_file = building_dir / "environment.yaml"
62+
with open(env_file, "w", encoding="utf8") as outfile:
63+
outfile.write(env_yaml)
64+
return env_file
65+
66+
67+
def sha256sum(path: Path) -> str:
68+
"""Return the hex sha256 digest of a file."""
69+
h = hashlib.sha256()
70+
with open(path, "rb") as f:
71+
for chunk in iter(lambda: f.read(1 << 20), b""):
72+
h.update(chunk)
73+
return h.hexdigest()
74+
3775
if __name__ == "__main__":
3876
logger.enable("bioimageio")
3977

4078
os.chdir(Path(__file__).parent)
4179

80+
# Conda environment file pinning cellpose (and bioimageio.core) so the model
81+
# works out of the box. Attached to the pytorch weights via `dependencies`.
82+
env_file = create_environment_file_for_model(Path("output"))
83+
env_descriptor = FileDescr(source=env_file, sha256=Sha256(sha256sum(env_file)))
84+
4285
descr = ModelDescr(
4386
name="Cellpose-SAM",
4487
inputs=[
@@ -119,12 +162,16 @@
119162
sha256=Sha256(
120163
"e1440429eb384f95afe32bcba6510f90d518eaedc917ede549bed6804004abe2"
121164
),
122-
architecture=ArchitectureFromLibraryDescr(
123-
callable=Identifier("Transformer"),
124-
import_from="cellpose.vit_sam",
165+
architecture=ArchitectureFromFileDescr(
166+
# Bundle the fixed CPnetBioImageIO so the model runs against a
167+
# stock cellpose install (no package patch / upstream PR needed).
168+
source=Path("cellpose_vit_bioimageio.py"),
169+
sha256=Sha256(sha256sum(Path("cellpose_vit_bioimageio.py"))),
170+
callable=Identifier("CPnetBioImageIO"),
125171
),
126172
pytorch_version=Version("2.10.0"),
127173
strict=False,
174+
dependencies=env_descriptor,
128175
),
129176
),
130177
config=Config(
@@ -145,5 +192,5 @@
145192
descr, output_path=Path("output/cpsam_bioimageio.zip")
146193
)
147194

148-
summary = test_model(descr, working_dir=Path("output/export_test"))
195+
summary = test_model(descr, working_dir=Path("output/export_test"), absolute_tolerance=1e-3, relative_tolerance=1e-3)
149196
summary.display()

‎example/export_cellpose_model/cellpose_original.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@
1010
import numpy as np
1111

1212
if __name__ == "__main__":
13-
cellpose_original = cellpose.models.CellposeModel(gpu=False)
13+
# Pin the exact same checkpoint the bioimageio export uses (HuggingFace `cpsam`,
14+
# sha256 e1440429...). CellposeModel() otherwise defaults to `cpsam_v2`, whose
15+
# weights differ (347/348 tensors), producing a non-reproducible reference.
16+
cellpose_original = cellpose.models.CellposeModel(
17+
gpu=False, pretrained_model="/home/dfranco/.cellpose/models/cpsam"
18+
)
1419

1520
os.chdir(Path(__file__).parent)
1621
input_array = imageio.imread("sample_input.png").transpose(2, 0, 1)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""BioImage.IO-compatible Cellpose-SAM architecture (bundled with the model).
2+
3+
This module ships a fixed ``CPnetBioImageIO`` so the exported bioimage.io model runs
4+
against a **stock, unmodified** cellpose install. It overrides only the three methods
5+
of cellpose's own ``cellpose.vit.CPnetBioImageIO`` that are out of sync with current
6+
cellpose / bioimageio.core:
7+
8+
* ``forward`` -> unpack the 2 tensors ``CPSAM.forward`` returns (it tried 3).
9+
* ``load_state_dict`` -> accept the ``strict`` kwarg bioimageio.core passes in.
10+
* ``load_state_dict`` -> guard the legacy ``output.2.weight`` key (cpsam uses ``out.*``).
11+
12+
The heavy base network (``CPSAM``) is imported unchanged from the installed cellpose
13+
package pinned in ``environment.yaml``; only this thin subclass lives here, so no
14+
modification of the cellpose package (nor an upstream PR) is required.
15+
"""
16+
17+
import torch
18+
from cellpose.vit import CPSAM
19+
20+
21+
class CPnetBioImageIO(CPSAM):
22+
"""CP-SAM subclass compatible with the BioImage.IO Spec (loads BMZ ``cpsam`` weights)."""
23+
24+
def forward(self, x):
25+
output_tensor, style_tensor = super().forward(x)
26+
return output_tensor, style_tensor
27+
28+
def load_model(self, filename, device=None):
29+
if (device is not None) and (device.type != "cpu"):
30+
state_dict = torch.load(filename, map_location=device, weights_only=True)
31+
else:
32+
self.__init__(self.nout)
33+
state_dict = torch.load(
34+
filename, map_location=torch.device("cpu"), weights_only=True
35+
)
36+
self.load_state_dict(state_dict)
37+
38+
def load_state_dict(self, state_dict, strict=False, **kwargs):
39+
if (
40+
"output.2.weight" in state_dict
41+
and state_dict["output.2.weight"].shape[0] != self.nout
42+
):
43+
for name in self.state_dict():
44+
if "output" not in name:
45+
self.state_dict()[name].copy_(state_dict[name])
46+
else:
47+
super().load_state_dict(
48+
{name: param for name, param in state_dict.items()}, strict=False
49+
)

0 commit comments

Comments
 (0)