Cellpose — Deep Learning Cell Segmentation
Overview
Cellpose uses a flow-based neural network to segment individual cells or nuclei in fluorescence microscopy images without manual parameter tuning. Pre-trained models (cyto3, nuclei, tissuenet) generalize across cell types, magnifications, and staining conditions — eliminating the need for manual threshold selection or watershed parameter optimization. Cellpose outputs integer label masks (each cell = unique integer) compatible with scikit-image regionprops for morphology measurement and with TrackPy for tracking. A built-in diameter estimator removes the need to specify cell size, though providing an approximate diameter improves accuracy.
When to Use
- Segmenting cells or nuclei in fluorescence microscopy images where rule-based thresholding fails due to varying intensity or cell touching
- Processing large microscopy datasets in batch without per-image parameter tuning
- Segmenting diverse cell types (adherent cells, blood cells, bacteria, organoids) with a single model
- Producing label masks for downstream region property measurement (area, intensity, shape) with scikit-image
- 3D volumetric segmentation of z-stack microscopy data with
do_3D=True - Use scikit-image watershed when cells are well-separated and rule-based thresholding is sufficient
- Use StarDist as an alternative deep learning segmenter optimized for star-convex cells (neurons, nuclei)
Prerequisites
- Python packages:
cellpose,numpy,matplotlib - Optional: GPU with CUDA for 10-50× speedup (
pip install cellpose[gui]for GUI) - Input: grayscale or multichannel TIFF/PNG images (2D or 3D arrays)
# Install Cellpose
pip install cellpose
# Install with GUI support
pip install cellpose[gui]
# Install with GPU (PyTorch CUDA)
pip install cellpose torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
# Verify
python -c "from cellpose import models; print('Cellpose ready')"
Quick Start
from cellpose import models
import numpy as np
from skimage import io
# Load image (grayscale or 2D array)
img = io.imread("cells.tif") # shape: (H, W) or (H, W, C)
# Initialize model and segment
model = models.Cellpose(model_type="cyto3", gpu=False)
masks, flows, styles, diams = model.eval(img, diameter=0, channels=[0, 0])
print(f"Cells segmented: {masks.max()}") # number of cells
print(f"Estimated diameter: {diams:.1f} px")
print(f"Mask shape: {masks.shape}")
Workflow
Step 1: Load and Inspect Images
Load microscopy images and inspect channel layout before segmentation.
import numpy as np
from skimage import io
import matplotlib.pyplot as plt
# Load single-channel fluorescence image
img_gray = io.imread("nucleus_dapi.tif") # shape: (H, W)
img_rgb = io.imread("cells_multichannel.tif") # shape: (H, W, C)
print(f"Grayscale shape: {img_gray.shape}, dtype: {img_gray.dtype}")
print(f"Multichannel shape: {img_rgb.shape}")
# Preview
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].imshow(img_gray, cmap="gray")
axes[0].set_title("DAPI (nuclei)")
axes[1].imshow(img_rgb[..., 0], cmap="green")
axes[1].set_title("GFP channel")
plt.tight_layout()
plt.savefig("image_preview.png", dpi=100)
print("Saved: image_preview.png")
Step 2: Segment Cells with a Pre-trained Model
Run Cellpose with the appropriate pre-trained model.
from cellpose import models
import numpy as np
from skimage import io
# Available models: 'cyto3' (cells), 'nuclei', 'tissuenet', 'cyto2', 'CP'
model = models.Cellpose(model_type="cyto3", gpu=False)
img = io.imread("cells.tif")
# channels=[cytoplasm_channel, nucleus_channel]
# Use [0, 0] for grayscale; [1, 3] for green cytoplasm + blue nucleus (1-indexed)
masks, flows, styles, diams = model.eval(
img,
diameter=0, # 0 = auto-estimate; or provide px estimate
channels=[0, 0], # grayscale
flow_threshold=0.4, # lower = fewer false positives; range 0.1-1.0
cellprob_threshold=0.0, # lower = more cells detected; range -6 to 6
)
print(f"Cells found: {masks.max()}")
print(f"Estimated cell diameter: {diams:.1f} pixels")
np.save("masks.npy", masks)
Step 3: Segment Nuclei from DAPI Channel
Use the nuclei model for DAPI-stained nuclei.
from cellpose import models
from skimage import io
import numpy as np
model = models.Cellpose(model_type="nuclei", gpu=False)
dapi = io.imread("dapi.tif")
# Nucleus-only segmentation: channels=[0, 0] (single channel)
masks, flows, styles, diams = model.eval(
dapi,
diameter=30, # approximate nucleus diameter in pixels
channels=[0, 0],
flow_threshold=0.4,
cellprob_threshold=0.0,
)
print(f"Nuclei segmented: {masks.max()}")
# Save label mask as TIFF for ImageJ/FIJI compatibility
from skimage import io as skio
skio.imsave("nuclei_masks.tif", masks.astype(np.uint16))
print("Saved: nuclei_masks.tif")
Step 4: Visualize Segmentation Results
Overlay masks on original images for quality control.
from cellpose import plot as cpplot
import matplotlib.pyplot as plt
import numpy as np
from skimage import io
img = io.imread("cells.tif")
masks = np.load("masks.npy")
flows_data = None # load if you saved them: flows = np.load("flows.npy", allow_pickle=True)
# Cellpose built-in visualization
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# Original image
axes[0].imshow(img, cmap="gray")
axes[0].set_title(f"Original image")
# Label mask (each cell = unique color)
axes[1].imshow(masks, cmap="tab20")
axes[1].set_title(f"Segmentation masks ({masks.max()} cells)")
# Overlay: outline on original
from skimage.segmentation import find_boundaries
boundaries = find_boundaries(masks, mode="inner")
overlay = np.stack([img / img.max()] * 3, axis=-1)
overlay[boundaries] = [1, 0, 0] # red outlines
axes[2].imshow(overlay)
axes[2].set_title("Outlines overlay")
plt.tight_layout()
plt.savefig("segmentation_result.png", dpi=150)
print("Saved: segmentation_result.png")
Step 5: Measure Cell Properties from Masks
Extract morphology and intensity measurements using scikit-image regionprops.
import numpy as np
import pandas as pd
from skimage.measure import regionprops_table
from skimage import io
masks = np.load("masks.npy")
img = io.imread("cells.tif")
# Measure morphology and intensity per cell
props = regionprops_table(
masks, intensity_image=img,
properties=["label", "area", "centroid", "eccentricity",
"mean_intensity", "max_intensity", "perimeter",
"equivalent_diameter_area"]
)
df = pd.DataFrame(props)
df.columns = ["cell_id", "area_px", "centroid_y", "centroid_x",
"eccentricity", "mean_intensity", "max_intensity",
"perimeter", "diameter_px"]
print(f"Cells measured: {len(df)}")
print(f"Median area: {df['area_px'].median():.0f} px²")
print(f"Median diameter: {df['diameter_px'].median():.1f} px")
print(df.head())
df.to_csv("cell_measurements.csv", index=False)
Step 6: Batch Segment Multiple Images
Process a directory of images and aggregate results.
from cellpose import models
from skimage import io
from skimage.measure import regionprops_table
import pandas as pd
import numpy as np
from pathlib import Path
model = models.Cellpose(model_type="cyto3", gpu=False)
image_dir = Path("images/")
output_dir = Path("results/")
output_dir.mkdir(exist_ok=True)
all_stats = []
for img_path in sorted(image_dir.glob("*.tif")):
img = io.imread(img_path)
masks, _, _, diams = model.eval(img, diameter=0, channels=[0, 0])
# Save mask
np.save(output_dir / f"{img_path.stem}_masks.npy", masks)
# Measure
if masks.max() > 0:
props = regionprops_table(masks, intensity_image=img,
properties=["label", "area", "mean_intensity"])
df = pd.DataFrame(props)
df["image"] = img_path.name
df["es