Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c0687ee
Port KF_EM, PP_EM, mPPCO decoding families + FitResSummary/Trial methods
Mar 10, 2026
2d0b120
Regenerate notebooks, fix readme examples, refresh images
Mar 10, 2026
4455083
Port remaining missing methods + fix getDesignMatrix dimension bug
Mar 10, 2026
f06819f
Fix final 4 parity gaps: validation lambda, burst stats, PPHybridFilt…
Mar 10, 2026
e1eec1f
Add target estimation, fix return signatures, and complete parity fixes
Mar 10, 2026
ad8d836
Fix nspikeTrain nstCopy/setMinTime/setMaxTime to compute statistics
Mar 10, 2026
bd2ccb2
Add Trial.getAllLabels() for Matlab parity
Mar 10, 2026
cb53376
Add psthGLM Monte Carlo CIs, GLM standard errors, and plot/resample f…
Mar 10, 2026
1859152
Fix SignalObj merge/xcov/periodogram/MTMspectrum for Matlab parity
Mar 10, 2026
65616e2
Fix Covariate normWindowedSignal and getSigRep for Matlab parity
Mar 10, 2026
e9d929a
Add Matlab-matching Kalman filter, augmented-state smoother, and MC CIs
Mar 10, 2026
3333fd1
Fix Analysis parameter passing and Granger causality coefficient extr…
Mar 10, 2026
8fb529a
Fix 6 behavioral parity gaps found by comprehensive Matlab audit
Mar 11, 2026
162a718
Rewrite paper examples as self-contained scripts with proper decoding…
Mar 11, 2026
932985b
Fix example01 figure layout and epoch boundary to match Matlab
Mar 11, 2026
e11b6ff
Update help files and README for v0.3.0 parity
Mar 11, 2026
4010428
Fix example03 Figure 5 to match Matlab: 3D mesh, Poisson link, fresh …
Mar 11, 2026
79895af
Fix example04 grid resolution and orientation to match Matlab
Mar 11, 2026
41db8b5
Regenerate NetworkTutorial.ipynb from builder to fix test
Mar 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ One of nSTAT's key strengths is point-process generalized linear models for spik
train signals that provide a formal statistical framework for processing signals
recorded from ensembles of single neurons. It also has extensive support for model
fitting, model-order analysis, and adaptive decoding — including state-space GLM
(SSGLM) estimation via EM, unscented Kalman filtering (UKF), and hybrid
discrete/continuous point-process filters.
(SSGLM) estimation via EM, unscented Kalman filtering (UKF), goal-directed
point-process adaptive filters (PPAF), and hybrid discrete/continuous
point-process filters (PPHF).

Although created with neural signal processing in mind, nSTAT can be used as a
generic tool for analyzing any types of discrete and continuous signals, and thus
Expand Down
20 changes: 17 additions & 3 deletions docs/ClassDefinitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,21 @@ Primary notebook: `notebooks/nstCollExamples.ipynb`
**Time operations**:
`shiftTime`, `setMinTime`, `setMaxTime`

**Data export**:
`dataToMatrix`, `resample`

**PSTH**:
`psth`, `psthGLM`, `estimateVarianceAcrossTrials`, `psthBars`

**SSGLM (state-space GLM)**:
`ssglm`, `ssglmFB`

**Basis generation**:
`generateUnitImpulseBasis`

**Plotting**:
`plot`

### `History` (`nstat.History`)

Primary notebook: `notebooks/HistoryExamples.ipynb`
Expand Down Expand Up @@ -149,7 +158,8 @@ Primary notebook: `notebooks/TrialExamples.ipynb`
`flattenMask`

**Utilities**:
`shiftCovariates`, `resampleEnsColl`, `restoreToOriginal`, `plot`
`shiftCovariates`, `resampleEnsColl`, `restoreToOriginal`, `getAllLabels`,
`plot`

### `TrialConfig` (`nstat.TrialConfig`)

Expand Down Expand Up @@ -230,19 +240,23 @@ Alias of `FitSummary`. Aggregates multiple `FitResult` objects.

**Plotting**:
`plotIC`, `plotAIC`, `plotBIC`, `plotlogLL`, `plotResidualSummary`,
`plotSummary`, `boxPlot`
`plotSummary`, `boxPlot`, `plotAllCoeffs`, `plot3dCoeffSummary`,
`plot2dCoeffSummary`, `plotKSSummary`

### `DecodingAlgorithms` (`nstat.DecodingAlgorithms`)

Primary notebook: `notebooks/DecodingExample.ipynb`

**Point-process decode filters**:
`PPDecodeFilterLinear`, `PPDecodeFilter`, `PPHybridFilterLinear`,
`ComputeStimulusCIs`
`ComputeStimulusCIs`, `computeSpikeRateCIs`

**Kalman and unscented Kalman filters**:
`kalman_filter`, `PP_fixedIntervalSmoother`, `ukf`

**Helper methods**:
`PPDecode_predict`, `PPDecode_updateLinear`

**State-space GLM (SSGLM) — EM algorithm**:
`PPSS_EStep`, `PPSS_MStep`, `PPSS_EM`, `PPSS_EMFB`

Expand Down
12 changes: 10 additions & 2 deletions docs/PaperOverview.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,16 @@ map to:
**Point-process adaptive filters (Section 2.5)**:

- `DecodingAlgorithms.PPDecodeFilterLinear` — linear-CIF point-process
adaptive filter for continuous stimulus decoding.
adaptive filter for continuous stimulus decoding. Supports goal-directed
decoding via backward information filter (Srinivasan et al. 2006) when
`yT` and `PiT` target parameters are provided.
- `DecodingAlgorithms.PPDecodeFilter` — general CIF version using symbolic
gradients/Jacobians.
- `DecodingAlgorithms.ComputeStimulusCIs` — stimulus confidence intervals.
- `DecodingAlgorithms.ComputeStimulusCIs` — stimulus confidence intervals
via Monte Carlo sampling (dual-path: 4-D SSGLM cross-trial + 3-D smoother
z-score).
- `DecodingAlgorithms.computeSpikeRateCIs` — spike rate confidence intervals
and pairwise significance testing across trials.
- `DecodingAlgorithms.PP_fixedIntervalSmoother` — fixed-interval smoother
for off-line smoothing of decode estimates.

Expand All @@ -100,6 +106,8 @@ map to:
- `DecodingAlgorithms.PPHybridFilterLinear` — joint discrete/continuous state
estimation combining point-process observations with a hidden Markov model
over discrete states and Kalman filtering over continuous kinematics.
Supports goal-directed decoding via per-model backward information filters
when `yT` and `PiT` are provided.

**Kalman and UKF filters**:

Expand Down
43 changes: 35 additions & 8 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,44 @@
# Python nSTAT Examples
# nSTAT Python Examples

## Basic examples
## Paper Examples (Self-Contained)

Five self-contained scripts mirroring the MATLAB paper examples. Each
generates publication-quality figures and supports `--export-figures`.

```bash
python3 examples/basic_data_workflow.py
python3 examples/fit_poisson_glm.py
python3 examples/simulate_population_psth.py
python examples/paper/example01_mepsc_poisson.py --export-figures
python examples/paper/example02_whisker_stimulus_thalamus.py --export-figures
python examples/paper/example03_psth_and_ssglm.py --export-figures
python examples/paper/example04_place_cells_continuous_stimulus.py --export-figures
python examples/paper/example05_decoding_ppaf_pphf.py --export-figures
```

## Paper-style example workflow
| Example | Focus | Paper Section |
|---|---|---|
| 01 | mEPSC Poisson models (constant vs piecewise baseline) | 2.3.1 |
| 02 | Whisker stimulus GLM with lag and history selection | 2.3.2 |
| 03 | PSTH and SSGLM across-trial dynamics | 2.3.3-2.3.4 |
| 04 | Place-cell receptive fields (Gaussian vs Zernike) | 2.3.5 |
| 05 | PPAF and hybrid filter decoding | 2.5-2.6 |

## Basic Examples

```bash
python3 examples/nstat_paper_examples.py --repo-root ..
python examples/basic_data_workflow.py
python examples/fit_poisson_glm.py
python examples/simulate_population_psth.py
```

This mirrors key analyses described in the nSTAT paper using the bundled Python APIs.
## README Examples (Quick Checks)

```bash
python examples/readme_examples/example1_multitaper_and_spectrogram.py
python examples/readme_examples/example2_simulate_cif_spiketrain_10s.py
python examples/readme_examples/example3_nstcoll_raster_from_example2.py
```

## Jupyter Notebooks

All 29 class-tutorial and data-analysis notebooks are in `notebooks/`.
They mirror the MATLAB helpfile examples one-to-one. See
[docs/Examples.md](../docs/Examples.md) for the full index.
77 changes: 20 additions & 57 deletions examples/paper/example01_mepsc_poisson.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,33 +128,14 @@ def run_example01(*, export_figures: bool = False, export_dir: Path | None = Non
print(f" AIC: {resultConst.AIC}")
print(f" BIC: {resultConst.BIC}")

# --- Figure 1: Constant Mg2+ raster + diagnostics + lambda ---
fig1, axes1 = plt.subplots(2, 2, figsize=(14, 9))

# Subplot 1: Neural raster
ax = axes1[0, 0]
spikeCollConst.plot(handle=ax)
ax.set_title("Neural Raster with constant Mg$^{2+}$ Concentration",
fontweight="bold", fontsize=12)
ax.set_xlabel("time [s]", fontsize=12, fontweight="bold")
ax.set_ylabel("mEPSCs", fontsize=12, fontweight="bold")
ax.set_yticks([0, 1])

# Subplot 2: Inverse Gaussian Transform (autocorrelation of rescaled residuals)
ax = axes1[0, 1]
resultConst.plotInvGausTrans(handle=ax)

# Subplot 3: KS plot
ax = axes1[1, 0]
resultConst.KSPlot(handle=ax)

# Subplot 4: Lambda estimate
ax = axes1[1, 1]
resultConst.lambda_signal.plot(handle=ax)
ax.set_xlabel("time [s]", fontsize=12, fontweight="bold")
ax.legend(["$\\lambda_{const}$"], loc="upper right")

fig1.suptitle("Example 01 — Figure 1: Constant Mg$^{2+}$ Summary", fontsize=14, fontweight="bold")
# --- Figure 1: Constant Mg2+ diagnostics (Matlab-matching plotResults) ---
# Matlab calls resultConst.plotResults which creates a 2x4 grid:
# [KSPlot (2-wide)] [InvGausTrans] [SeqCorr]
# [plotCoeffs (2-wide)] [plotResidual (2-wide)]
fig1 = plt.figure(figsize=(14, 9))
resultConst.plotResults(handle=fig1)
fig1.suptitle("Example 01 — Figure 1: Constant Mg$^{2+}$ Summary",
fontsize=14, fontweight="bold")
fig1.tight_layout()
figure_files.extend(_maybe_export(fig1, export_dir, "fig01_constant_mg_summary"))

Expand Down Expand Up @@ -198,8 +179,12 @@ def run_example01(*, export_figures: bool = False, export_dir: Path | None = Non
print("\n=== Part 3: Piecewise Baseline Model Comparison ===")

# Build piecewise indicator covariates
timeInd1 = np.searchsorted(timeWashout, 495.0)
timeInd2 = np.searchsorted(timeWashout, 765.0)
# Matlab: find(time<495,1,'last') — last index strictly before 495
# np.searchsorted gives first index >= 495, so subtract 1 isn't needed
# because Python slice [:idx] is exclusive. But Matlab's 1:timeInd1 is
# inclusive, so we need searchsorted(..., side='right') to include 495.
timeInd1 = np.searchsorted(timeWashout, 495.0, side="right")
timeInd2 = np.searchsorted(timeWashout, 765.0, side="right")
N = len(timeWashout)

constantRate = np.ones((N, 1))
Expand Down Expand Up @@ -233,34 +218,12 @@ def run_example01(*, export_figures: bool = False, export_dir: Path | None = Non
print(f" AIC: {resultWashout.AIC}")
print(f" BIC: {resultWashout.BIC}")

# --- Figure 3: Piecewise model diagnostics + lambda comparison ---
fig3, axes3 = plt.subplots(2, 2, figsize=(14, 9))

# Subplot 1: Raster with epoch boundaries
ax = axes3[0, 0]
spikeCollWashout.plot(handle=ax)
ax.set_title("Neural Raster with decreasing Mg$^{2+}$ Concentration",
fontweight="bold", fontsize=12)
ax.set_xlabel("time [s]", fontsize=12, fontweight="bold")
ax.axvline(495.0, color="r", linewidth=4)
ax.axvline(765.0, color="r", linewidth=4)

# Subplot 2: Inverse Gaussian Transform
ax = axes3[0, 1]
resultWashout.plotInvGausTrans(handle=ax)

# Subplot 3: KS plot
ax = axes3[1, 0]
resultWashout.KSPlot(handle=ax)

# Subplot 4: Lambda comparison
ax = axes3[1, 1]
resultWashout.lambda_signal.plot(handle=ax)
ax.set_ylim(0, 5)
ax.set_xlabel("time [s]", fontsize=12, fontweight="bold")
ax.legend(["$\\lambda_{const}$", "$\\lambda_{const-epoch}$"], loc="upper right")

fig3.suptitle("Example 01 — Figure 3: Piecewise Baseline Comparison", fontsize=14, fontweight="bold")
# --- Figure 3: Piecewise model diagnostics (Matlab-matching plotResults) ---
# Matlab calls resultWashout.plotResults which creates the same 2x4 grid.
fig3 = plt.figure(figsize=(14, 9))
resultWashout.plotResults(handle=fig3)
fig3.suptitle("Example 01 — Figure 3: Piecewise Baseline Comparison",
fontsize=14, fontweight="bold")
fig3.tight_layout()
figure_files.extend(_maybe_export(fig3, export_dir, "fig03_piecewise_baseline_comparison"))

Expand Down
Loading
Loading