jnwb
Dataset-agnostic Python library for Neurodata Without Borders (NWB 2.0+) electrophysiology: addressing, spikes, LFP, spectral analysis, statistics, population methods, decoding, connectivity, laminar CSD, filtering, QC, and visualization.
Documentation: https://jnwb.readthedocs.io/ | Source: 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 (
globalorwithin_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 |
| Visualization | raster_psth, setup_vector_graphics, save_figure_suite |
Installation
Requires Python 3.12 or newer. Tested in CI on 3.12, 3.13 and 3.14.
pip install jnwb # latest published release
pip install "jnwb[torch,gpu]" # optional backends
This checkout is 0.2.6. To install it from a clone instead of from PyPI:
pip install . # or: pip install -e ".[test,docs]" for development
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.
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"],
)
codes is jnwb's default column name, not an NWB requirement — a file from another lab
usually names it something else, which is why the column comes from inspect rather than
from habit. Naming a column that does not exist raises ColumnNotFoundError listing the
columns that do; omitting code_column on a table with no codes column returns the
onsets and warns.
Executable walkthroughs: Read the Docs tutorials or examples/tutorials/.
Quickstart (arrays)
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:
spikes = jnwb.unit_spike_times("session.nwb", unit_index=0)
lfp, fs_hz = jnwb.acquisition_channel("session.nwb", name="probe_0_lfp", channel=0)
start_s = next(a["starting_time"] for a in info["acquisitions"] if a["name"] == "probe_0_lfp")
epochs, t_axis_s = jnwb.epoch_continuous(lfp, onsets - start_s, win_s=(-0.1, 0.4), fs=fs_hz)
Unit and electrode census:
units = jnwb.get_all_units_metadata("session.nwb")
electrodes = jnwb.electrode_inventory("session.nwb")
Documentation
Guides, the public API (every symbol in jnwb.__all__), and common mistakes are on Read the Docs.
Contributing
Setup, the checks to run, the branch model and the release procedure are in
CONTRIBUTING.md. Work lands on dev; main holds releases.
For AI agents: see artifacts/agents.md.
License
MIT. See LICENSE.
Release files for jnwb 0.2.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jnwb-0.2.6.tar.gz | 386.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jnwb-0.2.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 757.5 kB
Release files / jnwb-0.2.6.tar.gz
| Download URL | jnwb-0.2.6.tar.gz |
|---|---|
| Size | 386.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
de65aa47b1bc686e451c0a04dd72387bc755825ce6a13e6b093938316df6b3cb
|
|
BLAKE2b-256 checksum How to use checksums |
6e86c5275d76e3e2a0a73263b066a1d4140aebcf4f307dedc352e7281e25419c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / jnwb-0.2.6-py3-none-any.whl
| Download URL | jnwb-0.2.6-py3-none-any.whl |
|---|---|
| Size | 371.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6f6ee66b4f9af808b99d73e7f90dbd5eaa905e52c7f8c9fcbe5ae8c0515cbdc4
|
|
BLAKE2b-256 checksum How to use checksums |
021a1c718b064f6d583774cd2efdbe6b9f8ad44de22ab0953cef3c64907edd77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log