From 8a9c3875ccf7955ca7fa41d8ef1fdc19c3c67bd9 Mon Sep 17 00:00:00 2001 From: Iahn Cajigas Date: Sat, 7 Mar 2026 00:19:12 -0500 Subject: [PATCH] Add MATLAB-style help topology and sync README gallery --- README.md | 60 ++++++++++++++++++---- docs/ClassDefinitions.md | 26 ++++++++++ docs/DocumentationSetup.md | 65 ++++++++++++++++++++++++ docs/Examples.md | 55 ++++++++++++++++++++ docs/NeuralSpikeAnalysis_top.md | 33 ++++++++++++ docs/PaperOverview.md | 72 +++++++++++++++++++++++++++ docs/figures/manifest.json | 10 ++-- docs/index.rst | 7 ++- docs/paper_examples.md | 20 ++++---- examples/paper/manifest.yml | 10 ++-- nstat/paper_gallery.py | 66 +++++++++++++++++++++++- parity/README.md | 4 +- parity/manifest.yml | 29 ++++++++++- parity/report.md | 4 +- tests/test_docs_surface.py | 27 ++++++++++ tests/test_paper_gallery_docs.py | 9 +++- tests/test_parity_manifest.py | 5 ++ tests/test_readme_examples_catalog.py | 15 ++++++ tools/paper_examples/build_gallery.py | 3 +- 19 files changed, 479 insertions(+), 41 deletions(-) create mode 100644 docs/ClassDefinitions.md create mode 100644 docs/DocumentationSetup.md create mode 100644 docs/Examples.md create mode 100644 docs/NeuralSpikeAnalysis_top.md create mode 100644 docs/PaperOverview.md diff --git a/README.md b/README.md index fce519e8..4d608cfa 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,23 @@ report = nstat_install() Python no-op because import paths are managed by the environment rather than a MATLAB-style saved user path. +## Quickstart + +```bash +git clone https://github.com/cajigaslab/nSTAT-python +cd nSTAT-python +python -m pip install -e .[dev] +python -m nstat.install --download-example-data prompt +pytest -q +``` + +For a gallery refresh that mirrors the MATLAB repo's paper-example workflow: + +```bash +python tools/paper_examples/build_gallery.py +python tools/parity/build_report.py +``` + ## Examples ### Paper Examples (Self-Contained) @@ -78,17 +95,17 @@ Single command to regenerate the paper-example gallery metadata: python tools/paper_examples/build_gallery.py ``` -This writes `docs/paper_examples.md` and `docs/figures/manifest.json`, and -ensures canonical figure-gallery directories exist under -`docs/figures/example01/` through `docs/figures/example05/`. +This writes `docs/paper_examples.md`, `docs/figures/manifest.json`, and +refreshes the canonical README paper-example table from +`examples/paper/manifest.yml`. | Example | Thumbnail | What question it answers | Run command | Links | |---|---|---|---|---| -| Example 01 | ![Example 01](docs/figures/example01/fig01_constant_mg_summary.png) | Does Mg2+ washout produce firing-rate dynamics beyond a constant Poisson baseline? | `python examples/paper/example01_mepsc_poisson.py` | [Script](examples/paper/example01_mepsc_poisson.py) · [Figures](docs/figures/example01/) | -| Example 02 | ![Example 02](docs/figures/example02/fig01_data_overview.png) | What stimulus lag and history order best explain whisker-evoked spike trains? | `python examples/paper/example02_whisker_stimulus_thalamus.py` | [Script](examples/paper/example02_whisker_stimulus_thalamus.py) · [Figures](docs/figures/example02/) | -| Example 03 | ![Example 03](docs/figures/example03/fig01_simulated_and_real_rasters.png) | How do PSTH and SSGLM differ in capturing trial learning dynamics? | `python examples/paper/example03_psth_and_ssglm.py` | [Script](examples/paper/example03_psth_and_ssglm.py) · [Figures](docs/figures/example03/) | -| Example 04 | ![Example 04](docs/figures/example04/fig01_example_cells_path_overlay.png) | How do Gaussian and Zernike basis models compare for place-field mapping? | `python examples/paper/example04_place_cells_continuous_stimulus.py` | [Script](examples/paper/example04_place_cells_continuous_stimulus.py) · [Figures](docs/figures/example04/) | -| Example 05 | ![Example 05](docs/figures/example05/fig01_univariate_setup.png) | How accurately can neural populations decode latent stimulus and reach state? | `python examples/paper/example05_decoding_ppaf_pphf.py` | [Script](examples/paper/example05_decoding_ppaf_pphf.py) · [Figures](docs/figures/example05/) | +| Example 01 | ![Example 01](docs/figures/example01/fig01_constant_mg_summary.png) | Do mEPSCs follow constant vs piecewise Poisson firing under Mg2+ washout? | `python examples/paper/example01_mepsc_poisson.py` | [Script](examples/paper/example01_mepsc_poisson.py) · [Figures](docs/figures/example01/) | +| Example 02 | ![Example 02](docs/figures/example02/fig01_data_overview.png) | How do explicit whisker stimulus and spike history improve thalamic GLM fits? | `python examples/paper/example02_whisker_stimulus_thalamus.py` | [Script](examples/paper/example02_whisker_stimulus_thalamus.py) · [Figures](docs/figures/example02/) | +| Example 03 | ![Example 03](docs/figures/example03/fig01_simulated_and_real_rasters.png) | How do PSTH and SSGLM capture within-trial and across-trial dynamics? | `python examples/paper/example03_psth_and_ssglm.py` | [Script](examples/paper/example03_psth_and_ssglm.py) · [Figures](docs/figures/example03/) | +| Example 04 | ![Example 04](docs/figures/example04/fig01_example_cells_path_overlay.png) | Which receptive-field basis (Gaussian vs Zernike) better fits place cells? | `python examples/paper/example04_place_cells_continuous_stimulus.py` | [Script](examples/paper/example04_place_cells_continuous_stimulus.py) · [Figures](docs/figures/example04/) | +| Example 05 | ![Example 05](docs/figures/example05/fig01_univariate_setup.png) | How well do adaptive/hybrid point-process filters decode stimulus and reach state? | `python examples/paper/example05_decoding_ppaf_pphf.py` | [Script](examples/paper/example05_decoding_ppaf_pphf.py) · [Figures](docs/figures/example05/) | Expanded paper-example index and figure gallery: - [docs/paper_examples.md](docs/paper_examples.md) @@ -102,11 +119,34 @@ These smaller demos remain useful as quick install and plotting checks. | Multitaper spectrum + spectrogram | `python examples/readme_examples/example1_multitaper_and_spectrogram.py` | [PNG](examples/readme_examples/images/readme_example1_multitaper_and_spectrogram.png) | | Simulated CIF spike train | `python examples/readme_examples/example2_simulate_cif_spiketrain_10s.py` | [PNG](examples/readme_examples/images/readme_example2_simulate_cif_spiketrain_10s.png) | | Spike-train raster | `python examples/readme_examples/example3_nstcoll_raster_from_example2.py` | [PNG](examples/readme_examples/images/readme_example3_nstcoll_raster.png) | - ## Documentation - Docs home: [cajigaslab.github.io/nSTAT-python](https://cajigaslab.github.io/nSTAT-python/) -- Help index: [cajigaslab.github.io/nSTAT-python/help](https://cajigaslab.github.io/nSTAT-python/help/) +- Help home: [cajigaslab.github.io/nSTAT-python/NeuralSpikeAnalysis_top.html](https://cajigaslab.github.io/nSTAT-python/NeuralSpikeAnalysis_top.html) +- Paper overview: [cajigaslab.github.io/nSTAT-python/PaperOverview.html](https://cajigaslab.github.io/nSTAT-python/PaperOverview.html) +- Example index: [cajigaslab.github.io/nSTAT-python/Examples.html](https://cajigaslab.github.io/nSTAT-python/Examples.html) +- Class definitions: [cajigaslab.github.io/nSTAT-python/ClassDefinitions.html](https://cajigaslab.github.io/nSTAT-python/ClassDefinitions.html) +- Documentation setup: [cajigaslab.github.io/nSTAT-python/DocumentationSetup.html](https://cajigaslab.github.io/nSTAT-python/DocumentationSetup.html) + +Source pages: +- [docs/NeuralSpikeAnalysis_top.md](docs/NeuralSpikeAnalysis_top.md) +- [docs/PaperOverview.md](docs/PaperOverview.md) +- [docs/Examples.md](docs/Examples.md) +- [docs/ClassDefinitions.md](docs/ClassDefinitions.md) +- [docs/DocumentationSetup.md](docs/DocumentationSetup.md) + +## Paper-Aligned Toolbox Map + +The Python port now mirrors the MATLAB repo's paper-aligned toolbox map through +[docs/PaperOverview.md](docs/PaperOverview.md). That page ties the Python +toolbox to the 2012 paper's workflow categories: + +- class hierarchy and object model (`SignalObj`, `Covariate`, `Trial`, + `Analysis`, `FitResult`, `DecodingAlgorithms`) +- fitting and assessment workflow (GLM fitting, diagnostics, summaries) +- simulation workflow (conditional intensity and thinning examples) +- decoding workflow (univariate, bivariate, and history-aware decoding) +- example-to-paper section mapping via `nSTATPaperExamples` ## Developer notes diff --git a/docs/ClassDefinitions.md b/docs/ClassDefinitions.md new file mode 100644 index 00000000..4ef19cf1 --- /dev/null +++ b/docs/ClassDefinitions.md @@ -0,0 +1,26 @@ +# Class Definitions + +The Python port preserves the MATLAB-facing class names as canonical imports or +compatibility adapters. + +| MATLAB-facing name | Python target | Primary example surface | +|---|---|---| +| `SignalObj` | `nstat.SignalObj` | `notebooks/SignalObjExamples.ipynb` | +| `Covariate` | `nstat.Covariate` | `notebooks/CovariateExamples.ipynb` | +| `ConfidenceInterval` | `nstat.ConfidenceInterval` | `notebooks/ConfidenceIntervalOverview.ipynb` | +| `CovColl` | `nstat.CovColl` | `notebooks/CovCollExamples.ipynb` | +| `Events` | `nstat.Events` | `notebooks/EventsExamples.ipynb` | +| `History` | `nstat.History` | `notebooks/HistoryExamples.ipynb` | +| `Trial` | `nstat.Trial` | `notebooks/TrialExamples.ipynb` | +| `TrialConfig` | `nstat.TrialConfig` | `notebooks/TrialConfigExamples.ipynb` | +| `ConfigColl` | `nstat.ConfigColl` | `notebooks/ConfigCollExamples.ipynb` | +| `nspikeTrain` | `nstat.nspikeTrain` | `notebooks/nSpikeTrainExamples.ipynb` | +| `nstColl` | `nstat.nstColl` | `notebooks/nstCollExamples.ipynb` | +| `Analysis` | `nstat.Analysis` | `notebooks/AnalysisExamples.ipynb` | +| `FitResult` | `nstat.FitResult` | `notebooks/FitResultExamples.ipynb` | +| `FitResSummary` | `nstat.FitResSummary` | `notebooks/FitResSummaryExamples.ipynb` | +| `CIF` | `nstat.CIF` | `notebooks/PPSimExample.ipynb` | +| `DecodingAlgorithms` | `nstat.DecodingAlgorithms` | `notebooks/DecodingExample.ipynb` | + +See [Examples](Examples.md) for the full help-style index and +[API Reference](api.rst) for the module layout. diff --git a/docs/DocumentationSetup.md b/docs/DocumentationSetup.md new file mode 100644 index 00000000..04e5be9e --- /dev/null +++ b/docs/DocumentationSetup.md @@ -0,0 +1,65 @@ +# Documentation Setup + +This page is the Python-native equivalent of the MATLAB help-integration +guide. `nSTAT-python` does not register pages inside the MATLAB help browser; +instead it ships a static Sphinx documentation site and local rebuild hooks. + +## Install and Configure + +Install from PyPI: + +```bash +python -m pip install nstat-toolbox +``` + +Install from source: + +```bash +git clone https://github.com/cajigaslab/nSTAT-python +cd nSTAT-python +python -m pip install -e .[dev] +``` + +Run the installer helper: + +```bash +nstat-install --download-example-data prompt +``` + +Equivalent module form: + +```bash +python -m nstat.install --download-example-data prompt +``` + +## Build and Refresh the Search Database + +The Python installer can rebuild the local Sphinx HTML search index: + +```bash +nstat-install +``` + +or directly: + +```bash +python -m sphinx -W -b html docs docs/_build/html +``` + +The resulting search index is written to `docs/_build/html/searchindex.js`. + +## Documentation Entry Points + +Use these pages as the Python documentation entry points: + +- [nSTAT Home](NeuralSpikeAnalysis_top.md) +- [Class Definitions](ClassDefinitions.md) +- [Example Index](Examples.md) +- [Paper Examples](paper_examples.md) + +## Troubleshooting + +- If example data is missing, rerun `nstat-install --download-example-data always`. +- If local docs are stale, rebuild with `python -m sphinx -W -b html docs docs/_build/html`. +- If you need a different example-data cache, set `NSTAT_DATA_DIR` before + running the installer or examples. diff --git a/docs/Examples.md b/docs/Examples.md new file mode 100644 index 00000000..b5bfbe76 --- /dev/null +++ b/docs/Examples.md @@ -0,0 +1,55 @@ +# Example Index + +This page mirrors the MATLAB `Examples` help index and points to the canonical +Python notebook or script equivalent for each workflow. + +Paper cross-reference: + +- [Paper-Aligned Toolbox Map](PaperOverview.md) + +## Class and Object Workflows + +- `SignalObjExamples`: `notebooks/SignalObjExamples.ipynb` +- `CovariateExamples`: `notebooks/CovariateExamples.ipynb` +- `CovCollExamples`: `notebooks/CovCollExamples.ipynb` +- `nSpikeTrainExamples`: `notebooks/nSpikeTrainExamples.ipynb` +- `nstCollExamples`: `notebooks/nstCollExamples.ipynb` +- `EventsExamples`: `notebooks/EventsExamples.ipynb` +- `HistoryExamples`: `notebooks/HistoryExamples.ipynb` +- `TrialExamples`: `notebooks/TrialExamples.ipynb` +- `TrialConfigExamples`: `notebooks/TrialConfigExamples.ipynb` +- `ConfigCollExamples`: `notebooks/ConfigCollExamples.ipynb` +- `ConfidenceIntervalOverview`: `notebooks/ConfidenceIntervalOverview.ipynb` + +## Fitting, Assessment, and Analysis + +- `AnalysisExamples`: `notebooks/AnalysisExamples.ipynb` +- `AnalysisExamples2`: `notebooks/AnalysisExamples2.ipynb` +- `FitResultExamples`: `notebooks/FitResultExamples.ipynb` +- `FitResultReference`: `notebooks/FitResultReference.ipynb` +- `FitResSummaryExamples`: `notebooks/FitResSummaryExamples.ipynb` + +## Simulation and Example-Data Analyses + +- `PPThinning`: `notebooks/PPThinning.ipynb` +- `PPSimExample`: `notebooks/PPSimExample.ipynb` +- `PSTHEstimation`: `notebooks/PSTHEstimation.ipynb` +- `ValidationDataSet`: `notebooks/ValidationDataSet.ipynb` +- `mEPSCAnalysis`: `notebooks/mEPSCAnalysis.ipynb` +- `ExplicitStimulusWhiskerData`: `notebooks/ExplicitStimulusWhiskerData.ipynb` +- `HippocampalPlaceCellExample`: `notebooks/HippocampalPlaceCellExample.ipynb` + +## Decoding and Network Workflows + +- `DecodingExample`: `notebooks/DecodingExample.ipynb` +- `DecodingExampleWithHist`: `notebooks/DecodingExampleWithHist.ipynb` +- `StimulusDecode2D`: `notebooks/StimulusDecode2D.ipynb` +- `HybridFilterExample`: `notebooks/HybridFilterExample.ipynb` +- `NetworkTutorial`: `notebooks/NetworkTutorial.ipynb` + +## Consolidated Paper Workflow + +- `nSTATPaperExamples`: `notebooks/nSTATPaperExamples.ipynb` +- Canonical standalone scripts: `examples/paper/example01_mepsc_poisson.py` + through `examples/paper/example05_decoding_ppaf_pphf.py` +- Generated gallery and figure index: [Paper Examples](paper_examples.md) diff --git a/docs/NeuralSpikeAnalysis_top.md b/docs/NeuralSpikeAnalysis_top.md new file mode 100644 index 00000000..f810c061 --- /dev/null +++ b/docs/NeuralSpikeAnalysis_top.md @@ -0,0 +1,33 @@ +# Neural Spike Train Analysis Toolbox (nSTAT) + +`nSTAT-python` is the standalone Python port of the neural spike-train analysis +toolbox. It preserves the MATLAB toolbox's public API naming, paper-example +structure, and help/example coverage wherever a Python equivalent is +reasonable. + +## Documentation Navigation + +- [Paper-Aligned Toolbox Map](PaperOverview.md) +- [Class Definitions](ClassDefinitions.md) +- [Example Index](Examples.md) +- [nSTAT Paper Examples](paper_examples.md) +- [Documentation Setup](DocumentationSetup.md) +- [API Reference](api.rst) + +## Purpose + +The toolbox consolidates point-process and GLM-based neural data analysis into +a coherent Python package with: + +- MATLAB-compatible public entry points such as `Analysis`, `TrialConfig`, + `FitResult`, `DecodingAlgorithms`, `nSTAT_Install`, and `getPaperDataDirs` +- Canonical paper examples exported as `examples/paper/example01` through + `example05` +- Notebook-backed help workflows mirroring the MATLAB helpfiles +- A generated figure gallery under `docs/figures/` + +## Citation + +Cajigas I, Malik WQ, Brown EN. *nSTAT: Open-source neural spike train analysis +toolbox for Matlab*. Journal of Neuroscience Methods 211:245-264 (2012). +DOI: [10.1016/j.jneumeth.2012.08.009](https://doi.org/10.1016/j.jneumeth.2012.08.009) diff --git a/docs/PaperOverview.md b/docs/PaperOverview.md new file mode 100644 index 00000000..72585a2f --- /dev/null +++ b/docs/PaperOverview.md @@ -0,0 +1,72 @@ +# Paper-Aligned Toolbox Map + +This page aligns the Python port with the original nSTAT toolbox paper: + +- Cajigas I, Malik WQ, Brown EN. *nSTAT: Open-source neural spike train + analysis toolbox for Matlab*. Journal of Neuroscience Methods 211:245-264 + (2012) +- DOI: [10.1016/j.jneumeth.2012.08.009](https://doi.org/10.1016/j.jneumeth.2012.08.009) +- PubMed: [22981419](https://pubmed.ncbi.nlm.nih.gov/22981419/) +- PMC full text: [PMC3491120](https://pmc.ncbi.nlm.nih.gov/articles/PMC3491120/) + +## Class Hierarchy and Object Model + +The Python port preserves the MATLAB toolbox's core object groupings: + +- Signal and covariate primitives: `SignalObj`, `Covariate`, + `ConfidenceInterval`, `CovColl` +- Spiking data structures: `nspikeTrain`, `nstColl`, `History`, `Events` +- Experiment and configuration objects: `Trial`, `TrialConfig`, `ConfigColl` +- Modeling and inference objects: `CIF`, `Analysis`, `FitResult`, + `FitResSummary`, `DecodingAlgorithms` + +Related navigation pages: + +- [Class Definitions](ClassDefinitions.md) +- [Example Index](Examples.md) + +## Fitting and Assessment Workflow + +The paper's core GLM workflow maps to Python as: + +1. Build trial data with `Trial`, `CovColl`, and `nstColl`. +2. Define model configurations with `TrialConfig` and `ConfigColl`. +3. Fit and evaluate analyses with `Analysis` and `FitResult`. +4. Summarize across fits with `FitResSummary`. + +Representative notebooks are indexed in [Examples](Examples.md), especially +`AnalysisExamples`, `FitResultExamples`, and `FitResSummaryExamples`. + +## Simulation Workflow + +The simulation workflow remains centered on conditional intensity functions +and thinning-based point-process simulation: + +- `CIF` +- `PPThinning` +- `PPSimExample` + +These are covered by the corresponding notebooks listed in +[Examples](Examples.md). + +## Decoding Workflow + +The adaptive filtering and decoding portions of the paper map to: + +- `DecodingAlgorithms` +- `DecodingExample` +- `DecodingExampleWithHist` +- `StimulusDecode2D` +- `HybridFilterExample` + +## Example-to-Paper Section Mapping + +The paper's representative workflows align to the following Python surfaces: + +- `mEPSCAnalysis` and `PSTHEstimation`: event-process and PSTH analysis +- `ExplicitStimulusWhiskerData` and `HippocampalPlaceCellExample`: + stimulus-response and receptive-field modeling +- `DecodingExample`, `DecodingExampleWithHist`, and `StimulusDecode2D`: + decoding and state estimation +- `nSTATPaperExamples` and the canonical gallery in [Paper Examples](paper_examples.md): + consolidated reproduction workflow for the toolbox paper diff --git a/docs/figures/manifest.json b/docs/figures/manifest.json index 41e3a3c5..0c953e9a 100644 --- a/docs/figures/manifest.json +++ b/docs/figures/manifest.json @@ -7,7 +7,7 @@ "source_script": "examples/paper/example01_mepsc_poisson.py", "matlab_source": "examples/paper/example01_mepsc_poisson.m", "description": "Fits constant and piecewise Poisson GLM baselines to mEPSC spike trains.", - "question": "Does Mg2+ washout produce firing-rate dynamics beyond a constant Poisson baseline?", + "question": "Do mEPSCs follow constant vs piecewise Poisson firing under Mg2+ washout?", "run_command": "python examples/paper/example01_mepsc_poisson.py", "figure_dir": "docs/figures/example01", "figure_files": [ @@ -26,7 +26,7 @@ "source_script": "examples/paper/example02_whisker_stimulus_thalamus.py", "matlab_source": "examples/paper/example02_whisker_stimulus_thalamus.m", "description": "Fits explicit-stimulus point-process GLMs and compares baseline, stimulus, and history models.", - "question": "What stimulus lag and history order best explain whisker-evoked spike trains?", + "question": "How do explicit whisker stimulus and spike history improve thalamic GLM fits?", "run_command": "python examples/paper/example02_whisker_stimulus_thalamus.py", "figure_dir": "docs/figures/example02", "figure_files": [ @@ -44,7 +44,7 @@ "source_script": "examples/paper/example03_psth_and_ssglm.py", "matlab_source": "examples/paper/example03_psth_and_ssglm.m", "description": "Bundles simulated PSTH and SSGLM examples from the canonical paper workflow.", - "question": "How do PSTH and SSGLM differ in capturing trial learning dynamics?", + "question": "How do PSTH and SSGLM capture within-trial and across-trial dynamics?", "run_command": "python examples/paper/example03_psth_and_ssglm.py", "figure_dir": "docs/figures/example03", "figure_files": [ @@ -67,7 +67,7 @@ "source_script": "examples/paper/example04_place_cells_continuous_stimulus.py", "matlab_source": "examples/paper/example04_place_cells_continuous_stimulus.m", "description": "Loads place-cell datasets and compares receptive-field model families.", - "question": "How do Gaussian and Zernike basis models compare for place-field mapping?", + "question": "Which receptive-field basis (Gaussian vs Zernike) better fits place cells?", "run_command": "python examples/paper/example04_place_cells_continuous_stimulus.py", "figure_dir": "docs/figures/example04", "figure_files": [ @@ -90,7 +90,7 @@ "source_script": "examples/paper/example05_decoding_ppaf_pphf.py", "matlab_source": "examples/paper/example05_decoding_ppaf_pphf.m", "description": "Bundles univariate, reaching, and hybrid decoding examples from the paper workflow.", - "question": "How accurately can neural populations decode latent stimulus and reach state?", + "question": "How well do adaptive/hybrid point-process filters decode stimulus and reach state?", "run_command": "python examples/paper/example05_decoding_ppaf_pphf.py", "figure_dir": "docs/figures/example05", "figure_files": [ diff --git a/docs/index.rst b/docs/index.rst index e22732c0..8f8465f6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,11 +1,16 @@ nSTAT Python Documentation ========================== -Minimal documentation for the standalone Python nSTAT core package. +Standalone Python documentation for the release-ready nSTAT port. .. toctree:: :maxdepth: 2 + NeuralSpikeAnalysis_top + PaperOverview + ClassDefinitions + Examples + DocumentationSetup api data_installation paper_examples diff --git a/docs/paper_examples.md b/docs/paper_examples.md index ddb99a36..98831b99 100644 --- a/docs/paper_examples.md +++ b/docs/paper_examples.md @@ -21,11 +21,11 @@ Outputs: | ID | Thumbnail | Standalone source | Question | Run command | Figure gallery | |---|---|---|---|---|---| -| `example01` | ![Example 01](figures/example01/fig01_constant_mg_summary.png) | [example01_mepsc_poisson.py](../examples/paper/example01_mepsc_poisson.py) | Does Mg2+ washout produce firing-rate dynamics beyond a constant Poisson baseline? | `python examples/paper/example01_mepsc_poisson.py` | [gallery page](./figures/example01/README.md) | -| `example02` | ![Example 02](figures/example02/fig01_data_overview.png) | [example02_whisker_stimulus_thalamus.py](../examples/paper/example02_whisker_stimulus_thalamus.py) | What stimulus lag and history order best explain whisker-evoked spike trains? | `python examples/paper/example02_whisker_stimulus_thalamus.py` | [gallery page](./figures/example02/README.md) | -| `example03` | ![Example 03](figures/example03/fig01_simulated_and_real_rasters.png) | [example03_psth_and_ssglm.py](../examples/paper/example03_psth_and_ssglm.py) | How do PSTH and SSGLM differ in capturing trial learning dynamics? | `python examples/paper/example03_psth_and_ssglm.py` | [gallery page](./figures/example03/README.md) | -| `example04` | ![Example 04](figures/example04/fig01_example_cells_path_overlay.png) | [example04_place_cells_continuous_stimulus.py](../examples/paper/example04_place_cells_continuous_stimulus.py) | How do Gaussian and Zernike basis models compare for place-field mapping? | `python examples/paper/example04_place_cells_continuous_stimulus.py` | [gallery page](./figures/example04/README.md) | -| `example05` | ![Example 05](figures/example05/fig01_univariate_setup.png) | [example05_decoding_ppaf_pphf.py](../examples/paper/example05_decoding_ppaf_pphf.py) | How accurately can neural populations decode latent stimulus and reach state? | `python examples/paper/example05_decoding_ppaf_pphf.py` | [gallery page](./figures/example05/README.md) | +| `example01` | ![Example 01](figures/example01/fig01_constant_mg_summary.png) | [example01_mepsc_poisson.py](../examples/paper/example01_mepsc_poisson.py) | Do mEPSCs follow constant vs piecewise Poisson firing under Mg2+ washout? | `python examples/paper/example01_mepsc_poisson.py` | [gallery page](./figures/example01/README.md) | +| `example02` | ![Example 02](figures/example02/fig01_data_overview.png) | [example02_whisker_stimulus_thalamus.py](../examples/paper/example02_whisker_stimulus_thalamus.py) | How do explicit whisker stimulus and spike history improve thalamic GLM fits? | `python examples/paper/example02_whisker_stimulus_thalamus.py` | [gallery page](./figures/example02/README.md) | +| `example03` | ![Example 03](figures/example03/fig01_simulated_and_real_rasters.png) | [example03_psth_and_ssglm.py](../examples/paper/example03_psth_and_ssglm.py) | How do PSTH and SSGLM capture within-trial and across-trial dynamics? | `python examples/paper/example03_psth_and_ssglm.py` | [gallery page](./figures/example03/README.md) | +| `example04` | ![Example 04](figures/example04/fig01_example_cells_path_overlay.png) | [example04_place_cells_continuous_stimulus.py](../examples/paper/example04_place_cells_continuous_stimulus.py) | Which receptive-field basis (Gaussian vs Zernike) better fits place cells? | `python examples/paper/example04_place_cells_continuous_stimulus.py` | [gallery page](./figures/example04/README.md) | +| `example05` | ![Example 05](figures/example05/fig01_univariate_setup.png) | [example05_decoding_ppaf_pphf.py](../examples/paper/example05_decoding_ppaf_pphf.py) | How well do adaptive/hybrid point-process filters decode stimulus and reach state? | `python examples/paper/example05_decoding_ppaf_pphf.py` | [gallery page](./figures/example05/README.md) | ```{toctree} :hidden: @@ -41,7 +41,7 @@ figures/example05/README ### Example 01: mEPSC Poisson Models Under Constant and Washout Magnesium -Question: Does Mg2+ washout produce firing-rate dynamics beyond a constant Poisson baseline? +Question: Do mEPSCs follow constant vs piecewise Poisson firing under Mg2+ washout? Run command: `python examples/paper/example01_mepsc_poisson.py` @@ -54,7 +54,7 @@ Expected figure files: ### Example 02: Whisker Stimulus GLM With Lag and History Selection -Question: What stimulus lag and history order best explain whisker-evoked spike trains? +Question: How do explicit whisker stimulus and spike history improve thalamic GLM fits? Run command: `python examples/paper/example02_whisker_stimulus_thalamus.py` @@ -66,7 +66,7 @@ Expected figure files: ### Example 03: PSTH and SSGLM Dynamics Example -Question: How do PSTH and SSGLM differ in capturing trial learning dynamics? +Question: How do PSTH and SSGLM capture within-trial and across-trial dynamics? Run command: `python examples/paper/example03_psth_and_ssglm.py` @@ -82,7 +82,7 @@ Expected figure files: ### Example 04: Place-Cell Receptive Fields (Gaussian vs Zernike) -Question: How do Gaussian and Zernike basis models compare for place-field mapping? +Question: Which receptive-field basis (Gaussian vs Zernike) better fits place cells? Run command: `python examples/paper/example04_place_cells_continuous_stimulus.py` @@ -99,7 +99,7 @@ Expected figure files: ### Example 05: Stimulus Decoding With PPAF and PPHF -Question: How accurately can neural populations decode latent stimulus and reach state? +Question: How well do adaptive/hybrid point-process filters decode stimulus and reach state? Run command: `python examples/paper/example05_decoding_ppaf_pphf.py` diff --git a/examples/paper/manifest.yml b/examples/paper/manifest.yml index 0aebfb56..fa2dcc0b 100644 --- a/examples/paper/manifest.yml +++ b/examples/paper/manifest.yml @@ -5,7 +5,7 @@ examples: script: examples/paper/example01_mepsc_poisson.py matlab_source: examples/paper/example01_mepsc_poisson.m title: mEPSC Poisson Models Under Constant and Washout Magnesium - question: Does Mg2+ washout produce firing-rate dynamics beyond a constant Poisson baseline? + question: Do mEPSCs follow constant vs piecewise Poisson firing under Mg2+ washout? description: Fits constant and piecewise Poisson GLM baselines to mEPSC spike trains. sections: - experiment1 @@ -18,7 +18,7 @@ examples: script: examples/paper/example02_whisker_stimulus_thalamus.py matlab_source: examples/paper/example02_whisker_stimulus_thalamus.m title: Whisker Stimulus GLM With Lag and History Selection - question: What stimulus lag and history order best explain whisker-evoked spike trains? + question: How do explicit whisker stimulus and spike history improve thalamic GLM fits? description: Fits explicit-stimulus point-process GLMs and compares baseline, stimulus, and history models. sections: - experiment2 @@ -30,7 +30,7 @@ examples: script: examples/paper/example03_psth_and_ssglm.py matlab_source: examples/paper/example03_psth_and_ssglm.m title: PSTH and SSGLM Dynamics Example - question: How do PSTH and SSGLM differ in capturing trial learning dynamics? + question: How do PSTH and SSGLM capture within-trial and across-trial dynamics? description: Bundles simulated PSTH and SSGLM examples from the canonical paper workflow. sections: - experiment3 @@ -47,7 +47,7 @@ examples: script: examples/paper/example04_place_cells_continuous_stimulus.py matlab_source: examples/paper/example04_place_cells_continuous_stimulus.m title: Place-Cell Receptive Fields (Gaussian vs Zernike) - question: How do Gaussian and Zernike basis models compare for place-field mapping? + question: Which receptive-field basis (Gaussian vs Zernike) better fits place cells? description: Loads place-cell datasets and compares receptive-field model families. sections: - experiment4 @@ -64,7 +64,7 @@ examples: script: examples/paper/example05_decoding_ppaf_pphf.py matlab_source: examples/paper/example05_decoding_ppaf_pphf.m title: Stimulus Decoding With PPAF and PPHF - question: How accurately can neural populations decode latent stimulus and reach state? + question: How well do adaptive/hybrid point-process filters decode stimulus and reach state? description: Bundles univariate, reaching, and hybrid decoding examples from the paper workflow. sections: - experiment5 diff --git a/nstat/paper_gallery.py b/nstat/paper_gallery.py index 59b32990..5e262960 100644 --- a/nstat/paper_gallery.py +++ b/nstat/paper_gallery.py @@ -109,6 +109,58 @@ def render_paper_examples_markdown(repo_root: Path | None = None) -> str: return "\n".join(lines).rstrip() + "\n" +def render_readme_examples_markdown(repo_root: Path | None = None) -> str: + manifest = build_gallery_manifest(repo_root) + lines = [ + "## Examples", + "", + "### Paper Examples (Self-Contained)", + "", + "Canonical source files:", + "- `examples/paper/*.py`", + "- `nstat/paper_examples_full.py`", + "", + "Single command to regenerate the paper-example gallery metadata:", + "", + "```bash", + "python tools/paper_examples/build_gallery.py", + "```", + "", + "This writes `docs/paper_examples.md`, `docs/figures/manifest.json`, and", + "refreshes the canonical README paper-example table from", + "`examples/paper/manifest.yml`.", + "", + "| Example | Thumbnail | What question it answers | Run command | Links |", + "|---|---|---|---|---|", + ] + for index, row in enumerate(manifest["examples"], start=1): + label = f"Example {index:02d}" + lines.append( + f"| {label} | ![{label}]({row['thumbnail_file']}) | {row['question']} | " + f"`{row['run_command']}` | [Script]({row['source_script']}) · [Figures]({row['figure_dir']}/) |" + ) + + lines.extend( + [ + "", + "Expanded paper-example index and figure gallery:", + "- [docs/paper_examples.md](docs/paper_examples.md)", + "", + "### Supplementary Examples", + "", + "These smaller demos remain useful as quick install and plotting checks.", + "", + "| Example | Run command | Output |", + "|---|---|---|", + "| Multitaper spectrum + spectrogram | `python examples/readme_examples/example1_multitaper_and_spectrogram.py` | [PNG](examples/readme_examples/images/readme_example1_multitaper_and_spectrogram.png) |", + "| Simulated CIF spike train | `python examples/readme_examples/example2_simulate_cif_spiketrain_10s.py` | [PNG](examples/readme_examples/images/readme_example2_simulate_cif_spiketrain_10s.png) |", + "| Spike-train raster | `python examples/readme_examples/example3_nstcoll_raster_from_example2.py` | [PNG](examples/readme_examples/images/readme_example3_nstcoll_raster.png) |", + "", + ] + ) + return "\n".join(lines) + + def ensure_gallery_dirs(repo_root: Path | None = None) -> list[Path]: base = _repo_root() if repo_root is None else repo_root.resolve() manifest = load_paper_example_manifest(base) @@ -126,7 +178,7 @@ def ensure_gallery_dirs(repo_root: Path | None = None) -> list[Path]: return created -def write_gallery_outputs(repo_root: Path | None = None) -> tuple[Path, Path]: +def write_gallery_outputs(repo_root: Path | None = None) -> tuple[Path, Path, Path]: base = _repo_root() if repo_root is None else repo_root.resolve() ensure_gallery_dirs(base) docs_dir = base / "docs" @@ -139,7 +191,16 @@ def write_gallery_outputs(repo_root: Path | None = None) -> tuple[Path, Path]: markdown_path = docs_dir / "paper_examples.md" markdown_path.write_text(render_paper_examples_markdown(base), encoding="utf-8") - return manifest_path, markdown_path + + readme_path = base / "README.md" + readme_text = readme_path.read_text(encoding="utf-8") + start = readme_text.index("## Examples") + end = readme_text.index("## Documentation") + readme_path.write_text( + readme_text[:start] + render_readme_examples_markdown(base) + readme_text[end:], + encoding="utf-8", + ) + return manifest_path, markdown_path, readme_path __all__ = [ @@ -147,5 +208,6 @@ def write_gallery_outputs(repo_root: Path | None = None) -> tuple[Path, Path]: "ensure_gallery_dirs", "load_paper_example_manifest", "render_paper_examples_markdown", + "render_readme_examples_markdown", "write_gallery_outputs", ] diff --git a/parity/README.md b/parity/README.md index 4787e04b..8eb3b0fc 100644 --- a/parity/README.md +++ b/parity/README.md @@ -14,6 +14,6 @@ python tools/parity/build_report.py Current headline status: - Public API coverage matches the MATLAB inventory except for the explicitly non-applicable `nstatOpenHelpPage`. -- Help/notebook parity covers the inventoried MATLAB help workflow surface. -- Canonical paper examples, gallery structure, and README/docs presentation now exist in Python, but the example outputs remain partial until the canonical figures are generated. +- Help/notebook parity covers the inventoried MATLAB help workflow surface, including the top-level `NeuralSpikeAnalysis_top`, `PaperOverview`, `Examples`, and `ClassDefinitions` navigation pages. +- Canonical paper examples, gallery structure, and README/docs presentation are committed and mapped in Python. - CI now validates API surface, dataset integrity, notebook smoke execution, paper gallery drift, and docs builds. diff --git a/parity/manifest.yml b/parity/manifest.yml index eb540278..fc5f3619 100644 --- a/parity/manifest.yml +++ b/parity/manifest.yml @@ -1,5 +1,5 @@ version: 1 -generated_on: 2026-03-06 +generated_on: 2026-03-07 source_repositories: matlab: https://github.com/cajigaslab/nSTAT python: https://github.com/cajigaslab/nSTAT-python @@ -15,7 +15,7 @@ summary: missing: 0 not_applicable: 1 help_workflows: - mapped: 29 + mapped: 34 partial: 0 missing: 0 not_applicable: 0 @@ -155,6 +155,31 @@ public_api: status: not_applicable notes: MATLAB help-browser integration does not have a direct Python equivalent. help_workflows: + - matlab: NeuralSpikeAnalysis_top + matlab_path: helpfiles/NeuralSpikeAnalysis_top.m + python_target: docs/NeuralSpikeAnalysis_top.md + status: mapped + notes: Python docs expose a top-level navigation page that mirrors the MATLAB help home. + - matlab: PaperOverview + matlab_path: helpfiles/PaperOverview.m + python_target: docs/PaperOverview.md + status: mapped + notes: Python docs expose a paper-aligned toolbox map page with the same workflow categories. + - matlab: ClassDefinitions + matlab_path: helpfiles/ClassDefinitions.m + python_target: docs/ClassDefinitions.md + status: mapped + notes: Python docs expose a class-definition index for the MATLAB-facing public API names. + - matlab: Examples + matlab_path: helpfiles/Examples.m + python_target: docs/Examples.md + status: mapped + notes: Python docs expose an example index mirroring the MATLAB helpfile categories and names. + - matlab: DocumentationSetup2025b + matlab_path: helpfiles/DocumentationSetup2025b.m + python_target: docs/DocumentationSetup.md + status: mapped + notes: Python replaces MATLAB help-browser integration with a Sphinx documentation setup and rebuild guide. - matlab: AnalysisExamples matlab_path: helpfiles/AnalysisExamples.mlx python_target: notebooks/AnalysisExamples.ipynb diff --git a/parity/report.md b/parity/report.md index 2469bfcd..2a48e9ee 100644 --- a/parity/report.md +++ b/parity/report.md @@ -5,14 +5,14 @@ Generated from `parity/manifest.yml`. - MATLAB reference: https://github.com/cajigaslab/nSTAT - Python target: https://github.com/cajigaslab/nSTAT-python - Inventory version: 1 -- Generated on: 2026-03-06 +- Generated on: 2026-03-07 ## Summary | Section | Mapped | Partial | Missing | Not Applicable | |---|---:|---:|---:|---:| | `public api` | 18 | 0 | 0 | 1 | -| `help workflows` | 29 | 0 | 0 | 0 | +| `help workflows` | 34 | 0 | 0 | 0 | | `paper examples` | 8 | 0 | 0 | 0 | | `docs gallery` | 8 | 0 | 0 | 0 | | `installer setup` | 4 | 0 | 0 | 3 | diff --git a/tests/test_docs_surface.py b/tests/test_docs_surface.py index 40296bda..3a1ab1a7 100644 --- a/tests/test_docs_surface.py +++ b/tests/test_docs_surface.py @@ -9,6 +9,15 @@ DOCS_CONF_PATH = REPO_ROOT / "docs" / "conf.py" DOCS_INDEX_PATH = REPO_ROOT / "docs" / "index.rst" WORKFLOW_PATH = REPO_ROOT / ".github" / "workflows" / "ci.yml" +README_PATH = REPO_ROOT / "README.md" + +MATLAB_NAV_PAGES = ( + "NeuralSpikeAnalysis_top", + "PaperOverview", + "ClassDefinitions", + "Examples", + "DocumentationSetup", +) def test_docs_index_includes_paper_examples_page() -> None: @@ -16,6 +25,24 @@ def test_docs_index_includes_paper_examples_page() -> None: assert "paper_examples" in text +def test_docs_index_includes_matlab_style_navigation_pages() -> None: + text = DOCS_INDEX_PATH.read_text(encoding="utf-8") + for page in MATLAB_NAV_PAGES: + assert page in text + + +def test_matlab_style_navigation_pages_exist() -> None: + for page in MATLAB_NAV_PAGES: + path = REPO_ROOT / "docs" / f"{page}.md" + assert path.exists(), f"Missing docs page: {path}" + + +def test_readme_links_to_help_navigation_pages() -> None: + text = README_PATH.read_text(encoding="utf-8") + for page in MATLAB_NAV_PAGES: + assert f"[docs/{page}.md](docs/{page}.md)" in text + + def test_docs_conf_enables_markdown_support() -> None: text = DOCS_CONF_PATH.read_text(encoding="utf-8") assert 'extensions = ["myst_parser"]' in text diff --git a/tests/test_paper_gallery_docs.py b/tests/test_paper_gallery_docs.py index be117abe..838bbee3 100644 --- a/tests/test_paper_gallery_docs.py +++ b/tests/test_paper_gallery_docs.py @@ -5,7 +5,7 @@ import yaml -from nstat.paper_gallery import build_gallery_manifest, render_paper_examples_markdown +from nstat.paper_gallery import build_gallery_manifest, render_paper_examples_markdown, render_readme_examples_markdown REPO_ROOT = Path(__file__).resolve().parents[1] @@ -22,6 +22,13 @@ def test_paper_examples_markdown_matches_generator() -> None: assert committed == render_paper_examples_markdown(REPO_ROOT) +def test_readme_examples_block_matches_generator() -> None: + committed = (REPO_ROOT / "README.md").read_text(encoding="utf-8") + start = committed.index("## Examples") + end = committed.index("## Documentation") + assert committed[start:end] == render_readme_examples_markdown(REPO_ROOT) + + def test_gallery_manifest_tracks_thumbnail_and_run_command_fields() -> None: built = build_gallery_manifest(REPO_ROOT) for row in built["examples"]: diff --git a/tests/test_parity_manifest.py b/tests/test_parity_manifest.py index 871a354b..d6549d06 100644 --- a/tests/test_parity_manifest.py +++ b/tests/test_parity_manifest.py @@ -33,13 +33,16 @@ EXPECTED_HELP_WORKFLOWS = { "AnalysisExamples", "AnalysisExamples2", + "ClassDefinitions", "ConfidenceIntervalOverview", "ConfigCollExamples", "CovCollExamples", "CovariateExamples", "DecodingExample", "DecodingExampleWithHist", + "DocumentationSetup2025b", "EventsExamples", + "Examples", "ExplicitStimulusWhiskerData", "FitResSummaryExamples", "FitResultExamples", @@ -48,6 +51,8 @@ "HistoryExamples", "HybridFilterExample", "NetworkTutorial", + "NeuralSpikeAnalysis_top", + "PaperOverview", "PPSimExample", "PPThinning", "PSTHEstimation", diff --git a/tests/test_readme_examples_catalog.py b/tests/test_readme_examples_catalog.py index 2e4d13c3..d463ec52 100644 --- a/tests/test_readme_examples_catalog.py +++ b/tests/test_readme_examples_catalog.py @@ -25,6 +25,14 @@ ), ] +EXPECTED_CANONICAL_QUESTIONS = { + "example01_mepsc_poisson": "Do mEPSCs follow constant vs piecewise Poisson firing under Mg2+ washout?", + "example02_whisker_stimulus_thalamus": "How do explicit whisker stimulus and spike history improve thalamic GLM fits?", + "example03_psth_and_ssglm": "How do PSTH and SSGLM capture within-trial and across-trial dynamics?", + "example04_place_cells_continuous_stimulus": "Which receptive-field basis (Gaussian vs Zernike) better fits place cells?", + "example05_decoding_ppaf_pphf": "How well do adaptive/hybrid point-process filters decode stimulus and reach state?", +} + def _extract_examples_block(text: str) -> str: match = re.search(r"## Examples\n(.*?)\n## Documentation\n", text, flags=re.S) @@ -62,6 +70,13 @@ def test_readme_paper_example_rows_track_manifest() -> None: assert f"[Figures](docs/figures/{row['example_id']}/)" in block +def test_paper_example_manifest_questions_match_matlab_gallery_wording() -> None: + manifest = yaml.safe_load(PAPER_MANIFEST_PATH.read_text(encoding="utf-8")) or {} + entries = manifest.get("examples", []) + questions = {str(row["name"]): str(row["question"]) for row in entries} + assert questions == EXPECTED_CANONICAL_QUESTIONS + + def test_readme_supplementary_examples_are_preserved() -> None: block = _extract_examples_block(README_PATH.read_text(encoding="utf-8")) diff --git a/tools/paper_examples/build_gallery.py b/tools/paper_examples/build_gallery.py index c516d103..99a51d4f 100644 --- a/tools/paper_examples/build_gallery.py +++ b/tools/paper_examples/build_gallery.py @@ -13,6 +13,7 @@ if __name__ == "__main__": - manifest_path, markdown_path = write_gallery_outputs(REPO_ROOT) + manifest_path, markdown_path, readme_path = write_gallery_outputs(REPO_ROOT) print(manifest_path) print(markdown_path) + print(readme_path)