Motion correction¶
Source: minicnmfe/motion_correction.py. Entry point:
motion_correction_rigid(...); called from CNMFe.fit_mc / CNMFe.fit.
The only motion-correction algorithm in the package is rigid — whole-frame
translation (dy, dx), no rotation or non-rigid warping. It is built to
reproduce CaImAn's shifts on real 1-photon data, so the specific OpenCV calls
below are load-bearing.
Per-frame algorithm¶
Each frame is registered to a fixed template in three steps
(_filter_estimate_apply):
-
High-pass filter (
high_pass_filter_space). For 1-photon data the broad background swamps cross-correlation, so the frame is convolved with a sum-to-zero Gaussian kernel of widthgSig_filt(cv2.getGaussianKernel→ outer product → subtract its mean on the high pixels, zero the rest, thencv2.filter2DwithBORDER_REFLECT). The kernel side length isint((3·gSig_filt)//2 · 2 + 1). Skipped whengSig_filtisNone(2-photon data). -
Estimate the shift (
register_translation_caiman). Phase cross-correlation in the Fourier domain:cv2-based FFT of frame and template, normalized cross-power spectrumimage_product / |image_product|, inverse FFT, then the peak of|cross_corr|gives the integer shift. The peak search is constrained to±max_shiftpixels by zeroing the interior of the correlation surface. Sub-pixel refinement to1/upsample_factorpx follows via an upsampled DFT around the integer peak (_upsampled_dft). -
Apply the shift (
apply_shift_caiman) to the raw (un-filtered) frame withcv2.warpAffine(an affine matrix with translation only,INTER_LINEAR,BORDER_REFLECT), thennan_to_numand clip to≥ 0.
The shift is estimated on the filtered frame but applied to the raw frame, so the high-pass filtering never enters the corrected output.
Template¶
The template is a bin-median of a strided sample of frames
(_build_template_streaming → caiman_bin_median): up to
template_max_frames evenly-spaced frames are read, high-pass filtered, grouped
into windows of bin_window (default 10), mean-reduced within each window, then
median-reduced across windows. Sampling caps template RAM at
template_max_frames · H · W · 4 bytes regardless of T.
With niter_rig > 1, each pass rebuilds the template from the previous pass's
corrected frames and the per-pass shifts are summed
(shifts_total += shifts_iter).
Template sharpening (sharpen_template, default on)¶
A plain bin-median over the strided sample is smeared when the drift is large
relative to the FOV: the sample spans every drift position, so the median lands
near the mean position and cross-correlating against that blur pulls each
frame's peak toward zero — a single pass then under-tracks (recovers the
right shift shape but a compressed amplitude). Historically you fixed this
with several niter_rig passes, each re-sharpening the template from the
corrected frames — but every pass re-reads and re-writes the whole movie.
sharpen_template=True (the default; CNMFeParams.mc_sharpen_template) does
the sharpening up front and cheaply (_build_sharpened_template): it aligns just
the in-RAM frame sample to convergence (the in-memory MC loop run on the
sample, no streaming IO) and builds the template from the aligned sample. A
single full-movie pass with that template then recovers the full amplitude —
matching a multi-pass run at ~one-pass cost. Because the sample is fixed-size
(≤ template_max_frames), the expensive full-movie sweep happens once
regardless of recording length, which is the dominant win on long sessions.
Set sharpen_template=False (mc_sharpen_template=False) for the legacy
smeared-median template — the exact CaImAn-equivalent behaviour. Sharpening is
skipped automatically when an explicit template is supplied.
Early-stop on convergence (converge_tol)¶
When you do run multiple full passes, converge_tol (e.g. 0.01;
CNMFeParams.mc_converge_tol) stops the loop early once a pass improves the
template's high-pass sharpness (_template_sharpness) by less than that relative
amount — a ground-truth-free convergence check, so niter_rig becomes just an
upper bound rather than an exact count. None (default) runs exactly
niter_rig passes. The sample-sharpening loop above always runs to its own
(fixed-tolerance) convergence, independent of this.
Supplying a template (fit_mc)¶
CNMFe.fit_mc(movie, ..., template=..., template_window=...) lets you bypass the
auto-built template:
template— a precomputed(H, W)array to register against (raw; MC high-pass filters it internally).template_window=(t0, t1)— build the template as the mean of frames[t0:t1). Pick a short, low-motion window; only that slice is read. Mutually exclusive withtemplate.
Both are also available on motion_correction_rigid(..., template=...).
Two execution paths (chosen automatically)¶
motion_correction_rigid dispatches on input type and output_path:
- Streaming (
_motion_correction_streaming) — used when the input is azarr.Arrayor anoutput_pathis given. Reads/writes batches ofbatch_sizeframes; peak RAM is(batch_size + template_max_frames) · H · W · 4bytes, independent ofT. The corrected movie is written to a zarr store. Forniter_rig ≥ 2it ping-pongs between two scratch zarrs andshutil.moves the final result into place. Zarr input requiresoutput_path(it refuses to silently load the store into RAM). - In-memory (
_motion_correction_in_memory) — used for a numpy input with nooutput_path(the small-movie / test path). Same algorithm, returns a numpy array.in_place=Truewarps frames back into the input buffer (~1× peak RAM) instead of allocating an output (~2×).
Both paths parallelize the per-frame work within a batch over n_jobs (the
frames are independent given a fixed template). Returns (corrected, shifts)
where shifts is (T, 2) float32 (dy, dx) per frame.
Convenience wrappers¶
apply_shift(img, shift)— alias forapply_shift_caiman.estimate_shifts(frame, template, ...)—register_translation_caimanwith optional high-pass filtering of both inputs.