From aa5538ad38af65889fba2f10d77a62524a29d6bd Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 13 Nov 2024 21:24:50 +0100 Subject: [PATCH 1/7] Fix bug in table2nwb --- +util/table2nwb.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+util/table2nwb.m b/+util/table2nwb.m index b2513b94..98553cb3 100644 --- a/+util/table2nwb.m +++ b/+util/table2nwb.m @@ -28,8 +28,8 @@ for col = T if ~strcmp(col.Properties.VariableNames{1},'id') - - if ~isempty(col.Properties.VariableDescriptions{1}) + if ~isempty(col.Properties.VariableDescriptions) ... + && ~isempty(col.Properties.VariableDescriptions{1}) description = col.Properties.VariableDescriptions{1}; else description = 'no description provided'; From 4ae0cde8d5215a9932743ad10c6326af3a9c37ba Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 13 Nov 2024 21:25:15 +0100 Subject: [PATCH 2/7] Add DecompositionSeries to ecephys tutorial --- tutorials/ecephys.mlx | Bin 349248 -> 348618 bytes tutorials/html/ecephys.html | 114 ++++++++++++++++++------------------ 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/tutorials/ecephys.mlx b/tutorials/ecephys.mlx index 5bffdbf273a2e88ca2edaa8e9cd9845149d16e5f..3dfb88747179a54197697d752f72eea417dca3b9 100644 GIT binary patch delta 10734 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 diff --git a/tutorials/html/ecephys.html b/tutorials/html/ecephys.html index 9806eb26..6914f79a 100644 --- a/tutorials/html/ecephys.html +++ b/tutorials/html/ecephys.html @@ -1,5 +1,5 @@ -Neurodata Without Borders Extracellular Electrophysiology Tutorial

Neurodata Without Borders Extracellular Electrophysiology Tutorial

Table of Contents
Neurodata Without Borders Extracellular Electrophysiology Tutorial +.S18 { margin: 15px 10px 5px 4px; padding: 0px; line-height: 18px; min-height: 0px; white-space: pre-wrap; color: rgb(33, 33, 33); font-family: Helvetica, Arial, sans-serif; font-style: normal; font-size: 17px; font-weight: 700; text-align: left; } +.S19 { margin: 10px 10px 5px 4px; padding: 0px; line-height: 18px; min-height: 0px; white-space: pre-wrap; color: rgb(33, 33, 33); font-family: Helvetica, Arial, sans-serif; font-style: normal; font-size: 15px; font-weight: 700; text-align: left; } +.S20 { margin: 15px 10px 5px 4px; padding: 0px; line-height: 28.8px; min-height: 0px; white-space: pre-wrap; color: rgb(192, 76, 11); font-family: Helvetica, Arial, sans-serif; font-style: normal; font-size: 24px; font-weight: 400; text-align: left; }

Neurodata Without Borders Extracellular Electrophysiology Tutorial

This tutorial

Create fake data for a hypothetical extracellular electrophysiology experiment. The types of data we will convert are:
  • Voltage recording
  • Local field potential (LFP)
  • Spike times
It is recommended to first work through the Introduction to MatNWB tutorial, which demonstrates installing MatNWB and creating an NWB file with subject information, animal position, and trials, as well as writing and reading NWB files in MATLAB.

Setting up the NWB File

An NWB file represents a single session of an experiment. Each file must have a session_description, identifier, and session start time. Create a new NWBFile object with those and additional metadata. For all MatNWB functions, we use the Matlab method of entering keyword argument pairs, where arguments are entered as name followed by value.
nwb = NwbFile( ...
'session_description', 'mouse in open exploration',...
'identifier', 'Mouse5_Day3', ...
'session_start_time', datetime(2018, 4, 25, 2, 30, 3, 'TimeZone', 'local'), ...
'timestamps_reference_time', datetime(2018, 4, 25, 3, 0, 45, 'TimeZone', 'local'), ...
'general_experimenter', 'Last Name, First Name', ... % optional
'general_session_id', 'session_1234', ... % optional
'general_institution', 'University of My Institution', ... % optional
'general_related_publications', {'DOI:10.1016/j.neuron.2016.12.011'}); % optional
nwb
nwb =
NwbFile with properties: + Python tutorials

This tutorial

Create fake data for a hypothetical extracellular electrophysiology experiment. The types of data we will convert are:
  • Voltage recording
  • Local field potential (LFP)
  • Spike times
It is recommended to first work through the Introduction to MatNWB tutorial, which demonstrates installing MatNWB and creating an NWB file with subject information, animal position, and trials, as well as writing and reading NWB files in MATLAB.

Setting up the NWB File

An NWB file represents a single session of an experiment. Each file must have a session_description, identifier, and session start time. Create a new NWBFile object with those and additional metadata. For all MatNWB functions, we use the Matlab method of entering keyword argument pairs, where arguments are entered as name followed by value.
nwb = NwbFile( ...
'session_description', 'mouse in open exploration',...
'identifier', 'Mouse5_Day3', ...
'session_start_time', datetime(2018, 4, 25, 2, 30, 3, 'TimeZone', 'local'), ...
'timestamps_reference_time', datetime(2018, 4, 25, 3, 0, 45, 'TimeZone', 'local'), ...
'general_experimenter', 'Last Name, First Name', ... % optional
'general_session_id', 'session_1234', ... % optional
'general_institution', 'University of My Institution', ... % optional
'general_related_publications', {'DOI:10.1016/j.neuron.2016.12.011'}); % optional
nwb
nwb =
NwbFile with properties: - nwb_version: '2.6.0' + nwb_version: '2.7.0' file_create_date: [] identifier: 'Mouse5_Day3' session_description: 'mouse in open exploration' - session_start_time: {[2018-04-25T02:30:03.000000-04:00]} - timestamps_reference_time: {[2018-04-25T03:00:45.000000-04:00]} + session_start_time: {[2018-04-25T02:30:03.000000+02:00]} + timestamps_reference_time: {[2018-04-25T03:00:45.000000+02:00]} acquisition: [0×1 types.untyped.Set] analysis: [0×1 types.untyped.Set] general: [0×1 types.untyped.Set] @@ -142,22 +118,7 @@ stimulus_presentation: [0×1 types.untyped.Set] stimulus_templates: [0×1 types.untyped.Set] units: [] -

Extracellular Electrophysiology

In order to store extracellular electrophysiology data, you first must create an electrodes table describing the electrodes that generated this data. Extracellular electrodes are stored in an electrodes table, which is also a DynamicTable. electrodes has several required fields: x, y, z, impedence, location, filtering, and electrode_group.

Electrodes Table

Since this is a DynamicTable, we can add additional metadata fields. We will be adding a "label" column to the table.
Here, we also demonstate another method for creating DynamicTables, by first creating a MATLAB native Table object and then calling util.table2nwb to convert this Table object into a DynamicTable.
numShanks = 4;
numChannelsPerShank = 3;
 
ElectrodesDynamicTable = types.hdmf_common.DynamicTable(...
'colnames', {'location', 'group', 'group_name', 'label'}, ...
'description', 'all electrodes');
 
Device = types.core.Device(...
'description', 'the best array', ...
'manufacturer', 'Probe Company 9000' ...
);
nwb.general_devices.set('array', Device);
for iShank = 1:numShanks
shankGroupName = sprintf('shank%d', iShank);
EGroup = types.core.ElectrodeGroup( ...
'description', sprintf('electrode group for %s', shankGroupName), ...
'location', 'brain area', ...
'device', types.untyped.SoftLink(Device) ...
);
nwb.general_extracellular_ephys.set(shankGroupName, EGroup);
for iElectrode = 1:numChannelsPerShank
ElectrodesDynamicTable.addRow( ...
'location', 'unknown', ...
'group', types.untyped.ObjectView(EGroup), ...
'group_name', shankGroupName, ...
'label', sprintf('%s-electrode%d', shankGroupName, iElectrode));
end
end
ElectrodesDynamicTable.toTable()
ans = 12×5 table
 idlocationgroupgroup_namelabel
10'unknown'1×1 ObjectView'shank1''shank1-electrode1'
21'unknown'1×1 ObjectView'shank1''shank1-electrode2'
32'unknown'1×1 ObjectView'shank1''shank1-electrode3'
43'unknown'1×1 ObjectView'shank2''shank2-electrode1'
54'unknown'1×1 ObjectView'shank2''shank2-electrode2'
65'unknown'1×1 ObjectView'shank2''shank2-electrode3'
76'unknown'1×1 ObjectView'shank3''shank3-electrode1'
87'unknown'1×1 ObjectView'shank3''shank3-electrode2'
98'unknown'1×1 ObjectView'shank3''shank3-electrode3'
109'unknown'1×1 ObjectView'shank4''shank4-electrode1'
1110'unknown'1×1 ObjectView'shank4''shank4-electrode2'
1211'unknown'1×1 ObjectView'shank4''shank4-electrode3'
 
nwb.general_extracellular_ephys_electrodes = ElectrodesDynamicTable;

Links

In the above loop, we create ElectrodeGroup objects. The electrodes table then uses an ObjectView in each row to link to the corresponding ElectrodeGroup object. An ObjectView is an object that allow you to create a link from one neurodata type referencing another.

ElectricalSeries

Voltage data are stored in ElectricalSeries objects. ElectricalSeries is a subclass of TimeSeries specialized for voltage data. In order to create our ElectricalSeries object, we will need to reference a set of rows in the electrodes table to indicate which electrodes were recorded. We will do this by creating a DynamicTableRegion, which is a type of link that allows you to reference specific rows of a DynamicTable, such as the electrodes table, by row indices.
Create a DynamicTableRegion that references all rows of the electrodes table.
electrode_table_region = types.hdmf_common.DynamicTableRegion( ...
'table', types.untyped.ObjectView(ElectrodesDynamicTable), ...
'description', 'all electrodes', ...
'data', (0:length(ElectrodesDynamicTable.id.data)-1)');
Now create an ElectricalSeries object to hold acquisition data collected during the experiment.
electrical_series = types.core.ElectricalSeries( ...
'starting_time', 0.0, ... % seconds
'starting_time_rate', 30000., ... % Hz
'data', randn(12, 3000), ...
'electrodes', electrode_table_region, ...
'data_unit', 'volts');
This is the voltage data recorded directly from our electrodes, so it goes in the acquisition group.
nwb.acquisition.set('ElectricalSeries', electrical_series);

LFP

Local field potential (LFP) refers in this case to data that has been downsampled and/or filtered from the original acquisition data and is used to analyze signals in the lower frequency range. Filtered and downsampled LFP data would also be stored in an ElectricalSeries. To help data analysis and visualization tools know that this ElectricalSeries object represents LFP data, store it inside an LFP object, then place the LFP object in a ProcessingModule named 'ecephys'. This is analogous to how we stored the SpatialSeries object inside of a Position object and stored the Position object in a ProcessingModule named 'behavior' earlier.
electrical_series = types.core.ElectricalSeries( ...
'starting_time', 0.0, ... % seconds
'starting_time_rate', 1000., ... % Hz
'data', randn(12, 100), ...
'electrodes', electrode_table_region, ...
'data_unit', 'volts');
 
lfp = types.core.LFP('ElectricalSeries', electrical_series);
 
ecephys_module = types.core.ProcessingModule(...
'description', 'extracellular electrophysiology');
 
ecephys_module.nwbdatainterface.set('LFP', lfp);
nwb.processing.set('ecephys', ecephys_module);

Spike Times

Ragged Arrays

Spike times are stored in another DynamicTable of subtype Units. The default Units table is at /units in the HDF5 file. You can add columns to the Units table just like you did for electrodes and trials. Here, we generate some random spike data and populate the table.
num_cells = 10;
firing_rate = 20;
spikes = cell(1, num_cells);
for iShank = 1:num_cells
spikes{iShank} = rand(1, randi([16, 28]));
end
spikes
spikes = 1×10 cell
 12345678910
11×22 double1×21 double1×19 double1×16 double1×26 double1×16 double1×17 double1×21 double1×28 double1×27 double
Spike times are an example of a ragged array- it's like a matrix, but each row has a different number of elements. We can represent this type of data as an indexed column of the units DynamicTable. These indexed columns have two components, the vector data object that holds the data and the vector index object that holds the indices in the vector that indicate the row breaks. You can use the convenience function util.create_indexed_column to create these objects.
[spike_times_vector, spike_times_index] = util.create_indexed_column(spikes);
 
nwb.units = types.core.Units( ...
'colnames', {'spike_times'}, ...
'description', 'units table', ...
'spike_times', spike_times_vector, ...
'spike_times_index', spike_times_index ...
);

Designating Electrophysiology Data

As mentioned above, ElectricalSeries objects are meant for storing specific types of extracellular recordings. In addition to this TimeSeries class, NWB provides some Processing Modules for designating the type of data you are storing. We will briefly discuss them here, and refer the reader to the API documentation and Intro to NWB for more details on using these objects.
For storing spike data, there are two options. Which one you choose depends on what data you have available. If you need to store complete and/or continuous raw voltage traces, you should store the traces with ElectricalSeries objects as acquisition data, and use the EventDetection class for identifying the spike events in your raw traces. If you do not want to store the raw voltage traces and only the waveform ‘snippets’ surrounding spike events, you should use the EventWaveform class, which can store one or more SpikeEventSeries objects.
The results of spike sorting (or clustering) should be stored in the top-level Units table. Note that it is not required to store spike waveforms in order to store spike events or mean waveforms–if you only want to store the spike times of clustered units you can use only the Units table.
For local field potential data, there are two options. Again, which one you choose depends on what data you have available. With both options, you should store your traces with ElectricalSeries objects. If you are storing unfiltered local field potential data, you should store the ElectricalSeries objects in LFP data interface object(s). If you have filtered LFP data, you should store the ElectricalSeries objects in FilteredEphys data interface object(s).

Writing the NWB File

nwbExport(nwb, 'ecephys_tutorial.nwb')

Reading NWB Data

Data arrays are read passively from the file. Calling TimeSeries.data does not read the data values, but presents an HDF5 object that can be indexed to read data. This allows you to conveniently work with datasets that are too large to fit in RAM all at once. load with no input arguments reads the entire dataset:
nwb2 = nwbRead('ecephys_tutorial.nwb', 'ignorecache');
nwb2.processing.get('ecephys'). ...
nwbdatainterface.get('LFP'). ...
electricalseries.get('ElectricalSeries'). ...
data.load;

Accessing Data Regions

If all you need is a data region, you can index a DataStub object like you would any normal array in MATLAB, as shown below. When indexing the dataset this way, only the selected region is read from disk into RAM. This allows you to handle very large datasets that would not fit entirely into RAM.
% read section of LFP
nwb2.processing.get('ecephys'). ...
nwbdatainterface.get('LFP'). ...
electricalseries.get('ElectricalSeries'). ...
data(1:5, 1:10)
ans = 5×10
-0.2598 -0.2503 -0.7306 -1.4552 -0.3254 -0.8368 -0.6304 -0.3502 -2.3128 0.0892 - -0.6888 0.1147 0.7930 -0.8179 -1.0429 -0.7021 -1.0593 0.3750 0.5636 0.1012 - -1.5281 -1.1489 -1.2679 1.5675 0.0814 -1.3598 0.8827 1.5249 -1.6651 0.2384 - 0.3405 0.1056 0.1532 0.1086 1.4486 0.0502 -1.4344 0.1349 1.0144 1.4378 - -0.6437 -1.2244 0.1417 -0.8462 0.2846 -0.0092 -0.2735 -1.0856 0.4303 0.8605 -
 
% You can use the getRow method of the table to load spike times of a specific unit.
% To get the values, unpack from the returned table.
nwb.units.getRow(1).spike_times{1}
ans = 22×1
0.3740 - 0.7428 - 0.4703 - 0.3847 - 0.4485 - 0.1652 - 0.2093 - 0.5587 - 0.4400 - 0.3532 -

Learn more!

See the API documentation to learn what data types are available.

MATLAB tutorials

Python tutorials

See our tutorials for more details about your data type:
Check out other tutorials that teach advanced NWB topics:

+

Extracellular Electrophysiology

In order to store extracellular electrophysiology data, you first must create an electrodes table describing the electrodes that generated this data. Extracellular electrodes are stored in an electrodes table, which is also a DynamicTable. electrodes has several required fields: x, y, z, impedence, location, filtering, and electrode_group.

Electrodes Table

Since this is a DynamicTable, we can add additional metadata fields. We will be adding a "label" column to the table.
Here, we also demonstate another method for creating DynamicTables, by first creating a MATLAB native Table object and then calling util.table2nwb to convert this Table object into a DynamicTable.
numShanks = 4;
numChannelsPerShank = 3;
 
ElectrodesDynamicTable = types.hdmf_common.DynamicTable(...
'colnames', {'location', 'group', 'group_name', 'label'}, ...
'description', 'all electrodes');
 
Device = types.core.Device(...
'description', 'the best array', ...
'manufacturer', 'Probe Company 9000' ...
);
nwb.general_devices.set('array', Device);
for iShank = 1:numShanks
shankGroupName = sprintf('shank%d', iShank);
EGroup = types.core.ElectrodeGroup( ...
'description', sprintf('electrode group for %s', shankGroupName), ...
'location', 'brain area', ...
'device', types.untyped.SoftLink(Device) ...
);
nwb.general_extracellular_ephys.set(shankGroupName, EGroup);
for iElectrode = 1:numChannelsPerShank
ElectrodesDynamicTable.addRow( ...
'location', 'unknown', ...
'group', types.untyped.ObjectView(EGroup), ...
'group_name', shankGroupName, ...
'label', sprintf('%s-electrode%d', shankGroupName, iElectrode));
end
end
ElectrodesDynamicTable.toTable()
ans = 12×5 table
 idlocationgroupgroup_namelabel
10'unknown'1×1 ObjectView'shank1''shank1-electrode1'
21'unknown'1×1 ObjectView'shank1''shank1-electrode2'
32'unknown'1×1 ObjectView'shank1''shank1-electrode3'
43'unknown'1×1 ObjectView'shank2''shank2-electrode1'
54'unknown'1×1 ObjectView'shank2''shank2-electrode2'
65'unknown'1×1 ObjectView'shank2''shank2-electrode3'
76'unknown'1×1 ObjectView'shank3''shank3-electrode1'
87'unknown'1×1 ObjectView'shank3''shank3-electrode2'
98'unknown'1×1 ObjectView'shank3''shank3-electrode3'
109'unknown'1×1 ObjectView'shank4''shank4-electrode1'
1110'unknown'1×1 ObjectView'shank4''shank4-electrode2'
1211'unknown'1×1 ObjectView'shank4''shank4-electrode3'
 
nwb.general_extracellular_ephys_electrodes = ElectrodesDynamicTable;

Links

In the above loop, we create ElectrodeGroup objects. The electrodes table then uses an ObjectView in each row to link to the corresponding ElectrodeGroup object. An ObjectView is an object that allow you to create a link from one neurodata type referencing another.

ElectricalSeries

Voltage data are stored in ElectricalSeries objects. ElectricalSeries is a subclass of TimeSeries specialized for voltage data. In order to create our ElectricalSeries object, we will need to reference a set of rows in the electrodes table to indicate which electrodes were recorded. We will do this by creating a DynamicTableRegion, which is a type of link that allows you to reference specific rows of a DynamicTable, such as the electrodes table, by row indices.
Create a DynamicTableRegion that references all rows of the electrodes table.
electrode_table_region = types.hdmf_common.DynamicTableRegion( ...
'table', types.untyped.ObjectView(ElectrodesDynamicTable), ...
'description', 'all electrodes', ...
'data', (0:length(ElectrodesDynamicTable.id.data)-1)');
Now create an ElectricalSeries object to hold acquisition data collected during the experiment.
electrical_series = types.core.ElectricalSeries( ...
'starting_time', 0.0, ... % seconds
'starting_time_rate', 30000., ... % Hz
'data', randn(12, 3000), ...
'electrodes', electrode_table_region, ...
'data_unit', 'volts');
This is the voltage data recorded directly from our electrodes, so it goes in the acquisition group.
nwb.acquisition.set('ElectricalSeries', electrical_series);

LFP

Local field potential (LFP) refers in this case to data that has been downsampled and/or filtered from the original acquisition data and is used to analyze signals in the lower frequency range. Filtered and downsampled LFP data would also be stored in an ElectricalSeries. To help data analysis and visualization tools know that this ElectricalSeries object represents LFP data, store it inside an LFP object, then place the LFP object in a ProcessingModule named 'ecephys'. This is analogous to how we stored the SpatialSeries object inside of a Position object and stored the Position object in a ProcessingModule named 'behavior' earlier.
lfp_electrical_series = types.core.ElectricalSeries( ...
'starting_time', 0.0, ... % seconds
'starting_time_rate', 1000., ... % Hz
'data', randn(12, 100), ...
'electrodes', electrode_table_region, ...
'data_unit', 'volts');
 
lfp = types.core.LFP('ElectricalSeries', lfp_electrical_series);
 
ecephys_module = types.core.ProcessingModule(...
'description', 'extracellular electrophysiology');
 
ecephys_module.nwbdatainterface.set('LFP', lfp);
nwb.processing.set('ecephys', ecephys_module);

Decomposition of LFP Data into Frequency Bands

In some cases, you may want to further process the LFP data and decompose the signal into different frequency bands for additional downstream analyses. You can store the processed data from these spectral analyses using a DecompositionSeries object. This object allows you to include metadata about the frequency bands and metric used (e.g., power, phase, amplitude), as well as link the decomposed data to the original TimeSeries signal the data was derived from.
Note: When adding data to a DecompositionSeries, the data argument is assumed to be 3D where the first dimension is time, the second dimension is channels, and the third dimension is bands. In MatNWB, the data needs to be permuted because the dimensions are written to file in reverse order (See the dimensionMapNoDataPipes tutorial)
% Define the frequency bands of interest (in Hz):
band_names = {'theta'; 'beta'; 'gamma'};
band_limits = [[4.0, 12.0]; [12.0, 30.0]; [30.0, 80.0]];
 
% The bands should be added to the DecompositionSeries as a dynamic table
bands = table(band_names, band_limits, 'VariableNames', {'band_names', 'band_limits'})
bands = 3×2 table
 band_namesband_limits
12
1'theta'412
2'beta'1230
3'gamma'3080
bands = util.table2nwb( bands );
 
% Generate random phase data for the demonstration.
phase_data = randn(50, 12, numel(band_names)); % 50 samples, 12 channels, 3 frequency bands
phase_data = permute(phase_data, [3,2,1]); % See dimensionMapNoDataPipes tutorial
 
decomp_series = types.core.DecompositionSeries(...
'data', phase_data, ...
'bands', bands, ...
'metric', 'phase', ...
'starting_time', 0.0, ... % seconds
'starting_time_rate', 1000.0, ... % Hz
'source_channels', electrode_table_region, ...
'source_timeseries', lfp_electrical_series);
 
% Add decomposition series to ecephys module
ecephys_module.nwbdatainterface.set('theta', decomp_series);

Spike Times

Ragged Arrays

Spike times are stored in another DynamicTable of subtype Units. The default Units table is at /units in the HDF5 file. You can add columns to the Units table just like you did for electrodes and trials. Here, we generate some random spike data and populate the table.
num_cells = 10;
firing_rate = 20;
spikes = cell(1, num_cells);
for iShank = 1:num_cells
spikes{iShank} = rand(1, randi([16, 28]));
end
spikes
Spike times are an example of a ragged array- it's like a matrix, but each row has a different number of elements. We can represent this type of data as an indexed column of the units DynamicTable. These indexed columns have two components, the vector data object that holds the data and the vector index object that holds the indices in the vector that indicate the row breaks. You can use the convenience function util.create_indexed_column to create these objects.
[spike_times_vector, spike_times_index] = util.create_indexed_column(spikes);
 
nwb.units = types.core.Units( ...
'colnames', {'spike_times'}, ...
'description', 'units table', ...
'spike_times', spike_times_vector, ...
'spike_times_index', spike_times_index ...
);

Designating Electrophysiology Data

As mentioned above, ElectricalSeries objects are meant for storing specific types of extracellular recordings. In addition to this TimeSeries class, NWB provides some Processing Modules for designating the type of data you are storing. We will briefly discuss them here, and refer the reader to the API documentation and Intro to NWB for more details on using these objects.
For storing spike data, there are two options. Which one you choose depends on what data you have available. If you need to store complete and/or continuous raw voltage traces, you should store the traces with ElectricalSeries objects as acquisition data, and use the EventDetection class for identifying the spike events in your raw traces. If you do not want to store the raw voltage traces and only the waveform ‘snippets’ surrounding spike events, you should use the EventWaveform class, which can store one or more SpikeEventSeries objects.
The results of spike sorting (or clustering) should be stored in the top-level Units table. Note that it is not required to store spike waveforms in order to store spike events or mean waveforms–if you only want to store the spike times of clustered units you can use only the Units table.
For local field potential data, there are two options. Again, which one you choose depends on what data you have available. With both options, you should store your traces with ElectricalSeries objects. If you are storing unfiltered local field potential data, you should store the ElectricalSeries objects in LFP data interface object(s). If you have filtered LFP data, you should store the ElectricalSeries objects in FilteredEphys data interface object(s).

Writing the NWB File

nwbExport(nwb, 'ecephys_tutorial.nwb')

Reading NWB Data

Data arrays are read passively from the file. Calling TimeSeries.data does not read the data values, but presents an HDF5 object that can be indexed to read data. This allows you to conveniently work with datasets that are too large to fit in RAM all at once. load with no input arguments reads the entire dataset:
nwb2 = nwbRead('ecephys_tutorial.nwb', 'ignorecache');
nwb2.processing.get('ecephys'). ...
nwbdatainterface.get('LFP'). ...
electricalseries.get('ElectricalSeries'). ...
data.load;

Accessing Data Regions

If all you need is a data region, you can index a DataStub object like you would any normal array in MATLAB, as shown below. When indexing the dataset this way, only the selected region is read from disk into RAM. This allows you to handle very large datasets that would not fit entirely into RAM.
% read section of LFP
nwb2.processing.get('ecephys'). ...
nwbdatainterface.get('LFP'). ...
electricalseries.get('ElectricalSeries'). ...
data(1:5, 1:10)
 
% You can use the getRow method of the table to load spike times of a specific unit.
% To get the values, unpack from the returned table.
nwb.units.getRow(1).spike_times{1}

Learn more!

See the API documentation to learn what data types are available.

MATLAB tutorials

Python tutorials

See our tutorials for more details about your data type:
Check out other tutorials that teach advanced NWB topics: