Skip to content

Read the NWB layout of the AqNWB-based NWB Format plugin - #51

Draft
oruebel wants to merge 5 commits into
open-ephys:mainfrom
oruebel:nwb-aqnwb-layout
Draft

oruebel wants to merge 5 commits into
open-ephys:mainfrom
oruebel:nwb-aqnwb-layout

Conversation

@oruebel

@oruebel oruebel commented Oct 21, 2026 •

Copy link
Copy Markdown

Context

This PR is related to open-ephys-plugins/nwb-format#6 . This PR adds support for the NWB files written by the new AqNWB-based version of the NWB Format plugin, and keeps reading files of the previous layout. I am creating this PR here so we can test and review, however, it'll be best to wait for open-ephys-plugins/nwb-format#6 to be finalized before merging.

Changes

Continuous data and spikes

  • Sample numbers are read from sync/sample_numbers (new layout) or from the sync dataset (previous layout).
  • The sample rate is read from starting_time.rate or computed from the timestamps. Previously it was read from timestamps.interval, which the NWB schema fixes to 1, so it can't hold the sample rate in compliant files.
  • Scaling follows the NWB specification: data.conversion * channel_conversion. Files of the previous layout store the factor in both, so only channel_conversion is applied for them, as before.

Events and messages

  • TTL events are read from the NWB 2.11 EventsTables in /events (line, state, full_word, sample_number). Tables are named <processor>-<id>.<stream>.TTL (TTL_<n> for further event channels), as the event folders of the Binary format. The .TTL TimeSeries of the previous layout are still read.
  • load_messages is implemented for the new (/events/messages) and the previous (/acquisition/messages) layout.
  • Empty event tables are skipped; the plugin creates a table for every event channel, including channels without events, and empty tables would turn the integer columns of the combined events into floats.
  • Event processor_id and stream_index stay float64, as in the Binary format and in earlier versions of this reader.
  • NWB files are opened by the names found in the Record Node directory: experiment<N>.nwb, and experiment<N>_recording<M>.nwb, which the plugin writes if experiment<N>.nwb already exists when recording M starts (e.g., after the data directory was changed between recordings and back). The files are ordered by experiment and recording number. Previously the file names were derived from the position in the alphabetically sorted list of files, which failed for missing experiment numbers and for 10 or more experiments.

Testing

  • New test file tests/data/v1.1.0_NWB (1.9 MB; GUI v1.1.0, File Reader -> Phase Detector -> Spike Detector -> Record Node recording channels 1-4 of the 16 example channels, two short recordings with spikes, Phase Detector TTL events, messages and session metadata) and four tests: test_aqnwb_continuous_data, test_aqnwb_spike_data, test_aqnwb_events, test_aqnwb_messages.
  • The previous layout: the output for the existing v0.6.7 test file (continuous data, spikes, events, messages) is value-identical to the output of the current main (4032c29).
  • All 21 tests pass with Python 3.12 (including a new test of the file names).

Note on the test file: the v1.1.0 NWB test file was recorded with the development version of the plugin (open-ephys-plugins/nwb-format#6). Once that PR is merged and released, the test file should be regenerated with the released plugin (and the tests updated if the layout changed during review). Until then, this PR should stay in draft.

Note on the Python version: the tests (and the package) need Python 3.11 or newer, also on main: src/open_ephys/analysis/recording.py imports enum.StrEnum, which was added in Python 3.11, while pyproject.toml declares requires-python = ">=3.9". With Python 3.9, importing the package fails with ImportError: cannot import name 'StrEnum' from 'enum'. This PR doesn't change that; either requires-python should be raised to >=3.11, or StrEnum replaced to keep supporting Python 3.9 and 3.10 (e.g., by a (str, Enum) subclass whose __str__ returns the value, which is what StrEnum adds). I can open a separate issue or PR for this if preferred.

AI disclosure

🤖 Generated with the help of Claude Code

oruebel and others added 4 commits October 20, 2026 18:20
- Continuous data and spikes:
  - sample numbers from sync/sample_numbers (new) or the legacy sync dataset
  - sample rate from starting_time.rate or from the timestamps instead of
    timestamps.interval (which the NWB schema fixes to 1)
  - scaling: data.conversion * channel_conversion (NWB specification);
    legacy files store the factor in both, so only channel_conversion is
    applied for them (as before)
- TTL events from the EventsTables in /events (line, state, full_word,
  sample_number), in addition to the legacy ".TTL" TimeSeries
- Implement load_messages for the new (/events/messages) and legacy
  (/acquisition/messages) layouts
- Skip empty event tables (avoids int columns turning into floats)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TTL EventsTables are named "<processor>-<id>.<stream>.TTL" (or
"TTL_<n>"), as the event folders of the Binary format. Event
processor_id/stream_index are float64 again, as in the Binary format and
earlier versions of this reader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a v1.1.0 NWB test file (1.9 MB: File Reader -> Phase Detector ->
Spike Detector -> Record Node recording channels 1-4 of 16, two short
recordings with spikes, Phase Detector TTL events, messages and session
metadata) and tests for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The NWB plugin writes one file per experiment, experiment<N>.nwb, or
experiment<N>_recording<M>.nwb if experiment<N>.nwb already exists when
recording M starts (e.g., after the data directory was changed between
recordings and back). Open the files found in the directory, with the
experiment and recording indexes taken from the file names, and order them
by these numbers. Previously the file names were derived from the
position in the alphabetically sorted list of files, which failed for
missing experiment numbers and for 10 or more experiments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
oruebel added a commit to NeurodataWithoutBorders/nwb-format that referenced this pull request Oct 25, 2026
If the data directory is changed between recordings and the new
directory already contains experiment<N>.nwb (e.g., after changing the
directory and back), name the new file after the experiment and its
first recording, e.g. experiment1_recording3.nwb, instead of appending
the recording to the open file in the previous directory. Existing files
are still never changed. This also applies when no file is open (the
recording was previously not saved in NWB format in this case).

Tested: recording into A, B, and A again (fixed directory name) gives
A/experiment1.nwb (recording 1), B/experiment1.nwb (recording 2) and
A/experiment1_recording3.nwb (recording 3); python-tools reads both
files in A (open-ephys/open-ephys-python-tools#51).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant