Skip to main content

jnwb

PyPI Docs CI/CD License DOI

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/ | 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 (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.

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

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

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.

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"],
)

The codes default, and the errors for a missing column, are on the 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 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, 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):

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:

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):

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):

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:

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.

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.

Metadata

Release files for jnwb 0.2.13

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jnwb 0.2.13
File Size Uploaded Provenance Actions
jnwb-0.2.13.tar.gz 560.5 kB With provenance Details for jnwb-0.2.13.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for jnwb 0.2.13
File Interpreter ABI Platform Actions
jnwb-0.2.13-py3-none-any.whl Python 3 none any Details for jnwb-0.2.13-py3-none-any.whl

Total release size: 1.1 MB

Release files / jnwb-0.2.13.tar.gz

Download URL jnwb-0.2.13.tar.gz
Size 560.5 kB
Tags Source
SHA-256 checksum
How to use checksums
2b15a1c93209928e8a2f83b3c5fc373cd0aed7b5b6a7967acaec0c1979ebf6e3
BLAKE2b-256 checksum
How to use checksums
dda6e2065cf29a8d6a75d9a5ff593862aeb7a6450fd36025d1b2f4106ebaafd7
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 Oct 21, 2026.

Transparency log

Release files / jnwb-0.2.13-py3-none-any.whl

Download URL jnwb-0.2.13-py3-none-any.whl
Size 562.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4a69cc8330a75dd479017887da442eccb75f5b93b12231d14bdaa5638890c271
BLAKE2b-256 checksum
How to use checksums
39d5b005f85a2a1a80d4ae9ca6754bcb7a28d2c3babfcd89c88aec61a91f2e85
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 Oct 21, 2026.

Transparency log

Security for jnwb 0.2.13

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for jnwb-0.2.13.tar.gz

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 Oct 21, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page