Ring background¶
Source: minicnmfe/background.py (compute_W, BackgroundSubtractor,
build_ring_indices) and pipeline.py:_fit_global_bg_rank1. This is the part of
CNMF-E that makes 1-photon data tractable.
Why a ring¶
In a miniscope the out-of-focus background is large and spatially correlated: neighbouring pixels share most of their background fluctuation. CNMF-E exploits this — a pixel's background is predicted from a ring of pixels around it (far enough out to exclude the pixel's own neuron, close enough to share the background). The model is
where X is the residual after removing the neural signal and a per-pixel
baseline, and W is a sparse weight matrix whose row i is nonzero only on the
ring around pixel i.
Ring geometry¶
build_ring_indices(dims, radius) precomputes, for every pixel, the flat indices
of the pixels at Euclidean distance in (radius, radius+1] (a one-pixel-thick
annulus, built by subtracting an inner disk from an outer disk). Border pixels
simply have fewer ring neighbours. The radius used by the pipeline is
ring_size_factor · (2·sigma + 1) (default ring_size_factor = 1.5).
Solving for W and b0¶
compute_W fits each pixel independently. The baseline is the temporal mean of
the residual, computed by streaming reductions so the full residual is never
materialized:
Then, for each pixel i, a ridge-regularized least squares over its ring
neighbours B = X[ring_i]:
with λ = ring_lambda (default 1e-5, expressed as a fraction of
trace(B·Bᵀ)). The per-pixel solves are independent and parallelize over
n_jobs; tsub (bg_tsub, default 5) subsamples time for the expensive
B·Bᵀ solve only — b0 always uses the full T.
Two performance options that don't change the math:
W_cached— reuse a previously solvedWand only refreshb0. The ring's spatial structure is a property of the data, not ofA/C, so the BCD loop solvesWonce and reuses it across iterations.- Streaming path — for a zarr-backed
Y_flat, the residual rows for each pixel batch are pulled on demand (_compute_w_streaming) so the full(H·W, T)residual is never in RAM.
constrain_sum=True (param ring_constrain_sum, non-standard) adds the
equality constraint Σ_j W[i,j] = 1 per row via a Lagrangian, so any spatially
uniform brightness change (LED flicker, uniform bleaching) cancels exactly in the
residual. Standard CNMF-E leaves it unconstrained (False).
Subtracting the background lazily¶
The background-free movie is
(subtract_background). Materializing that is (H·W) × T — too big for long
recordings — so the BCD loop uses BackgroundSubtractor instead, which
computes pixel-row slices on demand from the identity
For a zarr Y_flat it reads only the ring-neighbour rows it needs. It also offers
project_onto(A) = Y_bgᵀ·A → (T, K) without ever forming Y_bg, via
Y_bgᵀ·A = Yᵀ·(I − Wᵀ)·A − b0·(...). Both spatial and temporal updates consume
the subtractor through these two methods, which is how extraction stays
RAM-bounded.
Optional rank-1 global term¶
With global_bg_rank=1 (non-standard), an extra term b_f · f(t) is fit on top
of the ring (_fit_global_bg_rank1). It captures spatially non-uniform slow
drift (vignetting-coupled photobleaching, scope warm-up) that the ring alone
cannot. It is fit by a few alternating least-squares sweeps on the residual
R = (I − W)(Y − b0) − A·C, both factors unconstrained (the drift is a signed
deviation from b0), warm-started across BCD iterations. When present,
BackgroundSubtractor also subtracts b_f · f(t).