CNMFe — Automated Parameter-Tuning Workflow¶
Point one command at a recording → it tests the recording, suggests motion-correction + extraction parameters, validates them on the full recording, and writes a self-contained
report.html(recommended values + graphs) so you can judge quality by eye, no AI needed.For what each knob does, see Parameter Tuning Guide; for signatures see API reference.
tune.py is the single front door for the workflow in the tuning/
package: it runs the heuristics + graded sweep, then (by default, for AVI input)
full-recording validation, and writes both an HTML and a Markdown report.
validate_session.py (validate one session) and batch_tune.py (a list of
sessions in one background process) are internal stages it composes.
What it produces¶
A new timestamped folder runs/tune_<name>_<ts>/ (the default runs/ parent is
gitignored) containing:
| File | Contents |
|---|---|
report.html |
Open this. Self-contained (figures inlined): config + recommended-params table, a click-to-sort candidate table, every figure, and — when validated — the recommended-vs-lowthr comparison side by side. Judge candidates by eye, offline. |
recommended_params.json |
A real CNMFeParams JSON (NATIVE units) — feeds run_mc.py / run_extract.py directly |
downsample.json |
ssub / tsub (not CNMFeParams fields) — feeds run_extract.py --ds-meta |
report.md |
The same content as a Markdown file (terminal / GitHub view) |
fig_*.png |
One figure per suggested parameter + the sweep figures |
full/ |
Full-recording validation (when validating): comparison.md, cn.npy, run_<label>/ with the fitted model + diagnostic figures |
Quick start¶
# tune + full-recording validate + HTML, all in one background run:
python tune.py /path/to/miniscope_video --indicator gcamp8m --n-jobs -1 &
# then open runs/tune_<name>_<ts>/report.html in a browser
[!TIP]
--indicator gcamp8mis shorthand for--decay-time-ms 180(table: 6f→140, 7f→160, 8f→70, 8m→180, 8s→350, 6s/7s→1000).--no-validatestops after the cutout sweep;--no-htmlskips the HTML;--no-lowthrdrops the lower-recall validation set.
The recommended params then feed the staged pipeline directly:
python run_mc.py /path/to/movie -o mc/ --params runs/tune_*/recommended_params.json
python run_extract.py mc/mc.zarr -o results/ \
--params runs/tune_*/recommended_params.json --ds-meta runs/tune_*/downsample.json
Two depth modes (--mode)¶
| Mode | What it does | Speed |
|---|---|---|
heuristic |
Image-based suggestions only — blob_log neuron radius, the 3-method (min_corr, min_pnr) proposals, shift histograms. No extraction (the morphology seed is reported). |
Fast |
sweep |
Actually runs fit_extract across a grid of the key knobs and scores each candidate with quality proxies. |
Slower |
both (default) |
Heuristics seed the grid; the sweep refines and adds the temporal/eval knobs. | Slower |
Only sweep/both (or --existing-results <dir>) produce the Stage-4 temporal
suggestions, because those need a fitted model to read.
Two sweep regions (--region)¶
| Region | What it does | When |
|---|---|---|
cutout (default) |
Runs the grid on a representative spatial+temporal window (auto-picked as the activity-dense region of the correlation image). | Long recordings — seconds-to-minutes per candidate |
full |
Runs the grid on the whole recording (streaming). | Short recordings, or when you want the faithful answer |
[!NOTE] Cutout values are a fast proxy: the auto-picked window maximises summed CORR (activity-dense), not mean intensity (which chases vasculature / vignette). The report records the chosen crop so you can sanity-check it.
Inputs¶
- AVI folder (
0.avi … N.avi) — the live-session input. The tuner runs a quick fused MC on a subset (--max-avis) at the suggestedssub/tsubto produce a scratchmc.zarrfor the extraction sweep. mc.zarr— an already-corrected movie. Skips the quick MC and the motion-correction heuristics; tunes extraction directly.--reuse-mc-zarr PATH— point the extraction sweep at anmc.zarryou already have (e.g. on a local SSD) regardless of the primary input.
What gets suggested¶
Motion correction (mc_gSig_filt, max_shift, border_px) and the
downsample factors ssub/tsub.
Initialisation (sigma, min_corr, min_pnr, min_pixel) — sigma from
blob_log on the CORR·PNR image; min_pixel from the footprint-area distribution
of a fast greedy init. For the thresholds, three methods each propose a
(min_corr, min_pnr) operating point — morphology (threshold that maximises the
cell-like-blob count), separation (max Youden's J of CORR/PNR at detected neuron
centres vs background), and percentile (25th-pct of CORR/PNR at neuron centres) —
and the sweep tests all three as coupled seeds, picking the winner by quality
score (the winning method is named in report.md). The CORR/PNR images are computed
from a 2000-frame, chunk-aligned sample of mc.zarr (n_init_frames); the
chunk-aligned read avoids the strided-single-frame I/O blow-up on time-chunked zarrs.
Sweep grid (the most-impactful extraction knobs, per the real-recording
findings in Tuning long or dense recordings): sigma,
min_corr, min_pnr, merge_thr_corr, global_bg_rank (0 vs 1), init_stride.
Pass comma-lists, e.g. --grid-min-pnr 6,10,14 --grid-bg-rank 0,1.
Temporal / evaluation (decay_time_ms, g_prior_weight, merge_thr_corr,
auto_eval_snr_amp_thr) — from the best swept model (or --existing-results).
Reading the figures¶
- Stage 1 — temporal-std with detected blobs (neuron radius); shift
histograms (
max_shift); ssub/tsub rule tables. - Stage 3 — CORR / PNR / CORR·PNR triptych (
sigma); morphology curves + thresholded CORR/PNR images at the detected(min_corr, min_pnr); footprint-area histogram (min_pixel). - Stage 4 — per-component τ and AR-
gdistributions (decay_time_ms,g_prior_weight); pairwise-correlation histogram (merge_thr_corr); SNR histogram + footprint montage at the threshold boundary (auto_eval_snr_amp_thr). - Sweep — the density↔purity scatter (K vs median
corr(C, C+YrA), point size = accepted fraction, colour = score, best starred); footprints over the correlation image thresholded at that candidate'smin_corr(sub-threshold background dropped to black so each footprint reads against the cell it covers; the mean projection is a vignette-dominated blob on 1p data — see Tuning long or dense recordings);CvsC+YrAtraces for the best candidate.
Component diagnostics (tuning.report)¶
Packaged from the old diagnostics.ipynb / cutout_analysis.ipynb cells, these
take a fitted model (and import cleanly into a notebook —
from tuning.report import fig_footprint_grid, …; a registry is in
report.DIAGNOSTIC_FIGS):
fig_footprint_grid(model)— top-N footprints cropped around theirfootprint_center(soma centred), titledk n e(id / npix / eccentricity), red title = auto-eval rejected. The first thing to look at.fig_eccentricity(model)— area / peak / eccentricity histograms; area ≫π(2σ)²= halo or multi-cell; eccentricity → 1 = elongated/vasculature.fig_jaccard_merge(model)— K×K spatial-Jaccard + trace-correlation matrices with the "should-have-merged" pair count (both above threshold).fig_centroid_drift(model, cn, pnr=…)— argmax (footprint_center, what the algorithm uses) vs binary-mask COM centres on CORR/PNR, quantifying COM drift.fig_mean_proj_and_activity(movie)— streamed mean projection + per-frame activity trace (drift / photobleaching / lockstep firing at a glance).
The quality metrics¶
These are ground-truth-free proxies, not validation — there is no ground truth on a real recording.
| Metric | Meaning | Direction |
|---|---|---|
K / K_accepted / accepted_frac |
components extracted / passing auto-eval | context |
cprojcorr_median |
median per-cell corr(C, C+YrA) — primary purity signal |
higher = purer; falls as K rises in dense FOVs (YrA cross-talk) |
npix_median / npix_iqr |
footprint pixel-count distribution | tight, consistent = good |
snr_median |
per-component mean-amplitude SNR | higher = stronger |
composite_score |
cprojcorr_median + 0.5·accepted_frac − 0.25·(npix_iqr/npix_median) |
ranking only |
[!CAVEAT] Don't chase cell count with thresholds — there is a density↔purity sweet spot, and
cprojcorr_medianis itself a good knob for picking it. Thecomposite_scoreis transparent and re-derivable from the table; re-rank with your own weights if you prefer.
These proxies are the seam a future validation harness could reuse for true cross-method / paired-ephys validation.
Gotcha checklist¶
This is the single source of truth for interpreting a tuning run. Apply every
item when reading a comparison.md / report.html:
- Long recording →
global_bg_rank=1. On a long movie slow drift / photobleaching makes traces collinear andupdate_spatialsmears footprints into merged blobs. Confirm rank-1 background wins in the sweep and footprints stay compact (tightnpixIQR).validate_session'sgood_defaultsset it. decay_time_msfrom the data is drift-inflated. Yule-Walkergis biased upward by slow background on long recordings, so the auto decay estimate (e.g. ~560 ms) is NOT the indicator. Set the physical τ from the indicator andg_prior_weight ≈ 0.6; a large auto estimate is evidence of drift, not a slow indicator.min_pixelis a floor; SNR is the ghost discriminator. Rejections in the auto-eval line should come fromsnr_amp, notpixel_count. Ifmin_pixelis doing the rejecting (especially after downsampling), lower it (~60 native) and letauto_eval_snr_amp_thr(~20) cut ghosts.- Full-vs-cutout recall. Thresholds tuned on a short cutout over-prune the
full recording (PNR falls over long T). Judge recall on
footprints_on_corr.pngagainst the correlation image — count unclaimed bright blobs — not on rawK. Expect the lower-threshold run to recover more cells at a modest purity cost. - Density ↔ purity is a real tradeoff. Report it in numbers (K / accepted vs
cprojcorr_median) and recommend an operating point rather than "more is better";cprojcorr_medianfalls as K rises in a dense FOV (YrA cross-talk). - Hazy / out-of-focus FOV → over-estimated neuron radius. On a hazy or
out-of-focus recording
blob_logon the temporal-std latches onto broad background structure and reports a too-large radius (e.g. 6.3 px when the neurons are ~4 px), which cascades into a too-largesigma, an over-aggressivessub, a hugemin_pixel, and sprawling footprints.suggest_mc_gsig_and_sigmanow applies a spatial high-pass (highpass_sigma=8px, default on) beforeblob_logto strip the haze, so this is handled automatically. Tell-tale:sigma/ssub/min_pixelcome out ~2× a comparable in-focus session — checkfig_mc_gsig.png; the detected-radius histogram should sit at the neuron scale, not be dragged up by a tail of large blobs. Bumphighpass_sigmafurther only if a very hazy session still over-estimates. - Motion correction. From
mc_shifts.png: small shifts (≲ a few px) → one MC pass is fine; large/erratic shifts warrant a biggermax_shiftormc_n_iter=2.
Symptom → cause → knob (by-eye)¶
The full by-eye troubleshooting table (multi-peak / amoeba / donut / crescent /
streaky / fragmented / ghosts / lockstep / square-wave traces / …) lives in code
as tuning.report.SYMPTOM_CAUSE_KNOB and is embedded in every report.html. The
most common few:
| Symptom | Knob |
|---|---|
| Too many ghost components | raise min_pnr / auto_eval_snr_amp_thr |
| Too few neurons | lower min_corr / min_pnr; pin init_stride to 1–2 on long movies |
| Footprints sprawl / merge into blobs (long recording) | global_bg_rank=1, spatial_max_thr↑, spatial_circular_max_dist_factor↓ |
Footprints sprawl + sigma·ssub·min_pixel ~2× a comparable session (hazy/out-of-focus FOV) |
radius over-estimated by blob_log on haze — auto-fixed by the highpass_sigma pre-filter; check fig_mc_gsig.png, raise highpass_sigma if it persists |
| Distinct neighbours fused | raise merge_thr_corr, lower merge_centre_dist_factor |
| "Shark-fin" / over-smoothed traces | set decay_time_ms + frame_rate_hz (Bayesian g prior) |
See Parameter Tuning Guide for the full per-knob discussion, and the Component diagnostics figures above to identify which symptom you have.
Full-recording validation (validate_session.py)¶
The tuner sweeps on a cutout; to confirm the params on the whole recording —
where drift accumulates and PNR falls — use the validation CLI. It fuses motion
correction once, transposes the pixel-major Y_flat once, then runs
fit_extract for each min_corr:min_pnr set you ask for, reusing the
(threshold-independent) Y_flat so each extra candidate skips the expensive MC +
transpose. It writes per-run results, diagnostic figures, and a comparison.md.
python validate_session.py /path/to/miniscope_video -o out/ \
--indicator gcamp8m --thresholds "0.8:10,0.7:6"
It bakes in the long-real-recording defaults learned on a real session
(global_bg_rank=1, low min_pixel floor with the SNR check doing ghost
rejection, the physical-decay prior, pinned init_stride) via
tuning.validate.good_defaults. Frame rate and dims are auto-read from the
session's metaData.json. tune.py already runs this stage for you (it calls
tuning.validate.tune_then_validate); use validate_session.py directly only to
re-validate an existing recommendation or to add threshold sets.
[!CAVEAT] See the Gotcha checklist above — in particular the drift-inflated
decay_time_msand the full-vs-cutout recall caveats — when reading the comparison.
Batch — many sessions in one background process¶
For several sessions, point the front door at a list:
This delegates to batch_tune.run_batch, which runs one tune.py --validate
subprocess per session under bounded concurrency (--jobs), each pinned to
--cores and BLAS-capped so jobs·cores threads don't oversubscribe (keep
jobs·cores ≤ nproc−2). Each
session is tuned independently (its own measured sigma/ssub/thresholds —
assume different animals/scopes). It writes per-session folders + a
batch_summary.md. tuning.validate.resolve_session_paths parses the list
(multiple args or a .txt; dedups, skips missing). batch_tune.py is the same
thing as a standalone CLI.
Relation to other docs¶
- usage guide — what each parameter means and the long/dense-recording findings.
- API reference —
CNMFeParamsfields and method signatures. - architecture — where the staged CLIs (
run_mc/run_extract) fit.