Spatial update¶
Source: minicnmfe/spatial.py. Entry: update_spatial(...). Re-estimates the
footprints A given the current traces C and the background-subtracted movie.
The per-pixel regression¶
Each pixel is modelled as a non-negative mix of the traces of the components that reach it:
So row p of A is found by regressing that pixel's (background-subtracted)
trace onto C[active]. active(p) is small — only components whose footprints
are near pixel p — which keeps every solve tiny and well-conditioned.
compute_support builds these sets by dilating each component's binary footprint
by dilation_radius pixels (default 2) and recording which pixels each dilated
footprint covers.
The solve is a non-negative elastic-net coordinate descent, calling
sklearn's Cython kernel enet_coordinate_descent_gram directly (or an
equivalent numba prange kernel on the parallel path). For each pixel it builds
the small Gram C_active · C_activeᵀ and C_active · y_p, then runs cyclic
coordinate descent with:
- L1 penalty
α = λ_p · T, whereλ_p = lambda_scale · 0.5 · sn_p · √(max_energy) / T(max_energy = max diag(Gram)). This is the per-pixel sparsity threshold a pixel'sC_k·ymust clear to turn on.lambda_scale > 1tightens footprints at the source (default 1). - L2 penalty
β = spatial_ridge · max_energy(defaultspatial_ridge = 1e-2). This is not a size knob — it bounds the condition number of the per-pixel Gram to ≈1/spatial_ridgeso the descent converges in tens of iterations even when active traces are near-duplicates (a near-singular Gram otherwise crawls to themax_itercap). Set0for pure LASSO.
Note: the real solver is the elastic-net coordinate descent described above (
enet_coordinate_descent_gram), notLassoLars.
The result prints a one-line update_spatial stats: diagnostic (mean/max CD
iterations, how many hit the cap, mean active-set size) so a slow update can be
attributed to non-convergence vs. dense active sets vs. sheer pixel count.
Footprint cleanup¶
Each raw column is then cleaned by threshold_footprint, in order:
- 3×3 median filter (removes isolated pixels), clip to
≥ 0. - Threshold faint pixels, one of two methods:
"max"— zero pixels belowmax_thr · peak(peak-relative; the legacy behaviour, thethreshold_footprintfunction default)."nrg"— energy thresholding: keep the brightest pixels whose summeda²reachesnrg_throf the total. Squaring discounts a footprint's dim skirt, so sprawled low-contrast footprints get trimmed cleanly. This is theCNMFeParamsdefault (spatial_thr_method="nrg",spatial_nrg_thr=0.95).- Binary closing (
closing_radius, default 1 = 3×3) to fuse one-pixel gaps, so a jagged LASSO support survives as one blob rather than fragmenting. - Largest connected component only.
- Circular constraint (optional) — clip pixels farther than
circular_max_dist_factor · √(area/π)from the centroid, trimming "tendril" extensions toward neighbours. Withmax_radius_factor > 0, also cap the clip distance at an absolutemax_radius_factor · sigmapx (the area-derived radius grows with a sprawled footprint and stops biting; the absolute cap doesn't).
Components whose footprint went all-zero are dropped by the caller after the
update. The cleanup runs per component and is parallelized over n_jobs.
Sizing knobs (dense FOVs)¶
Footprint size is set in three complementary places, all exposed on
CNMFeParams: spatial_lambda_scale gates which pixels the LASSO turns on
(sparsity), spatial_thr_method="nrg" / spatial_nrg_thr trims dim skirts
(intensity), and spatial_max_radius_factor clips far pixels (geometry). They
all default to no-op or the validated nrg@0.95, so the bit-for-bit serial path
is preserved unless you opt in.