Note
Go to the end to download the full example code
Creating NWB files using NWBZarrIO
¶
Similar to pynwb.NWBHDF5IO
, the NWBZarrIO
extends the basic
ZarrIO
to perform default setup for BuildManager, loading or namespaces etc.,
in the context of the NWB format, to simplify using hdmf-zarr with the NWB data standard. With this we
can use NWBZarrIO
directly with the PyNWB API to read/write NWB files to/from Zarr.
I.e., we can follow the standard PyNWB tutorials for using NWB files, and only need to replace
pynwb.NWBHDF5IO
with NWBZarrIO
for read/write (and replace the
use of py:class:H5DataIO with ZarrDataIO
).
Creating and NWB extracellular electrophysiology file¶
As an example, we here create an extracellular electrophysiology NWB file. This example is derived from Extracellular Electrophysiology tutorial.
# Ignore warnings about the development of the ZarrIO backend
from datetime import datetime
from dateutil.tz import tzlocal
import numpy as np
from pynwb import NWBFile
from pynwb.ecephys import ElectricalSeries, LFP
# Create the NWBFile
nwbfile = NWBFile(
session_description="my first synthetic recording",
identifier="EXAMPLE_ID",
session_start_time=datetime.now(tzlocal()),
experimenter="Dr. Bilbo Baggins",
lab="Bag End Laboratory",
institution="University of Middle Earth at the Shire",
experiment_description="I went on an adventure with thirteen dwarves "
"to reclaim vast treasures.",
session_id="LONELYMTN",
)
# Create a device
device = nwbfile.create_device(
name="array", description="the best array", manufacturer="Probe Company 9000"
)
# Add electrodes and electrode groups to the NWB file
nwbfile.add_electrode_column(name="label", description="label of electrode")
nshanks = 4
nchannels_per_shank = 3
electrode_counter = 0
for ishank in range(nshanks):
# create an electrode group for this shank
electrode_group = nwbfile.create_electrode_group(
name="shank{}".format(ishank),
description="electrode group for shank {}".format(ishank),
device=device,
location="brain area",
)
# add electrodes to the electrode table
for ielec in range(nchannels_per_shank):
nwbfile.add_electrode(
x=5.3, y=1.5, z=8.5, imp=np.nan,
filtering='unknown',
group=electrode_group,
label="shank{}elec{}".format(ishank, ielec),
location="brain area",
)
electrode_counter += 1
# Create a table region to select the electrodes to record from
all_table_region = nwbfile.create_electrode_table_region(
region=list(range(electrode_counter)), # reference row indices 0 to N-1
description="all electrodes",
)
# Add a mock electrical recording acquisition to the NWBFile
raw_data = np.random.randn(50, len(all_table_region))
raw_electrical_series = ElectricalSeries(
name="ElectricalSeries",
data=raw_data,
electrodes=all_table_region,
starting_time=0.0, # timestamp of the first sample in seconds relative to the session start time
rate=20000.0, # in Hz
)
nwbfile.add_acquisition(raw_electrical_series)
# Add a mock LFP processing result to the NWBFile
lfp_data = np.random.randn(50, len(all_table_region))
lfp_electrical_series = ElectricalSeries(
name="ElectricalSeries",
data=lfp_data,
electrodes=all_table_region,
starting_time=0.0,
rate=200.0,
)
lfp = LFP(electrical_series=lfp_electrical_series)
ecephys_module = nwbfile.create_processing_module(
name="ecephys", description="processed extracellular electrophysiology data"
)
ecephys_module.add(lfp)
# Add mock spike times and units to the NWBFile
nwbfile.add_unit_column(name="quality", description="sorting quality")
poisson_lambda = 20
firing_rate = 20
n_units = 10
for n_units_per_shank in range(n_units):
n_spikes = np.random.poisson(lam=poisson_lambda)
spike_times = np.round(
np.cumsum(np.random.exponential(1 / firing_rate, n_spikes)), 5
)
nwbfile.add_unit(
spike_times=spike_times, quality="good", waveform_mean=[1.0, 2.0, 3.0, 4.0, 5.0]
)
Writing the NWB file to Zarr¶
from hdmf_zarr.nwb import NWBZarrIO
import os
path = "ecephys_tutorial.nwb.zarr"
absolute_path = os.path.abspath(path)
with NWBZarrIO(path=path, mode="w") as io:
io.write(nwbfile)
Test opening with the absolute path instead¶
The main reason for using the absolute_path
here is for testing purposes
to ensure links and references work as expected. Otherwise, using the
relative path
here instead is fine.
with NWBZarrIO(path=absolute_path, mode="r") as io:
infile = io.read()