Metadata-Version: 2.4
Name: jnwb
Version: 0.2.12
Summary: Generic Python library for high-density electrophysiology, time-frequency analysis, and statistical neuroscience on Neurodata Without Borders (NWB) datasets.
Author: Hamed Nejat
Maintainer: Hamed Nejat
License: MIT
Project-URL: Homepage, https://github.com/HNXJ/jnwb
Project-URL: Repository, https://github.com/HNXJ/jnwb.git
Project-URL: Documentation, https://jnwb.readthedocs.io/
Project-URL: Bug Tracker, https://github.com/HNXJ/jnwb/issues
Keywords: neuroscience,electrophysiology,nwb,neurodata-without-borders,lfp,spikes,time-frequency,morlet-wavelet,granger-causality,phase-slope-index
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=2.0.0
Requires-Dist: scipy>=1.17.0
Requires-Dist: pandas>=2.2.2
Requires-Dist: h5py>=3.11.0
Requires-Dist: pynwb>=3.1.0
Requires-Dist: hdmf>=4.1.0
Requires-Dist: matplotlib>=3.8.4
Requires-Dist: scikit-learn>=1.4.2
Requires-Dist: statsmodels>=0.14.5
Requires-Dist: joblib>=1.2.0
Provides-Extra: validate
Requires-Dist: nwbinspector>=0.6; extra == "validate"
Requires-Dist: dandi>=0.70; extra == "validate"
Provides-Extra: mcp
Requires-Dist: mcp<2.0; extra == "mcp"
Provides-Extra: torch
Requires-Dist: torch>=2.2.0; extra == "torch"
Provides-Extra: gpu
Requires-Dist: cupy-cuda12x; extra == "gpu"
Requires-Dist: jax; extra == "gpu"
Provides-Extra: vis
Requires-Dist: plotly>=6.1.1; extra == "vis"
Requires-Dist: kaleido>=1.0.0; extra == "vis"
Provides-Extra: wavemap
Requires-Dist: umap-learn>=0.5.6; extra == "wavemap"
Requires-Dist: networkx>=3.0; extra == "wavemap"
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Requires-Dist: pytest-xdist>=3.0; extra == "test"
Requires-Dist: build>=1.0.0; extra == "test"
Requires-Dist: twine>=4.0.0; extra == "test"
Requires-Dist: pyyaml>=6.0.1; extra == "test"
Requires-Dist: setuptools>=61.0.0; extra == "test"
Requires-Dist: mcp<2.0; extra == "test"
Requires-Dist: nbclient>=0.10; extra == "test"
Requires-Dist: nbformat>=5.10; extra == "test"
Requires-Dist: ipykernel>=6.0; extra == "test"
Provides-Extra: docs
Requires-Dist: mkdocs>=1.6.1; extra == "docs"
Requires-Dist: mkdocs-material>=9.7.7; extra == "docs"
Requires-Dist: pymdown-extensions>=11.0.1; extra == "docs"
Provides-Extra: all
Requires-Dist: jnwb[docs,gpu,mcp,test,torch,validate,vis,wavemap]; extra == "all"
Dynamic: license-file

<p align="center">
  <img src="https://raw.githubusercontent.com/HNXJ/jnwb/main/docs/assets/jnwb-logo.png" alt="jnwb" width="200">
</p>

<p align="center">
  <a href="https://pypi.org/project/jnwb/"><img src="https://img.shields.io/pypi/v/jnwb?color=brightgreen" alt="PyPI"></a>
  <a href="https://jnwb.readthedocs.io/en/latest/"><img src="https://img.shields.io/readthedocs/jnwb" alt="Docs"></a>
  <a href="https://github.com/HNXJ/jnwb/actions/workflows/workflow.yml"><img src="https://github.com/HNXJ/jnwb/actions/workflows/workflow.yml/badge.svg?branch=main" alt="CI/CD"></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
  <a href="https://doi.org/10.5281/zenodo.22949846"><img src="https://zenodo.org/badge/DOI/10.5281/zenodo.22949846.svg" alt="DOI"></a>
</p>

# `jnwb`

Dataset-agnostic Python library for Neurodata Without Borders (NWB 2.0+) electrophysiology: addressing, spikes, LFP, spectral analysis, statistics, population methods, decoding, connectivity, laminar depth (CSD, the published vFLIP2), filtering, QC, and visualization.

**Documentation**: [https://jnwb.readthedocs.io/](https://jnwb.readthedocs.io/) | **Source**: [https://github.com/HNXJ/jnwb](https://github.com/HNXJ/jnwb)

`jnwb` is a toolbox, not a pipeline. It supplies small operations over NWB files, arrays, and metadata tables. Task structure, condition codes, and experimental hypotheses stay in project code.

- **Dataset-agnostic.** No experiment condition names or manuscript results live in the library.
- **Explicit nulls.** Label permutation requires an exchangeability scheme (`global` or `within_group`).
- **Preserved signal semantics.** Units, sampling rates, coordinate frames, and 0- vs 1-indexing do not change across a function boundary.

## Capabilities

| Area | Representative API |
| --- | --- |
| NWB discovery & events | `inspect`, `events`, `event_onsets`, `unit_spike_times`, `acquisition_channel` |
| NWB metadata & addressing | `get_all_units_metadata`, `electrode_inventory`, `map_peak_channel_to_area`, `classify_layer_from_depth` |
| Spiking | `raster_psth`, `compute_response_metrics`, `causal_exp_smooth`, `fit_exponential_onset`, `pairwise_phase_consistency` |
| LFP & spectral | `compute_psd`, `compute_multitaper_psd`, `band_power`, `complex_tfr`, `aggregate_to_db`, `current_source_density_1d` |
| Filtering | `bandpass_filter`, `notch_filter` |
| Statistics | `permute_labels`, `cluster_permutation_test`, `shuffle_pvalue_paired`, `paired_fire_prob_test` |
| Population & decoding | `jrsa`, `nested_cv_linear_svm`, `compute_population_trajectory` |
| Connectivity | `granger`, `phase_slope_index`, `transfer_entropy`, `directed_network` |
| Quality control | `channel_correlation_matrix`, `repair_lfp_trials`, `audit_units`, `audit_electrodes` |
| Waveform classes | `waveform_features`, `normalize_waveforms`, `wavemap`, `wavemap_resolution_sweep` |
| Visualization | `setup_vector_graphics`, `save_figure_suite`, plotting in `jnwb.vis` |

## Installation

Requires Python **3.12 or newer**. Tested in CI on 3.12, 3.13, 3.14 and 3.15 on Linux, and on Windows for all but the last.

```bash
pip install jnwb                  # latest published release
pip install "jnwb[torch,gpu]"     # optional backends
```

This checkout is `0.2.12`. To install it from a clone instead of from PyPI:

```bash
pip install .                     # or: pip install -e ".[test,docs,vis]" for development
```

The `vis` extra exports SVG and PNG through kaleido, which needs a Chrome or Chromium: see [Install](https://jnwb.readthedocs.io/en/latest/install/).

Core dependencies: `numpy`, `scipy`, `pandas`, `h5py`, `pynwb`, `hdmf`, `matplotlib`, `scikit-learn`, `statsmodels`, `joblib`.

## NWB workflow

`jnwb.inspect` lists acquisitions, electrodes, units, and every interval table with column samples. It does not pick a default event table. Event **codes** are opaque labels in a named column (default `codes`). **Onsets** from `jnwb.event_onsets` are in **seconds**. When several interval tables exist, pass `table=` explicitly; jnwb raises rather than guessing.

```python
import jnwb

info = jnwb.inspect("session.nwb")

# Read the layout off the inspection rather than assuming it.
for table in info["interval_tables"]:
    print(table["name"], [column["name"] for column in table["columns"]])
# trials ['id', 'start_time', 'stimulus', 'stop_time']

table = jnwb.events("session.nwb", table="trials", code_column="stimulus")
onsets = jnwb.event_onsets(
    "session.nwb", table="trials", code_column="stimulus", codes=["grating"],
)
```

The `codes` default, and the errors for a missing column, are on the [Errors](https://jnwb.readthedocs.io/en/latest/errors/) page. On a table with no `codes` column, `events` returns the onsets and warns; `event_onsets` without `codes=` returns them silently.

Executable walkthroughs: [Read the Docs tutorials](https://jnwb.readthedocs.io/) or `examples/tutorials/`.

## Quickstart (arrays)

```python
import numpy as np
import jnwb

rng = np.random.default_rng(42)
events = np.arange(1.0, 21.0, 0.5)                          # 40 trials, 0.5 s apart
lags = 0.060 + rng.uniform(0.0, 0.34, (events.size, 25))    # each responds from t0 = 60 ms
spikes = np.sort(np.concatenate([rng.uniform(0.0, 21.5, 110),   # homogeneous background
                                 (events[:, None] + lags).ravel()]))

time_bins, rate_hz, _ = jnwb.raster_psth(spikes, events, win_ms=(-100.0, 400.0), bin_ms=10.0)
smooth_hz = jnwb.causal_exp_smooth(rate_hz, bin_ms=10.0, tau_ms=25.0)
fit = jnwb.fit_exponential_onset(time_bins, smooth_hz, t0_bounds_ms=(0.0, 200.0))
print(f"Onset t0: {fit['t0']:.1f} ms of a true 60.0 "
      f"(R2={fit['r2']:.2f}, {fit['bound_status'] or 'interior'})")

fs = 1000.0
lfp = rng.normal(size=1000)
tfr = jnwb.complex_tfr(lfp, fs=fs, freqs=np.linspace(10.0, 60.0, 10))
beta = jnwb.band_power(lfp, fs=fs, freq_range=jnwb.CANONICAL_BANDS["beta"], normalize=False)
print(f"TFR shape: {tfr.shape}, beta power: {beta:.4f}")
```

Read spikes and LFP for alignment after you have onsets. Onsets are session time; sample 0
of the LFP is at its `starting_time`, or at its first timestamp when the series stores
`timestamps` (`starting_time` is `None` and `acquisition_channel` raises, so the samples are
read from the file):

```python
import numpy as np
from pynwb import NWBHDF5IO

spikes = jnwb.unit_spike_times("session.nwb", unit_index=0)
entry = next(a for a in info["acquisitions"] if a["name"] == "probe_0_lfp")
if entry["starting_time"] is not None:       # stored with starting_time and rate
    lfp, fs_hz = jnwb.acquisition_channel("session.nwb", name="probe_0_lfp", channel=0)
    start_s = entry["starting_time"]
else:                                        # stored with timestamps, sampled regularly
    with NWBHDF5IO("session.nwb", "r") as io:
        series = io.read().acquisition["probe_0_lfp"]
        t, lfp = series.timestamps[:], series.data[:, 0]   # entry["layout"], raw samples
    fs_hz, start_s = 1.0 / np.median(np.diff(t)), float(t[0])
epochs, t_axis_s = jnwb.epoch_continuous(lfp, onsets - start_s, win_s=(-0.1, 0.4), fs=fs_hz)
```

Unit and electrode census:

```python
units = jnwb.get_all_units_metadata("session.nwb")
electrodes = jnwb.electrode_inventory("session.nwb")
```

## More examples

Each block plants a known effect in synthetic data and prints what jnwb recovers.

Direction between two signals (`x` leads `y` by 3 samples):

```python
import numpy as np
import jnwb

rng = np.random.default_rng(0)
x = rng.normal(size=5000)
y = 0.6 * np.roll(x, 3) + rng.normal(size=5000)
gc = jnwb.granger(x, y, order=5)
print(f"x->y {gc.x_to_y:.3f} (p={gc.p_x_to_y:.3f}), y->x {gc.y_to_x:.3f} (p={gc.p_y_to_x:.3f})")
# x->y 0.311 (p=0.000), y->x 0.001 (p=0.452)
```

A condition difference in time, tested with a cluster permutation (Maris & Oostenveld, 2007):

```python
import numpy as np
import jnwb

rng = np.random.default_rng(1)
a = rng.normal(size=(20, 100))               # trials x time points
b = rng.normal(size=(20, 100))
b[:, 40:60] += 1.5                           # effect at points 40-59
res = jnwb.cluster_permutation_test(a, b, n_permutations=500, rng=1)
best = min(res["clusters"], key=lambda c: c["p_value"])
span = np.flatnonzero(best["mask"])
print(f"cluster {span.min()}-{span.max()}, p = {best['p_value']:.3f}")
# cluster 39-59, p = 0.002
```

Decoding against a label-permutation null with a named scheme:

```python
import numpy as np
import jnwb

rng = np.random.default_rng(2)
labels = np.repeat([0, 1], 40)
X = rng.normal(size=(80, 30))                # trials x units
X[labels == 1, :5] += 1.2                    # 5 informative units
dec = jnwb.nested_cv_linear_svm(X, labels, n_splits=5, rng=2)
shuffled = jnwb.permute_labels(labels, scheme="global", rng=3)
null = jnwb.nested_cv_linear_svm(X, shuffled, n_splits=5, rng=2)
print(f"accuracy {dec['accuracy']:.2f}, shuffled {null['accuracy']:.2f}, "
      f"majority {dec['majority_baseline_accuracy']:.2f}")
# accuracy 0.88, shuffled 0.42, majority 0.50
```

## Documentation

Guides, the public API (every symbol in `jnwb.__all__`), and common mistakes are on [Read the Docs](https://jnwb.readthedocs.io/).

## Contributing

Setup, the checks to run, the branch model and the release procedure are in
[CONTRIBUTING.md](https://github.com/HNXJ/jnwb/blob/main/.github/CONTRIBUTING.md). Work lands on `dev`; `main` holds releases.

For AI agents: see [artifacts/agents.md](https://github.com/HNXJ/jnwb/blob/main/artifacts/agents.md).

## License

MIT. See [LICENSE](https://github.com/HNXJ/jnwb/blob/main/LICENSE).
