Skip to content

NWB stored in a Zarr store, not HDF5 (0.11.0) - #11

Merged
aaronkanzer merged 2 commits into
mainfrom
feat/nwb-zarr
Oct 26, 2026
Merged

aaronkanzer merged 2 commits into
mainfrom
feat/nwb-zarr

Conversation

@aaronkanzer

Copy link
Copy Markdown
Member

hdmf-zarr writes the same NWB model into a Zarr directory instead of an HDF5 file, and DANDI hosts assets in that form. Until now those decoded as a bare zarr-group.

Both container versions are read, from one metadata document each:

Container File read Sample verified against
Zarr v3 root zarr.json, whose consolidated_metadata holds the whole tree as a flat path map a store written by hdmf-zarr 0.14.0 with pynwb 4.2.0
Zarr v2 .zmetadata, keyed <path>/.zattrs, /.zgroup, /.zarray, folded into the same node map the real consolidated metadata of DANDI 000719's icephys asset, 3170 keys and 139 acquisition series

Both fixtures are committed whole rather than trimmed. The DANDI one is 1.4 MB of JSON that packs to about 52 KB.

Worth a reviewer's attention:

  • The record is honestly smaller than the HDF5 one. In HDF5 a scalar string sits in the object header, so hdf5.py reads the identifier, session description and subject straight out of metadata. In Zarr those are chunk data, in the sample behind zstd, and no chunk is ever opened here, the same rule the rest of zarr.py follows. So those keys are absent rather than null, because null already means "not present" in the HDF5 record, and stringValuesUnread names the paths a reader would have to open. test_string_values_are_named_not_guessed asserts the absence as deliberately as the other tests assert values.
  • .zmetadata is a new registered sidecar name, tried before .zattrs because sources._decode_directory walks the names in registration order. It is claimed only for an NWB store; any other consolidated document declines and the dispatch falls through exactly as before, which test_non_nwb_zarr_is_untouched pins.
  • It reads more than the other sidecars, 8 MiB against 1 MiB, because a consolidated document has to parse whole and the real DANDI one is 1.4 MB. A larger store declines here and falls back to .zattrs, which still names and versions the file.
  • The Zarr path carries something HDF5 does not: specifications/<namespace>/<version> gives the schema version each namespace was cached at, so specifications is {"core": ["2.11.0"]} rather than a bare name list.

596 passed, 2 skipped, green on Python 3.9 and 3.12.

hdmf-zarr writes the same NWB model into a Zarr directory. The root group's
attributes carry nwb_version, namespace and neurodata_type, and a store
written with consolidated metadata keeps the whole tree in one document, so
a single bounded read describes the session.

Both container versions: Zarr v3 from the root zarr.json, Zarr v2 from the
consolidated .zmetadata, which is now a registered sidecar name tried before
.zattrs. .zmetadata is claimed only for an NWB store, so every other
consolidated document declines and the directory dispatch falls through to
the per-node sidecars as before.

What this cannot report, and the HDF5 flavour can, is the scalar strings:
identifier, session description, start time, institution, lab, subject. In
HDF5 those sit in the object header; in Zarr they are chunk data behind a
compressor this library does not carry. They are absent rather than
guessed, and stringValuesUnread names the paths that hold them.

Verified against two real files, both committed whole: the consolidated
metadata of a DANDI 000719 asset (Zarr v2, 3170 keys, 139 acquisition
series, fetched from the DANDI S3 bucket) and the root of a store written
by hdmf-zarr 0.14.0 with pynwb 4.2.0 (Zarr v3).
Zarr writers emit bare NaN, Infinity and -Infinity tokens, which Python's
json reads as floats and strict JSON rejects. An NWB TimeSeries writes
"resolution": NaN whenever the resolution is unspecified, 278 times over
in the committed DANDI store. No reported field carries one today, so the
fixture is unchanged, but an attribute this record does carry could, and
the Go port turns the same token into null. Both now agree on absent.
@aaronkanzer
aaronkanzer merged commit 19f5378 into main Oct 26, 2026
3 checks passed
@aaronkanzer
aaronkanzer deleted the feat/nwb-zarr branch October 26, 2026 16:51
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