nstat.extras.decoding.place_field_decoder — Place-cell encoding + 2-D PPAF decoding
A one-call wrapper around the canonical 2-D place-cell encoding and
decoding pattern from
examples/paper/example08_real_place_cells.py.
The example08 script spells out the full pipeline — tensor-product
B-spline Poisson GLM (encoding), per-cell quadratic conditional
intensity refit, and DecodingAlgorithms.PPDecodeFilterLinear (the
history-free O(C·T) fast path) on the held-out trajectory. This
wrapper packages that pipeline behind one function:
fit_place_field_decoder(trial, position).
This is a pure-core module — no opt-deps beyond the numpy + scipy
stack that nstat already requires.
Install
pip install nstat-toolbox
No extras group needed. The wrapper imports
nstat.extras.spatial.basis.bspline_basis_2d,
nstat.glm.fit_poisson_glm, nstat.glm.fit_binomial_glm,
nstat.CIF, and nstat.DecodingAlgorithms.PPDecodeFilter* — all of
which ship in the core toolbox.
API
Symbol |
Notes |
|---|---|
|
Frozen dataclass. Validates field values in |
|
Fit the encoding + decoding pipeline on a single |
|
Frozen dataclass holding |
Choosing decode_filter
"linear"(default) callsPPDecodeFilterLinear— the O(C·T) fast path documented in PR #198. Whencif_kind="quadratic", the per-cell coefficients are Taylor-linearised at the trajectory mean so the filter consumes the linear(mu, beta)form. Stable on smooth long-running trajectories where the trajectory mean is a good operating point; can diverge on short or atypically-shaped walks."nonlinear"callsPPDecodeFilter(CIF-object branch) — slower but evaluates the full quadratic CIF analytically each step, so the decode is robust to off-mean excursions of the state.
Choosing cif_kind
"quadratic"matches example08: six coefficients per cell[1, x, y, x^2, y^2, x*y]— captures the asymmetric falloff of a real place field."linear"is the lighter[1, x, y]form — sometimes cleaner for sparsely-spiking cells where the quadratic terms over-fit.
Recipe
import numpy as np
from nstat import Covariate, CovariateCollection, SpikeTrainCollection, Trial, nspikeTrain
from nstat.extras.decoding import PlaceFieldDecoderConfig, fit_place_field_decoder
# Build a synthetic single-cell trial — see the demo for the full
# multi-cell version with smooth OU position.
T, fs = 30.0, 50.0
t = np.arange(int(T * fs)) / fs
x_pos = 0.5 + 0.3 * np.sin(2 * np.pi * t / T)
y_pos = 0.5 + 0.3 * np.cos(2 * np.pi * t / T)
# 200 Poisson spikes concentrated near (0.25, 0.5).
rng = np.random.default_rng(0)
rates = 30.0 * np.exp(-((x_pos - 0.25) ** 2 + (y_pos - 0.5) ** 2) / (2 * 0.15 ** 2))
spike_count = rng.poisson(rates * (1.0 / fs))
spike_times = np.sort(np.concatenate([
[t[k] + rng.uniform(0, 1.0 / fs)] for k in range(t.size) for _ in range(int(spike_count[k]))
])) if spike_count.sum() else np.array([])
trial = Trial(
spike_collection=SpikeTrainCollection(
[nspikeTrain(spike_times, minTime=0.0, maxTime=T)]
),
covariate_collection=CovariateCollection([
Covariate(t, x_pos, "x", "time", "s", "m", ["x"]),
Covariate(t, y_pos, "y", "time", "s", "m", ["y"]),
]),
)
position = np.column_stack([
np.asarray(trial.covarColl.getCov(0).data).reshape(-1),
np.asarray(trial.covarColl.getCov(1).data).reshape(-1),
])
cfg = PlaceFieldDecoderConfig(decode_filter="nonlinear")
result = fit_place_field_decoder(trial, position, config=cfg)
print(result.decoded_position.shape, result.mean_decoding_error)
Live runnable demo (3-cell synthetic trial with the same pipeline):
examples/extras/decoding_place_field_demo.py.
Notes on the underlying pipeline
The unwrapped reference workflow lives in
examples/paper/example08_real_place_cells.py
(lines 270-420). Read it when you need to customise the basis,
expose the held-out spatial GoF diagnostics, or alter the
quadratic-CIF refit (this wrapper hard-codes the example08 choices).
Scope
Feature |
Status |
|---|---|
B-spline Poisson encoder per cell |
shipped |
Quadratic / linear CIF refit per cell |
shipped |
PPAF decoding via |
shipped |
PPAF decoding via |
shipped |
Per-cell silent-cell skipping + UserWarning |
shipped |
Spatial goodness-of-fit (held-out g(r), rescaled ACF) |
not in scope — see |
Cross-validated bandwidth selection |
not in scope |
References
Brown EN, Frank LM, Tang D, Quirk MC, Wilson MA (1998). A statistical paradigm for neural spike train decoding applied to position prediction from ensemble firing patterns of rat hippocampal place cells. J Neurosci 18(18):7411.
Eden UT, Frank LM, Barbieri R, Solo V, Brown EN (2004). Dynamic analysis of neural encoding by point process adaptive filtering. Neural Comput 16(5):971.
nstat-pythonPR #194 (Animal-1 demo / example08).nstat-pythonPR #198 (history-free O(C·T) fast path that this wrapper exercises viadecode_filter="linear").