diff --git a/docs/source/_static/html/tutorials/behavior.html b/docs/source/_static/html/tutorials/behavior.html index 14c85489..f53a16ba 100644 --- a/docs/source/_static/html/tutorials/behavior.html +++ b/docs/source/_static/html/tutorials/behavior.html @@ -43,7 +43,7 @@ .S7 { margin: 3px 10px 5px 4px; padding: 0px; line-height: 25px; min-height: 0px; white-space: pre-wrap; color: rgb(33, 33, 33); font-family: Helvetica, Arial, sans-serif, Helvetica, Arial, sans-serif; font-style: normal; font-size: 20px; font-weight: 700; text-align: left; } .S8 { border-left: 1px solid rgb(217, 217, 217); border-right: 1px solid rgb(217, 217, 217); border-top: 1px solid rgb(217, 217, 217); border-bottom: 1px solid rgb(217, 217, 217); border-radius: 4px; padding: 6px 45px 4px 13px; line-height: 18.004px; min-height: 0px; white-space: nowrap; color: rgb(33, 33, 33); font-family: Menlo, Monaco, Consolas, "Courier New", monospace, Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 14px; } .S9 { border-left: 1px solid rgb(217, 217, 217); border-right: 1px solid rgb(217, 217, 217); border-top: 0px none rgb(33, 33, 33); border-bottom: 1px solid rgb(217, 217, 217); border-radius: 0px 0px 4px 4px; padding: 0px 45px 4px 13px; line-height: 18.004px; min-height: 0px; white-space: nowrap; color: rgb(33, 33, 33); font-family: Menlo, Monaco, Consolas, "Courier New", monospace, Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 14px; } -.S10 { 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, Helvetica, Arial, sans-serif; font-style: normal; font-size: 17px; font-weight: 700; text-align: left; }

Behavior Data

This tutorial will guide you in writing behavioral data to NWB.

Creating an NWB File

Create an NWBFile object with the required fields (session_description, identifier, and session_start_time) and additional metadata.
nwb = NwbFile( ...
'session_description', 'mouse in open exploration',...
'identifier', 'Mouse5_Day3', ...
'session_start_time', datetime(2018, 4, 25, 2, 30, 3, 'TimeZone', 'local'), ...
'general_experimenter', 'My 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: +.S10 { 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, Helvetica, Arial, sans-serif; font-style: normal; font-size: 17px; font-weight: 700; text-align: left; }

Behavior Data

This tutorial will guide you in writing behavioral data to NWB.

Creating an NWB File

Create an NWBFile object with the required fields (session_description, identifier, and session_start_time) and additional metadata.
nwb = NwbFile( ...
'session_description', 'mouse in open exploration',...
'identifier', 'Mouse5_Day3', ...
'session_start_time', datetime(2018, 4, 25, 2, 30, 3, 'TimeZone', 'local'), ...
'general_experimenter', 'My 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.9.0' file_create_date: [] @@ -96,7 +96,7 @@ stimulus_presentation: [0×1 types.untyped.Set] stimulus_templates: [0×1 types.untyped.Set] units: [] -

SpatialSeries: Storing continuous spatial data

SpatialSeries is a subclass of TimeSeries that represents data in space, such as the spatial direction e.g., of gaze or travel or position of an animal over time.
Create data that corresponds to x, y position over time.
position_data = [linspace(0, 10, 50); linspace(0, 8, 50)]; % 2 x nT array
In SpatialSeries data, the first dimension is always time (in seconds), the second dimension represents the x, y position. However, as described in the dimensionMapNoDataPipes tutorial, when a MATLAB array is exported to HDF5, the array is transposed. Therefore, in order to correctly export the data, in MATLAB the last dimension of an array should be time. SpatialSeries data should be stored as one continuous stream as it is acquired, not by trials as is often reshaped for analysis. Data can be trial-aligned on-the-fly using the trials table. See the trials tutorial for further information.
For position data reference_frame indicates the zero-position, e.g. the 0,0 point might be the bottom-left corner of an enclosure, as viewed from the tracking camera.
timestamps = linspace(0, 50, 50)/ 200;
position_spatial_series = types.core.SpatialSeries( ...
'description', 'Postion (x, y) in an open field.', ...
'data', position_data, ...
'timestamps', timestamps, ...
'reference_frame', '(0,0) is the bottom left corner.' ...
)
position_spatial_series =
SpatialSeries with properties: +

SpatialSeries: Storing continuous spatial data

SpatialSeries is a subclass of TimeSeries that represents data in space, such as the spatial direction e.g., of gaze or travel or position of an animal over time.
Create data that corresponds to x, y position over time.
position_data = [linspace(0, 10, 50); linspace(0, 8, 50)]; % 2 x nT array
In SpatialSeries data, the first dimension is always time (in seconds), the second dimension represents the x, y position. However, as described in the dimensionMapNoDataPipes tutorial, when a MATLAB array is exported to HDF5, the array is transposed. Therefore, in order to correctly export the data, in MATLAB the last dimension of an array should be time. SpatialSeries data should be stored as one continuous stream as it is acquired, not by trials as is often reshaped for analysis. Data can be trial-aligned on-the-fly using the trials table. See the trials tutorial for further information.
For position data reference_frame indicates the zero-position, e.g. the 0,0 point might be the bottom-left corner of an enclosure, as viewed from the tracking camera.
timestamps = linspace(0, 50, 50)/ 200;
position_spatial_series = types.core.SpatialSeries( ...
'description', 'Postion (x, y) in an open field.', ...
'data', position_data, ...
'timestamps', timestamps, ...
'reference_frame', '(0,0) is the bottom left corner.' ...
)
position_spatial_series =
SpatialSeries with properties: reference_frame: '(0,0) is the bottom left corner.' starting_time_unit: 'seconds' @@ -115,7 +115,7 @@ starting_time: [] starting_time_rate: [] timestamps: [0 0.0051 0.0102 0.0153 0.0204 0.0255 0.0306 0.0357 0.0408 0.0459 0.0510 0.0561 0.0612 0.0663 0.0714 0.0765 0.0816 0.0867 0.0918 0.0969 0.1020 0.1071 0.1122 0.1173 0.1224 0.1276 0.1327 0.1378 0.1429 0.1480 0.1531 … ] (1×50 double) -

Position: Storing position measured over time

To help data analysis and visualization tools know that this SpatialSeries object represents the position of the subject, store the SpatialSeries object inside a Position object, which can hold one or more SpatialSeries objects.
position = types.core.Position();
position.spatialseries.set('SpatialSeries', position_spatial_series);

Create a Behavior Processing Module

Create a processing module called "behavior" for storing behavioral data in the NWBFile, then add the Position object to the processing module.
behavior_processing_module = types.core.ProcessingModule('description', 'stores behavioral data.');
behavior_processing_module.nwbdatainterface.set("Position", position);
nwb.processing.set("behavior", behavior_processing_module);

CompassDirection: Storing view angle measured over time

Analogous to how position can be stored, we can create a SpatialSeries object for representing the view angle of the subject.
For direction data reference_frame indicates the zero direction, for instance in this case "straight ahead" is 0 radians.
view_angle_data = linspace(0, 4, 50);
direction_spatial_series = types.core.SpatialSeries( ...
'description', 'View angle of the subject measured in radians.', ...
'data', view_angle_data, ...
'timestamps', timestamps, ...
'reference_frame', 'straight ahead', ...
'data_unit', 'radians' ...
);
direction = types.core.CompassDirection();
direction.spatialseries.set('spatial_series', direction_spatial_series);
We can add a CompassDirection object to the behavior processing module the same way we have added the position data.
behavior_processing_module.nwbdatainterface.set('CompassDirection', direction);

BehaviorTimeSeries: Storing continuous behavior data

BehavioralTimeSeries is an interface for storing continuous behavior data, such as the speed of a subject.
speed_data = linspace(0, 0.4, 50);
 
speed_time_series = types.core.TimeSeries( ...
'data', speed_data, ...
'starting_time', 1.0, ... % NB: Important to set starting_time when using starting_time_rate
'starting_time_rate', 10.0, ... % Hz
'description', 'he speed of the subject measured over time.', ...
'data_unit', 'm/s' ...
);
 
behavioral_time_series = types.core.BehavioralTimeSeries();
behavioral_time_series.timeseries.set('speed', speed_time_series);
 
% Add behavioral_time_series to the processing module
behavior_processing_module.nwbdatainterface.set('BehavioralTimeSeries', behavioral_time_series);

BehavioralEvents: Storing behavioral events

BehavioralEvents is an interface for storing behavioral events. We can use it for storing the timing and amount of rewards (e.g. water amount) or lever press times.
reward_amount = [1.0, 1.5, 1.0, 1.5];
event_timestamps = [1.0, 2.0, 5.0, 6.0];
 
time_series = types.core.TimeSeries( ...
'data', reward_amount, ...
'timestamps', event_timestamps, ...
'description', 'The water amount the subject received as a reward.', ...
'data_unit', 'ml' ...
);
 
behavioral_events = types.core.BehavioralEvents();
behavioral_events.timeseries.set('lever_presses', time_series);
 
% Add behavioral_events to the processing module
behavior_processing_module.nwbdatainterface.set('BehavioralEvents', behavioral_events);
Storing only the timestamps of the events is possible with the ndx-events NWB extension. You can also add labels associated with the events with this extension. You can find information about installation and example usage here.

BehavioralEpochs: Storing intervals of behavior data

BehavioralEpochs is for storing intervals of behavior data. BehavioralEpochs uses IntervalSeries to represent the time intervals. Create an IntervalSeries object that represents the time intervals when the animal was running. IntervalSeries uses 1 to indicate the beginning of an interval and -1 to indicate the end.
run_intervals = types.core.IntervalSeries( ...
'description', 'Intervals when the animal was running.', ...
'data', [1, -1, 1, -1, 1, -1], ...
'timestamps', [0.5, 1.5, 3.5, 4.0, 7.0, 7.3] ...
);
 
behavioral_epochs = types.core.BehavioralEpochs();
behavioral_epochs.intervalseries.set('running', run_intervals);
You can add more than one IntervalSeries to a BehavioralEpochs object.
sleep_intervals = types.core.IntervalSeries( ...
'description', 'Intervals when the animal was sleeping', ...
'data', [1, -1, 1, -1], ...
'timestamps', [15.0, 30.0, 60.0, 95.0] ...
);
behavioral_epochs.intervalseries.set('sleeping', sleep_intervals);
 
% Add behavioral_epochs to the processing module
behavior_processing_module.nwbdatainterface.set('BehavioralEpochs', behavioral_epochs);

Another approach: TimeIntervals

Using TimeIntervals to represent time intervals is often preferred over BehavioralEpochs and IntervalSeries. TimeIntervals is a subclass of DynamicTable, which offers flexibility for tabular data by allowing the addition of optional columns which are not defined in the standard DynamicTable class.
sleep_intervals = types.core.TimeIntervals( ...
'description', 'Intervals when the animal was sleeping.', ...
'colnames', {'start_time', 'stop_time', 'stage'} ...
);
 
sleep_intervals.addRow('start_time', 0.3, 'stop_time', 0.35, 'stage', 1);
sleep_intervals.addRow('start_time', 0.7, 'stop_time', 0.9, 'stage', 2);
sleep_intervals.addRow('start_time', 1.3, 'stop_time', 3.0, 'stage', 3);
 
nwb.intervals.set('sleep_intervals', sleep_intervals);

EyeTracking: Storing continuous eye-tracking data of gaze direction

EyeTracking is for storing eye-tracking data which represents direction of gaze as measured by an eye tracking algorithm. An EyeTracking object holds one or more SpatialSeries objects that represent the gaze direction over time extracted from a video.
eye_position_data = [linspace(-20, 30, 50); linspace(30, -20, 50)];
 
right_eye_position = types.core.SpatialSeries( ...
'description', 'The position of the right eye measured in degrees.', ...
'data', eye_position_data, ...
'starting_time', 1.0, ... % NB: Important to set starting_time when using starting_time_rate
'starting_time_rate', 50.0, ... % Hz
'reference_frame', '(0,0) is middle', ...
'data_unit', 'degrees' ...
);
 
left_eye_position = types.core.SpatialSeries( ...
'description', 'The position of the right eye measured in degrees.', ...
'data', eye_position_data, ...
'starting_time', 1.0, ... % NB: Important to set starting_time when using starting_time_rate
'starting_time_rate', 50.0, ... % Hz
'reference_frame', '(0,0) is middle', ...
'data_unit', 'degrees' ...
);
 
eye_tracking = types.core.EyeTracking();
eye_tracking.spatialseries.set('right_eye_position', right_eye_position);
eye_tracking.spatialseries.set('left_eye_position', left_eye_position);
 
behavior_processing_module.nwbdatainterface.set('EyeTracking', eye_tracking);

PupilTracking: Storing continuous eye-tracking data of pupil size

PupilTracking is for storing eye-tracking data which represents pupil size. PupilTracking holds one or more TimeSeries objects that can represent different features such as the dilation of the pupil measured over time by a pupil tracking algorithm.
pupil_diameter = types.core.TimeSeries( ...
'description', 'Pupil diameter extracted from the video of the right eye.', ...
'data', linspace(0.001, 0.002, 50), ...
'starting_time', 1.0, ... % NB: Important to set starting_time when using starting_time_rate
'starting_time_rate', 20.0, ... % Hz
'data_unit', 'meters' ...
);
 
pupil_tracking = types.core.PupilTracking();
pupil_tracking.timeseries.set('pupil_diameter', pupil_diameter);
 
behavior_processing_module.nwbdatainterface.set('PupilTracking', pupil_tracking);

Writing the behavior data to an NWB file

All of the above commands build an NWBFile object in-memory. To write this file, use nwbExport.
% Save to tutorials/tutorial_nwb_files folder
nwbFilePath = misc.getTutorialNwbFilePath('behavior_tutorial.nwb');
nwbExport(nwb, nwbFilePath);
fprintf('Exported NWB file to "%s"\n', 'behavior_tutorial.nwb')
Exported NWB file to "behavior_tutorial.nwb"
+

Position: Storing position measured over time

To help data analysis and visualization tools know that this SpatialSeries object represents the position of the subject, store the SpatialSeries object inside a Position object, which can hold one or more SpatialSeries objects.
position = types.core.Position();
position.add('SpatialSeries', position_spatial_series);

Create a Behavior Processing Module

Create a processing module called "behavior" for storing behavioral data in the NWBFile, then add the Position object to the processing module.
behavior_processing_module = types.core.ProcessingModule('description', 'stores behavioral data.');
behavior_processing_module.add("Position", position);
nwb.processing.add("behavior", behavior_processing_module);

CompassDirection: Storing view angle measured over time

Analogous to how position can be stored, we can create a SpatialSeries object for representing the view angle of the subject.
For direction data reference_frame indicates the zero direction, for instance in this case "straight ahead" is 0 radians.
view_angle_data = linspace(0, 4, 50);
direction_spatial_series = types.core.SpatialSeries( ...
'description', 'View angle of the subject measured in radians.', ...
'data', view_angle_data, ...
'timestamps', timestamps, ...
'reference_frame', 'straight ahead', ...
'data_unit', 'radians' ...
);
direction = types.core.CompassDirection();
direction.add('SpatialSeries', direction_spatial_series);
We can add a CompassDirection object to the behavior processing module the same way we have added the position data.
behavior_processing_module.add('CompassDirection', direction);

BehaviorTimeSeries: Storing continuous behavior data

BehavioralTimeSeries is an interface for storing continuous behavior data, such as the speed of a subject.
speed_data = linspace(0, 0.4, 50);
 
speed_time_series = types.core.TimeSeries( ...
'data', speed_data, ...
'starting_time', 1.0, ... % NB: Important to set starting_time when using starting_time_rate
'starting_time_rate', 10.0, ... % Hz
'description', 'he speed of the subject measured over time.', ...
'data_unit', 'm/s' ...
);
 
behavioral_time_series = types.core.BehavioralTimeSeries();
behavioral_time_series.add('speed', speed_time_series);
 
% Add behavioral_time_series to the processing module
behavior_processing_module.add('BehavioralTimeSeries', behavioral_time_series);

BehavioralEvents: Storing behavioral events

BehavioralEvents is an interface for storing behavioral events. We can use it for storing the timing and amount of rewards (e.g. water amount) or lever press times.
reward_amount = [1.0, 1.5, 1.0, 1.5];
event_timestamps = [1.0, 2.0, 5.0, 6.0];
 
time_series = types.core.TimeSeries( ...
'data', reward_amount, ...
'timestamps', event_timestamps, ...
'description', 'The water amount the subject received as a reward.', ...
'data_unit', 'ml' ...
);
 
behavioral_events = types.core.BehavioralEvents();
behavioral_events.add('lever_presses', time_series);
 
% Add behavioral_events to the processing module
behavior_processing_module.add('BehavioralEvents', behavioral_events);
Storing only the timestamps of the events is possible with the ndx-events NWB extension. You can also add labels associated with the events with this extension. You can find information about installation and example usage here.

BehavioralEpochs: Storing intervals of behavior data

BehavioralEpochs is for storing intervals of behavior data. BehavioralEpochs uses IntervalSeries to represent the time intervals. Create an IntervalSeries object that represents the time intervals when the animal was running. IntervalSeries uses 1 to indicate the beginning of an interval and -1 to indicate the end.
run_intervals = types.core.IntervalSeries( ...
'description', 'Intervals when the animal was running.', ...
'data', [1, -1, 1, -1, 1, -1], ...
'timestamps', [0.5, 1.5, 3.5, 4.0, 7.0, 7.3] ...
);
 
behavioral_epochs = types.core.BehavioralEpochs();
behavioral_epochs.add('running', run_intervals);
You can add more than one IntervalSeries to a BehavioralEpochs object.
sleep_intervals = types.core.IntervalSeries( ...
'description', 'Intervals when the animal was sleeping', ...
'data', [1, -1, 1, -1], ...
'timestamps', [15.0, 30.0, 60.0, 95.0] ...
);
behavioral_epochs.add('sleeping', sleep_intervals);
 
% Add behavioral_epochs to the processing module
behavior_processing_module.add('BehavioralEpochs', behavioral_epochs);

Another approach: TimeIntervals

Using TimeIntervals to represent time intervals is often preferred over BehavioralEpochs and IntervalSeries. TimeIntervals is a subclass of DynamicTable, which offers flexibility for tabular data by allowing the addition of optional columns which are not defined in the standard DynamicTable class.
sleep_intervals = types.core.TimeIntervals( ...
'description', 'Intervals when the animal was sleeping.', ...
'colnames', {'start_time', 'stop_time', 'stage'} ...
);
 
sleep_intervals.addRow('start_time', 0.3, 'stop_time', 0.35, 'stage', 1);
sleep_intervals.addRow('start_time', 0.7, 'stop_time', 0.9, 'stage', 2);
sleep_intervals.addRow('start_time', 1.3, 'stop_time', 3.0, 'stage', 3);
 
nwb.intervals.add('sleep_intervals', sleep_intervals);

EyeTracking: Storing continuous eye-tracking data of gaze direction

EyeTracking is for storing eye-tracking data which represents direction of gaze as measured by an eye tracking algorithm. An EyeTracking object holds one or more SpatialSeries objects that represent the gaze direction over time extracted from a video.
eye_position_data = [linspace(-20, 30, 50); linspace(30, -20, 50)];
 
right_eye_position = types.core.SpatialSeries( ...
'description', 'The position of the right eye measured in degrees.', ...
'data', eye_position_data, ...
'starting_time', 1.0, ... % NB: Important to set starting_time when using starting_time_rate
'starting_time_rate', 50.0, ... % Hz
'reference_frame', '(0,0) is middle', ...
'data_unit', 'degrees' ...
);
 
left_eye_position = types.core.SpatialSeries( ...
'description', 'The position of the right eye measured in degrees.', ...
'data', eye_position_data, ...
'starting_time', 1.0, ... % NB: Important to set starting_time when using starting_time_rate
'starting_time_rate', 50.0, ... % Hz
'reference_frame', '(0,0) is middle', ...
'data_unit', 'degrees' ...
);
 
eye_tracking = types.core.EyeTracking();
eye_tracking.add('right_eye_position', right_eye_position);
eye_tracking.add('left_eye_position', left_eye_position);
 
behavior_processing_module.add('EyeTracking', eye_tracking);

PupilTracking: Storing continuous eye-tracking data of pupil size

PupilTracking is for storing eye-tracking data which represents pupil size. PupilTracking holds one or more TimeSeries objects that can represent different features such as the dilation of the pupil measured over time by a pupil tracking algorithm.
pupil_diameter = types.core.TimeSeries( ...
'description', 'Pupil diameter extracted from the video of the right eye.', ...
'data', linspace(0.001, 0.002, 50), ...
'starting_time', 1.0, ... % NB: Important to set starting_time when using starting_time_rate
'starting_time_rate', 20.0, ... % Hz
'data_unit', 'meters' ...
);
 
pupil_tracking = types.core.PupilTracking();
pupil_tracking.add('pupil_diameter', pupil_diameter);
 
behavior_processing_module.add('PupilTracking', pupil_tracking);

Writing the behavior data to an NWB file

All of the above commands build an NWBFile object in-memory. To write this file, use nwbExport.
% Save to tutorials/tutorial_nwb_files folder
nwbFilePath = misc.getTutorialNwbFilePath('behavior_tutorial.nwb');
nwbExport(nwb, nwbFilePath);
fprintf('Exported NWB file to "%s"\n', 'behavior_tutorial.nwb')
Exported NWB file to "behavior_tutorial.nwb"

-
- - \ No newline at end of file +
+ \ No newline at end of file diff --git a/docs/source/_static/html/tutorials/read_demo_dandihub.html b/docs/source/_static/html/tutorials/read_demo_dandihub.html index b996fb9c..a74067c5 100644 --- a/docs/source/_static/html/tutorials/read_demo_dandihub.html +++ b/docs/source/_static/html/tutorials/read_demo_dandihub.html @@ -1,6 +1,5 @@ -Reading NWB Data in MATLAB (DandiHub edition) -

Reading NWB Data in MATLAB (DandiHub edition)

Authors: Ryan Ly, with modification by Lawrence Niu
DandiHub edition* authors: Thomas Kuenzel & Vijay Iyer
Last Updated: 2023-09-05
(*) minimally modified to utilize dandi package for download and to skip the MatNWB installation

Introduction

In this tutorial, we will read single neuron spiking data that is in the NWB standard format and do a basic visualization of the data. More thorough documentation regarding reading files as well as the NwbFile class, can be found in the NWB Overview Documentation
Table of Contents

Download the Dataset

Use the dandi Python package to download the dataset to the user-local dandisets folder. If you are running this livescript on DandiHub, the dandi package is already pre-installed. On a local environment, you need to install the dandi package in the python environment returned by running pyenv() in MATLAB.
environment = "DandiHub";
switch environment
case "DandiHub"
targetFolder = "/home/jovyan/dandisets/000004";
case "Local"
targetFolder = fullfile(userpath(), "dandisets", "000004");
end
py.dandi.download.download("dandi://dandi/000004/sub-P11HMH/", targetFolder, existing='overwrite')
PATH SIZE DONE DONE% CHECKSUM STATUS MESSAGE -sub-P11HMH/sub-P11HMH_ses-20061101_ecephys+image.nwb 72.6 MB error OSError
Error using download
Python Error: RuntimeError: Encountered 1 error while downloading.
Summary: 72.6 MB 0 Bytes 1 error 1 OSError - 0.00%

Read the NWB file

You can read any NWB file using nwbRead. You will find that the print out for this shows a summary of the data within.
nwb = nwbRead(fullfile(targetFolder, "sub-P11HMH", "sub-P11HMH_ses-20061101_ecephys+image.nwb"))

Stimulus

Now lets take a look at the visual stimuli presented to the subject. They will be in nwb.stimulus_presentation
nwb.stimulus_presentation
This results shows us that nwb.stimulus_presentation is a Set object that contains a single data object called StimulusPresentation, which is an OpticalSeries neurodata type. Use the get method to return this OpticalSeries. Set objects store a collection of other NWB objects.
nwb.stimulus_presentation.get('StimulusPresentation')
OpticalSeries is a neurodata type that stores information about visual stimuli presented to subjects. This print out shows all of the attributes in the OpticalSeries object named StimulusPresentation. The images are stored in StimulusPresentation.data
StimulusImageData = nwb.stimulus_presentation.get('StimulusPresentation').data
When calling a data object directly, the data is not read but instead a DataStub is returned. This is because data is read "lazily" in MatNWB. Instead of reading the entire dataset into memory, this provides a "window" into the data stored on disk that allows you to read only a section of the data. In this case, the last dimension indexes over images. You can index into any DataStub as you would any MATLAB matrix.
% get the image and display it
% the dimension order is provided as follows:
% [rgb, y, x, image index]
img = StimulusImageData(1:3, 1:300, 1:400, 32);
A bit of manipulation allows us to display the image using MATLAB's imshow.
img = permute(img,[3, 2, 1]); % fix orientation
img = flip(img, 3); % reverse color order
F = figure();
imshow(img, 'InitialMagnification', 'fit');
daspect([3, 5, 5]);
To read an entire dataset, use the DataStub.load method without any input arguments. We will use this approach to read all of the image display timestamps into memory.
stimulus_times = nwb.stimulus_presentation.get('StimulusPresentation').timestamps.load();

Quick PSTH and raster

Here, I will pull out spike times of a particular unit, align them to the image display times, and finally display the results.
First, let us show the first row of the NWB Units table representing the first unit.
nwb.units.getRow(1)
Let us specify some parameters for creating a cell array of spike times aligned to each stimulus time.
%% Align spikes by stimulus presentations
 
unit_ind =8;
before =1;
after =3;
getRow provides a convenient method for reading this data out.
unit_spikes = nwb.units.getRow(unit_ind, 'columns', {'spike_times'}).spike_times{1}
Spike times from this unit are aligned to each stimulus time and compiled in a cell array
results = cell(1, length(stimulus_times));
for itime = 1:length(stimulus_times)
stimulus_time = stimulus_times(itime);
spikes = unit_spikes - stimulus_time;
spikes = spikes(spikes > -before);
spikes = spikes(spikes < after);
results{itime} = spikes;
end

Plot results

Finally, here is a (slightly sloppy) peri-stimulus time histogram
figure();
hold on
for i = 1:length(results)
spikes = results{i};
yy = ones(length(spikes)) * i;
 
plot(spikes, yy, 'k.');
end
hold off
ylabel('trial');
xlabel('time (s)');
axis('tight')
figure();
all_spikes = cat(1, results{:});
histogram(all_spikes, 30);
ylabel('count')
xlabel('time (s)');
axis('tight')

Conclusion

This is an example of how to get started with understanding and analyzing public NWB datasets. This particular dataset was published with an extensive open analysis conducted in both MATLAB and Python, which you can find here. For more datasets, or to publish your own NWB data for free, check out the DANDI archive here.
+.S13 { border-left: 1px solid rgb(217, 217, 217); border-right: 1px solid rgb(217, 217, 217); border-top: 1px solid rgb(217, 217, 217); border-bottom: 1px solid rgb(217, 217, 217); border-radius: 4px; padding: 6px 45px 4px 13px; line-height: 18.004px; min-height: 0px; white-space: nowrap; color: rgb(33, 33, 33); font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 14px; } +.S14 { margin: 10px 10px 9px 4px; padding: 0px; line-height: 21px; min-height: 0px; white-space: pre-wrap; color: rgb(33, 33, 33); font-family: Helvetica, Arial, sans-serif; font-style: normal; font-size: 14px; font-weight: 400; text-align: left; } +.S15 { border-left: 1px solid rgb(217, 217, 217); border-right: 1px solid rgb(217, 217, 217); border-top: 0px none rgb(33, 33, 33); border-bottom: 1px solid rgb(217, 217, 217); border-radius: 0px 0px 4px 4px; padding: 0px 45px 4px 13px; line-height: 18.004px; min-height: 0px; white-space: nowrap; color: rgb(33, 33, 33); font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 14px; }

Reading NWB Data in MATLAB (DandiHub edition)

Authors: Ryan Ly, with modification by Lawrence Niu
DandiHub edition* authors: Thomas Kuenzel & Vijay Iyer
Last Updated: 2023-09-05
(*) minimally modified to utilize dandi package for download and to skip the MatNWB installation

Introduction

In this tutorial, we will read single neuron spiking data that is in the NWB standard format and do a basic visualization of the data. More thorough documentation regarding reading files as well as the NwbFile class, can be found in the NWB Overview Documentation

Download the Dataset

Use the dandi Python package to download the dataset to the user-local dandisets folder. If you are running this livescript on DandiHub, the dandi package is already pre-installed. On a local environment, you need to install the dandi package in the python environment returned by running pyenv() in MATLAB. For more details on using Python from MATLAB, please refer to the MATLAB documentation.
environment = "Local"; % Local or DandiHub
switch environment
case "DandiHub"
targetFolder = "/home/jovyan/dandisets/000004";
case "Local"
targetFolder = fullfile(userpath(), "dandisets", "000004");
end
py.dandi.download.download("dandi://dandi/000004/sub-P11HMH/", targetFolder, existing='overwrite')
PATH SIZE DONE DONE% CHECKSUM STATUS MESSAGE +sub-P11HMH/sub-P11HMH_ses-20061101_ecephys+image.nwb 72.6 MB 72.6 MB 100% ok done +Summary: 72.6 MB 72.6 MB 1 done + 100.00%

Read the NWB file

You can read any NWB file using nwbRead. You will find that the print out for this shows a summary of the data within.
nwb = nwbRead(fullfile(targetFolder, "sub-P11HMH", "sub-P11HMH_ses-20061101_ecephys+image.nwb"))
Error using nwbRead
The class 'types.core.DynamicTable' is not a superclass of class 'types.core.TimeIntervals', as required to invoke a superclass constructor or method.

Caused by:
This error typically occurs if NWB objects created with a different version are still loaded in memory. Try using `clear all` and run `nwbRead` again.

Stimulus

Now lets take a look at the visual stimuli presented to the subject. They will be in nwb.stimulus_presentation
nwb.stimulus_presentation
This result shows that nwb.stimulus_presentation is a Set object. In MatNWB, a Set object stores a collection of other NWB objects. In this case, the Set contains a single data object named StimulusPresentation, which is of the OpticalSeries neurodata type. You can use dot notation of the form Set.objectName to access this OpticalSeries:
nwb.stimulus_presentation.StimulusPresentation
OpticalSeries is a neurodata type that stores information about visual stimuli presented to subjects. This print out shows all of the attributes in the OpticalSeries object named StimulusPresentation. The images are stored in StimulusPresentation.data
StimulusImageData = nwb.stimulus_presentation.StimulusPresentation.data
When calling a data object directly, the data is not read but instead a DataStub is returned. This is because data is read "lazily" in MatNWB. Instead of reading the entire dataset into memory, this provides a "window" into the data stored on disk that allows you to read only a section of the data. In this case, the last dimension indexes over images. You can index into any DataStub as you would any MATLAB matrix.
% get the image and display it
% the dimension order is provided as follows:
% [rgb, y, x, image index]
img = StimulusImageData(1:3, 1:300, 1:400, 32);
A bit of manipulation allows us to display the image using MATLAB's imshow.
img = permute(img,[3, 2, 1]); % fix orientation
img = flip(img, 3); % reverse color order
F = figure();
imshow(img, 'InitialMagnification', 'fit');
daspect([3, 5, 5]);
To read an entire dataset, use the DataStub.load method without any input arguments. We will use this approach to read all of the image display timestamps into memory.
stimulus_times = nwb.stimulus_presentation.StimulusPresentation.timestamps.load();

Quick PSTH and raster

Here, I will pull out spike times of a particular unit, align them to the image display times, and finally display the results.
First, let us show the first row of the NWB Units table representing the first unit.
nwb.units.getRow(1)
Let us specify some parameters for creating a cell array of spike times aligned to each stimulus time.
%% Align spikes by stimulus presentations
 
unit_ind =8;
before =1;
after =3;
getRow provides a convenient method for reading this data out.
unit_spikes = nwb.units.getRow(unit_ind, 'columns', {'spike_times'}).spike_times{1}
Spike times from this unit are aligned to each stimulus time and compiled in a cell array
results = cell(1, length(stimulus_times));
for itime = 1:length(stimulus_times)
stimulus_time = stimulus_times(itime);
spikes = unit_spikes - stimulus_time;
spikes = spikes(spikes > -before);
spikes = spikes(spikes < after);
results{itime} = spikes;
end

Plot results

Finally, here is a (slightly sloppy) peri-stimulus time histogram
figure();
hold on
for i = 1:length(results)
spikes = results{i};
yy = ones(length(spikes)) * i;
 
plot(spikes, yy, 'k.');
end
hold off
ylabel('trial');
xlabel('time (s)');
axis('tight')
figure();
all_spikes = cat(1, results{:});
histogram(all_spikes, 30);
ylabel('count')
xlabel('time (s)');
axis('tight')

Conclusion

This is an example of how to get started with understanding and analyzing public NWB datasets. This particular dataset was published with an extensive open analysis conducted in both MATLAB and Python, which you can find here. For more datasets, or to publish your own NWB data for free, check out the DANDI archive here.

-
- - \ No newline at end of file +
+ \ No newline at end of file diff --git a/tutorials/behavior.mlx b/tutorials/behavior.mlx index e0ef01d0..5bced949 100644 Binary files a/tutorials/behavior.mlx and b/tutorials/behavior.mlx differ diff --git a/tutorials/intro.mlx b/tutorials/intro.mlx index 1c6718e7..6c4d7d13 100644 Binary files a/tutorials/intro.mlx and b/tutorials/intro.mlx differ diff --git a/tutorials/private/mcode/behavior.m b/tutorials/private/mcode/behavior.m index 9636b8e4..0b3012a7 100644 --- a/tutorials/private/mcode/behavior.m +++ b/tutorials/private/mcode/behavior.m @@ -52,15 +52,15 @@ % |SpatialSeries|> objects. position = types.core.Position(); -position.spatialseries.set('SpatialSeries', position_spatial_series); +position.add('SpatialSeries', position_spatial_series); %% Create a Behavior Processing Module % Create a processing module called "behavior" for storing behavioral data in % the NWBFile, then add the object to the processing module. behavior_processing_module = types.core.ProcessingModule('description', 'stores behavioral data.'); -behavior_processing_module.nwbdatainterface.set("Position", position); -nwb.processing.set("behavior", behavior_processing_module); +behavior_processing_module.add("Position", position); +nwb.processing.add("behavior", behavior_processing_module); %% CompassDirection: Storing view angle measured over time % Analogous to how position can be stored, we can create a object for representing the view angle of the subject. @@ -77,13 +77,13 @@ 'data_unit', 'radians' ... ); direction = types.core.CompassDirection(); -direction.spatialseries.set('spatial_series', direction_spatial_series); +direction.add('SpatialSeries', direction_spatial_series); %% % We can add a object to the behavior processing module the same way we % have added the position data. -behavior_processing_module.nwbdatainterface.set('CompassDirection', direction); +behavior_processing_module.add('CompassDirection', direction); %% BehaviorTimeSeries: Storing continuous behavior data % is an interface for storing continuous behavior data, @@ -100,10 +100,10 @@ ); behavioral_time_series = types.core.BehavioralTimeSeries(); -behavioral_time_series.timeseries.set('speed', speed_time_series); +behavioral_time_series.add('speed', speed_time_series); % Add behavioral_time_series to the processing module -behavior_processing_module.nwbdatainterface.set('BehavioralTimeSeries', behavioral_time_series); +behavior_processing_module.add('BehavioralTimeSeries', behavioral_time_series); %% BehavioralEvents: Storing behavioral events % is an interface for storing behavioral events. We can use @@ -121,10 +121,10 @@ ); behavioral_events = types.core.BehavioralEvents(); -behavioral_events.timeseries.set('lever_presses', time_series); +behavioral_events.add('lever_presses', time_series); % Add behavioral_events to the processing module -behavior_processing_module.nwbdatainterface.set('BehavioralEvents', behavioral_events); +behavior_processing_module.add('BehavioralEvents', behavioral_events); %% % Storing only the timestamps of the events is possible with the ndx-events % NWB extension. You can also add labels associated with the events with this @@ -146,7 +146,7 @@ ); behavioral_epochs = types.core.BehavioralEpochs(); -behavioral_epochs.intervalseries.set('running', run_intervals); +behavioral_epochs.add('running', run_intervals); %% % You can add more than one to a to represent time intervals is often preferred over is for storing eye-tracking data which represents direction of @@ -209,10 +209,10 @@ ); eye_tracking = types.core.EyeTracking(); -eye_tracking.spatialseries.set('right_eye_position', right_eye_position); -eye_tracking.spatialseries.set('left_eye_position', left_eye_position); +eye_tracking.add('right_eye_position', right_eye_position); +eye_tracking.add('left_eye_position', left_eye_position); -behavior_processing_module.nwbdatainterface.set('EyeTracking', eye_tracking); +behavior_processing_module.add('EyeTracking', eye_tracking); %% PupilTracking: Storing continuous eye-tracking data of pupil size % is for storing eye-tracking data which represents pupil size. @@ -230,9 +230,9 @@ ); pupil_tracking = types.core.PupilTracking(); -pupil_tracking.timeseries.set('pupil_diameter', pupil_diameter); +pupil_tracking.add('pupil_diameter', pupil_diameter); -behavior_processing_module.nwbdatainterface.set('PupilTracking', pupil_tracking); +behavior_processing_module.add('PupilTracking', pupil_tracking); %% Writing the behavior data to an NWB file % All of the above commands build an NWBFile object in-memory. To write this % file, use object which is a subclass of object contains our object named |'SpatialSeries'|. -read_spatial_series = read_nwbfile.processing.get('behavior'). ... - nwbdatainterface.get('Position').spatialseries.get('SpatialSeries') +read_spatial_series = read_nwbfile.processing.behavior.Position.SpatialSeries % Reading Data % Counter to normal MATLAB workflow, data arrays are read passively from the % file. Calling |*read_spatial_series.data*| does not read the data values, but diff --git a/tutorials/private/mcode/read_demo_dandihub.m b/tutorials/private/mcode/read_demo_dandihub.m index c712f16c..b7c15090 100644 --- a/tutorials/private/mcode/read_demo_dandihub.m +++ b/tutorials/private/mcode/read_demo_dandihub.m @@ -14,10 +14,14 @@ % %% Download the Dataset -% Use the pre-installed |dandi| Python package to download the dataset to the -% user-local dandisets folder: +% Use the |dandi| Python package to download the dataset to the user-local dandisets +% folder. If you are running this livescript on DandiHub, the |dandi| package +% is already pre-installed. On a local environment, you need to install the |dandi| +% package in the python environment returned by running |pyenv()| in MATLAB. For +% more details on using Python from MATLAB, please refer to the . -environment = "Local"; +environment = "Local"; % Local or DandiHub switch environment case "DandiHub" targetFolder = "/home/jovyan/dandisets/000004"; @@ -36,18 +40,19 @@ nwb.stimulus_presentation %% -% This results shows us that |nwb.stimulus_presentation| is a |Set| object that -% contains a single data object called |StimulusPresentation|, which is an |OpticalSeries| -% neurodata type. Use the |get| method to return this |OpticalSeries|. |Set| objects -% store a collection of other NWB objects. +% This result shows that |nwb.stimulus_presentation| is a |Set| object. In MatNWB, +% a |Set| object stores a collection of other NWB objects. In this case, the |Set| +% contains a single data object named |StimulusPresentation|, which is of the +% |OpticalSeries| neurodata type. You can use dot notation of the form |Set.objectName| +% to access this |OpticalSeries|: -nwb.stimulus_presentation.get('StimulusPresentation') +nwb.stimulus_presentation.StimulusPresentation %% % |OpticalSeries| is a neurodata type that stores information about visual stimuli % presented to subjects. This print out shows all of the attributes in the |OpticalSeries| % object named |StimulusPresentation|. The images are stored in |StimulusPresentation.data| -StimulusImageData = nwb.stimulus_presentation.get('StimulusPresentation').data +StimulusImageData = nwb.stimulus_presentation.StimulusPresentation.data %% % When calling a data object directly, the data is not read but instead a |DataStub| % is returned. This is because data is read "lazily" in MatNWB. Instead of reading @@ -73,7 +78,7 @@ % arguments. We will use this approach to read all of the image display timestamps % into memory. -stimulus_times = nwb.stimulus_presentation.get('StimulusPresentation').timestamps.load(); +stimulus_times = nwb.stimulus_presentation.StimulusPresentation.timestamps.load(); %% Quick PSTH and raster % Here, I will pull out spike times of a particular unit, align them to the % image display times, and finally display the results. diff --git a/tutorials/read_demo_dandihub.mlx b/tutorials/read_demo_dandihub.mlx index c6cfc3e2..781c3e3f 100644 Binary files a/tutorials/read_demo_dandihub.mlx and b/tutorials/read_demo_dandihub.mlx differ