This repository contains a reproducible Nextflow pipeline and preserved exploratory notebooks for segmenting and tracking cells in 2D darkfield time-lapse microscopy movies acquired on a patterned hydrogel background.
The central difficulty is that cell signal is mixed with a strong structured background pattern that can drift slightly over time. The pipeline therefore combines illumination correction, pattern alignment, low-rank/sparse decomposition, foundation-model segmentation, and global tracking.
The production workflow is orchestrated by main.nf and implemented as shared Python modules under src/cell_track/. The original exploratory workflow remains available in two notebooks:
basicpy.ipynb— BaSiCPy illumination correction.main.ipynb— ECC alignment, robust PCA background removal, Cellpose-SAM segmentation, and Ultrack tracking.
Conceptually:
raw time-lapse TIFF
↓
BaSiCPy illumination correction
↓
ECC alignment of the hydrogel pattern
↓
Robust PCA: low-rank pattern + sparse cell signal
↓
Cellpose-SAM segmentation
↓
Ultrack global tracking
↓
tracks_df.csv + tracked_labels.tif
main.nf Nextflow entry point
nextflow.config defaults, resources, and execution profiles
modules/local/ one Nextflow process per pipeline stage
src/cell_track/ shared Python implementation
src/cell_track/cli/ command-line adapters used by Nextflow
envs/ runner and stage-specific Conda environments
params/example.yml example pipeline parameters
tests/ deterministic unit and integration tests
basicpy.ipynb preserved BaSiCPy exploration notebook
main.ipynb preserved segmentation/tracking notebook
- Apple Silicon macOS or x86-64 Linux.
- Miniforge (recommended) or another compatible Conda installation.
- At least 16 GB of RAM is recommended for the current resource settings.
The provided runner environment installs the tested Java and Nextflow versions, so they do not need to be installed separately. From the repository root, create it once:
conda env create -f envs/runner.yml
conda activate cell-track-runnerIf envs/runner.yml changes later, update the environment with:
conda env update \
--name cell-track-runner \
--file envs/runner.yml \
--pruneExperienced users may instead use an existing compatible installation of Java 17+ and Nextflow 24.10+ together with Conda.
From the repository root:
nextflow run main.nf \
--input 'images/stacked.tif' \
--outdir results \
-profile standard \
-resumeThe standard profile creates the stage-specific environments from envs/ and caches them under an OS/architecture-specific subdirectory of .conda/. This first environment setup can take time; later runs reuse the cache. The runner environment only supplies Java and Nextflow and remains separate from the scientific environments.
To place the stage-environment cache somewhere else, set NXF_CONDA_CACHEDIR before starting Nextflow:
export NXF_CONDA_CACHEDIR="$HOME/.cache/cell-track/conda"You can also use the example parameter file:
nextflow run main.nf \
-params-file params/example.yml \
-profile standard \
-resumeUse a glob as the input:
nextflow run main.nf \
--input 'data/*.tif' \
--outdir results \
-profile standard \
-resumeEach TIFF basename becomes its sample ID. Input files must therefore have unique basenames after removing .tif or .tiff.
Nextflow caches each stage. Always use -resume when continuing or repeating a run:
nextflow run main.nf -params-file params/example.yml -profile standard -resumeChanging an Ultrack parameter reruns Ultrack and QC but reuses BaSiCPy, ECC/RPCA, and Cellpose. Changing a Cellpose parameter reruns Cellpose and downstream stages. Do not delete work/ if you want to retain resumability.
Changing only pixel_size_um, frame_interval_min, or qc_frames reruns QC without rerunning preprocessing, segmentation, or tracking, provided the upstream inputs and cached work files are unchanged.
The current environment recipes are intended for Apple Silicon macOS and x86-64 Linux. Intel macOS and ARM64 Linux are not currently tested because the pinned PyTorch and Higra binary support differs on those platforms.
cellpose_device: auto uses Apple MPS when it is available, CUDA on a compatible Linux setup, and otherwise CPU. You may request a device explicitly with --cellpose_device mps, cuda, or cpu.
The default Ultrack allocation is four CPUs and four workers. The Nextflow process also caps the requested worker count at task.cpus, preventing Ultrack from starting more workers than the task was allocated. Use an additional Nextflow config file if a larger workstation should allocate more CPUs.
Results are organized by sample:
results/
├── pipeline_info/
│ ├── parameters.json
│ ├── execution_report.html
│ ├── execution_timeline.html
│ ├── execution_trace.txt
│ └── workflow_dag.html
└── <sample>/
├── preprocessing/
│ ├── basicpy_diagnostics/
│ └── ecc_rpca_diagnostics/
├── segmentation/
│ ├── cellpose_labels.tif
│ ├── cell_counts.csv
│ └── cellpose_metadata.json
├── tracking/
│ ├── tracks_df.csv
│ ├── tracked_labels.tif
│ ├── cell_areas.csv
│ └── tracking_metadata.json
└── qc/
├── cells_per_frame.png
├── segmentation_overlays.png
├── track_durations.png
├── tracking_overlays.png
├── track_metrics.csv
└── summary.json
Large corrected TIFF intermediates are retained in Nextflow's work/ directory for -resume but are only copied to results/ when:
--save_intermediates true
Other optional outputs are controlled by:
--save_aligned_stacks true
--save_normalized_stack true
--save_ultrack_database true
When tracking databases are requested, one database is saved for each contiguous non-empty time segment under tracking/ultrack_databases/.
QC always writes track_metrics.csv, with one row per track_id (a track segment, not necessarily a cell followed through the entire movie). The table has a stable set of columns:
| Columns | Meaning |
|---|---|
track_id, parent_track_id |
Track and parent segment IDs; parent IDs are preserved from the input (-1 means no parent, blank means not supplied). |
n_positions, first_frame, last_frame |
Number of observations and zero-based first/last frame indices. |
duration_frames |
Elapsed frame intervals: last_frame - first_frame, not the number of observations. |
total_distance_px |
Sum of Euclidean distances between successive observed centroids, sorted by time within each track. |
net_displacement_px |
Euclidean distance between the first and last observed positions. |
mean_speed_px_per_frame |
Total distance divided by elapsed frame intervals. |
max_step_distance_px |
Largest distance between successive observed positions; includes steps spanning gaps. |
has_gaps, sufficient_observations |
Whether any successive observations are more than one frame apart, and whether at least two positions exist. |
duration_min |
Elapsed duration in minutes; requires frame_interval_min. |
total_distance_um, net_displacement_um, max_step_distance_um |
Corresponding distances in micrometers; require pixel_size_um. |
mean_speed_um_per_min |
Mean speed in micrometers/minute; requires both calibration parameters. |
Both calibration parameters default to null; unavailable physical values are blank, never calculated using an assumed scale. pixel_size_um must describe the processed image resolution, including any earlier resizing, and assumes square pixels. frame_interval_min assumes uniform acquisition timing. Supplied values must be finite and greater than zero. These parameters apply to every sample in the run; use separate runs for movies with different calibrations.
Parent and daughter segments are measured separately. Missing frames are bridged by a straight-line step between observations and flagged by has_gaps; movement during the gap is unknown. Tracks containing only one position have blank movement values rather than zero. Empty tracking results produce a header-only CSV. Duplicate track/frame pairs, non-finite coordinates, and invalid frame indices are rejected.
The calculation uses the exported tracking coordinates without smoothing or additional drift correction. Distances are observed 2D centroid-path estimates, not guaranteed true cell travel: inspect identity switches, segmentation changes, residual drift, and observation duration before comparing cells. The existing QC summary.json records calibration and calculation policies under track_metrics and lists the CSV under outputs.
Defaults are defined in nextflow.config, and a complete editable example is provided in params/example.yml. Important groups are:
basicpy_*— frame registration, darkfield estimation, and autotuning.ecc_*— motion model and ECC convergence.rpca_*— sparse penalty, tolerance, and maximum iterations.cellpose_*— model, compute device, diameter, thresholds, and batch size.ultrack_*— contour smoothing, area filters, linking distance, solver weights, gap, and time limit.qc_frames— selected frames for static overlays; accepts a YAML list or comma-separated CLI values.pixel_size_um— optional micrometers per processed image pixel for QC distance conversion.frame_interval_min— optional minutes between consecutive frames for QC duration and speed conversion.
For example:
nextflow run main.nf \
--input 'images/stacked.tif' \
--cellpose_device mps \
--cellpose_flow_threshold 0.4 \
--ultrack_max_distance 80 \
--qc_frames 0,22,43 \
-profile standard \
-resumeThe project uses Python's built-in unittest runner. Developers can create the named stage environments directly from the same files used by Nextflow:
conda env create -f envs/basicpy.yml
conda env create -f envs/analysis.yml
conda env create -f envs/cellpose.yml
conda env create -f envs/ultrack.ymlRun the BaSiCPy tests with:
PYTHONPATH=src conda run -n cell-track-basicpy \
python -m unittest -v tests.test_common tests.test_basicpy tests.test_cliRun the ECC/RPCA and QC tests with:
PYTHONPATH=src conda run -n cell-track-analysis \
python -m unittest -v \
tests.test_common \
tests.test_ecc_rpca \
tests.test_qc \
tests.test_cliRun the Cellpose adapter tests with:
PYTHONPATH=src conda run -n cell-track-cellpose \
python -m unittest -v tests.test_common tests.test_segmentation tests.test_cliRun the Ultrack tests with:
PYTHONPATH=src conda run -n cell-track-ultrack \
python -m unittest -v tests.test_common tests.test_tracking tests.test_cliValidate the workflow without running the analysis:
nextflow run main.nf \
--input 'images/stacked.tif' \
-profile standard \
-preview
nextflow run main.nf \
--input 'images/stacked.tif' \
-profile standard \
-stub-runThe first preprocessing stage uses BaSiCPy 1 to correct smooth illumination artifacts such as shading, vignetting, lamp drift, and frame-to-frame baseline variation. This is run in a separate basicpy environment because BaSiCPy dependencies are easier to isolate from the main ultrack environment.
BaSiCPy reduces low-frequency illumination effects, but it is not intended to remove the sharp, spatially structured hydrogel pattern. That remaining structured background is handled by the ECC + robust PCA stages.
Robust PCA works best when the background is spatially consistent across frames. In this data, the hydrogel pattern can translate or rotate slightly over time, so each BaSiCPy-corrected frame is aligned to a temporal-median template using Enhanced Correlation Coefficient (ECC) image registration 2.
The current notebook uses a Euclidean motion model:
ECC_MOTION = cv2.MOTION_EUCLIDEANThis allows translation and rotation. The estimated transforms align the movie before robust PCA, and the extracted sparse cell signal is then transformed back to the original movie coordinates.
After alignment, the movie is reshaped into a matrix:
Robust PCA models the movie as:
where:
Lis a low-rank component representing the recurring hydrogel/background pattern.Sis a sparse component representing transient foreground signal, primarily cells.
This decomposition is appropriate here because the aligned hydrogel pattern is present in every frame and therefore highly correlated over time, whereas cells occupy a relatively small fraction of pixels and change position/morphology across frames.
The implementation uses an inexact augmented Lagrange multiplier approach. The main tuning parameter is RPCA_LAMBDA_MULTIPLIER, which controls the penalty on the sparse component. Larger values can suppress residual pattern more strongly, but may also remove faint cell signal if set too high.
The background-subtracted sparse component is normalized frame-by-frame and segmented with Cellpose-SAM 5 through the Cellpose v4 API. The default model is cpsam_v2, with cpsam as a fallback.
Cellpose-SAM is a good fit for this data because the cells can undergo large morphology changes, from rounded to spread or star-like shapes. The pipeline does not rely on a star-convex or fixed-shape assumption.
The segmentation output is saved as: outputs/cellpose_labels.tif
Large full-stack napari label display is avoided by default because it can freeze the VS Code/Jupyter Qt event loop on macOS.
Cellpose labels are independent in each frame; label IDs are not temporally consistent. Ultrack 6, 7 converts the per-frame labels into foreground and boundary maps, builds candidate segmentation hypotheses and frame-to-frame links, and solves a global integer linear program to recover consistent tracks.
The main tracking outputs are:
outputs/tracks_df.csv
outputs/tracked_labels.tif
tracks_df.csvstores track IDs, time points, and centroid coordinates.tracked_labels.tifstores label images with temporally consistent track IDs.
If Gurobi is unavailable, Ultrack falls back to the CBC solver. CBC works, but exact optimization can be slow; exploratory runs may benefit from a nonzero solution gap, a time limit, or temporal windowing.
Common generated files include:
images/*_basicpy_timelapse.tif
images/*_basicpy_flatfield_only.tif
images/basicpy_diagnostics/
outputs/cellpose_labels.tif
outputs/tracks_df.csv
outputs/tracked_labels.tif
data.db
metadata.toml
These files can be large and are usually treated as generated artifacts rather than source files.
-
Peng, T., Thorn, K., Schroeder, T., Wang, L., Theis, F. J., Marr, C., & Navab, N. (2017). A BaSiC tool for background and shading correction of optical microscopy images. Nature Communications, 8, 14836. https://doi.org/10.1038/ncomms14836
-
Evangelidis, G. D., & Psarakis, E. Z. (2008). Parametric image alignment using enhanced correlation coefficient maximization. IEEE Transactions on Pattern Analysis and Machine Intelligence, 30(10), 1858–1865. https://doi.org/10.1109/TPAMI.2008.113
-
Candès, E. J., Li, X., Ma, Y., & Wright, J. (2011). Robust principal component analysis? Journal of the ACM, 58(3), 1–37. https://doi.org/10.1145/1970392.1970395
-
Lin, Z., Chen, M., & Ma, Y. (2010). The augmented Lagrange multiplier method for exact recovery of corrupted low-rank matrices. arXiv:1009.5055. https://arxiv.org/abs/1009.5055
-
Pachitariu, M., Rariden, M., & Stringer, C. (2025). Cellpose-SAM: superhuman generalization for cellular segmentation. bioRxiv (Cold Spring Harbor Laboratory). https://doi.org/10.1101/2025.04.28.651001
-
Bragantini, J., Lange, M., & Royer, L. (2024). Large-Scale multi-hypotheses cell tracking using ultrametric contours maps. In Lecture notes in computer science (pp. 36–54). https://doi.org/10.1007/978-3-031-72986-7_3
-
Bragantini, J., Theodoro, I., Zhao, X. et al. Ultrack: pushing the limits of cell tracking across biological scales. Nat Methods 22, 2423–2436 (2025). https://doi.org/10.1038/s41592-025-02778-0