From 508734b2cbaa3443511335870c0424d4272026ec Mon Sep 17 00:00:00 2001 From: ehennestad Date: Thu, 21 Nov 2024 20:55:42 +0100 Subject: [PATCH] Improve intro tutorial Remove duplicate code Use consistent variable naming --- tutorials/html/intro.html | 273 ++++++++++++++++++++++++++++---- tutorials/intro.mlx | Bin 218829 -> 221807 bytes tutorials/private/mcode/intro.m | 31 ++-- 3 files changed, 253 insertions(+), 51 deletions(-) diff --git a/tutorials/html/intro.html b/tutorials/html/intro.html index 84721cb4..b73348b8 100644 --- a/tutorials/html/intro.html +++ b/tutorials/html/intro.html @@ -9,24 +9,232 @@ .S6 { 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: 0px none rgb(33, 33, 33); border-radius: 4px 4px 0px 0px; padding: 6px 45px 0px 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; } .S7 { 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: 0px none rgb(33, 33, 33); border-radius: 0px; padding: 0px 45px 0px 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; } .S8 { 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; } -.S9 { margin: 2px 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: center; } -.S10 { margin: 10px 0px 20px; padding-left: 0px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; } -.S11 { margin-left: 56px; line-height: 21px; min-height: 0px; text-align: left; white-space: pre-wrap; } -.S12 { 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; } -.S13 { margin: 3px 10px 5px 4px; padding: 0px; line-height: 20px; min-height: 0px; white-space: pre-wrap; color: rgb(33, 33, 33); font-family: Helvetica, Arial, sans-serif; font-style: normal; font-size: 20px; font-weight: 700; text-align: left; } -.S14 { 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; } -.S15 { 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; }

Introduction to MatNWB

Installing MatNWB

Use the code below within the brackets to install MatNWB from source. MatNWB works by automatically creating API classes based on the schema.
%{
!git clone https://github.com/NeurodataWithoutBorders/matnwb.git
addpath(genpath(pwd));
%}

Set 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 using the NwbFile command. For all MatNWB classes and functions, we use the Matlab method of entering keyword argument pairs, where arguments are entered as name followed by value. Ellipses are used for clarity.
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', 'Last, First', ... % 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

Subject Information

You can also provide information about your subject in the NWB file. Create a Subject object to store information such as age, species, genotype, sex, and a freeform description. Then set nwb.general_subject to the Subject object.
Each of these fields is free-form, so any values will be valid, but here are our recommendations:
subject = types.core.Subject( ...
'subject_id', '001', ...
'age', 'P90D', ...
'description', 'mouse 5', ...
'species', 'Mus musculus', ...
'sex', 'M' ...
);
nwb.general_subject = subject;
 
subject
Note: the DANDI archive requires all NWB files to have a subject object with subject_id specified, and strongly encourages specifying the other fields.

Time Series Data

TimeSeries is a common base class for measurements sampled over time, and provides fields for data and timestamps (regularly or irregularly sampled). You will also need to supply the name and unit of measurement (SI unit).
For instance, we can store a TimeSeries data where recording started 0.0 seconds after start_time and sampled every second (1 Hz):
time_series_with_rate = types.core.TimeSeries( ...
'description', 'an example time series', ...
'data', linspace(0, 100, 10), ...
'data_unit', 'm', ...
'starting_time', 0.0, ...
'starting_time_rate', 1.0);
For irregularly sampled recordings, we need to provide the timestamps for the data:
time_series_with_timestamps = types.core.TimeSeries( ...
'description', 'an example time series', ...
'data', linspace(0, 100, 10), ...
'data_unit', 'm', ...
'timestamps', linspace(0, 1, 10));
The TimeSeries class serves as the foundation for all other time series types in the NWB format. Several specialized subclasses extend the functionality of TimeSeries, each tailored to handle specific kinds of data. In the next section, we’ll explore one of these specialized types. For a full overview, please check out the type hierarchy in the NWB schema documentation.

Other Types of Time Series

As mentioned previously, there are many subtypes of TimeSeries in MatNWB that are used to store different kinds of data. One example is AnnotationSeries, a subclass of TimeSeries that stores text-based records about the experiment. Similar to our TimeSeries example above, we can create an AnnotationSeries object with text information about a stimulus and add it to the stimulus_presentation group in the NWBFile. Below is an example where we create an AnnotationSeries object with annotations for airpuff stimuli and add it to the NWBFile.
% Create an AnnotationSeries object with annotations for airpuff stimuli
annotations = types.core.AnnotationSeries( ...
'description', 'Airpuff events delivered to the animal', ...
'data', {'Left Airpuff', 'Right Airpuff', 'Right Airpuff'}, ...
'timestamps', [1.0, 3.0, 8.0] ...
);
 
% Add the AnnotationSeries to the NWBFile's stimulus group
nwb.stimulus_presentation.set('Airpuffs', annotations)

Behavior

SpatialSeries and Position

Many types of data have special data types in NWB. To store the spatial position of a subject, we will use the SpatialSeries and Position classes.
Note: These diagrams follow a standard convention called "UML class diagram" to express the object-oriented relationships between NWB classes. For our purposes, all you need to know is that an open triangle means "extends" and an open diamond means "is contained within." Learn more about class diagrams on the wikipedia page.
SpatialSeries is a subclass of TimeSeries, a common base class for measurements sampled over time, and provides fields for data and time (regularly or irregularly sampled). Here, we put a SpatialSeries object called 'SpatialSeries' in a Position object. If the data is sampled at a regular interval, it is recommended to specify the starting_time and the sampling rate (starting_time_rate), although it is still possible to specify timestamps as in the time_series_with_timestamps example above.
% create SpatialSeries object
spatial_series_ts = types.core.SpatialSeries( ...
'data', [linspace(0,10,100); linspace(0,8,100)], ...
'reference_frame', '(0,0) is bottom left corner', ...
'starting_time', 0, ...
'starting_time_rate', 200 ...
);
 
% create Position object and add SpatialSeries
Position = types.core.Position('SpatialSeries', spatial_series_ts);
 
% create processing module
behavior_mod = types.core.ProcessingModule('description', 'contains behavioral data');
 
% add the Position object (that holds the SpatialSeries object)
behavior_mod.nwbdatainterface.set('Position', Position);
NWB differentiates between raw, acquired data, which should never change, and processed data, which are the results of preprocessing algorithms and could change. Let's assume that the animal's position was computed from a video tracking algorithm, so it would be classified as processed data. Since processed data can be very diverse, NWB allows us to create processing modules, which are like folders, to store related processed data or data that comes from a single algorithm.
Create a processing module called "behavior" for storing behavioral data in the NWBFile and add the Position object to the module.
% create processing module
behavior_mod = types.core.ProcessingModule('description', 'contains behavioral data');
 
% add the Position object (that holds the SpatialSeries object) to the
% module and name the Position object "Position"
behavior_mod.nwbdatainterface.set('Position', Position);
 
% add the processing module to the NWBFile object, and name the processing module "behavior"
nwb.processing.set('behavior', behavior_mod);

Trials

Trials are stored in a TimeIntervals object which is a subclass of DynamicTable. DynamicTable objects are used to store tabular metadata throughout NWB, including for trials, electrodes, and sorted units. They offer flexibility for tabular data by allowing required columns, optional columns, and custom columns.
The trials DynamicTable can be thought of as a table with this structure:
Trials are stored in a TimeIntervals object which subclasses DynamicTable. Here, we are adding 'correct', which will be a logical array.
trials = types.core.TimeIntervals( ...
'colnames', {'start_time', 'stop_time', 'correct'}, ...
'description', 'trial data and properties');
 
trials.addRow('start_time', 0.1, 'stop_time', 1.0, 'correct', false)
trials.addRow('start_time', 1.5, 'stop_time', 2.0, 'correct', true)
trials.addRow('start_time', 2.5, 'stop_time', 3.0, 'correct', false)
 
trials.toTable() % visualize the table
nwb.intervals_trials = trials;
 
% If you have multiple trials tables, you will need to use custom names for
% each one:
nwb.intervals.set('custom_intervals_table_name', trials);

Write

Now, to write the NWB file that we have built so far:
nwbExport(nwb, 'intro_tutorial.nwb')
We can use the HDFView application to inspect the resulting NWB file.

Read

We can then read the file back in using MatNWB and inspect its contents.
read_nwbfile = nwbRead('intro_tutorial.nwb', 'ignorecache')
We can print the SpatialSeries data traversing the hierarchy of objects. The processing module called 'behavior' contains our Position object named 'Position'. The Position object contains our SpatialSeries object named 'SpatialSeries'.
read_spatial_series = read_nwbfile.processing.get('behavior'). ...
nwbdatainterface.get('Position').spatialseries.get('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 presents a DataStub object that can be indexed to read data.
read_spatial_series.data
This allows you to conveniently work with datasets that are too large to fit in RAM all at once. Access all the data in the matrix using the load method with no arguments.
read_spatial_series.data.load
If you only need a section of the data, you can read only that section by indexing the DataStub object like a normal array in MATLAB. This will just read the selected region from disk into RAM. This technique is particularly useful if you are dealing with a large dataset that is too big to fit entirely into your available RAM.
read_spatial_series.data(:, 1:10)

Next Steps

This concludes the introductory tutorial. Please proceed to one of the specialized tutorials, which are designed to follow this one.
See the API documentation to learn what data types are available.
+.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; 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; } +.S10 { color: rgb(33, 33, 33); padding: 10px 0px 6px 17px; background: rgb(255, 255, 255) none repeat scroll 0% 0% / auto padding-box border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 14px; overflow-x: hidden; line-height: 17.234px; } +/* Styling that is common to warnings and errors is in diagnosticOutput.css */.embeddedOutputsErrorElement { min-height: 18px; max-height: 550px;} +.embeddedOutputsErrorElement .diagnosticMessage-errorType { overflow: auto;} +.embeddedOutputsErrorElement.inlineElement {} +.embeddedOutputsErrorElement.rightPaneElement {} +/* Styling that is common to warnings and errors is in diagnosticOutput.css */.embeddedOutputsWarningElement { min-height: 18px; max-height: 550px;} +.embeddedOutputsWarningElement .diagnosticMessage-warningType { overflow: auto;} +.embeddedOutputsWarningElement.inlineElement {} +.embeddedOutputsWarningElement.rightPaneElement {} +/* Copyright 2015-2023 The MathWorks, Inc. *//* In this file, styles are not scoped to rtcContainer since they could be in the Dojo Tooltip */.diagnosticMessage-wrapper { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 12px;} +.diagnosticMessage-wrapper.diagnosticMessage-warningType { /*This fallback value will be used for appdesigner warnings*/ color: var(--rtc-warning-output-color, var(--mw-color-matlabWarning));} +.diagnosticMessage-wrapper.diagnosticMessage-warningType a { /*This fallback value will be used for appdesigner warnings*/ color: var(--rtc-warning-output-color, var(--mw-color-matlabWarning)); text-decoration: underline;} +.rtcThemeDefaultOverride .diagnosticMessage-wrapper.diagnosticMessage-warningType,.rtcThemeDefaultOverride .diagnosticMessage-wrapper.diagnosticMessage-warningType a { color: var(--mw-color-matlabWarning) !important;} +.diagnosticMessage-wrapper.diagnosticMessage-errorType { /*This fallback value will be used in appdesigner error tooltip text*/ color: var(--rtc-error-output-color, var(--mw-color-matlabErrors));} +.diagnosticMessage-wrapper.diagnosticMessage-errorType a { /*This fallback value will be used in appdesigner error tooltip text*/ color: var(--rtc-error-output-color, var(--mw-color-matlabErrors)); text-decoration: underline;} +.rtcThemeDefaultOverride .diagnosticMessage-wrapper.diagnosticMessage-errorType,.rtcThemeDefaultOverride .diagnosticMessage-wrapper.diagnosticMessage-errorType a { color: var(--mw-color-matlabErrors) !important;} +.diagnosticMessage-wrapper .diagnosticMessage-messagePart,.diagnosticMessage-wrapper .diagnosticMessage-causePart { white-space: pre-wrap;} +.diagnosticMessage-wrapper .diagnosticMessage-stackPart { white-space: pre;} +.embeddedOutputsTextElement,.embeddedOutputsVariableStringElement { white-space: pre; word-wrap: initial; min-height: 18px; max-height: 550px;} +.embeddedOutputsTextElement .textElement,.embeddedOutputsVariableStringElement .textElement { overflow: auto;} +.textElement,.rtcDataTipElement .textElement { padding-top: 2px;} +.embeddedOutputsTextElement.inlineElement,.embeddedOutputsVariableStringElement.inlineElement {} +.inlineElement .textElement {} +.embeddedOutputsTextElement.rightPaneElement,.embeddedOutputsVariableStringElement.rightPaneElement { min-height: 16px;} +.rightPaneElement .textElement { padding-top: 2px; padding-left: 9px;} +.S11 { margin: 2px 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: center; } +.S12 { margin: 10px 0px 20px; padding-left: 0px; font-family: Helvetica, Arial, sans-serif; font-size: 14px; } +.S13 { margin-left: 56px; line-height: 21px; min-height: 0px; text-align: left; white-space: pre-wrap; } +.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 { margin: 3px 10px 5px 4px; padding: 0px; line-height: 20px; min-height: 0px; white-space: pre-wrap; color: rgb(33, 33, 33); font-family: Helvetica, Arial, sans-serif; font-style: normal; font-size: 20px; font-weight: 700; text-align: left; } +.S16 { 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; } +.embeddedOutputsVariableTableElement .ClientViewDiv table tr { height: 22px; white-space: nowrap;} +.embeddedOutputsVariableTableElement .ClientViewDiv table tr td,.embeddedOutputsVariableTableElement .ClientViewDiv table tr th { background-color:white; text-overflow: ellipsis; font-family: 'Arial', sans-serif; font-size: 12px; overflow : hidden;} +.embeddedOutputsVariableTableElement .ClientViewDiv table tr span { text-overflow: ellipsis; padding: 3px;} +.embeddedOutputsVariableTableElement .ClientViewDiv table tr th { color: rgba(0,0,0,0.5); padding: 3px; font-size: 9px;} +/* ClientDocument has a summary bar child that takes up 17px, this clashes with overflow on the view which allots space for scrollbars. On print preview, this causes headers from to overlap on subsequent pages. Displaying Document as flex renders summarybar and view in column format and fixes the issue g2788485 */.embeddedOutputsVariableTableElement .ClientDocument { display: flex; flex-direction: column;} +.S17 { 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: 0px none rgb(33, 33, 33); border-radius: 0px; padding: 6px 45px 0px 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; } +.S18 { 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; } +.S19 { 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 4px 0px 0px; 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; } +.variableValue { width: 100% !important; } +.embeddedOutputsMatrixElement,.eoOutputWrapper .matrixElement { min-height: 18px; box-sizing: border-box;} +.embeddedOutputsMatrixElement .matrixElement,.eoOutputWrapper .matrixElement,.rtcDataTipElement .matrixElement { position: relative;} +.matrixElement .variableValue,.rtcDataTipElement .matrixElement .variableValue { white-space: pre; display: inline-block; vertical-align: top; overflow: hidden;} +.embeddedOutputsMatrixElement.inlineElement {} +.embeddedOutputsMatrixElement.inlineElement .topHeaderWrapper { display: none;} +.embeddedOutputsMatrixElement.inlineElement .veTable .body { padding-top: 0 !important; max-height: 100px;} +.inlineElement .matrixElement { max-height: 300px;} +.embeddedOutputsMatrixElement.rightPaneElement {} +.rightPaneElement .matrixElement,.rtcDataTipElement .matrixElement { overflow: hidden; padding-left: 9px;} +.rightPaneElement .matrixElement { margin-bottom: -1px;} +.embeddedOutputsMatrixElement .matrixElement .valueContainer,.eoOutputWrapper .matrixElement .valueContainer,.rtcDataTipElement .matrixElement .valueContainer { white-space: nowrap; margin-bottom: 3px;} +.embeddedOutputsMatrixElement .matrixElement .valueContainer .horizontalEllipsis.hide,.embeddedOutputsMatrixElement .matrixElement .verticalEllipsis.hide,.eoOutputWrapper .matrixElement .valueContainer .horizontalEllipsis.hide,.eoOutputWrapper .matrixElement .verticalEllipsis.hide,.rtcDataTipElement .matrixElement .valueContainer .horizontalEllipsis.hide,.rtcDataTipElement .matrixElement .verticalEllipsis.hide { display: none;} +.embeddedOutputsVariableMatrixElement .matrixElement .valueContainer.hideEllipses .verticalEllipsis, .embeddedOutputsVariableMatrixElement .matrixElement .valueContainer.hideEllipses .horizontalEllipsis { display:none;} +.embeddedOutputsMatrixElement .matrixElement .valueContainer .horizontalEllipsis,.eoOutputWrapper .matrixElement .valueContainer .horizontalEllipsis { margin-bottom: -3px;} +.eoOutputWrapper .embeddedOutputsVariableMatrixElement .matrixElement .valueContainer { cursor: default !important;} +.embeddedOutputsVariableElement { white-space: pre-wrap; word-wrap: break-word; min-height: 18px; max-height: 250px; overflow: auto;} +.eoOutputWrapper .variableElement { padding-top: 2px;} +.embeddedOutputsVariableElement.inlineElement {} +.inlineElement .variableElement {} +.embeddedOutputsVariableElement.rightPaneElement { min-height: 16px;} +.rightPaneElement .variableElement { padding-left: 9px;} +.outputsOnRight .embeddedOutputsVariableElement.rightPaneElement .eoOutputContent { /* Remove extra space allocated for navigation border */ margin-top: 0; margin-bottom: 0;} +.variableNameElement { margin-bottom: 3px; display: inline-block;} +/* * Ellipses as base64 for HTML export. */.matrixElement .horizontalEllipsis,.rtcDataTipElement .matrixElement .horizontalEllipsis { display: inline-block; margin-top: 3px; /* base64 encoded version of images-liveeditor/HEllipsis.png */ width: 30px; height: 12px; background-repeat: no-repeat; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAJCAYAAADO1CeCAAAAJUlEQVR42mP4//8/A70xw0i29BUDFPxnAEtTW37wWDqakIa4pQDvOOG89lHX2gAAAABJRU5ErkJggg==");} +.matrixElement .verticalEllipsis,.textElement .verticalEllipsis,.rtcDataTipElement .matrixElement .verticalEllipsis,.rtcDataTipElement .textElement .verticalEllipsis { margin-left: 35px; /* base64 encoded version of images-liveeditor/VEllipsis.png */ width: 12px; height: 30px; background-repeat: no-repeat; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAZCAYAAAAIcL+IAAAALklEQVR42mP4//8/AzGYgWyFMECMwv8QddRS+P//KyimlmcGUOFoOI6GI/UVAgDnd8Dd4+NCwgAAAABJRU5ErkJggg==");}

Introduction to MatNWB

Installing MatNWB

Use the code below within the brackets to install MatNWB from source. MatNWB works by automatically creating API classes based on the schema.
%{
!git clone https://github.com/NeurodataWithoutBorders/matnwb.git
addpath(genpath(pwd));
%}

Set 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 using the NwbFile command. For all MatNWB classes and functions, we use the Matlab method of entering keyword argument pairs, where arguments are entered as name followed by value. Ellipses are used for clarity.
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', 'Last, First', ... % 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.7.0' + file_create_date: [] + identifier: 'Mouse5_Day3' + session_description: 'mouse in open exploration' + session_start_time: {[2018-04-25T02:30:03.000000+02:00]} + timestamps_reference_time: [] + acquisition: [0×1 types.untyped.Set] + analysis: [0×1 types.untyped.Set] + general: [0×1 types.untyped.Set] + general_data_collection: '' + general_devices: [0×1 types.untyped.Set] + general_experiment_description: '' + general_experimenter: 'Last, First' + general_extracellular_ephys: [0×1 types.untyped.Set] + general_extracellular_ephys_electrodes: [] + general_institution: 'University of My Institution' + general_intracellular_ephys: [0×1 types.untyped.Set] + general_intracellular_ephys_experimental_conditions: [] + general_intracellular_ephys_filtering: '' + general_intracellular_ephys_intracellular_recordings: [] + general_intracellular_ephys_repetitions: [] + general_intracellular_ephys_sequential_recordings: [] + general_intracellular_ephys_simultaneous_recordings: [] + general_intracellular_ephys_sweep_table: [] + general_keywords: '' + general_lab: '' + general_notes: '' + general_optogenetics: [0×1 types.untyped.Set] + general_optophysiology: [0×1 types.untyped.Set] + general_pharmacology: '' + general_protocol: '' + general_related_publications: {'DOI:10.1016/j.neuron.2016.12.011'} + general_session_id: 'session_1234' + general_slices: '' + general_source_script: '' + general_source_script_file_name: '' + general_stimulus: '' + general_subject: [] + general_surgery: '' + general_virus: '' + intervals: [0×1 types.untyped.Set] + intervals_epochs: [] + intervals_invalid_times: [] + intervals_trials: [] + processing: [0×1 types.untyped.Set] + scratch: [0×1 types.untyped.Set] + stimulus_presentation: [0×1 types.untyped.Set] + stimulus_templates: [0×1 types.untyped.Set] + units: [] + +Warning: The following required properties are missing for instance for type "NwbFile": + timestamps_reference_time

Subject Information

You can also provide information about your subject in the NWB file. Create a Subject object to store information such as age, species, genotype, sex, and a freeform description. Then set nwb.general_subject to the Subject object.
Each of these fields is free-form, so any values will be valid, but here are our recommendations:
  • For age, we recommend using the ISO 8601 Duration format
  • For species, we recommend using the formal latin binomal name (e.g. mouse -> Mus musculus, human -> Homo sapiens)
  • For sex, we recommend using F (female), M (male), U (unknown), and O (other)
subject = types.core.Subject( ...
'subject_id', '001', ...
'age', 'P90D', ...
'description', 'mouse 5', ...
'species', 'Mus musculus', ...
'sex', 'M' ...
);
nwb.general_subject = subject;
 
subject
subject =
Subject with properties: + + age: 'P90D' + age_reference: 'birth' + date_of_birth: [] + description: 'mouse 5' + genotype: '' + sex: 'M' + species: 'Mus musculus' + strain: '' + subject_id: '001' + weight: '' +
Note: the DANDI archive requires all NWB files to have a subject object with subject_id specified, and strongly encourages specifying the other fields.

Time Series Data

TimeSeries is a common base class for measurements sampled over time, and provides fields for data and timestamps (regularly or irregularly sampled). You will also need to supply the name and unit of measurement (SI unit).
For instance, we can store a TimeSeries data where recording started 0.0 seconds after start_time and sampled every second (1 Hz):
time_series_with_rate = types.core.TimeSeries( ...
'description', 'an example time series', ...
'data', linspace(0, 100, 10), ...
'data_unit', 'm', ...
'starting_time', 0.0, ...
'starting_time_rate', 1.0);
For irregularly sampled recordings, we need to provide the timestamps for the data:
time_series_with_timestamps = types.core.TimeSeries( ...
'description', 'an example time series', ...
'data', linspace(0, 100, 10), ...
'data_unit', 'm', ...
'timestamps', linspace(0, 1, 10));
The TimeSeries class serves as the foundation for all other time series types in the NWB format. Several specialized subclasses extend the functionality of TimeSeries, each tailored to handle specific kinds of data. In the next section, we’ll explore one of these specialized types. For a full overview, please check out the type hierarchy in the NWB schema documentation.

Other Types of Time Series

As mentioned previously, there are many subtypes of TimeSeries in MatNWB that are used to store different kinds of data. One example is AnnotationSeries, a subclass of TimeSeries that stores text-based records about the experiment. Similar to our TimeSeries example above, we can create an AnnotationSeries object with text information about a stimulus and add it to the stimulus_presentation group in the NWBFile. Below is an example where we create an AnnotationSeries object with annotations for airpuff stimuli and add it to the NWBFile.
% Create an AnnotationSeries object with annotations for airpuff stimuli
annotations = types.core.AnnotationSeries( ...
'description', 'Airpuff events delivered to the animal', ...
'data', {'Left Airpuff', 'Right Airpuff', 'Right Airpuff'}, ...
'timestamps', [1.0, 3.0, 8.0] ...
);
 
% Add the AnnotationSeries to the NWBFile's stimulus group
nwb.stimulus_presentation.set('Airpuffs', annotations)
ans =
Set with properties: + + Airpuffs: [types.core.AnnotationSeries] +

Behavior

SpatialSeries and Position

Many types of data have special data types in NWB. To store the spatial position of a subject, we will use the SpatialSeries and Position classes.
Note: These diagrams follow a standard convention called "UML class diagram" to express the object-oriented relationships between NWB classes. For our purposes, all you need to know is that an open triangle means "extends" (i.e., is a specialized subtype of), and an open diamond means "is contained within." Learn more about class diagrams on the wikipedia page.
SpatialSeries is a subclass of TimeSeries, a common base class for measurements sampled over time, and provides fields for data and time (regularly or irregularly sampled). Here, we put a SpatialSeries object called 'SpatialSeries' in a Position object. If the data is sampled at a regular interval, it is recommended to specify the starting_time and the sampling rate (starting_time_rate), although it is still possible to specify timestamps as in the time_series_with_timestamps example above.
% create SpatialSeries object
spatial_series_ts = types.core.SpatialSeries( ...
'data', [linspace(0,10,100); linspace(0,8,100)], ...
'reference_frame', '(0,0) is bottom left corner', ...
'starting_time', 0, ...
'starting_time_rate', 200 ...
);
 
% create Position object and add SpatialSeries
position = types.core.Position('SpatialSeries', spatial_series_ts);
NWB differentiates between raw, acquired data, which should never change, and processed data, which are the results of preprocessing algorithms and could change. Let's assume that the animal's position was computed from a video tracking algorithm, so it would be classified as processed data. Since processed data can be very diverse, NWB allows us to create processing modules, which are like folders, to store related processed data or data that comes from a single algorithm.
Create a processing module called "behavior" for storing behavioral data in the NWBFile and add the Position object to the module.
% create processing module
behavior_module = types.core.ProcessingModule('description', 'contains behavioral data');
 
% add the Position object (that holds the SpatialSeries object) to the module
% and name the Position object "Position"
behavior_module.nwbdatainterface.set('Position', position);
 
% add the processing module to the NWBFile object, and name the processing module "behavior"
nwb.processing.set('behavior', behavior_module);

Trials

Trials are stored in a TimeIntervals object which is a subclass of DynamicTable. DynamicTable objects are used to store tabular metadata throughout NWB, including for trials, electrodes, and sorted units. They offer flexibility for tabular data by allowing required columns, optional columns, and custom columns.
The trials DynamicTable can be thought of as a table with this structure:
Trials are stored in a TimeIntervals object which subclasses DynamicTable. Here, we are adding 'correct', which will be a logical array.
trials = types.core.TimeIntervals( ...
'colnames', {'start_time', 'stop_time', 'correct'}, ...
'description', 'trial data and properties');
 
trials.addRow('start_time', 0.1, 'stop_time', 1.0, 'correct', false)
trials.addRow('start_time', 1.5, 'stop_time', 2.0, 'correct', true)
trials.addRow('start_time', 2.5, 'stop_time', 3.0, 'correct', false)
 
trials.toTable() % visualize the table
ans = 3×4 table
 idstart_timestop_timecorrect
100.100010
211.500021
322.500030
nwb.intervals_trials = trials;
 
% If you have multiple trials tables, you will need to use custom names for
% each one:
nwb.intervals.set('custom_intervals_table_name', trials);

Write

Now, to write the NWB file that we have built so far:
nwbExport(nwb, 'intro_tutorial.nwb')
We can use the HDFView application to inspect the resulting NWB file.

Read

We can then read the file back in using MatNWB and inspect its contents.
read_nwbfile = nwbRead('intro_tutorial.nwb', 'ignorecache')
read_nwbfile =
NwbFile with properties: + + nwb_version: '2.7.0' + file_create_date: [1×1 types.untyped.DataStub] + identifier: 'Mouse5_Day3' + session_description: 'mouse in open exploration' + session_start_time: [1×1 types.untyped.DataStub] + timestamps_reference_time: [1×1 types.untyped.DataStub] + acquisition: [0×1 types.untyped.Set] + analysis: [0×1 types.untyped.Set] + general: [0×1 types.untyped.Set] + general_data_collection: '' + general_devices: [0×1 types.untyped.Set] + general_experiment_description: '' + general_experimenter: [1×1 types.untyped.DataStub] + general_extracellular_ephys: [0×1 types.untyped.Set] + general_extracellular_ephys_electrodes: [] + general_institution: 'University of My Institution' + general_intracellular_ephys: [0×1 types.untyped.Set] + general_intracellular_ephys_experimental_conditions: [] + general_intracellular_ephys_filtering: '' + general_intracellular_ephys_intracellular_recordings: [] + general_intracellular_ephys_repetitions: [] + general_intracellular_ephys_sequential_recordings: [] + general_intracellular_ephys_simultaneous_recordings: [] + general_intracellular_ephys_sweep_table: [] + general_keywords: '' + general_lab: '' + general_notes: '' + general_optogenetics: [0×1 types.untyped.Set] + general_optophysiology: [0×1 types.untyped.Set] + general_pharmacology: '' + general_protocol: '' + general_related_publications: [1×1 types.untyped.DataStub] + general_session_id: 'session_1234' + general_slices: '' + general_source_script: '' + general_source_script_file_name: '' + general_stimulus: '' + general_subject: [1×1 types.core.Subject] + general_surgery: '' + general_virus: '' + intervals: [1×1 types.untyped.Set] + intervals_epochs: [] + intervals_invalid_times: [] + intervals_trials: [1×1 types.core.TimeIntervals] + processing: [1×1 types.untyped.Set] + scratch: [0×1 types.untyped.Set] + stimulus_presentation: [1×1 types.untyped.Set] + stimulus_templates: [0×1 types.untyped.Set] + units: [] +
We can print the SpatialSeries data traversing the hierarchy of objects. The processing module called 'behavior' contains our Position object named 'Position'. The Position object contains our SpatialSeries object named 'SpatialSeries'.
read_spatial_series = read_nwbfile.processing.get('behavior'). ...
nwbdatainterface.get('Position').spatialseries.get('SpatialSeries')
read_spatial_series =
SpatialSeries with properties: + + reference_frame: '(0,0) is bottom left corner' + starting_time_unit: 'seconds' + timestamps_interval: 1 + timestamps_unit: 'seconds' + data: [1×1 types.untyped.DataStub] + comments: 'no comments' + control: [] + control_description: '' + data_continuity: '' + data_conversion: 1 + data_offset: 0 + data_resolution: -1 + data_unit: 'meters' + description: 'no description' + starting_time: 0 + starting_time_rate: 200 + timestamps: [] +

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 presents a DataStub object that can be indexed to read data.
read_spatial_series.data
ans =
DataStub with properties: + + filename: 'intro_tutorial.nwb' + path: '/processing/behavior/Position/SpatialSeries/data' + dims: [2 100] + ndims: 2 + dataType: 'double' +
This allows you to conveniently work with datasets that are too large to fit in RAM all at once. Access all the data in the matrix using the load method with no arguments.
read_spatial_series.data.load
ans = 2×100
0 0.1010 0.2020 0.3030 0.4040 0.5051 0.6061 0.7071 0.8081 0.9091 1.0101 1.1111 1.2121 1.3131 1.4141 1.5152 1.6162 1.7172 1.8182 1.9192 2.0202 2.1212 2.2222 2.3232 2.4242 2.5253 2.6263 2.7273 2.8283 2.9293 3.0303 3.1313 3.2323 3.3333 3.4343 3.5354 3.6364 3.7374 3.8384 3.9394 4.0404 4.1414 4.2424 4.3434 4.4444 4.5455 4.6465 4.7475 4.8485 4.9495 + 0 0.0808 0.1616 0.2424 0.3232 0.4040 0.4848 0.5657 0.6465 0.7273 0.8081 0.8889 0.9697 1.0505 1.1313 1.2121 1.2929 1.3737 1.4545 1.5354 1.6162 1.6970 1.7778 1.8586 1.9394 2.0202 2.1010 2.1818 2.2626 2.3434 2.4242 2.5051 2.5859 2.6667 2.7475 2.8283 2.9091 2.9899 3.0707 3.1515 3.2323 3.3131 3.3939 3.4747 3.5556 3.6364 3.7172 3.7980 3.8788 3.9596 +
If you only need a section of the data, you can read only that section by indexing the DataStub object like a normal array in MATLAB. This will just read the selected region from disk into RAM. This technique is particularly useful if you are dealing with a large dataset that is too big to fit entirely into your available RAM.
read_spatial_series.data(:, 1:10)
ans = 2×10
0 0.1010 0.2020 0.3030 0.4040 0.5051 0.6061 0.7071 0.8081 0.9091 + 0 0.0808 0.1616 0.2424 0.3232 0.4040 0.4848 0.5657 0.6465 0.7273 +

Next Steps

This concludes the introductory tutorial. Please proceed to one of the specialized tutorials, which are designed to follow this one.
See the API documentation to learn what data types are available.

\ No newline at end of file diff --git a/tutorials/intro.mlx b/tutorials/intro.mlx index 9e74d858b99e212d82e5d8dfc04ae9d9b8ac8389..43b78de6185efd528b3e398650619a3ceb8a4694 100644 GIT binary patch delta 9650 zcmY*-g|DHu3o*Wzgo4v zhpv8DT|HTecr}1na{)xEw(`On#sUCP`~U!SKyvL*AZ9!Y2o)TsSDWeFWX!YGLM^9M zTVL<`^Cxvkz_$o0VHrQwhMzy3!TTRu?{R0g01T=#3mFGn-k1+I7`8D;JT<`RMb}TvnBOQ9du`^|Fm# zj>VBhdZ8(vczNk8N<|dQ_QLhN)Ud;vhUzSrN(vWrqK92M(uQ%%#5ySCWR?Zk?a~|l zH|y*7!^PpkOnzyOB`P#8=yrF8IaT;AEc-weVYVN=>_>ymI0K4&;rlzn{X#ocEU5WD z3s5alCA7mGF;7vaJU&Pz`%&jUX!IUlw8xVW3JKBnq!*st)ui-l820o^Bg7muU+T2V ziR^{V(ac|?d%skyn)QqnFEO+hci%B$+{_rT^vy+TP%dxXM z><9@jiMqR5ymY8ze&j(UG78DiY3;|DWYRPC^`3=?hrfI93G_wI&|a`?Vj{GeB{O5l z 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 diff --git a/tutorials/private/mcode/intro.m b/tutorials/private/mcode/intro.m index 175b0880..417c24f8 100644 --- a/tutorials/private/mcode/intro.m +++ b/tutorials/private/mcode/intro.m @@ -125,9 +125,10 @@ % % Note: These diagrams follow a standard convention called "UML class diagram" % to express the object-oriented relationships between NWB classes. For our purposes, -% all you need to know is that an open triangle means "extends" and an open diamond -% means "is contained within." Learn more about class diagrams on . +% all you need to know is that an open triangle means "extends" (i.e., is a specialized +% subtype of), and an open diamond means "is contained within." Learn more about +% class diagrams on . % % is a subclass of object to the module. % create processing module -behavior_mod = types.core.ProcessingModule('description', 'contains behavioral data'); +behavior_module = types.core.ProcessingModule('description', 'contains behavioral data'); -% add the Position object (that holds the SpatialSeries object) to the -% module and name the Position object "Position" -behavior_mod.nwbdatainterface.set('Position', Position); +% add the Position object (that holds the SpatialSeries object) to the module +% and name the Position object "Position" +behavior_module.nwbdatainterface.set('Position', position); % add the processing module to the NWBFile object, and name the processing module "behavior" -nwb.processing.set('behavior', behavior_mod); +nwb.processing.set('behavior', behavior_module); % Trials % Trials are stored in a object which is a subclass of %% % See the to learn what data types are available. \ No newline at end of file +% documentation> to learn what data types are available. +% +% \ No newline at end of file