⚠️ Disclaimer — This pipeline is an experiment and written almost exclusively by an AI agent under human supervision. It is still under construction and the documentation so far has been written by AI. The results have been validated against Caiman and on synthetic data. However, use it under your own risk and assess results for plausibility. If you find errors please raise an issue so that they can be fixed! Enjoy and stay tuned for changes :)
Minicnmfe is a clean Python implementation of CNMF-E (Constrained Non-negative Matrix Factorization for Endoscopic data) for 1-photon miniscope calcium imaging — the standard algorithm for extracting neurons from miniscope recordings. It is heavily inspired by CaImAn (https://github.com/flatironinstitute/caiman) but does not import anything from the caiman library.
Apart from it being an experiment, the focus of this pipeline is on automatic parameter estimation from raw videos — the tuning/ library in this repository.
The notebooks are the intended way to learn this pipeline. Everything under
docs/is AI-written and has not been reviewed line-by-line — treat it as a convenience reference, not ground truth. The notebooks indemo_notebooks/run end-to-end on generated demo movies and validate their own output, so they are the reliable place to understand how to use the pipeline. Read Notebooks — start here first.
Given a raw miniscope recording, CNMFe returns:
| Output | Shape | Description |
|---|---|---|
A |
(H·W, K) sparse |
Spatial footprints — where each neuron lives |
C |
(K, T) |
Denoised calcium traces |
S |
(K, T) |
Inferred spike trains |
The pipeline handles the core challenge of 1-photon imaging: a large, spatially-correlated background that swamps individual neuron signals. It uses a ring-model background — per-pixel ridge regression on a ring of surrounding pixels — to separate neural signal from diffuse background before source extraction.
Requires Python ≥ 3.10.
git clone https://github.com/FlorianNeurosci/miniCNMFe.git
cd miniCNMFe
pip install -e .AR deconvolution (oasis-deconv) ships as a core dependency, so a plain
pip install -e . already gets the fast compiled OASIS path. The [oasis] extra
is kept only for back-compat:
pip install -e ".[oasis]" # equivalent — oasis-deconv is already a core depIf oasis-deconv is unavailable, the package falls back to a pure-Python PAVA
implementation for AR(1).
Optional — tests / tutorials / dev:
pip install -e ".[test]" # pytest + pytest-cov
pip install -e ".[tutorial]" # jupyter + ipywidgets
pip install -e ".[dev]" # test + tutorial + oasis + ruffJust want to run miniCNMFe on your own recording? Start with
00_analyze_your_own_session.ipynb — a
copy-paste template that takes one session from raw AVIs to saved results. Set your paths,
run it top to bottom. The numbered notebooks below teach each stage in depth on the bundled
demo movies.
The notebooks in demo_notebooks/ are the primary way to learn the
pipeline. They run end-to-end on the reproducible simulated movies, so after a fresh clone
you can execute them from top to bottom without any data of your own:
python generate_demo_movies.py # creates demo_movies/*.avi + ground-truth *_meta.npz
python convert_to_zarr.py # creates demo_movies/*.zarrThen work through them in order — this is the real-data workflow: load & motion-correct → tune → extract → advanced.
| # | Notebook | What it covers |
|---|---|---|
| 1 | 01_load_and_motion_correct.ipynb |
Ingest a session (numbered AVIs → one zarr) and run streaming rigid motion correction; verify the recovered shifts against the simulated ground-truth drift. |
| 2 | 02_tuning.ipynb |
Pick parameters (sigma, seed thresholds, downsample factors) with the automated tuner, validated against ground truth on a calibrated movie. Run this before extraction. |
| 3 | 03_extract_components.ipynb |
The full extraction (seeding → ring background → spatial/temporal updates → merging → auto-eval) on a clean movie, scored against ground-truth footprints and traces. |
| 4 | 04_advanced_features.ipynb |
Production knobs for real recordings: nrg footprint tightening, region cutouts, downsample-once + upsample, the rank-1 global background, and fused AVI → motion correction. |
The fastest route is the template notebook
00_analyze_your_own_session.ipynb —
set your session path and run it top to bottom. Under the hood it follows the recommended
path below, which mirrors the numbered notebooks — motion-correct → tune → extract → refine:
-
Load & motion-correct (notebook 1) — concatenate the AVIs and run streaming rigid motion correction, producing a corrected
mc.zarr. The fusedCNMFe.fit_mc_from_avis(folder, output_dir=...)does decode + MC in one pass. -
Tune (notebook 2) — let the automated tuner pick
sigma, seed thresholds and downsample factors for your recording, rather than guessing. From the CLI:python tune.py /path/to/session # heuristics + extraction sweep + validation -> report.html + recommended_params.json(The
/tune-sessionskill wraps the same workflow.) Set the seed thresholds from the recording's own CORR/PNR distribution — the single most important habit on real data. -
Extract (notebook 3) — run the full pipeline with those parameters and save
A,C,S,YrA. -
Refine (notebook 4) — reach for the production knobs (footprint tightening, rank-1 global background, cutouts) on long, dense or drifting recordings.
For batch or headless runs the same workflow is available as scripts:
# One-shot: concatenate AVIs, run the full pipeline, save results/
python -m minicnmfe.concat_avis_to_zarr /path/to/recording/
python full_pipeline.py /path/to/recording/movie.zarr --sigma 3.0 --n-jobs -1Results are written to /path/to/recording/results/:
A.npz spatial footprints (scipy CSC, H*W x K)
C.npy OASIS-deconvolved traces (K x T)
S.npy spike trains (K x T)
YrA.npy residuals; C + YrA is the noisy projected trace (K x T)
shifts.npy per-frame motion correction shifts (T x 2)
sn.npy per-pixel noise std (H x W)
params.json all pipeline parameters
For finer control, the pipeline is split into staged CLIs with a disk handoff between
each step (--params p.json carries a CNMFeParams between stages):
run_preprocess.py (downsample) → run_mc.py (motion correction) → run_extract.py
(extraction) → run_evaluate.py (re-run auto-eval / retune thresholds without
re-extracting). See docs/getting-started/ and
docs/tuning/.
AVI / zarr movie (T × H × W)
│
▼ motion_correction_rigid() — cv2 filter2D high-pass + DFT shift estimation
│ + cv2 warpAffine apply (matches CaImAn)
▼ estimate_noise() — per-pixel noise std from high-freq PSD
▼ greedy_corr_pnr() — seed detection → extract → subtract → repeat
▼ compute_W() — ring-model background weights
│
└─ for n_iter_main:
BackgroundSubtractor — lazy (Y − b0) − W(Y − b0), never materialised
update_spatial() — per-pixel non-negative elastic-net CD
update_temporal() — block coordinate descent + OASIS AR deconvolution
merge_components() — merge spatially overlapping + correlated pairs
compute_W() — refresh b0 (W cached)
│
▼ auto_evaluate_components() — record per-component quality (gate OFF by default; opt-in ghost tagging)
▼ update_temporal() — final deconvolution pass
│
A, C, S
The notebooks above are the recommended, guided way to run the pipeline. This is the minimal programmatic form:
from minicnmfe import CNMFe, CNMFeParams
from minicnmfe.io import avi_to_zarr
# Convert video to zarr (streams frame-by-frame, never loads full movie)
movie = avi_to_zarr("recording.avi", "/tmp/movie.zarr")
params = CNMFeParams(
sigma=3.0, # neuron radius in pixels — most important parameter
min_corr=0.8, # minimum local correlation for a seed
min_pnr=10.0, # minimum peak-to-noise ratio for a seed
)
model = CNMFe(params).fit(movie)
print(f"Found {model.A.shape[1]} neurons")
A = model.A # (H*W, K) sparse — spatial footprints
C = model.C # (K, T) — calcium traces
S = model.S # (K, T) — spike trainsFrom a numpy array (no video file):
import numpy as np
from minicnmfe import CNMFe, CNMFeParams
movie = np.load("movie.npy") # (T, H, W) float32
model = CNMFe(CNMFeParams(sigma=3.0)).fit(movie, do_motion_correction=False)params = CNMFeParams(sigma=3.0, n_jobs=-1) # use all available CPU coresEvery bottleneck step that can be parallelised — the ring-background solve, the
per-pixel spatial elastic-net coordinate descent, temporal OASIS deconvolution,
and the per-frame motion-correction / CORR-PNR work — respects n_jobs. The
default is 1 (serial); -1 uses all cores.
Windows:
n_jobs != 1usesspawn-based multiprocessing. WrapCNMFe(...).fit(...)calls insideif __name__ == "__main__":in scripts.
| Parameter | Default | Effect |
|---|---|---|
sigma |
3.0 |
Neuron Gaussian radius in pixels — set this first |
min_corr |
0.8 |
Minimum local correlation to accept a seed (lower → more neurons) |
min_pnr |
10.0 |
Minimum peak-to-noise ratio for a seed (lower → more neurons) |
min_pixel |
3 |
Greedy-init footprint pixel floor (also the gate's pixel check when opted in) |
auto_eval_snr_amp_thr |
0.0 |
Acceptance-gate SNR threshold; 0 = gate off (report-only). Raise (~3) to opt in to ghost tagging |
n_iter_main |
2 |
Full spatial + temporal + merge cycles |
ar_order |
1 |
AR model order for calcium dynamics (1 or 2) |
ring_size_factor |
1.5 |
Ring radius = factor × (2σ + 1) |
init_stride |
auto | Temporal stride for greedy init (None = max(1, T // 5000)) |
init_patches |
True |
Parallelize the serial greedy seed loop across overlapping FOV patches (auto-skips for small FOV / streaming movies; see usage guide) |
spatial_ridge |
1e-2 |
Elastic-net L2 on the per-pixel update_spatial LASSO; keeps the CD converging when components are correlated (0.0 = pure LASSO) |
spatial_max_iter |
1000 |
Per-pixel CD iteration cap (backstop; rarely hit with spatial_ridge) |
n_jobs |
1 |
CPU workers (-1 = all cores) |
Pick these automatically for a recording with tune.py (see Analyze your own data).
The following pass the automated test suite (on synthetic data) but have not yet been validated on real recordings. They're off by default and don't change standard behaviour unless you enable them — but treat their output as provisional and sanity-check before relying on it:
- Cutout analysis —
temporal_crop/spatial_crop/spatial_mask_pathcrop or mask the movie at ingestion (withplace_in_full_fov()to map results back). Note: not supported on the streamingY_flat_zarrpath. - Detrending —
ar_detrend_order/temporal_detrend_order(polynomial detrend before AR estimation / OASIS) and thedetrend_moviepreprocessor. Non-standard; defaults are0(disabled = standard CNMF-E). - Running many sessions concurrently — one process per session with capped
BLAS threads (
OMP_NUM_THREADS=1, etc.) and a unique scratch dir per session. This workflow is not covered by automated tests — validate on a small batch first.
pip install -e ".[test]"
pytest tests/ -vA comprehensive pytest suite (300+ tests) covers every module plus end-to-end pipeline, motion correction streaming, multiprocessing correctness, and auto-evaluation. All tests use synthetic ground-truth movies generated in tests/conftest.py and tests/miniscope_simulator.py.
minicnmfe/
├── __init__.py # Public API: CNMFe, CNMFeParams, auto_evaluate_components, ...
├── _utils.py # Shared helpers (make_2d, get_xp, to_numpy, ...)
├── io.py # AVI/MP4 -> zarr converter, open/save zarr
├── motion_correction.py # Rigid motion correction (cv2 filter2D + DFT + warpAffine)
├── preprocess.py # Noise estimation, center-surround PSF, CORR/PNR
├── background.py # Ring-model background (compute_W, BackgroundSubtractor)
├── initialization.py # Greedy CORR-PNR seed detection and extraction
├── spatial.py # Spatial footprint update (elastic-net coordinate descent per pixel)
├── temporal.py # Temporal update + OASIS AR deconvolution
├── merging.py # Component merging (overlap + correlation)
├── evaluate.py # Auto-evaluation: per-component quality metrics (opt-in gate)
└── pipeline.py # CNMFeParams dataclass + CNMFe.fit() orchestrator
tests/ # pytest suite (300+ tests) — synthetic ground-truth data
docs/ # AI-written documentation (getting-started, concepts, api, guides, tuning)
demo_movies/ # Generated demo AVIs + zarr stores (created by scripts)
demo_notebooks/ # Tutorial notebooks (see "Notebooks — start here" above)
generate_demo_movies.py # Generate demo_movies/*.avi with ground-truth sidecars
convert_to_zarr.py # Batch-convert demo_movies/*.avi -> *.zarr
full_pipeline.py # CLI: load zarr, run full pipeline, save results to disk
The prose docs below were written by AI and have not been reviewed line-by-line. They are a convenience reference — the notebooks are the trusted, executable source of truth for how to use the pipeline.
Documentation lives in docs/ and renders directly on GitHub:
| Page | Contents |
|---|---|
docs/getting-started/ |
Install, quick-start, end-to-end workflow, CLI, troubleshooting |
docs/api/ |
Every public function and CNMFeParams field — signatures, parameters, returns |
docs/concepts/ |
Algorithm math + intuition, architecture, ring background, CaImAn comparison |
docs/guides/ |
Per-stage implementation walkthroughs (motion correction → evaluation) |
docs/tuning/ |
Automated parameter-tuning workflow + the tuning guide |
All algorithms are reimplemented from scratch. The CaImAn repository is used as an algorithmic reference only — no code is imported.
| Algorithm | CaImAn reference | Implementation |
|---|---|---|
| Rigid registration | motion_correction.py:1442 |
cv2.filter2D (1p high-pass) + cv2.dft cross-correlation |
| Shift application | motion_correction.py:1643 |
cv2.warpAffine (bit-identical to CaImAn) |
| CORR/PNR images | summary_images.py:286 |
scipy.ndimage + numpy.fft.rfft |
| Noise estimation | pre_processing.py:128 |
numpy.fft.rfft along time axis |
| Ring background | initialization.py:1900 |
vectorised ridge regression per pixel |
| GreedyCorr init | initialization.py:1380 |
reimplemented, sequential greedy loop |
| Spatial update | spatial.py:29 |
sklearn enet_coordinate_descent_gram (positive elastic-net CD) |
| Temporal update | temporal.py:64 |
coordinate descent + OASIS package |
| Deconvolution | deconvolution.py:16 |
oasis-deconv + pure-Python AR(1) fallback |
| Merging | merging.py:19 |
scipy.sparse graph connected components |
| Component eval | estimates.evaluate_components |
pixel-count + amplitude-SNR filter (evaluate.py) |
| Package | Used for |
|---|---|
numpy |
Array operations throughout |
scipy |
FFT, sparse matrices, ndimage filters |
scikit-image |
Peak detection, blob feature filters |
scikit-learn |
Elastic-net coordinate descent (enet_coordinate_descent_gram) for spatial update |
opencv-python |
Motion correction (filter2D, dft, warpAffine) |
av (PyAV) |
AVI decoding via imageio's pyav plugin |
zarr |
Lazy, chunked movie storage |
joblib |
CPU multiprocessing |
imageio / imageio-ffmpeg |
AVI/MP4 reading and writing |
matplotlib |
Plotting (tutorial notebooks) |
tqdm |
Progress bars |
oasis-deconv |
Fast compiled OASIS AR deconvolution (core dep; pure-Python PAVA fallback if unavailable) |
numba |
GIL-free per-pixel coordinate descent in the spatial update |
pytest, pytest-cov ([test] extra) |
Test runner |
jupyter, ipywidgets ([tutorial] extra) |
Demo notebooks |