From 5aa27d92cba1b9136612cc8ab80d0c1791975997 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Mon, 4 Nov 2024 20:28:45 +0100 Subject: [PATCH 01/16] Update load_mat_style.m (#602) * Update load_mat_style.m * Update PynwbTutorialTest.m Add plot_read_basics to list of tutorials to skip. This tutorial does not produce NWB files that can be read with matnwb. * Simplify fix for load_mat_style --- +tests/+unit/PynwbTutorialTest.m | 5 +++-- +types/+untyped/@DataStub/load_mat_style.m | 14 +++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/+tests/+unit/PynwbTutorialTest.m b/+tests/+unit/PynwbTutorialTest.m index 6f687c15..93113a56 100644 --- a/+tests/+unit/PynwbTutorialTest.m +++ b/+tests/+unit/PynwbTutorialTest.m @@ -21,6 +21,7 @@ properties (Constant) % SkippedTutorials - Tutorials from pynwb to skip SkippedTutorials = {... + 'plot_read_basics.py', ... % Downloads file from dandi archive, does not export nwb file 'streaming.py', ... % Requires that HDF5 library is installed with the ROS3 driver enabled which is not a given 'object_id.py', ... % Does not export nwb file 'plot_configurator.py', ... % Does not export nwb file @@ -31,7 +32,7 @@ SkippedFiles = {'family_nwb_file_0.nwb'} % requires family driver from h5py % PythonDependencies - Package dependencies for running pynwb tutorials - PythonDependencies = {'hdmf-zarr', 'dataframe-image', 'matplotlib', 'dandi'} + PythonDependencies = {'hdmf-zarr', 'dataframe-image', 'matplotlib'} end properties (Access = private) @@ -101,7 +102,7 @@ function testTutorial(testCase, tutorialFile) pythonPath = tests.util.getPythonPath(); - cmd = sprintf('"%s" %s', pythonPath, tutorialFile ); + cmd = sprintf('%s %s', pythonPath, tutorialFile); [status, cmdout] = system(cmd); if status == 1 diff --git a/+types/+untyped/@DataStub/load_mat_style.m b/+types/+untyped/@DataStub/load_mat_style.m index a6fa1f16..f32312c0 100644 --- a/+types/+untyped/@DataStub/load_mat_style.m +++ b/+types/+untyped/@DataStub/load_mat_style.m @@ -46,6 +46,18 @@ end points = cell(length(dataDimensions), 1); + + if isscalar(dataDimensions) + % Starting in MATLAB R2024b, the input argument for the size + % of an array in ind2sub must be a vector of positive integers + % with two or more elements. This fix replicates the behavior of + % older MATLAB versions, where it was assumed that the a scalar + % size referred to the row dimension. For scalar dimensions + % (i.e., row or column vectors), we can still assume this + % to be true in matnwb. + dataDimensions = [dataDimensions, 1]; + end + [points{:}] = ind2sub(dataDimensions, orderedSelection); readSpaceId = H5S.copy(spaceId); H5S.select_none(readSpaceId); @@ -187,4 +199,4 @@ indexKeyIndex(indexKeyIndexNextIndex) = indexKeyIndex(indexKeyIndexNextIndex) + 1; indexKeyIndex((indexKeyIndexNextIndex+1):end) = 1; end -end \ No newline at end of file +end From d970ef16ea14c8e43a975740ca0f15d0e6899ac8 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Fri, 8 Nov 2024 15:02:47 +0100 Subject: [PATCH 02/16] Add ErrorID and warningID for all user-facing code (#617) * Add warning IDs for where missing * Add errorID where missing * Add errorIDs to class generators and update regenerate types with errorIDs * Add errorID to checkCustomConstraint method of TimeSeries --- +file/Dataset.m | 22 ++++++++------ +file/Group.m | 8 ++++- +file/fillConstructor.m | 5 ++-- +file/fillCustomConstraint.m | 2 ++ +file/fillProps.m | 6 ++-- +file/fillValidators.m | 8 ++--- +io/getBaseType.m | 3 +- +io/parseGroup.m | 4 +-- +io/resolvePath.m | 3 +- +io/timestamp2datetime.m | 7 ++--- +io/writeCompound.m | 31 +++++++++++++++++++- +io/writeDataset.m | 3 +- +misc/str2validName.m | 4 ++- +schemes/Namespace.m | 10 +++---- +types/+core/AnnotationSeries.m | 4 +-- +types/+core/CurrentClampSeries.m | 2 +- +types/+core/CurrentClampStimulusSeries.m | 2 +- +types/+core/ElectricalSeries.m | 2 +- +types/+core/ElectrodeGroup.m | 2 +- +types/+core/IZeroClampSeries.m | 2 +- +types/+core/IndexSeries.m | 2 +- +types/+core/IntervalSeries.m | 4 +-- +types/+core/IntracellularElectrodesTable.m | 2 +- +types/+core/IntracellularRecordingsTable.m | 2 +- +types/+core/IntracellularResponsesTable.m | 2 +- +types/+core/IntracellularStimuliTable.m | 2 +- +types/+core/OptogeneticSeries.m | 2 +- +types/+core/SpikeEventSeries.m | 6 ++-- +types/+core/TimeSeries.m | 4 ++- +types/+core/TimeSeriesReferenceVectorData.m | 2 +- +types/+core/VoltageClampSeries.m | 2 +- +types/+core/VoltageClampStimulusSeries.m | 2 +- +types/+util/+dynamictable/addRow.m | 2 +- +types/+util/+dynamictable/addTableColumn.m | 2 +- +types/+util/+dynamictable/getRow.m | 2 +- +types/+util/checkDependent.m | 3 +- +util/loadTimeSeriesData.m | 9 ++++-- +util/loadTimeSeriesTimestamps.m | 5 ++-- +util/read_indexed_column.m | 2 +- 39 files changed, 120 insertions(+), 67 deletions(-) diff --git a/+file/Dataset.m b/+file/Dataset.m index 77a2bbda..ff3a3323 100644 --- a/+file/Dataset.m +++ b/+file/Dataset.m @@ -121,15 +121,19 @@ %constrained % error unless it defines the object. - - if isempty(obj.type) - error('You shouldn''t be calling getProps on an untyped dataset'); - end - - if obj.isConstrainedSet && ~obj.definesType - error('You shouldn''t be calling getProps on a constrained dataset'); - end - + + assert(... + ~isempty(obj.type), ... + 'NWB:Dataset:UnsupportedOperation', ... + 'The method `getProps` should not be called on an untyped dataset.' ... + ); + + assert( ... + ~obj.isConstrainedSet || obj.definesType, ... + 'NWB:Dataset:UnsupportedOperation', ... + 'The method `getProps` should not be called on constrained dataset.' ... + ); + if ~isempty(obj.dtype) props('data') = obj.dtype; end diff --git a/+file/Group.m b/+file/Group.m index d9fababe..00e62146 100644 --- a/+file/Group.m +++ b/+file/Group.m @@ -146,8 +146,14 @@ %should never happen if obj.isConstrainedSet && ~obj.definesType - error('getProps shouldn''t be called on a constrained set.'); + error('NWB:Group:UnsupportedOperation', ... + 'The method `getProps` should not be called on a constrained dataset.'); end + assert( ... + ~obj.isConstrainedSet || obj.definesType, ... + 'NWB:Group:UnsupportedOperation', ... + 'The method `getProps` should not be called on a constrained group.' ... + ); %datasets for i=1:length(obj.datasets) diff --git a/+file/fillConstructor.m b/+file/fillConstructor.m index 124330d4..269829d2 100644 --- a/+file/fillConstructor.m +++ b/+file/fillConstructor.m @@ -100,7 +100,8 @@ end end - %warn for missing namespaces/property types + % warn for missing namespaces/property types + warningId = 'NWB:ClassGenerator:NamespaceOrTypeNotFound'; warnmsg = ['`' parentName '`''s constructor is unable to check for type `%1$s` ' ... 'because its namespace or type specifier could not be found. Try generating ' ... 'the namespace or class definition for type `%1$s` or fix its schema.']; @@ -109,7 +110,7 @@ invalidWarn = invalid & (dynamicConstrained | isAnonymousType) & ~isAttribute; invalidVars = varnames(invalidWarn); for i=1:length(invalidVars) - warning(warnmsg, invalidVars{i}); + warning(warningId, warnmsg, invalidVars{i}); end varnames = lower(varnames); diff --git a/+file/fillCustomConstraint.m b/+file/fillCustomConstraint.m index 5be7d9e8..afe34a52 100644 --- a/+file/fillCustomConstraint.m +++ b/+file/fillCustomConstraint.m @@ -12,9 +12,11 @@ customConstraintStr = sprintf( [... 'function checkCustomConstraint(obj)\n', ... ' assert(~isempty(obj.timestamps) || ~isempty(obj.starting_time), ...\n', ... + ' ''NWB:TimeSeries:TimeNotSpecified'', ...\n ', ... ' "''timestamps'' or ''starting_time'' must be specified")\n', ... ' if ~isempty(obj.starting_time)\n', ... ' assert(~isempty(obj.starting_time_rate), ...\n', ... + ' ''NWB:TimeSeries:RateMissing'', ...\n', ... ' "''starting_time_rate'' must be specified when ''starting_time'' is specified")\n', ... ' end\n', ... 'end'] ); diff --git a/+file/fillProps.m b/+file/fillProps.m index 114234f7..51062cd2 100644 --- a/+file/fillProps.m +++ b/+file/fillProps.m @@ -58,7 +58,8 @@ case 'object' refTypeName = 'Object'; otherwise - error('Invalid reftype found whilst filling Constructor prop docs.'); + error('NWB:ClassGenerator:InvalidRefType', ... + 'Invalid reftype found while filling description for class property "%s".', propName); end typeStr = sprintf('%s Reference to %s', refTypeName, prop.dtype('target_type')); else @@ -71,7 +72,8 @@ case 'object' refTypeName = 'object'; otherwise - error('Invalid reftype found whilst filling Constructor prop docs.'); + error('NWB:ClassGenerator:InvalidRefType', ... + 'Invalid reftype found while filling description for class property "%s".', propName); end typeStr = sprintf('%s Reference to %s', refTypeName, prop('target_type')); elseif isa(prop, 'file.interface.HasProps') diff --git a/+file/fillValidators.m b/+file/fillValidators.m index caacadb2..e2daac36 100644 --- a/+file/fillValidators.m +++ b/+file/fillValidators.m @@ -240,7 +240,7 @@ ' return;'... 'end'... 'if ~istable(val) && ~isstruct(val) && ~isa(val, ''containers.Map'')'... - [' error(''Property `' name '` must be a table,struct, or containers.Map.'');']... + [' error(''NWB:Type:InvalidPropertyType'', ''Property `' name '` must be a table, struct, or containers.Map.'');']... 'end'... 'vprops = struct();'... }, newline); @@ -294,8 +294,7 @@ classNameSplit = strsplit(className, '.'); shortName = classNameSplit{end}; - - errorStr = sprintf( 'error(''Unable to set the ''''%s'''' property of class ''''%s'''' because it is read-only.'')', name, className, shortName); + errorStr = sprintf( 'error(''NWB:Type:ReadOnlyProperty'', ''Unable to set the ''''%s'''' property of class ''''%s'''' because it is read-only.'')', name, className, shortName); if ischar(value) condition = strjoin({ ... @@ -311,7 +310,8 @@ % Note: According to the documentation for Attribute specification keys % (https://schema-language.readthedocs.io/en/latest/description.html#sec-attributes-spec), % the above cases should be sufficient. - error('Unhandled case') + error('NWB:ClassGenerator:ReadOnlyValidatorNotImplemented', ... + 'Read-only validator is not implemented for values of type "%s"', class(value)) end fdvstr = strjoin({... diff --git a/+io/getBaseType.m b/+io/getBaseType.m index 680019a0..bd1a37f2 100644 --- a/+io/getBaseType.m +++ b/+io/getBaseType.m @@ -45,6 +45,7 @@ id = [prefix suffix]; else - error('Type `%s` is not a supported raw type', type); + error('NWB:IO:UnsupportedBaseType', ... + 'Type `%s` is not a supported raw type', type); end end \ No newline at end of file diff --git a/+io/parseGroup.m b/+io/parseGroup.m index 46a87c6c..791f2ecf 100644 --- a/+io/parseGroup.m +++ b/+io/parseGroup.m @@ -116,14 +116,14 @@ if any(leads) %since set has been edited, we bubble up deletion of the old keys. subset = elide(pvalue, prop(leads), pvar); - elided = [elided; subset]; + elided = [elided; subset]; %#ok if pvalue.Count == 0 drop(i) = true; elseif any(strcmp(pvar, prop)) elided(pvar) = pvalue; drop(i) = true; else - warning('Unable to match property `%s` under prefix `%s`',... + warning('NWB:Parse:UnmatchedProperty', 'Unable to match property `%s` under prefix `%s`',... pvar, prefix); end end diff --git a/+io/resolvePath.m b/+io/resolvePath.m index 7ffcdbd7..0a1cb3f6 100644 --- a/+io/resolvePath.m +++ b/+io/resolvePath.m @@ -8,7 +8,6 @@ %process slash tokens o = nwb; -errmsg = 'Could not resolve path `%s`.'; while ~isempty(tokens) if isa(o, 'types.untyped.Set') [o, tokens] = resolveSet(o, tokens); @@ -18,7 +17,7 @@ [o, tokens] = resolveObj(o, tokens); end if isempty(o) - error(errmsg, path); + error('NWB:IO:UnresolvedPath', 'Could not resolve path `%s`.', path); end end end diff --git a/+io/timestamp2datetime.m b/+io/timestamp2datetime.m index e1db9198..f1fd165f 100644 --- a/+io/timestamp2datetime.m +++ b/+io/timestamp2datetime.m @@ -113,9 +113,8 @@ elseif ischar(timestamps) cells = {timestamps}; else - errorId = "NWB:timestamp2datetime:MustBeCharCellArrayOrString"; - errorMsg = ['timestamps must be a string, character array, ', ... - 'or cell array of strings/character arrays.']; - error(errorId, errorMsg); + error('NWB:timestamp2datetime:MustBeCharCellArrayOrString', ... + [ 'Timestamps must be a string, character array, ', ... + 'or cell array of strings/character arrays.' ]); end end diff --git a/+io/writeCompound.m b/+io/writeCompound.m index 503b03b2..1e1b7131 100644 --- a/+io/writeCompound.m +++ b/+io/writeCompound.m @@ -1,4 +1,32 @@ function writeCompound(fid, fullpath, data, varargin) +% writeCompound - Write structured data to an HDF5 compound dataset. +% +% io.writeCompound(fid, fullpath, data, varargin) converts data (in table, +% struct, or containers.Map format) into a scalar struct, optimizes it for +% HDF5 storage, and writes it to an HDF5 compound dataset specified by fid +% and fullpath. +% +% Inputs: +% fid - File identifier for an open HDF5 file. +% fullpath - Full path within the HDF5 file where data will be stored. +% data - Data to write, provided as a table, struct, or containers.Map. +% varargin - Additional optional arguments. +% +% Functionality: +% - Converts input data into a scalar struct, rearranging fields and types as needed. +% - Detects data types, sizes, and handles compound HDF5 type creation. +% - Optimizes data for HDF5 by transposing column vectors and converting logicals. +% - Manages references to external data objects, regions, or untyped views. +% - Attempts to extend or overwrite existing datasets if a compound dataset at +% the specified path already exists. +% +% Notes: +% - If `fullpath` already exists in the HDF5 file, the function tries to adjust +% dimensions if the dataset is chunked, and issues a warning if resizing is not allowed. +% +% Example: +% io.writeCompound(fid, '/group/dataset', data); + %convert to a struct if istable(data) data = table2struct(data); @@ -103,7 +131,8 @@ function writeCompound(fid, fullpath, data, varargin) if is_chunked H5D.set_extent(did, dims); else - warning('Attempted to change size of continuous compound `%s`. Skipping.',... + warning('NWB:WriteCompund:ContinuousCompoundResize', ... + 'Attempted to change size of continuous compound `%s`. Skipping.', ... fullpath); end end diff --git a/+io/writeDataset.m b/+io/writeDataset.m index 7601864b..031b1281 100644 --- a/+io/writeDataset.m +++ b/+io/writeDataset.m @@ -26,7 +26,8 @@ function writeDataset(fid, fullpath, data, varargin) if ~is_same_dims && is_chunked H5D.set_extent(did, dims); elseif ~is_same_dims - warning('Attempted to change size of continuous dataset `%s`. Skipping.',... + warning('NWB:WriteDataset:ContinuousDatasetResize', ... + 'Attempted to change size of continuous dataset `%s`. Skipping.',... fullpath); H5S.close(sid); H5D.close(did); diff --git a/+misc/str2validName.m b/+misc/str2validName.m index 126e6c8c..dd93de15 100644 --- a/+misc/str2validName.m +++ b/+misc/str2validName.m @@ -3,6 +3,7 @@ % Converts the property name into a valid matlab property name. % propname: the offending property name % prefix: optional prefix to use instead of the ambiguous "dyn" + if ~iscell(propname) && isvarname(propname) valid = propname; return; @@ -12,7 +13,8 @@ prefix = 'dyn_'; else if ~isvarname(prefix) - warning('Prefix contains invalid variable characters. Reverting to "dyn"'); + warning('NWB:CreateValidPropertyName:InvalidPrefix', ... + 'Prefix contains invalid variable characters. Reverting to "dyn"'); prefix = 'dyn_'; end end diff --git a/+schemes/Namespace.m b/+schemes/Namespace.m index 271d77d3..6899f542 100644 --- a/+schemes/Namespace.m +++ b/+schemes/Namespace.m @@ -41,7 +41,7 @@ function parent = getParent(obj, classname) class = obj.getClass(classname); if isempty(class) - error('Could not find class %s', classname); + error('NWB:Namespace:ClassNotFound', 'Could not find class %s', classname); end parent = []; @@ -49,10 +49,10 @@ if any(hasParentKey) parentName = class(obj.PARENT_KEYS{hasParentKey}); parent = obj.getClass(parentName); - assert(~isempty(parent),... - 'Parent %s for class %s doesn''t exist! Missing Dependency?',... - parentName,... - classname); + assert(~isempty(parent), ... + 'NWB:Namespace:ParentNotFound', ... + 'Parent %s for class %s doesn''t exist! Missing Dependency?', ... + parentName, classname); end end diff --git a/+types/+core/AnnotationSeries.m b/+types/+core/AnnotationSeries.m index b199c3af..c821b8d6 100644 --- a/+types/+core/AnnotationSeries.m +++ b/+types/+core/AnnotationSeries.m @@ -52,14 +52,14 @@ if isequal(val, -1) val = -1; else - error('Unable to set the ''data_resolution'' property of class ''AnnotationSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''data_resolution'' property of class ''AnnotationSeries'' because it is read-only.') end end function val = validate_data_unit(obj, val) if isequal(val, 'n/a') val = 'n/a'; else - error('Unable to set the ''data_unit'' property of class ''AnnotationSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''data_unit'' property of class ''AnnotationSeries'' because it is read-only.') end end %% EXPORT diff --git a/+types/+core/CurrentClampSeries.m b/+types/+core/CurrentClampSeries.m index 8def6770..fe27e0cb 100644 --- a/+types/+core/CurrentClampSeries.m +++ b/+types/+core/CurrentClampSeries.m @@ -109,7 +109,7 @@ if isequal(val, 'volts') val = 'volts'; else - error('Unable to set the ''data_unit'' property of class ''CurrentClampSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''data_unit'' property of class ''CurrentClampSeries'' because it is read-only.') end end %% EXPORT diff --git a/+types/+core/CurrentClampStimulusSeries.m b/+types/+core/CurrentClampStimulusSeries.m index ec31d0db..ab8d7a4d 100644 --- a/+types/+core/CurrentClampStimulusSeries.m +++ b/+types/+core/CurrentClampStimulusSeries.m @@ -35,7 +35,7 @@ if isequal(val, 'amperes') val = 'amperes'; else - error('Unable to set the ''data_unit'' property of class ''CurrentClampStimulusSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''data_unit'' property of class ''CurrentClampStimulusSeries'' because it is read-only.') end end %% EXPORT diff --git a/+types/+core/ElectricalSeries.m b/+types/+core/ElectricalSeries.m index 212e88ff..9a81aa87 100644 --- a/+types/+core/ElectricalSeries.m +++ b/+types/+core/ElectricalSeries.m @@ -97,7 +97,7 @@ if isequal(val, 'volts') val = 'volts'; else - error('Unable to set the ''data_unit'' property of class ''ElectricalSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''data_unit'' property of class ''ElectricalSeries'' because it is read-only.') end end function val = validate_electrodes(obj, val) diff --git a/+types/+core/ElectrodeGroup.m b/+types/+core/ElectrodeGroup.m index 174c0e6d..d44737b8 100644 --- a/+types/+core/ElectrodeGroup.m +++ b/+types/+core/ElectrodeGroup.m @@ -93,7 +93,7 @@ return; end if ~istable(val) && ~isstruct(val) && ~isa(val, 'containers.Map') - error('Property `position` must be a table,struct, or containers.Map.'); + error('NWB:Type:InvalidPropertyType', 'Property `position` must be a table, struct, or containers.Map.'); end vprops = struct(); vprops.x = 'single'; diff --git a/+types/+core/IZeroClampSeries.m b/+types/+core/IZeroClampSeries.m index 39e5814e..03049eb6 100644 --- a/+types/+core/IZeroClampSeries.m +++ b/+types/+core/IZeroClampSeries.m @@ -90,7 +90,7 @@ if isequal(val, 'N/A') val = 'N/A'; else - error('Unable to set the ''stimulus_description'' property of class ''IZeroClampSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''stimulus_description'' property of class ''IZeroClampSeries'' because it is read-only.') end end %% EXPORT diff --git a/+types/+core/IndexSeries.m b/+types/+core/IndexSeries.m index 1c47e17f..8ba26b10 100644 --- a/+types/+core/IndexSeries.m +++ b/+types/+core/IndexSeries.m @@ -124,7 +124,7 @@ if isequal(val, 'N/A') val = 'N/A'; else - error('Unable to set the ''data_unit'' property of class ''IndexSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''data_unit'' property of class ''IndexSeries'' because it is read-only.') end end function val = validate_indexed_images(obj, val) diff --git a/+types/+core/IntervalSeries.m b/+types/+core/IntervalSeries.m index 22edd6fc..27fd745a 100644 --- a/+types/+core/IntervalSeries.m +++ b/+types/+core/IntervalSeries.m @@ -52,14 +52,14 @@ if isequal(val, -1) val = -1; else - error('Unable to set the ''data_resolution'' property of class ''IntervalSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''data_resolution'' property of class ''IntervalSeries'' because it is read-only.') end end function val = validate_data_unit(obj, val) if isequal(val, 'n/a') val = 'n/a'; else - error('Unable to set the ''data_unit'' property of class ''IntervalSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''data_unit'' property of class ''IntervalSeries'' because it is read-only.') end end %% EXPORT diff --git a/+types/+core/IntracellularElectrodesTable.m b/+types/+core/IntracellularElectrodesTable.m index b3f2b4fd..ce75faef 100644 --- a/+types/+core/IntracellularElectrodesTable.m +++ b/+types/+core/IntracellularElectrodesTable.m @@ -41,7 +41,7 @@ if isequal(val, 'Table for storing intracellular electrode related metadata.') val = 'Table for storing intracellular electrode related metadata.'; else - error('Unable to set the ''description'' property of class ''IntracellularElectrodesTable'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''description'' property of class ''IntracellularElectrodesTable'' because it is read-only.') end end function val = validate_electrode(obj, val) diff --git a/+types/+core/IntracellularRecordingsTable.m b/+types/+core/IntracellularRecordingsTable.m index 41be93db..1fc8d5c9 100644 --- a/+types/+core/IntracellularRecordingsTable.m +++ b/+types/+core/IntracellularRecordingsTable.m @@ -53,7 +53,7 @@ if isequal(val, 'A table to group together a stimulus and response from a single electrode and a single simultaneous recording and for storing metadata about the intracellular recording.') val = 'A table to group together a stimulus and response from a single electrode and a single simultaneous recording and for storing metadata about the intracellular recording.'; else - error('Unable to set the ''description'' property of class ''IntracellularRecordingsTable'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''description'' property of class ''IntracellularRecordingsTable'' because it is read-only.') end end function val = validate_electrodes(obj, val) diff --git a/+types/+core/IntracellularResponsesTable.m b/+types/+core/IntracellularResponsesTable.m index 2f98eee7..1b4979b7 100644 --- a/+types/+core/IntracellularResponsesTable.m +++ b/+types/+core/IntracellularResponsesTable.m @@ -41,7 +41,7 @@ if isequal(val, 'Table for storing intracellular response related metadata.') val = 'Table for storing intracellular response related metadata.'; else - error('Unable to set the ''description'' property of class ''IntracellularResponsesTable'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''description'' property of class ''IntracellularResponsesTable'' because it is read-only.') end end function val = validate_response(obj, val) diff --git a/+types/+core/IntracellularStimuliTable.m b/+types/+core/IntracellularStimuliTable.m index 8e933b92..b0cf39e9 100644 --- a/+types/+core/IntracellularStimuliTable.m +++ b/+types/+core/IntracellularStimuliTable.m @@ -50,7 +50,7 @@ if isequal(val, 'Table for storing intracellular stimulus related metadata.') val = 'Table for storing intracellular stimulus related metadata.'; else - error('Unable to set the ''description'' property of class ''IntracellularStimuliTable'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''description'' property of class ''IntracellularStimuliTable'' because it is read-only.') end end function val = validate_stimulus(obj, val) diff --git a/+types/+core/OptogeneticSeries.m b/+types/+core/OptogeneticSeries.m index 5741153c..92176f50 100644 --- a/+types/+core/OptogeneticSeries.m +++ b/+types/+core/OptogeneticSeries.m @@ -58,7 +58,7 @@ if isequal(val, 'watts') val = 'watts'; else - error('Unable to set the ''data_unit'' property of class ''OptogeneticSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''data_unit'' property of class ''OptogeneticSeries'' because it is read-only.') end end function val = validate_site(obj, val) diff --git a/+types/+core/SpikeEventSeries.m b/+types/+core/SpikeEventSeries.m index 80611ca7..e397634b 100644 --- a/+types/+core/SpikeEventSeries.m +++ b/+types/+core/SpikeEventSeries.m @@ -56,7 +56,7 @@ if isequal(val, 'volts') val = 'volts'; else - error('Unable to set the ''data_unit'' property of class ''SpikeEventSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''data_unit'' property of class ''SpikeEventSeries'' because it is read-only.') end end function val = validate_timestamps(obj, val) @@ -81,14 +81,14 @@ if isequal(val, 1) val = 1; else - error('Unable to set the ''timestamps_interval'' property of class ''SpikeEventSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''timestamps_interval'' property of class ''SpikeEventSeries'' because it is read-only.') end end function val = validate_timestamps_unit(obj, val) if isequal(val, 'seconds') val = 'seconds'; else - error('Unable to set the ''timestamps_unit'' property of class ''SpikeEventSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''timestamps_unit'' property of class ''SpikeEventSeries'' because it is read-only.') end end %% EXPORT diff --git a/+types/+core/TimeSeries.m b/+types/+core/TimeSeries.m index 5245289c..2fad2f65 100644 --- a/+types/+core/TimeSeries.m +++ b/+types/+core/TimeSeries.m @@ -418,9 +418,11 @@ %% CUSTOM CONSTRAINTS function checkCustomConstraint(obj) assert(~isempty(obj.timestamps) || ~isempty(obj.starting_time), ... - "'timestamps' or 'starting_time' must be specified") + 'NWB:TimeSeries:TimeNotSpecified', ... + "'timestamps' or 'starting_time' must be specified") if ~isempty(obj.starting_time) assert(~isempty(obj.starting_time_rate), ... + 'NWB:TimeSeries:RateMissing', ... "'starting_time_rate' must be specified when 'starting_time' is specified") end end diff --git a/+types/+core/TimeSeriesReferenceVectorData.m b/+types/+core/TimeSeriesReferenceVectorData.m index 20f77944..719c12d2 100644 --- a/+types/+core/TimeSeriesReferenceVectorData.m +++ b/+types/+core/TimeSeriesReferenceVectorData.m @@ -30,7 +30,7 @@ return; end if ~istable(val) && ~isstruct(val) && ~isa(val, 'containers.Map') - error('Property `data` must be a table,struct, or containers.Map.'); + error('NWB:Type:InvalidPropertyType', 'Property `data` must be a table, struct, or containers.Map.'); end vprops = struct(); vprops.idx_start = 'int32'; diff --git a/+types/+core/VoltageClampSeries.m b/+types/+core/VoltageClampSeries.m index 7591fdd9..50984fc5 100644 --- a/+types/+core/VoltageClampSeries.m +++ b/+types/+core/VoltageClampSeries.m @@ -139,7 +139,7 @@ if isequal(val, 'amperes') val = 'amperes'; else - error('Unable to set the ''data_unit'' property of class ''VoltageClampSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''data_unit'' property of class ''VoltageClampSeries'' because it is read-only.') end end function val = validate_resistance_comp_bandwidth(obj, val) diff --git a/+types/+core/VoltageClampStimulusSeries.m b/+types/+core/VoltageClampStimulusSeries.m index e81651bd..2df68b7e 100644 --- a/+types/+core/VoltageClampStimulusSeries.m +++ b/+types/+core/VoltageClampStimulusSeries.m @@ -35,7 +35,7 @@ if isequal(val, 'volts') val = 'volts'; else - error('Unable to set the ''data_unit'' property of class ''VoltageClampStimulusSeries'' because it is read-only.') + error('NWB:Type:ReadOnlyProperty', 'Unable to set the ''data_unit'' property of class ''VoltageClampStimulusSeries'' because it is read-only.') end end %% EXPORT diff --git a/+types/+util/+dynamictable/addRow.m b/+types/+util/+dynamictable/addRow.m index 3afceb70..328223c1 100644 --- a/+types/+util/+dynamictable/addRow.m +++ b/+types/+util/+dynamictable/addRow.m @@ -40,7 +40,7 @@ function addRow(DynamicTable, varargin) 'If this was produced with pynwb, please enable chunking for this table.']); if istable(varargin{1}) - error("NWB:DynamicTable", ... + error('NWB:DynamicTable', ... ['Using MATLAB tables as input to the addRow DynamicTable method has '... 'been deprecated. Please, use key-value pairs instead']); else diff --git a/+types/+util/+dynamictable/addTableColumn.m b/+types/+util/+dynamictable/addTableColumn.m index 38503809..dfdba534 100644 --- a/+types/+util/+dynamictable/addTableColumn.m +++ b/+types/+util/+dynamictable/addTableColumn.m @@ -1,4 +1,4 @@ function addTableColumn(DynamicTable, subTable) -error("NWB:DynamicTable", ... +error('NWB:DynamicTable', ... ['Using MATLAB tables as input to the addColumn DynamicTable method has '... 'been deprecated. Please, use key-value pairs instead']) \ No newline at end of file diff --git a/+types/+util/+dynamictable/getRow.m b/+types/+util/+dynamictable/getRow.m index bc5c00a6..97850f21 100644 --- a/+types/+util/+dynamictable/getRow.m +++ b/+types/+util/+dynamictable/getRow.m @@ -63,7 +63,7 @@ if is_row_dim(1) && is_row_dim(end) % Last dimension takes precedence is_row_dim(1:end-1) = false; - warning(... + warning('NWB:DynamicTable:VectorDataAmbiguousSize', ... ['The length of the first and last dimensions of ', ... 'VectorData for column "%s" match the number of ', ... 'rows in the dynamic table. Data is rearranged based on ', ... diff --git a/+types/+util/checkDependent.m b/+types/+util/checkDependent.m index 8de45586..f1b72344 100644 --- a/+types/+util/checkDependent.m +++ b/+types/+util/checkDependent.m @@ -3,7 +3,8 @@ function checkDependent(parent, children, unconstructed) for i=1:length(children) child = children{i}; if any(strcmp(child, unconstructed)) - error('Dependent type `%s` is required for parent property `%s`', child, parent); + error('NWB:CheckDependentType:TypeRequiredForParent', ... + 'Dependent type `%s` is required for parent property `%s`', child, parent); end end end diff --git a/+util/loadTimeSeriesData.m b/+util/loadTimeSeriesData.m index 4fc58d5b..6c6ce212 100644 --- a/+util/loadTimeSeriesData.m +++ b/+util/loadTimeSeriesData.m @@ -30,7 +30,8 @@ if isfinite(interval(2)) data = NaN(ceil(diff(interval) * fs), length(electrodes)); else - error('must specify time interval'); + error('NWB:LoadTimeSeries:UnspecifiedTimeInterval', ... + 'must specify time interval'); end for i = 1:length(electrodes) data(:,i) = util.loadTimeSeriesData(timeseries, interval, ... @@ -47,7 +48,8 @@ fs = timeseries.starting_time_rate; t0 = timeseries.starting_time; if interval(1) < t0 - error('interval bounds outside of time range'); + error('NWB:LoadTimeSeries:InvalidTimeInterval', ... + 'interval bounds outside of time range'); end start_ind = (interval(1) - t0) * fs; end @@ -63,7 +65,8 @@ fs = timeseries.starting_time_rate; t0 = timeseries.starting_time; if interval(2) > (dims(end) * fs + t0) - error('interval bounds outside of time range'); + error('NWB:LoadTimeSeries:InvalidTimeInterval', ... + 'interval bounds outside of time range'); end end_ind = (interval(2) - t0) * fs; end diff --git a/+util/loadTimeSeriesTimestamps.m b/+util/loadTimeSeriesTimestamps.m index 1d5b5efc..c2579c2f 100644 --- a/+util/loadTimeSeriesTimestamps.m +++ b/+util/loadTimeSeriesTimestamps.m @@ -28,8 +28,9 @@ else if downsample_factor ~= 1 - warning(['Downsampling a timestamps of a timeseries that may'... - 'not be uniformly sampled. This may have unintended behavior']) + warning('NWB:LoadTimeStamps:DownsampleNonUniformTimestamps', ... + [ 'Downsampling timestamps of a timeseries that may' ... + 'not be uniformly sampled. This may have unintended behavior']) end start_ind = fastsearch(timeseries.timestamps, interval(1), 1); if isinf(interval(2)) diff --git a/+util/read_indexed_column.m b/+util/read_indexed_column.m index 90af97ec..34e0c9c6 100644 --- a/+util/read_indexed_column.m +++ b/+util/read_indexed_column.m @@ -1,5 +1,5 @@ function data = read_indexed_column(vector_index, vector_data, row) -error("NWB:read_indexed_column", ... +error('NWB:read_indexed_column', ... ['The utility function read_indexed_column has been reprecated. Please,' ... ' use the getRow method of DynamicTable objects instead'] ... ) \ No newline at end of file From f2c8fc5c82e79b91b1d91f99369dc7b4fd2678d4 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Thu, 14 Nov 2024 10:30:06 +0100 Subject: [PATCH 03/16] Add AnnotationSeries example to intro tutorial (#622) --- tutorials/html/intro.html | 295 +++++++++++++++++++++----------------- tutorials/intro.mlx | Bin 220876 -> 221751 bytes 2 files changed, 167 insertions(+), 128 deletions(-) diff --git a/tutorials/html/intro.html b/tutorials/html/intro.html index 9fb4c9db..af630409 100644 --- a/tutorials/html/intro.html +++ b/tutorials/html/intro.html @@ -1,28 +1,22 @@ -Introduction to MatNWB

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.1.0' - file_create_date: [] - general_devices: [0×1 types.untyped.Set] - identifier: 'Mouse5_Day3' - session_description: 'mouse in open exploration' - session_start_time: {[2018-04-25T02:30:03.000000-04: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_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_filtering: '' - 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: [] -

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: + Reading Data +Next Steps

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: '' -

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).
Note: the DANDI archive requires all NWB files to have a subject object with subject_id specified, and strongly encourages specifying the other fields.
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));

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.
% 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', ...
'timestamps', linspace(0, 100)/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
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.1.0' - file_create_date: [1×1 types.untyped.DataStub] - general_devices: [0×1 types.untyped.Set] - 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_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_filtering: '' - 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: [0×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: +
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" 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.
% 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', ...
'timestamps', linspace(0, 100)/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
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' @@ -206,27 +214,27 @@ 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: [] starting_time_rate: [] timestamps: [1×1 types.untyped.DataStub] -

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: +

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 +
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 +
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.
+

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/html/icephys.html b/tutorials/html/icephys.html index c992dd45..5b095148 100644 --- a/tutorials/html/icephys.html +++ b/tutorials/html/icephys.html @@ -1,42 +1,47 @@ -Intracellular electrophysiology

Intracellular electrophysiology

Table of Contents
Creating an NWBFile +.embeddedOutputsTextElement.rightPaneElement,.embeddedOutputsVariableStringElement.rightPaneElement { min-height: 16px;} +.rightPaneElement .textElement { padding-top: 2px; padding-left: 9px;}

Intracellular electrophysiology

The following tutorial describes storage of intracellular electrophysiology data in NWB. NWB supports storage of the time series describing the stimulus and response, information about the electrode and device used, as well as metadata about the organization of the experiment.
Illustration of the hierarchy of metadata tables used to describe the organization of intracellular electrophysiology experiments.

Creating an NWBFile

When creating an NWB file, the first step is to create the NWBFile, which you can create using the NwbFile command.
session_start_time = datetime(2018, 3, 1, 12, 0, 0, 'TimeZone', 'local');
nwbfile = NwbFile( ...
'session_description', 'my first synthetic recording', ...
'identifier', 'EXAMPLE_ID', ...
'session_start_time', session_start_time, ...
'general_experimenter', 'Dr. Bilbo Baggins', ...
'general_lab', 'Bag End Laboratory', ...
'general_institution', 'University of Middle Earth at the Shire', ...
'general_experiment_description', 'I went on an adventure with thirteen dwarves to reclaim vast treasures.', ...
'general_session_id', 'LONELYMTN' ...
);

Device metadata

Device metadata is represented by Device objects.
device = types.core.Device();
nwbfile.general_devices.set('Heka ITC-1600', device);

Electrode metadata

Intracellular electrode metadata is represented by IntracellularElectrode objects. Create an electrode object, which requires a link to the device of the previous step. Then add it to the NWB file.
electrode = types.core.IntracellularElectrode( ...
'description', 'a mock intracellular electrode', ...
'device', types.untyped.SoftLink(device) ...
);
nwbfile.general_intracellular_ephys.set('elec0', electrode);

Stimulus and response data

Intracellular stimulus and response data are represented with subclasses of PatchClampSeries. A stimulus is described by a time series representing voltage or current stimulation with a particular set of parameters. There are two classes for representing stimulus data:
The response is then described by a time series representing voltage or current recorded from a single cell using a single intracellular electrode via one of the following classes:
Below we create a simple example stimulus/response recording data pair.
ccss = types.core.VoltageClampStimulusSeries( ...
'data', [1, 2, 3, 4, 5], ...
'starting_time', 123.6, ...
'starting_time_rate', 10e3, ...
'electrode', types.untyped.SoftLink(electrode), ...
'gain', 0.02, ...
'sweep_number', uint64(15), ...
'stimulus_description', 'N/A' ...
);
nwbfile.stimulus_presentation.set('ccss', ccss);
vcs = types.core.VoltageClampSeries( ...
'data', [0.1, 0.2, 0.3, 0.4, 0.5], ...
'data_conversion', 1e-12, ...
'data_resolution', NaN, ...
'starting_time', 123.6, ...
'starting_time_rate', 20e3, ...
'electrode', types.untyped.SoftLink(electrode), ...
'gain', 0.02, ...
'capacitance_slow', 100e-12, ...
'resistance_comp_correction', 70.0, ...
'stimulus_description', 'N/A', ...
'sweep_number', uint64(15) ...
);
nwbfile.acquisition.set('vcs', vcs);

Adding an intracellular recording

The IntracellularRecordingsTable relates electrode, stimulus and response pairs and describes metadata specific to individual recordings.
Illustration of the structure of the IntracellularRecordingsTable
ic_rec_table = types.core.IntracellularRecordingsTable( ...
'categories', {'electrodes', 'stimuli', 'responses'}, ...
'colnames', {'recordings_tag'}, ...
'description', [ ...
'A table to group together a stimulus and response from a single ', ...
'electrode and a single simultaneous recording and for storing ', ...
'metadata about the intracellular recording.'], ...
'id', types.hdmf_common.ElementIdentifiers('data', int64([0, 1, 2])), ...
'recordings_tag', types.hdmf_common.VectorData( ...
'data', repmat({'Tag'}, 3, 1), ...
'description', 'Column for storing a custom recordings tag' ...
) ...
);
ic_rec_table.electrodes = types.core.IntracellularElectrodesTable( ...
'description', 'Table for storing intracellular electrode related metadata.', ...
'colnames', {'electrode'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64([0, 1, 2]) ...
), ...
'electrode', types.hdmf_common.VectorData( ...
'data', repmat(types.untyped.ObjectView(electrode), 3, 1), ...
'description', 'Column for storing the reference to the intracellular electrode' ...
) ...
);
ic_rec_table.stimuli = types.core.IntracellularStimuliTable( ...
'description', 'Table for storing intracellular stimulus related metadata.', ...
'colnames', {'stimulus'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64([0, 1, 2]) ...
), ...
'stimulus', types.core.TimeSeriesReferenceVectorData( ...
'description', 'Column storing the reference to the recorded stimulus for the recording (rows)', ...
'data', struct( ...
'idx_start', [0, 1, -1], ...
'count', [5, 3, -1], ...
'timeseries', [ ...
types.untyped.ObjectView(ccss), ...
types.untyped.ObjectView(ccss), ...
types.untyped.ObjectView(vcs) ...
] ...
)...
)...
);
ic_rec_table.responses = types.core.IntracellularResponsesTable( ...
'description', 'Table for storing intracellular response related metadata.', ...
'colnames', {'response'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64([0, 1, 2]) ...
), ...
'response', types.core.TimeSeriesReferenceVectorData( ...
'description', 'Column storing the reference to the recorded response for the recording (rows)', ...
'data', struct( ...
'idx_start', [0, 2, 0], ...
'count', [5, 3, 5], ...
'timeseries', [ ...
types.untyped.ObjectView(vcs), ...
types.untyped.ObjectView(vcs), ...
types.untyped.ObjectView(vcs) ...
] ...
)...
)...
);
The IntracellularRecordingsTable table is not just a DynamicTable but an AlignedDynamicTable. The AlignedDynamicTable type is itself a DynamicTable that may contain an arbitrary number of additional DynamicTable, each of which defines a "category." This is similar to a table with “sub-headings”. In the case of the IntracellularRecordingsTable, we have three predefined categories, i.e., electrodes, stimuli, and responses. We can also dynamically add new categories to the table. As each category corresponds to a DynamicTable, this means we have to create a new DynamicTable and add it to our table.
% add category
ic_rec_table.categories = [ic_rec_table.categories, {'recording_lab_data'}];
ic_rec_table.dynamictable.set( ...
'recording_lab_data', types.hdmf_common.DynamicTable( ...
'description', 'category table for lab-specific recording metadata', ...
'colnames', {'location'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64([0, 1, 2]) ...
), ...
'location', types.hdmf_common.VectorData( ...
'data', {'Mordor', 'Gondor', 'Rohan'}, ...
'description', 'Recording location in Middle Earth' ...
) ...
) ...
);
In an AlignedDynamicTable all category tables must align with the main table, i.e., all tables must have the same number of rows and rows are expected to correspond to each other by index.
We can also add custom columns to any of the subcategory tables, i.e., the electrodes, stimuli, and responses tables, and any custom subcategory tables. All we need to do is indicate the name of the category we want to add the column to.
% Add voltage threshold as column of electrodes table
ic_rec_table.electrodes.colnames = [ic_rec_table.electrodes.colnames {'voltage_threshold'}];
ic_rec_table.electrodes.vectordata.set('voltage_threshold', types.hdmf_common.VectorData( ...
'data', [0.1, 0.12, 0.13], ...
'description', 'Just an example column on the electrodes category table' ...
) ...
);
nwbfile.general_intracellular_ephys_intracellular_recordings = ic_rec_table;

Hierarchical organization of recordings

To describe the organization of intracellular experiments, the metadata is organized hierarchically in a sequence of tables. All of the tables are so-called DynamicTables enabling users to add columns for custom metadata. Storing data in hierarchical tables has the advantage that it allows us to avoid duplication of metadata. E.g., for a single experiment we only need to describe the metadata that is constant across an experimental condition as a single row in the SimultaneousRecordingsTable without having to replicate the same information across all repetitions and sequential-, simultaneous-, and individual intracellular recordings. For analysis, this means that we can easily focus on individual aspects of an experiment while still being able to easily access information about information from related tables. All of these tables are optional, but to use one you must use all of the lower level tables, even if you only need a single row.

Add a simultaneous recording

The SimultaneousRecordingsTable groups intracellular recordings from the IntracellularRecordingsTable together that were recorded simultaneously from different electrodes and/or cells and describes metadata that is constant across the simultaneous recordings. In practice a simultaneous recording is often also referred to as a sweep. This example adds a custom column, "simultaneous_recording_tag."
% create simultaneous recordings table with custom column
% 'simultaneous_recording_tag'
[recordings_vector_data, recordings_vector_index] = util.create_indexed_column( ...
{[0, 1, 2],}, ...
'Column with references to one or more rows in the IntracellularRecordingsTable table', ...
ic_rec_table);
ic_sim_recs_table = types.core.SimultaneousRecordingsTable( ...
'description', [ ...
'A table for grouping different intracellular recordings from ', ...
'the IntracellularRecordingsTable table together that were recorded ', ...
'simultaneously from different electrodes.'...
], ...
'colnames', {'recordings', 'simultaneous_recording_tag'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64(12) ...
), ...
'recordings', recordings_vector_data, ...
'recordings_index', recordings_vector_index, ...
'simultaneous_recording_tag', types.hdmf_common.VectorData( ...
'description', 'A custom tag for simultaneous_recordings', ...
'data', {'LabTag1'} ...
) ...
);
Depending on the lab workflow, it may be useful to add complete columns to a table after we have already populated the table with rows. That would be done like so:
ic_sim_recs_table.colnames = [ic_sim_recs_table.colnames, {'simultaneous_recording_type'}];
ic_sim_recs_table.vectordata.set( ...
'simultaneous_recording_type', types.hdmf_common.VectorData(...
'description', 'Description of the type of simultaneous_recording', ...
'data', {'SimultaneousRecordingType1'} ...
) ...
);
nwbfile.general_intracellular_ephys_simultaneous_recordings = ic_sim_recs_table;

Add a sequential recording

The SequentialRecordingsTable groups simultaneously recorded intracellular recordings from the SimultaneousRecordingsTable together and describes metadata that is constant across the simultaneous recordings. In practice a sequential recording is often also referred to as a sweep sequence. A common use of sequential recordings is to group together simultaneous recordings where a sequence of stimuli of the same type with varying parameters have been presented in a sequence (e.g., a sequence of square waveforms with varying amplitude).
[simultaneous_recordings_vector_data, simultaneous_recordings_vector_index] = util.create_indexed_column( ...
{0,}, ...
'Column with references to one or more rows in the SimultaneousRecordingsTable table', ...
ic_sim_recs_table);
sequential_recordings = types.core.SequentialRecordingsTable( ...
'description', [ ...
'A table for grouping different intracellular recording ', ...
'simultaneous_recordings from the SimultaneousRecordingsTable ', ...
'table together. This is typically used to group together ', ...
'simultaneous_recordings where the a sequence of stimuli of ', ...
'the same type with varying parameters have been presented in ', ...
'a sequence.' ...
], ...
'colnames', {'simultaneous_recordings', 'stimulus_type'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64(15) ...
), ...
'simultaneous_recordings', simultaneous_recordings_vector_data, ...
'simultaneous_recordings_index', simultaneous_recordings_vector_index, ...
'stimulus_type', types.hdmf_common.VectorData( ...
'description', 'Column storing the type of stimulus used for the sequential recording', ...
'data', {'square'} ...
) ...
);
nwbfile.general_intracellular_ephys_sequential_recordings = sequential_recordings;

Add repetitions table

The RepetitionsTable groups sequential recordings from the SequentialRecordingsTable. In practice, a repetition is often also referred to a run. A typical use of the RepetitionsTable is to group sets of different stimuli that are applied in sequence that may be repeated.
[sequential_recordings_vector_data, sequential_recordings_vector_index] = util.create_indexed_column( ...
{0,}, ...
'Column with references to one or more rows in the SequentialRecordingsTable table', ...
sequential_recordings);
nwbfile.general_intracellular_ephys_repetitions = types.core.RepetitionsTable( ...
'description', [ ...
'A table for grouping different intracellular recording sequential ', ...
'recordings together. With each SimultaneousRecording typically ', ...
'representing a particular type of stimulus, the RepetitionsTable ', ...
'table is typically used to group sets of stimuli applied in sequence.' ...
], ...
'colnames', {'sequential_recordings'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64(17) ...
), ...
'sequential_recordings', sequential_recordings_vector_data, ...
'sequential_recordings_index', sequential_recordings_vector_index ...
);

Add experimental condition table

The ExperimentalConditionsTable groups repetitions of intracellular recording from the RepetitionsTable together that belong to the same experimental conditions.
[repetitions_vector_data, repetitions_vector_index] = util.create_indexed_column( ...
{0, 0}, ...
'Column with references to one or more rows in the RepetitionsTable table', ...
nwbfile.general_intracellular_ephys_repetitions);
nwbfile.general_intracellular_ephys_experimental_conditions = types.core.ExperimentalConditionsTable( ...
'description', [ ...
'A table for grouping different intracellular recording ', ...
'repetitions together that belong to the same experimental ', ...
'conditions.' ...
], ...
'colnames', {'repetitions', 'tag'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64([19, 21]) ...
), ...
'repetitions', repetitions_vector_data, ...
'repetitions_index', repetitions_vector_index, ...
'tag', types.hdmf_common.VectorData( ...
'description', 'integer tag for a experimental condition', ...
'data', [1,3] ...
) ...
);

Write the NWB file

nwbExport(nwbfile, 'test_new_icephys.nwb');

Read the NWB file

nwbfile2 = nwbRead('test_new_icephys.nwb', 'ignorecache')
nwbfile2 =
NwbFile with properties: +Read the NWB file
The following tutorial describes storage of intracellular electrophysiology data in NWB. NWB supports storage of the time series describing the stimulus and response, information about the electrode and device used, as well as metadata about the organization of the experiment.
Illustration of the hierarchy of metadata tables used to describe the organization of intracellular electrophysiology experiments.

Creating an NWBFile

When creating an NWB file, the first step is to create the NWBFile, which you can create using the NwbFile command.
session_start_time = datetime(2018, 3, 1, 12, 0, 0, 'TimeZone', 'local');
 
 
nwbfile = NwbFile( ...
'session_description', 'my first synthetic recording', ...
'identifier', 'EXAMPLE_ID', ...
'session_start_time', session_start_time, ...
'general_experimenter', 'Dr. Bilbo Baggins', ...
'general_lab', 'Bag End Laboratory', ...
'general_institution', 'University of Middle Earth at the Shire', ...
'general_experiment_description', 'I went on an adventure with thirteen dwarves to reclaim vast treasures.', ...
'general_session_id', 'LONELYMTN' ...
);
 

Device metadata

Device metadata is represented by Device objects.
 
device = types.core.Device();
nwbfile.general_devices.set('Heka ITC-1600', device);

Electrode metadata

Intracellular electrode metadata is represented by IntracellularElectrode objects. Create an electrode object, which requires a link to the device of the previous step. Then add it to the NWB file.
electrode = types.core.IntracellularElectrode( ...
'description', 'a mock intracellular electrode', ...
'device', types.untyped.SoftLink(device), ...
'cell_id', 'a very interesting cell' ...
);
nwbfile.general_intracellular_ephys.set('elec0', electrode);

Stimulus and response data

Intracellular stimulus and response data are represented with subclasses of PatchClampSeries. A stimulus is described by a time series representing voltage or current stimulation with a particular set of parameters. There are two classes for representing stimulus data:
The response is then described by a time series representing voltage or current recorded from a single cell using a single intracellular electrode via one of the following classes:
Below we create a simple example stimulus/response recording data pair.
ccss = types.core.VoltageClampStimulusSeries( ...
'data', [1, 2, 3, 4, 5], ...
'starting_time', 123.6, ...
'starting_time_rate', 10e3, ...
'electrode', types.untyped.SoftLink(electrode), ...
'gain', 0.02, ...
'sweep_number', uint64(15), ...
'stimulus_description', 'N/A' ...
);
 
nwbfile.stimulus_presentation.set('ccss', ccss);
 
vcs = types.core.VoltageClampSeries( ...
'data', [0.1, 0.2, 0.3, 0.4, 0.5], ...
'data_conversion', 1e-12, ...
'data_resolution', NaN, ...
'starting_time', 123.6, ...
'starting_time_rate', 20e3, ...
'electrode', types.untyped.SoftLink(electrode), ...
'gain', 0.02, ...
'capacitance_slow', 100e-12, ...
'resistance_comp_correction', 70.0, ...
'stimulus_description', 'N/A', ...
'sweep_number', uint64(15) ...
);
nwbfile.acquisition.set('vcs', vcs);

Adding an intracellular recording

The IntracellularRecordingsTable relates electrode, stimulus and response pairs and describes metadata specific to individual recordings.
Illustration of the structure of the IntracellularRecordingsTable
ic_rec_table = types.core.IntracellularRecordingsTable( ...
'categories', {'electrodes', 'stimuli', 'responses'}, ...
'colnames', {'recordings_tag'}, ...
'description', [ ...
'A table to group together a stimulus and response from a single ', ...
'electrode and a single simultaneous recording and for storing ', ...
'metadata about the intracellular recording.'], ...
'id', types.hdmf_common.ElementIdentifiers('data', int64([0, 1, 2])), ...
'recordings_tag', types.hdmf_common.VectorData( ...
'data', repmat({'Tag'}, 3, 1), ...
'description', 'Column for storing a custom recordings tag' ...
) ...
);
 
ic_rec_table.electrodes = types.core.IntracellularElectrodesTable( ...
'description', 'Table for storing intracellular electrode related metadata.', ...
'colnames', {'electrode'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64([0, 1, 2]) ...
), ...
'electrode', types.hdmf_common.VectorData( ...
'data', repmat(types.untyped.ObjectView(electrode), 3, 1), ...
'description', 'Column for storing the reference to the intracellular electrode' ...
) ...
);
 
ic_rec_table.stimuli = types.core.IntracellularStimuliTable( ...
'description', 'Table for storing intracellular stimulus related metadata.', ...
'colnames', {'stimulus'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64([0, 1, 2]) ...
), ...
'stimulus', types.core.TimeSeriesReferenceVectorData( ...
'description', 'Column storing the reference to the recorded stimulus for the recording (rows)', ...
'data', struct( ...
'idx_start', [0, 1, -1], ...
'count', [5, 3, -1], ...
'timeseries', [ ...
types.untyped.ObjectView(ccss), ...
types.untyped.ObjectView(ccss), ...
types.untyped.ObjectView(vcs) ...
] ...
)...
)...
);
 
ic_rec_table.responses = types.core.IntracellularResponsesTable( ...
'description', 'Table for storing intracellular response related metadata.', ...
'colnames', {'response'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64([0, 1, 2]) ...
), ...
'response', types.core.TimeSeriesReferenceVectorData( ...
'description', 'Column storing the reference to the recorded response for the recording (rows)', ...
'data', struct( ...
'idx_start', [0, 2, 0], ...
'count', [5, 3, 5], ...
'timeseries', [ ...
types.untyped.ObjectView(vcs), ...
types.untyped.ObjectView(vcs), ...
types.untyped.ObjectView(vcs) ...
] ...
)...
)...
);
 
The IntracellularRecordingsTable table is not just a DynamicTable but an AlignedDynamicTable. The AlignedDynamicTable type is itself a DynamicTable that may contain an arbitrary number of additional DynamicTable, each of which defines a "category." This is similar to a table with “sub-headings”. In the case of the IntracellularRecordingsTable, we have three predefined categories, i.e., electrodes, stimuli, and responses. We can also dynamically add new categories to the table. As each category corresponds to a DynamicTable, this means we have to create a new DynamicTable and add it to our table.
% add category
ic_rec_table.categories = [ic_rec_table.categories, {'recording_lab_data'}];
ic_rec_table.dynamictable.set( ...
'recording_lab_data', types.hdmf_common.DynamicTable( ...
'description', 'category table for lab-specific recording metadata', ...
'colnames', {'location'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64([0, 1, 2]) ...
), ...
'location', types.hdmf_common.VectorData( ...
'data', {'Mordor', 'Gondor', 'Rohan'}, ...
'description', 'Recording location in Middle Earth' ...
) ...
) ...
);
In an AlignedDynamicTable all category tables must align with the main table, i.e., all tables must have the same number of rows and rows are expected to correspond to each other by index.
We can also add custom columns to any of the subcategory tables, i.e., the electrodes, stimuli, and responses tables, and any custom subcategory tables. All we need to do is indicate the name of the category we want to add the column to.
% Add voltage threshold as column of electrodes table
ic_rec_table.electrodes.colnames = [ic_rec_table.electrodes.colnames {'voltage_threshold'}];
ic_rec_table.electrodes.vectordata.set('voltage_threshold', types.hdmf_common.VectorData( ...
'data', [0.1, 0.12, 0.13], ...
'description', 'Just an example column on the electrodes category table' ...
) ...
);
 
nwbfile.general_intracellular_ephys_intracellular_recordings = ic_rec_table;

Hierarchical organization of recordings

To describe the organization of intracellular experiments, the metadata is organized hierarchically in a sequence of tables. All of the tables are so-called DynamicTables enabling users to add columns for custom metadata. Storing data in hierarchical tables has the advantage that it allows us to avoid duplication of metadata. E.g., for a single experiment we only need to describe the metadata that is constant across an experimental condition as a single row in the SimultaneousRecordingsTable without having to replicate the same information across all repetitions and sequential-, simultaneous-, and individual intracellular recordings. For analysis, this means that we can easily focus on individual aspects of an experiment while still being able to easily access information about information from related tables. All of these tables are optional, but to use one you must use all of the lower level tables, even if you only need a single row.

Add a simultaneous recording

The SimultaneousRecordingsTable groups intracellular recordings from the IntracellularRecordingsTable together that were recorded simultaneously from different electrodes and/or cells and describes metadata that is constant across the simultaneous recordings. In practice a simultaneous recording is often also referred to as a sweep. This example adds a custom column, "simultaneous_recording_tag."
% create simultaneous recordings table with custom column
% 'simultaneous_recording_tag'
 
[recordings_vector_data, recordings_vector_index] = util.create_indexed_column( ...
{[0, 1, 2],}, ...
'Column with references to one or more rows in the IntracellularRecordingsTable table', ...
ic_rec_table);
 
ic_sim_recs_table = types.core.SimultaneousRecordingsTable( ...
'description', [ ...
'A table for grouping different intracellular recordings from ', ...
'the IntracellularRecordingsTable table together that were recorded ', ...
'simultaneously from different electrodes.'...
], ...
'colnames', {'recordings', 'simultaneous_recording_tag'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64(12) ...
), ...
'recordings', recordings_vector_data, ...
'recordings_index', recordings_vector_index, ...
'simultaneous_recording_tag', types.hdmf_common.VectorData( ...
'description', 'A custom tag for simultaneous_recordings', ...
'data', {'LabTag1'} ...
) ...
);
 
Depending on the lab workflow, it may be useful to add complete columns to a table after we have already populated the table with rows. That would be done like so:
ic_sim_recs_table.colnames = [ic_sim_recs_table.colnames, {'simultaneous_recording_type'}];
ic_sim_recs_table.vectordata.set( ...
'simultaneous_recording_type', types.hdmf_common.VectorData(...
'description', 'Description of the type of simultaneous_recording', ...
'data', {'SimultaneousRecordingType1'} ...
) ...
);
 
nwbfile.general_intracellular_ephys_simultaneous_recordings = ic_sim_recs_table;

Add a sequential recording

The SequentialRecordingsTable groups simultaneously recorded intracellular recordings from the SimultaneousRecordingsTable together and describes metadata that is constant across the simultaneous recordings. In practice a sequential recording is often also referred to as a sweep sequence. A common use of sequential recordings is to group together simultaneous recordings where a sequence of stimuli of the same type with varying parameters have been presented in a sequence (e.g., a sequence of square waveforms with varying amplitude).
[simultaneous_recordings_vector_data, simultaneous_recordings_vector_index] = util.create_indexed_column( ...
{0,}, ...
'Column with references to one or more rows in the SimultaneousRecordingsTable table', ...
ic_sim_recs_table);
 
sequential_recordings = types.core.SequentialRecordingsTable( ...
'description', [ ...
'A table for grouping different intracellular recording ', ...
'simultaneous_recordings from the SimultaneousRecordingsTable ', ...
'table together. This is typically used to group together ', ...
'simultaneous_recordings where the a sequence of stimuli of ', ...
'the same type with varying parameters have been presented in ', ...
'a sequence.' ...
], ...
'colnames', {'simultaneous_recordings', 'stimulus_type'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64(15) ...
), ...
'simultaneous_recordings', simultaneous_recordings_vector_data, ...
'simultaneous_recordings_index', simultaneous_recordings_vector_index, ...
'stimulus_type', types.hdmf_common.VectorData( ...
'description', 'Column storing the type of stimulus used for the sequential recording', ...
'data', {'square'} ...
) ...
);
 
nwbfile.general_intracellular_ephys_sequential_recordings = sequential_recordings;

Add repetitions table

The RepetitionsTable groups sequential recordings from the SequentialRecordingsTable. In practice, a repetition is often also referred to a run. A typical use of the RepetitionsTable is to group sets of different stimuli that are applied in sequence that may be repeated.
[sequential_recordings_vector_data, sequential_recordings_vector_index] = util.create_indexed_column( ...
{0,}, ...
'Column with references to one or more rows in the SequentialRecordingsTable table', ...
sequential_recordings);
 
 
nwbfile.general_intracellular_ephys_repetitions = types.core.RepetitionsTable( ...
'description', [ ...
'A table for grouping different intracellular recording sequential ', ...
'recordings together. With each SimultaneousRecording typically ', ...
'representing a particular type of stimulus, the RepetitionsTable ', ...
'table is typically used to group sets of stimuli applied in sequence.' ...
], ...
'colnames', {'sequential_recordings'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64(17) ...
), ...
'sequential_recordings', sequential_recordings_vector_data, ...
'sequential_recordings_index', sequential_recordings_vector_index ...
);

Add experimental condition table

The ExperimentalConditionsTable groups repetitions of intracellular recording from the RepetitionsTable together that belong to the same experimental conditions.
[repetitions_vector_data, repetitions_vector_index] = util.create_indexed_column( ...
{0, 0}, ...
'Column with references to one or more rows in the RepetitionsTable table', ...
nwbfile.general_intracellular_ephys_repetitions);
 
nwbfile.general_intracellular_ephys_experimental_conditions = types.core.ExperimentalConditionsTable( ...
'description', [ ...
'A table for grouping different intracellular recording ', ...
'repetitions together that belong to the same experimental ', ...
'conditions.' ...
], ...
'colnames', {'repetitions', 'tag'}, ...
'id', types.hdmf_common.ElementIdentifiers( ...
'data', int64([19, 21]) ...
), ...
'repetitions', repetitions_vector_data, ...
'repetitions_index', repetitions_vector_index, ...
'tag', types.hdmf_common.VectorData( ...
'description', 'integer tag for a experimental condition', ...
'data', [1,3] ...
) ...
);

Write the NWB file

nwbExport(nwbfile, 'test_new_icephys.nwb');

Read the NWB file

nwbfile2 = nwbRead('test_new_icephys.nwb', 'ignorecache')
nwbfile2 =
NwbFile with properties: - nwb_version: {'2.6.0'} + nwb_version: '2.7.0' file_create_date: [1×1 types.untyped.DataStub] - identifier: {'EXAMPLE_ID'} - session_description: {'my first synthetic recording'} - session_start_time: 2018-03-01T12:00:00.000000-05:00 - timestamps_reference_time: 2018-03-01T12:00:00.000000-05:00 + identifier: 'EXAMPLE_ID' + session_description: 'my first synthetic recording' + session_start_time: [1×1 types.untyped.DataStub] + timestamps_reference_time: [1×1 types.untyped.DataStub] acquisition: [1×1 types.untyped.Set] analysis: [0×1 types.untyped.Set] general: [0×1 types.untyped.Set] - general_data_collection: [] + general_data_collection: '' general_devices: [1×1 types.untyped.Set] - general_experiment_description: {'I went on an adventure with thirteen dwarves to reclaim vast treasures.'} + general_experiment_description: 'I went on an adventure with thirteen dwarves to reclaim vast treasures.' general_experimenter: [1×1 types.untyped.DataStub] general_extracellular_ephys: [0×1 types.untyped.Set] general_extracellular_ephys_electrodes: [] - general_institution: {'University of Middle Earth at the Shire'} + general_institution: 'University of Middle Earth at the Shire' general_intracellular_ephys: [1×1 types.untyped.Set] general_intracellular_ephys_experimental_conditions: [1×1 types.core.ExperimentalConditionsTable] - general_intracellular_ephys_filtering: [] + general_intracellular_ephys_filtering: '' general_intracellular_ephys_intracellular_recordings: [1×1 types.core.IntracellularRecordingsTable] general_intracellular_ephys_repetitions: [1×1 types.core.RepetitionsTable] general_intracellular_ephys_sequential_recordings: [1×1 types.core.SequentialRecordingsTable] general_intracellular_ephys_simultaneous_recordings: [1×1 types.core.SimultaneousRecordingsTable] general_intracellular_ephys_sweep_table: [] - general_keywords: [] - general_lab: {'Bag End Laboratory'} - general_notes: [] + general_keywords: '' + general_lab: 'Bag End Laboratory' + general_notes: '' general_optogenetics: [0×1 types.untyped.Set] general_optophysiology: [0×1 types.untyped.Set] - general_pharmacology: [] - general_protocol: [] - general_related_publications: [] - general_session_id: {'LONELYMTN'} - general_slices: [] - general_source_script: [] - general_source_script_file_name: [] - general_stimulus: [] + general_pharmacology: '' + general_protocol: '' + general_related_publications: '' + general_session_id: 'LONELYMTN' + general_slices: '' + general_source_script: '' + general_source_script_file_name: '' + general_stimulus: '' general_subject: [] - general_surgery: [] - general_virus: [] + general_surgery: '' + general_virus: '' intervals: [0×1 types.untyped.Set] intervals_epochs: [] intervals_invalid_times: [] @@ -98,10 +103,12 @@ stimulus_presentation: [1×1 types.untyped.Set] stimulus_templates: [0×1 types.untyped.Set] units: [] -

+
+
\ No newline at end of file +--> +
\ No newline at end of file diff --git a/tutorials/html/images.html b/tutorials/html/images.html index 7f7c05fc..bbdaecdf 100644 --- a/tutorials/html/images.html +++ b/tutorials/html/images.html @@ -39,20 +39,20 @@ .S12 { 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; } .S13 { 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; } .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: 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; }

Storing Image Data in NWB

Image data can be a collection of individual images or movie segments (as a movie is simply a series of images), about the subject, the environment, the presented stimuli, or other parts related to the experiment. This tutorial focuses in particular on the usage of:

Create an NWB File

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', 'LastName, FirstName', ... % 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: +.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; }

Storing Image Data in NWB

Image data can be a collection of individual images or movie segments (as a movie is simply a series of images), about the subject, the environment, the presented stimuli, or other parts related to the experiment. This tutorial focuses in particular on the usage of:

Create an NWB File

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', 'LastName, FirstName', ... % 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' @@ -101,7 +101,7 @@ stimulus_presentation: [0×1 types.untyped.Set] stimulus_templates: [0×1 types.untyped.Set] units: [] -

OpticalSeries: Storing series of images as stimuli

OpticalSeries is for time series of images that were presented to the subject as stimuli. We will create an OpticalSeries object with the name "StimulusPresentation" representing what images were shown to the subject and at what times.
Image data can be stored either in the HDF5 file or as an external image file. For this tutorial, we will use fake image data with shape of ('RGB', 'y', 'x', 'time') = (200, 50, 50, 3). As in all TimeSeries, the first dimension is time. The second and third dimensions represent x and y. The fourth dimension represents the RGB value (length of 3) for color images.
NWB differentiates between acquired data and data that was presented as stimulus. We can add it to the NWBFile object as stimulus data.
If the sampling rate is constant, use rate and starting_time to specify time. For irregularly sampled recordings, use timestamps to specify time for each sample image.
image_data = randi(255, [3, 50, 50, 200]);
optical_series = types.core.OpticalSeries( ...
'distance', 0.7, ... % required
'field_of_view', [0.2, 0.3, 0.7], ... % required
'orientation', 'lower left', ... % required
'data', image_data, ...
'data_unit', 'n.a.', ...
'starting_time_rate', 1.0, ...
'starting_time', 0.0, ...
'description', 'The images presented to the subject as stimuli' ...
);
 
nwb.stimulus_presentation.set('StimulusPresentation', optical_series);

AbstractFeatureSeries: Storing features of visual stimuli

While it is usually recommended to store the entire image data as an OpticalSeries, sometimes it is useful to store features of the visual stimuli instead of or in addition to the raw image data. For example, you may want to store the mean luminance of the image, the contrast, or the spatial frequency. This can be done using an instance of AbstractFeatureSeries. This class is a general container for storing time series of features that are derived from the raw image data.
% Create some fake feature data
feature_data = rand(200, 3); % 200 time points, 3 features
 
% Create an AbstractFeatureSeries object
abstract_feature_series = types.core.AbstractFeatureSeries( ...
'data', feature_data, ...
'timestamps', linspace(0, 1, 200), ...
'description', 'Features of the visual stimuli', ...
'features', {'luminance', 'contrast', 'spatial frequency'}, ...
'feature_units', {'n.a.', 'n.a.', 'cycles/degree'} ...
);
% Add the AbstractFeatureSeries to the NWBFile
nwb.stimulus_presentation.set('StimulusFeatures', abstract_feature_series);

ImageSeries: Storing series of images as acquisition

ImageSeries is a general container for time series of images acquired during the experiment. Image data can be stored either in the HDF5 file or as an external image file. When color images are stored in the HDF5 file the color channel order is expected to be RGB.
image_data = randi(255, [3, 50, 50, 200]);
behavior_images = types.core.ImageSeries( ...
'data', image_data, ...
'description', 'Image data of an animal in environment', ...
'data_unit', 'n.a.', ...
'starting_time_rate', 1.0, ...
'starting_time', 0.0 ...
);
 
nwb.acquisition.set('ImageSeries', behavior_images);

External Files

External files (e.g. video files of the behaving animal) can be added to the NWBFile by creating an ImageSeries object using the external_file attribute that specifies the path to the external file(s) on disk. The file(s) path must be relative to the path of the NWB file. Either external_file or data must be specified, but not both. external_file can be a cell array of multiple video files.
The starting_frame attribute serves as an index to indicate the starting frame of each external file, allowing you to skip the beginning of videos.
external_files = {'video1.pmp4', 'video2.pmp4'};
 
timestamps = [0.0, 0.04, 0.07, 0.1, 0.14, 0.16, 0.21];
behavior_external_file = types.core.ImageSeries( ...
'description', 'Behavior video of animal moving in environment', ...
'data_unit', 'n.a.', ...
'external_file', external_files, ...
'format', 'external', ...
'external_file_starting_frame', [0, 2, 4], ...
'timestamps', timestamps ...
);
 
nwb.acquisition.set('ExternalVideos', behavior_external_file);

Static Images

Static images can be stored in an NWBFile object by creating an RGBAImage, RGBImage or GrayscaleImage object with the image data. All of these image types provide an optional description parameter to include text description about the image and the resolution parameter to specify the pixels/cm resolution of the image.

RGBAImage: for color images with transparency

RGBAImage is for storing data of color image with transparency. data must be 3D where the first and second dimensions represent x and y. The third dimension has length 4 and represents the RGBA value.
image_data = randi(255, [4, 200, 200]);
 
rgba_image = types.core.RGBAImage( ...
'data', image_data, ... % required
'resolution', 70.0, ...
'description', 'RGBA image' ...
);

RGBImage: for color images

RGBImage is for storing data of RGB color image. data must be 3D where the first and second dimensions represent x and y. The third dimension has length 3 and represents the RGB value.
image_data = randi(255, [3, 200, 200]);
 
rgb_image = types.core.RGBImage( ...
'data', image_data, ... % required
'resolution', 70.0, ...
'description', 'RGB image' ...
);

GrayscaleImage: for grayscale images

GrayscaleImage is for storing grayscale image data. data must be 2D where the first and second dimensions represent x and y.
image_data = randi(255, [200, 200]);
 
grayscale_image = types.core.GrayscaleImage( ...
'data', image_data, ... % required
'resolution', 70.0, ...
'description', 'Grayscale image' ...
);

Images: a container for images

Add the images to an Images container that accepts any of these image types.
image_collection = types.core.Images( ...
'description', 'A collection of logo images presented to the subject.'...
);
 
image_collection.image.set('rgba_image', rgba_image);
image_collection.image.set('rgb_image', rgb_image);
image_collection.image.set('grayscale_image', grayscale_image);
 
nwb.acquisition.set('image_collection', image_collection);

Index Series for Repeated Images

You may want to set up a time series of images where some images are repeated many times. You could create an ImageSeries that repeats the data each time the image is shown, but that would be inefficient, because it would store the same data multiple times. A better solution would be to store the unique images once and reference those images. This is how IndexSeries works. First, create an Images container with the order of images defined using an ImageReferences. Then create an IndexSeries that indexes into the Images.
rgbImage = imread('street2.jpg');
grayImage = uint8(sum(double(rgbImage), 3) ./ double(max(max(max(rgbImage)))));
GsStreet = types.core.GrayscaleImage(...
'data', grayImage, ...
'description', 'grayscale image of a street.', ...
'resolution', 28 ...
);
 
RgbStreet = types.core.RGBImage( ...
'data', rgbImage, ...
'resolution', 28, ...
'description', 'RGB Street' ...
);
 
ImageOrder = types.core.ImageReferences(...
'data', [types.untyped.ObjectView(RgbStreet), types.untyped.ObjectView(GsStreet)] ...
);
Images = types.core.Images( ...
'gs_face', GsStreet, ...
'rgb_face', RgbStreet, ...
'description', 'A collection of streets.', ...
'order_of_images', ImageOrder ...
);
 
types.core.IndexSeries(...
'data', [0, 1, 0, 1], ... % NOTE: 0-indexed
'indexed_images', Images, ...
'timestamps', [0.1, 0.2, 0.3, 0.4] ...
)
ans =
IndexSeries with properties: +

OpticalSeries: Storing series of images as stimuli

OpticalSeries is for time series of images that were presented to the subject as stimuli. We will create an OpticalSeries object with the name "StimulusPresentation" representing what images were shown to the subject and at what times.
Image data can be stored either in the HDF5 file or as an external image file. For this tutorial, we will use fake image data with shape of ('time', 'x', 'y', 'RGB') = (200, 50, 50, 3). As in all TimeSeries, the first dimension is time. The second and third dimensions represent x and y. The fourth dimension represents the RGB value (length of 3) for color images. Please note: 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, we will need to create a transposed array, where the dimensions are in reverse order compared to the type specification.
NWB differentiates between acquired data and data that was presented as stimulus. We can add it to the NWBFile object as stimulus data.
If the sampling rate is constant, use rate and starting_time to specify time. For irregularly sampled recordings, use timestamps to specify time for each sample image.
image_data = randi(255, [3, 50, 50, 200]); % NB: Array is transposed
optical_series = types.core.OpticalSeries( ...
'distance', 0.7, ... % required
'field_of_view', [0.2, 0.3, 0.7], ... % required
'orientation', 'lower left', ... % required
'data', image_data, ...
'data_unit', 'n.a.', ...
'starting_time_rate', 1.0, ...
'starting_time', 0.0, ...
'description', 'The images presented to the subject as stimuli' ...
);
 
nwb.stimulus_presentation.set('StimulusPresentation', optical_series);

AbstractFeatureSeries: Storing features of visual stimuli

While it is usually recommended to store the entire image data as an OpticalSeries, sometimes it is useful to store features of the visual stimuli instead of or in addition to the raw image data. For example, you may want to store the mean luminance of the image, the contrast, or the spatial frequency. This can be done using an instance of AbstractFeatureSeries. This class is a general container for storing time series of features that are derived from the raw image data.
% Create some fake feature data
feature_data = rand(3, 200); % 200 time points, 3 features
 
% Create an AbstractFeatureSeries object
abstract_feature_series = types.core.AbstractFeatureSeries( ...
'data', feature_data, ...
'timestamps', linspace(0, 1, 200), ...
'description', 'Features of the visual stimuli', ...
'features', {'luminance', 'contrast', 'spatial frequency'}, ...
'feature_units', {'n.a.', 'n.a.', 'cycles/degree'} ...
);
% Add the AbstractFeatureSeries to the NWBFile
nwb.stimulus_presentation.set('StimulusFeatures', abstract_feature_series);

ImageSeries: Storing series of images as acquisition

ImageSeries is a general container for time series of images acquired during the experiment. Image data can be stored either in the HDF5 file or as an external image file. When color images are stored in the HDF5 file the color channel order is expected to be RGB.
image_data = randi(255, [3, 50, 50, 200]);
behavior_images = types.core.ImageSeries( ...
'data', image_data, ...
'description', 'Image data of an animal in environment', ...
'data_unit', 'n.a.', ...
'starting_time_rate', 1.0, ...
'starting_time', 0.0 ...
);
 
nwb.acquisition.set('ImageSeries', behavior_images);

External Files

External files (e.g. video files of the behaving animal) can be added to the NWBFile by creating an ImageSeries object using the external_file attribute that specifies the path to the external file(s) on disk. The file(s) path must be relative to the path of the NWB file. Either external_file or data must be specified, but not both. external_file can be a cell array of multiple video files.
The starting_frame attribute serves as an index to indicate the starting frame of each external file, allowing you to skip the beginning of videos.
external_files = {'video1.pmp4', 'video2.pmp4'};
 
timestamps = [0.0, 0.04, 0.07, 0.1, 0.14, 0.16, 0.21];
behavior_external_file = types.core.ImageSeries( ...
'description', 'Behavior video of animal moving in environment', ...
'data_unit', 'n.a.', ...
'external_file', external_files, ...
'format', 'external', ...
'external_file_starting_frame', [0, 2, 4], ...
'timestamps', timestamps ...
);
 
nwb.acquisition.set('ExternalVideos', behavior_external_file);

Static Images

Static images can be stored in an NWBFile object by creating an RGBAImage, RGBImage or GrayscaleImage object with the image data. All of these image types provide an optional description parameter to include text description about the image and the resolution parameter to specify the pixels/cm resolution of the image.

RGBAImage: for color images with transparency

RGBAImage is for storing data of color image with transparency. data must be 3D where the first and second dimensions represent x and y. The third dimension has length 4 and represents the RGBA value.
image_data = randi(255, [4, 200, 200]);
 
rgba_image = types.core.RGBAImage( ...
'data', image_data, ... % required
'resolution', 70.0, ...
'description', 'RGBA image' ...
);

RGBImage: for color images

RGBImage is for storing data of RGB color image. data must be 3D where the first and second dimensions represent x and y. The third dimension has length 3 and represents the RGB value.
image_data = randi(255, [3, 200, 200]);
 
rgb_image = types.core.RGBImage( ...
'data', image_data, ... % required
'resolution', 70.0, ...
'description', 'RGB image' ...
);

GrayscaleImage: for grayscale images

GrayscaleImage is for storing grayscale image data. data must be 2D where the first and second dimensions represent x and y.
image_data = randi(255, [200, 200]);
 
grayscale_image = types.core.GrayscaleImage( ...
'data', image_data, ... % required
'resolution', 70.0, ...
'description', 'Grayscale image' ...
);

Images: a container for images

Add the images to an Images container that accepts any of these image types.
image_collection = types.core.Images( ...
'description', 'A collection of logo images presented to the subject.'...
);
 
image_collection.image.set('rgba_image', rgba_image);
image_collection.image.set('rgb_image', rgb_image);
image_collection.image.set('grayscale_image', grayscale_image);
 
nwb.acquisition.set('image_collection', image_collection);

Index Series for Repeated Images

You may want to set up a time series of images where some images are repeated many times. You could create an ImageSeries that repeats the data each time the image is shown, but that would be inefficient, because it would store the same data multiple times. A better solution would be to store the unique images once and reference those images. This is how IndexSeries works. First, create an Images container with the order of images defined using an ImageReferences. Then create an IndexSeries that indexes into the Images.
rgbImage = imread('street2.jpg');
grayImage = uint8(sum(double(rgbImage), 3) ./ double(max(max(max(rgbImage)))));
GsStreet = types.core.GrayscaleImage(...
'data', grayImage, ...
'description', 'grayscale image of a street.', ...
'resolution', 28 ...
);
 
RgbStreet = types.core.RGBImage( ...
'data', rgbImage, ...
'resolution', 28, ...
'description', 'RGB Street' ...
);
 
ImageOrder = types.core.ImageReferences(...
'data', [types.untyped.ObjectView(RgbStreet), types.untyped.ObjectView(GsStreet)] ...
);
Images = types.core.Images( ...
'gs_face', GsStreet, ...
'rgb_face', RgbStreet, ...
'description', 'A collection of streets.', ...
'order_of_images', ImageOrder ...
);
 
types.core.IndexSeries(...
'data', [0, 1, 0, 1], ... % NOTE: 0-indexed
'indexed_images', Images, ...
'timestamps', [0.1, 0.2, 0.3, 0.4] ...
)
ans =
IndexSeries with properties: indexed_images: [1×1 types.core.Images] indexed_timeseries: [] @@ -121,7 +121,7 @@ starting_time: [] starting_time_rate: [] timestamps: [0.1000 0.2000 0.3000 0.4000] -
Here data contains the (0-indexed) index of the displayed image as they are ordered in the ImageReference.

Writing the images to an NWB File

Now use nwbExport to write the file.
nwbExport(nwb, "images_test.nwb");
+
Here data contains the (0-indexed) index of the displayed image as they are ordered in the ImageReference.

Writing the images to an NWB File

Now use nwbExport to write the file.
nwbExport(nwb, "images_test.nwb");

\ No newline at end of file diff --git a/tutorials/private/mcode/ecephys.m b/tutorials/private/mcode/ecephys.m index 73c6793c..2258695c 100644 --- a/tutorials/private/mcode/ecephys.m +++ b/tutorials/private/mcode/ecephys.m @@ -1,22 +1,30 @@ %% 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: +%% About This Tutorial +% This tutorial describes storage of hypothetical data from extracellular electrophysiology +% experiments in NWB for the following data categories: %% -% * Voltage recording -% * Local field potential (LFP) +% * Raw voltage recording +% * Local field potential (LFP) and filtered electrical signals % * Spike times -%% +%% Before You Begin % It is recommended to first work through the , 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. +% +% *Important*: The dimensions of timeseries data in MatNWB should be defined +% in the opposite order of how it is defined in the nwb-schemas. In NWB, time +% is always stored in the first dimension of the data, whereas in MatNWB time +% should be stored in the last dimension of the data. This is explained in more +% detail here: HDF5 Dimension Mapping>. %% 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 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. +% a |session_description|, |identifier|, and |session_start_time|. Create a new +% +% object these required fields along with any additional metadata. In MatNWB, +% arguments are specified using MATLAB's keyword argument pair convention, where +% each argument name is followed by its value. nwb = NwbFile( ... 'session_description', 'mouse in open exploration',... @@ -28,13 +36,13 @@ 'general_institution', 'University of My Institution', ... % optional 'general_related_publications', {'DOI:10.1016/j.neuron.2016.12.011'}); % optional nwb -%% Extracellular Electrophysiology +%% Electrode Information % 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 . |electrodes| has several required fields: |x|, |y|, |z|, % |impedance|, |location|, |filtering|, and |electrode_group|. -%% Electrodes Table +% Electrodes Table % % % Since this is a objects. The |electrodes| table then uses an |ObjectView| % in each row to link to the corresponding 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 objects. is a subclass of specialized for voltage data. In order to create our 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 object. An |ObjectView| is a construct that enables linking +% one neurodata type to another, allowing a neurodata type to reference another +% within the NWB file. +%% Recorded Extracellular Signals +% Voltage data are stored using the class, a subclass of the class specialized for voltage data. +% Referencing Electrodes +% In order to create our object, we first need to reference a set of rows in the +% |electrodes| table to indicate which electrode (channel) each entry in the electrical +% series were recorded from. We will do this by creating a , which is a type of link that allows you to reference % specific rows of a , such as the |electrodes| table, by row indices. +% |*DynamicTable*|>, such as the |electrodes| table, using row indices. % % Create a that references all rows of the |electrodes| table. electrode_table_region = types.hdmf_common.DynamicTableRegion( ... - 'table', types.untyped.ObjectView(ElectrodesDynamicTable), ... + 'table', types.untyped.ObjectView(electrodesDynamicTable), ... 'description', 'all electrodes', ... - 'data', (0:length(ElectrodesDynamicTable.id.data)-1)'); -%% + 'data', (0:length(electrodesDynamicTable.id.data)-1)'); +% Raw Voltage Data % Now create an |ElectricalSeries| object to hold acquisition data collected % during the experiment. % % -electrical_series = types.core.ElectricalSeries( ... +raw_electrical_series = types.core.ElectricalSeries( ... 'starting_time', 0.0, ... % seconds 'starting_time_rate', 30000., ... % Hz - 'data', randn(12, 3000), ... + 'data', randn(numChannels, 3000), ... % nChannels x nTime '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 . To help data analysis and visualization tools know that % this object represents LFP data, store it inside an |LFP| object, -% then place the |LFP| object in a object represents LFP data, we store it inside an object and then place the object in a named |'ecephys'|. This is analogous to how we stored % the object inside of a object and stored the object in a named |'behavior'| earlier. +% |*ProcessingModule*|> named |'behavior'| in the tutorial % % -electrical_series = types.core.ElectricalSeries( ... +lfp_electrical_series = types.core.ElectricalSeries( ... 'starting_time', 0.0, ... % seconds 'starting_time_rate', 1000., ... % Hz - 'data', randn(12, 100), ... + 'data', randn(numChannels, 100), ... nChannels x nTime + 'filtering', 'Low-pass filter at 300 Hz', ... 'electrodes', electrode_table_region, ... 'data_unit', 'volts'); -lfp = types.core.LFP('ElectricalSeries', electrical_series); +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); -%% Sorted Spike Times -% Ragged Arrays -% Spike times are stored in another of subtype . The default |Units| table is at |/units| in the HDF5 file. You can -% add columns to the table just like you did for |electrodes| and |trials|. Here, we generate -% some random spike data and populate the table. +% Other Types of Filtered Electrical Signals +% If your derived data is filtered for frequency ranges other than LFP—such +% as Gamma or Theta—you should store it in an and encapsulate it within a object instead of the object. + +% Generate filtered data +filtered_data = randn(50, 12); % 50 time points, 12 channels +filtered_data = permute(filtered_data, [2, 1]); % permute timeseries for matnwb + +% Create an ElectricalSeries object +filtered_electrical_series = types.core.ElectricalSeries( ... + 'description', 'Data filtered in the Theta range', ... + 'data', filtered_data, ... + 'electrodes', electrode_table_region, ... + 'filtering', 'Band-pass filtered between 4 and 8 Hz', ... + 'starting_time', 0.0, ... + 'starting_time_rate', 200.0 ... + ); + +% Create a FilteredEphys object and add the filtered electrical series +filtered_ephys = types.core.FilteredEphys(); +filtered_ephys.electricalseries.set('FilteredElectricalSeries', filtered_electrical_series); + +% Add the FilteredEphys object to the ecephys module +ecephys_module.nwbdatainterface.set('FilteredEphys', filtered_ephys); +%% Spike Times and Extracellular Events +% Sorted Spike Times +% Spike times are stored in a table, a specialization of the class. The default table is located at |/units| in the HDF5 file. You can add columns +% to the table just like you did for |electrodes| and |trials| (see ). 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 -%% +% Ragged Arrays % 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 . 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. +% an indexed column of the table. These indexed columns have two components, the object that holds the data and the 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. For more information about ragged arrays, we refer +% you to the *"Ragged Array Columns"* section of the tutorial. % % @@ -184,8 +227,8 @@ ); nwb.units.toTable -%% Unsorted Spike Times -% In MATLAB, while the table is used to store spike times and waveform data for spike-sorted, % single-unit activity, you may also want to store spike times and waveform snippets % of unsorted spiking activity. This is useful for recording multi-unit activity @@ -202,7 +245,7 @@ % Create electrode table region referencing electrodes 0, 1, and 2 shank0_table_region = types.hdmf_common.DynamicTableRegion( ... - 'table', types.untyped.ObjectView(ElectrodesDynamicTable), ... + 'table', types.untyped.ObjectView(electrodesDynamicTable), ... 'description', 'shank0', ... 'data', (0:2)'); @@ -216,23 +259,60 @@ % Add spike event series to NWB file acquisition nwb.acquisition.set('SpikeEvents_Shank0', spike_events); -%% Designating Electrophysiology Data +% Detected Events +% If you need to store the complete, continuous raw voltage traces, along with +% unsorted spike times, you should store the traces in objects in the acquisition group, and use the class to identify the spike events in your raw traces. + +% Create the EventDetection object +event_detection = types.core.EventDetection( ... + 'detection_method', 'thresholding, 1.5 * std', ... + 'source_electricalseries', types.untyped.SoftLink(raw_electrical_series), ... + 'source_idx', [1000; 2000; 3000], ... + 'times', [.033, .066, .099] ... +); + +% Add the EventDetection object to the ecephys module +ecephys_module.nwbdatainterface.set('ThresholdEvents', event_detection); +% Storing Spike Features (e.g Principal Components) +% NWB also provides a way to store features of spikes, such as principal components, +% using the class. + +% Generate random feature data (time x channel x feature) +features = rand(3, 12, 4); % 3 time points, 12 channels, 4 features +features = permute(features, [3,2,1]) % reverse dimension order for matnwb + +% Create the FeatureExtraction object +feature_extraction = types.core.FeatureExtraction( ... + 'description', {'PC1', 'PC2', 'PC3', 'PC4'}, ... % Feature descriptions + 'electrodes', electrode_table_region, ... % DynamicTableRegion referencing the electrodes table + 'times', [.033; .066; .099], ... % Column vector for times + 'features', features ... +); + +% Add the FeatureExtraction object to the ecephys module (if required) +ecephys_module.nwbdatainterface.set('PCA_features', feature_extraction); +%% Choosing NWB-Types for Electrophysiology Data (A Summary) % As mentioned above, objects are meant for storing specific types of extracellular -% recordings. In addition to this class, NWB provides some for designating the type of data you are storing. We will -% briefly discuss them here, and refer the reader to the and for more details on using these objects. +% |*ElectricalSeries*|> objects are meant for storing electrical timeseries data +% like raw voltage signals or processed signals like LFP or other filtered signals. +% In addition to the class, NWB provides some more classes for storing event-based +% electropysiological data. We will briefly discuss them here, and refer the reader +% to the and the section on in the "NWB Format Specification" for more details +% on using these objects. % % For storing unsorted spiking 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 objects as acquisition data, and use the 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 objects. % % The results of spike sorting (or clustering) should be stored in the top-level @@ -240,19 +320,8 @@ % |*Units*|> table. The table can hold just the spike times of sorted units or, optionally, % include additional waveform information. You can use the optional predefined -% columns |waveform_mean|, |waveform_sd|, and |waveforms| in the table to store individual and mean waveform data. -% -% 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 objects. If you are storing unfiltered local field potential -% data, you should store the objects in data interface object(s). If you have filtered LFP data, you should -% store the objects in data interface object(s). %% Writing the NWB File nwbExport(nwb, 'ecephys_tutorial.nwb') @@ -307,5 +376,4 @@ % * % * -%% \ No newline at end of file +% Advanced HDF5 I/O> \ No newline at end of file From d46adae26d9c988e703cb43ee5e9700ac0105819 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Mon, 25 Nov 2024 14:25:49 +0100 Subject: [PATCH 10/16] Add MotionCorrection, CorrectedImageStack and DeltaFOverF to ophys tutorial (#629) * Add MotionCorrection, CorrectedImageStack and DeltaFOverF to ophys tutorial * Fix, add original of CorrectedImageStack as link --- +tests/+util/getPythonPath.m | 2 +- tutorials/html/ophys.html | 176 ++++++++++++++++++++++---------- tutorials/ophys.mlx | Bin 172455 -> 173820 bytes tutorials/private/mcode/ophys.m | 114 +++++++++++++++++---- 4 files changed, 218 insertions(+), 74 deletions(-) diff --git a/+tests/+util/getPythonPath.m b/+tests/+util/getPythonPath.m index b7b3c494..304cdd10 100644 --- a/+tests/+util/getPythonPath.m +++ b/+tests/+util/getPythonPath.m @@ -1,7 +1,7 @@ function pythonPath = getPythonPath() envPath = fullfile('+tests', 'env.mat'); - if isfile(envPath) + if isfile(fullfile(misc.getMatnwbDir, envPath)) Env = load(envPath, '-mat'); if isfield(Env, 'pythonPath') pythonPath = Env.pythonPath; diff --git a/tutorials/html/ophys.html b/tutorials/html/ophys.html index c9cab142..8290f7c9 100644 --- a/tutorials/html/ophys.html +++ b/tutorials/html/ophys.html @@ -39,11 +39,10 @@ .S12 { 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; } .S13 { 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; } .S14 { 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; } -.S15 { margin: 3px 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; } -.S16 { 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; } -.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 { 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; } +.S15 { 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; } +.S16 { 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; } +.S17 { margin: 3px 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; } +.S18 { 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; } .variableValue { width: 100% !important; } .embeddedOutputsMatrixElement,.eoOutputWrapper .matrixElement { min-height: 18px; box-sizing: border-box;} .embeddedOutputsMatrixElement .matrixElement,.eoOutputWrapper .matrixElement,.rtcDataTipElement .matrixElement { position: relative;} @@ -70,25 +69,27 @@ .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==");} -.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; }

MatNWB Optical Physiology Tutorial

Introduction

In this tutorial, we will create fake data for a hypothetical optical physiology experiment with a freely moving animal. The types of data we will convert are:
  • Acquired two-photon images
  • Image segmentation (ROIs)
  • Fluorescence and dF/F response
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.

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. 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', 'LastName, FirstName', ... % 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' +.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: 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; } +.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; }

MatNWB Optical Physiology Tutorial

Introduction

In this tutorial, we will create fake data for a hypothetical optical physiology experiment with a freely moving animal. The types of data we will convert are:
  • Acquired two-photon images
  • Image segmentation (ROIs)
  • Fluorescence and dF/F response
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.
Please note: The dimensions of timeseries data in MatNWB should be defined in the opposite order of how it is defined in the nwb-schemas. In NWB, time is always stored in the first dimension of the data, whereas in MatNWB data should be specified with time along the last dimension. This is explained in more detail here: MatNWB <-> HDF5 Dimension Mapping.

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. 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', 'LastName, FirstName', ... % 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' @@ -137,19 +138,14 @@ stimulus_presentation: [0×1 types.untyped.Set] stimulus_templates: [0×1 types.untyped.Set] units: [] -

Optical Physiology

Optical physiology results are written in four steps:
  1. Create imaging plane
  2. Acquired two-photon images
  3. Image segmentation
  4. Fluorescence and dF/F responses

Imaging Plane

First, you must create an ImagingPlane object, which will hold information about the area and method used to collect the optical imaging data. This requires creation of a Device object for the microscope and an OpticalChannel object. Then you can create an ImagingPlane.
optical_channel = types.core.OpticalChannel( ...
'description', 'description', ...
'emission_lambda', 500.);
 
device = types.core.Device();
nwb.general_devices.set('Device', device);
 
imaging_plane_name = 'imaging_plane';
imaging_plane = types.core.ImagingPlane( ...
'optical_channel', optical_channel, ...
'description', 'a very interesting part of the brain', ...
'device', types.untyped.SoftLink(device), ...
'excitation_lambda', 600., ...
'imaging_rate', 5., ...
'indicator', 'GFP', ...
'location', 'my favorite brain location');
 
nwb.general_optophysiology.set(imaging_plane_name, imaging_plane);

Storing Two-Photon Data

You can create a TwoPhotonSeries class representing two photon imaging data. TwoPhotonSeries, like SpatialSeries, inherits from TimeSeries and is similar in behavior to OnePhotonSeries.
InternalTwoPhoton = types.core.TwoPhotonSeries( ...
'imaging_plane', types.untyped.SoftLink(imaging_plane), ...
'starting_time', 0.0, ...
'starting_time_rate', 3.0, ...
'data', ones(200, 100, 1000), ...
'data_unit', 'lumens');
 
nwb.acquisition.set('2pInternal', InternalTwoPhoton);

Storing One-Photon Data

Now that we have our ImagingPlane, we can create a OnePhotonSeries object to store raw one-photon imaging data.
% using internal data. this data will be stored inside the NWB file
InternalOnePhoton = types.core.OnePhotonSeries( ...
'data', ones(100, 100, 1000), ...
'imaging_plane', types.untyped.SoftLink(imaging_plane), ...
'starting_time', 0., ...
'starting_time_rate', 1.0, ...
'data_unit', 'normalized amplitude' ...
);
nwb.acquisition.set('1pInternal', InternalOnePhoton);

Plane Segmentation

Image segmentation stores the detected regions of interest in the TwoPhotonSeries data. ImageSegmentation allows you to have more than one segmentation by creating more PlaneSegmentation objects.

Regions of interest (ROIs)

ROIs can be added to a PlaneSegmentation either as an image_mask or as a pixel_mask. An image mask is an array that is the same size as a single frame of the TwoPhotonSeries, and indicates where a single region of interest is. This image mask may be boolean or continuous between 0 and 1. A pixel_mask, on the other hand, is a list of indices (i.e coordinates) and weights for the ROI. The pixel_mask is represented as a compound data type using a ragged array and below is an example demonstrating how to create either an image_mask or a pixel_mask. Changing the dropdown selection will update the PlaneSegmentation object accordingly.
selection = "Create Image Mask"; % "Create Image Mask" or "Create Pixel Mask"
 
% generate fake image_mask data
imaging_shape = [100, 100];
x = imaging_shape(1);
y = imaging_shape(2);
 
n_rois = 20;
image_mask = zeros(y, x, n_rois);
center = randi(90,2,n_rois);
for i = 1:n_rois
image_mask(center(1,i):center(1,i)+10, center(2,i):center(2,i)+10, i) = 1;
end
 
if selection == "Create Pixel Mask"
ind = find(image_mask);
[y_ind, x_ind, roi_ind] = ind2sub(size(image_mask), ind);
 
pixel_mask_struct = struct();
pixel_mask_struct.x = uint32(x_ind); % Add x coordinates to struct field x
pixel_mask_struct.y = uint32(y_ind); % Add y coordinates to struct field y
pixel_mask_struct.weight = single(ones(size(x_ind)));
% Create pixel mask vector data
pixel_mask = types.hdmf_common.VectorData(...
'data', struct2table(pixel_mask_struct), ...
'description', 'pixel masks');
 
% When creating a pixel mask, it is also necessary to specify a
% pixel_mask_index vector. See the documentation for ragged arrays linked
% above to learn more.
num_pixels_per_roi = zeros(n_rois, 1); % Column vector
for i_roi = 1:n_rois
num_pixels_per_roi(i_roi) = sum(roi_ind == i_roi);
end
 
pixel_mask_index = uint16(cumsum(num_pixels_per_roi)); % Note: Use an integer
% type that can accommodate the maximum value of the cumulative sum
 
% Create pixel_mask_index vector
pixel_mask_index = types.hdmf_common.VectorIndex(...
'description', 'Index into pixel_mask VectorData', ...
'data', pixel_mask_index, ...
'target', types.untyped.ObjectView(pixel_mask) );
 
plane_segmentation = types.core.PlaneSegmentation( ...
'colnames', {'pixel_mask'}, ...
'description', 'roi pixel position (x,y) and pixel weight', ...
'imaging_plane', types.untyped.SoftLink(imaging_plane), ...
'pixel_mask_index', pixel_mask_index, ...
'pixel_mask', pixel_mask ...
);
 
else % selection == "Create Image Mask"
plane_segmentation = types.core.PlaneSegmentation( ...
'colnames', {'image_mask'}, ...
'description', 'output from segmenting my favorite imaging plane', ...
'imaging_plane', types.untyped.SoftLink(imaging_plane), ...
'image_mask', types.hdmf_common.VectorData(...
'data', image_mask, ...
'description', 'image masks') ...
);
end

Adding ROIs to NWB file

Now create an ImageSegmentation object and put the plane_segmentation object inside of it, naming it PlaneSegmentation.
img_seg = types.core.ImageSegmentation();
img_seg.planesegmentation.set('PlaneSegmentation', plane_segmentation);
Now create a ProcessingModule called "ophys" and put our img_seg object in it, calling it "ImageSegmentation", and add the ProcessingModule to nwb.
ophys_module = types.core.ProcessingModule( ...
'description', 'contains optical physiology data')
ophys_module =
ProcessingModule with properties: - - description: 'contains optical physiology data' - dynamictable: [0×1 types.untyped.Set] - nwbdatainterface: [0×1 types.untyped.Set] -
ophys_module.nwbdatainterface.set('ImageSegmentation', img_seg);
nwb.processing.set('ophys', ophys_module);

Storing fluorescence of ROIs over time

Now that ROIs are stored, you can store fluorescence dF/F data for these regions of interest. This type of data is stored using the RoiResponseSeries class. You will not need to instantiate this class directly to create objects of this type, but it is worth noting that this is the class you will work with after you read data back in.
First, create a data interface to store this data in
roi_table_region = types.hdmf_common.DynamicTableRegion( ...
'table', types.untyped.ObjectView(plane_segmentation), ...
'description', 'all_rois', ...
'data', (0:n_rois-1)');
 
roi_response_series = types.core.RoiResponseSeries( ...
'rois', roi_table_region, ...
'data', NaN(n_rois, 100), ...
'data_unit', 'lumens', ...
'starting_time_rate', 3.0, ...
'starting_time', 0.0);
 
fluorescence = types.core.Fluorescence();
fluorescence.roiresponseseries.set('RoiResponseSeries', roi_response_series);
 
ophys_module.nwbdatainterface.set('Fluorescence', fluorescence);
Finally, the ophys ProcessingModule is added to the NwbFile.
nwb.processing.set('ophys', ophys_module);

Writing the NWB file

nwb_file_name = 'ophys_tutorial.nwb';
if isfile(nwb_file_name); delete(nwb_file_name); end
nwbExport(nwb, nwb_file_name);

Reading the NWB file

read_nwb = nwbRead(nwb_file_name, 'ignorecache');
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.
read_nwb.processing.get('ophys').nwbdatainterface.get('Fluorescence')...
.roiresponseseries.get('RoiResponseSeries').data
ans =
DataStub with properties: +

Optical Physiology

Optical physiology results are written in four steps:
  1. Create imaging plane
  2. Acquired two-photon images
  3. Image segmentation
  4. Fluorescence and dF/F responses

Imaging Plane

First, you must create an ImagingPlane object, which will hold information about the area and method used to collect the optical imaging data. This requires creation of a Device object for the microscope and an OpticalChannel object. Then you can create an ImagingPlane.
optical_channel = types.core.OpticalChannel( ...
'description', 'description', ...
'emission_lambda', 500.);
 
device = types.core.Device();
nwb.general_devices.set('Device', device);
 
imaging_plane_name = 'imaging_plane';
imaging_plane = types.core.ImagingPlane( ...
'optical_channel', optical_channel, ...
'description', 'a very interesting part of the brain', ...
'device', types.untyped.SoftLink(device), ...
'excitation_lambda', 600., ...
'imaging_rate', 5., ...
'indicator', 'GFP', ...
'location', 'my favorite brain location');
 
nwb.general_optophysiology.set(imaging_plane_name, imaging_plane);

Storing Two-Photon Data

You can create a TwoPhotonSeries class representing two photon imaging data. TwoPhotonSeries, like SpatialSeries, inherits from TimeSeries and is similar in behavior to OnePhotonSeries.
InternalTwoPhoton = types.core.TwoPhotonSeries( ...
'imaging_plane', types.untyped.SoftLink(imaging_plane), ...
'starting_time', 0.0, ...
'starting_time_rate', 3.0, ...
'data', ones(200, 100, 1000), ...
'data_unit', 'lumens');
 
nwb.acquisition.set('2pInternal', InternalTwoPhoton);

Storing One-Photon Data

Now that we have our ImagingPlane, we can create a OnePhotonSeries object to store raw one-photon imaging data.
% using internal data. this data will be stored inside the NWB file
InternalOnePhoton = types.core.OnePhotonSeries( ...
'data', ones(100, 100, 1000), ...
'imaging_plane', types.untyped.SoftLink(imaging_plane), ...
'starting_time', 0., ...
'starting_time_rate', 1.0, ...
'data_unit', 'normalized amplitude' ...
);
nwb.acquisition.set('1pInternal', InternalOnePhoton);

Motion Correction (optional)

You can also store the result of motion correction using a MotionCorrection object, a container type that can hold one or more CorrectedImageStack objects.
% Create the corrected ImageSeries
corrected = types.core.ImageSeries( ...
'description', 'A motion corrected image stack', ...
'data', ones(100, 100, 1000), ... % 3D data array
'data_unit', 'n/a', ...
'format', 'raw', ...
'starting_time', 0.0, ...
'starting_time_rate', 1.0 ...
);
 
% Create the xy_translation TimeSeries
xy_translation = types.core.TimeSeries( ...
'description', 'x,y translation in pixels', ...
'data', ones(2, 1000), ... % 2D data array
'data_unit', 'pixels', ...
'starting_time', 0.0, ...
'starting_time_rate', 1.0 ...
);
 
% Create the CorrectedImageStack
corrected_image_stack = types.core.CorrectedImageStack( ...
'corrected', corrected, ...
'original', types.untyped.SoftLink(InternalOnePhoton), ... % Ensure `InternalOnePhoton` exists
'xy_translation', xy_translation ...
);
 
% Create the MotionCorrection object
motion_correction = types.core.MotionCorrection();
motion_correction.correctedimagestack.set('CorrectedImageStack', corrected_image_stack);
The motion corrected data is considered processed data and will be added to the processing field of the nwb object using a ProcessingModule called "ophys". First, create the ProcessingModule object and then add the motion_correction object to it, naming it "MotionCorrection".
ophys_module = types.core.ProcessingModule( ...
'description', 'Contains optical physiology data');
ophys_module.nwbdatainterface.set('MotionCorrection', motion_correction);
Finally, add the "ophys" ProcessingModule to the nwb (Note that we can continue adding objects to the "ophys" ProcessingModule without needing to explicitly update the nwb):
nwb.processing.set('ophys', ophys_module);

Plane Segmentation

Image segmentation stores the detected regions of interest in the TwoPhotonSeries data. ImageSegmentation allows you to have more than one segmentation by creating more PlaneSegmentation objects.

Regions of interest (ROIs)

ROIs can be added to a PlaneSegmentation either as an image_mask or as a pixel_mask. An image mask is an array that is the same size as a single frame of the TwoPhotonSeries, and indicates where a single region of interest is. This image mask may be boolean or continuous between 0 and 1. A pixel_mask, on the other hand, is a list of indices (i.e coordinates) and weights for the ROI. The pixel_mask is represented as a compound data type using a ragged array and below is an example demonstrating how to create either an image_mask or a pixel_mask. Changing the dropdown selection will update the PlaneSegmentation object accordingly.
selection = "Create Image Mask"; % "Create Image Mask" or "Create Pixel Mask"
 
% generate fake image_mask data
imaging_shape = [100, 100];
x = imaging_shape(1);
y = imaging_shape(2);
 
n_rois = 20;
image_mask = zeros(y, x, n_rois);
center = randi(90,2,n_rois);
for i = 1:n_rois
image_mask(center(1,i):center(1,i)+10, center(2,i):center(2,i)+10, i) = 1;
end
 
if selection == "Create Pixel Mask"
ind = find(image_mask);
[y_ind, x_ind, roi_ind] = ind2sub(size(image_mask), ind);
 
pixel_mask_struct = struct();
pixel_mask_struct.x = uint32(x_ind); % Add x coordinates to struct field x
pixel_mask_struct.y = uint32(y_ind); % Add y coordinates to struct field y
pixel_mask_struct.weight = single(ones(size(x_ind)));
% Create pixel mask vector data
pixel_mask = types.hdmf_common.VectorData(...
'data', struct2table(pixel_mask_struct), ...
'description', 'pixel masks');
 
% When creating a pixel mask, it is also necessary to specify a
% pixel_mask_index vector. See the documentation for ragged arrays linked
% above to learn more.
num_pixels_per_roi = zeros(n_rois, 1); % Column vector
for i_roi = 1:n_rois
num_pixels_per_roi(i_roi) = sum(roi_ind == i_roi);
end
 
pixel_mask_index = uint16(cumsum(num_pixels_per_roi)); % Note: Use an integer
% type that can accommodate the maximum value of the cumulative sum
 
% Create pixel_mask_index vector
pixel_mask_index = types.hdmf_common.VectorIndex(...
'description', 'Index into pixel_mask VectorData', ...
'data', pixel_mask_index, ...
'target', types.untyped.ObjectView(pixel_mask) );
 
plane_segmentation = types.core.PlaneSegmentation( ...
'colnames', {'pixel_mask'}, ...
'description', 'roi pixel position (x,y) and pixel weight', ...
'imaging_plane', types.untyped.SoftLink(imaging_plane), ...
'pixel_mask_index', pixel_mask_index, ...
'pixel_mask', pixel_mask ...
);
 
else % selection == "Create Image Mask"
plane_segmentation = types.core.PlaneSegmentation( ...
'colnames', {'image_mask'}, ...
'description', 'output from segmenting my favorite imaging plane', ...
'imaging_plane', types.untyped.SoftLink(imaging_plane), ...
'image_mask', types.hdmf_common.VectorData(...
'data', image_mask, ...
'description', 'image masks') ...
);
end

Adding ROIs to NWB file

Now create an ImageSegmentation object and put the plane_segmentation object inside of it, naming it "PlaneSegmentation".
img_seg = types.core.ImageSegmentation();
img_seg.planesegmentation.set('PlaneSegmentation', plane_segmentation);
Add the img_seg object to the "ophys" ProcessingModule we created before, naming it "ImageSegmentation".
ophys_module.nwbdatainterface.set('ImageSegmentation', img_seg);

Storing fluorescence of ROIs over time

Now that ROIs are stored, you can store fluorescence data for these regions of interest. This type of data is stored using the RoiResponseSeries class.
To create a RoiResponseSeries object, we will need to reference a set of rows from the PlaneSegmentation table to indicate which ROIs correspond to which rows of your recorded data matrix. This is done using a DynamicTableRegion, which is a type of link that allows you to reference specific rows of a DynamicTable, such as a PlaneSegmentation table by row indices.
First, we create a DynamicTableRegion that references the ROIs of the PlaneSegmentation table.
roi_table_region = types.hdmf_common.DynamicTableRegion( ...
'table', types.untyped.ObjectView(plane_segmentation), ...
'description', 'all_rois', ...
'data', (0:n_rois-1)');
Then we create a RoiResponseSeries object to store fluorescence data for those ROIs.
roi_response_series = types.core.RoiResponseSeries( ...
'rois', roi_table_region, ...
'data', NaN(n_rois, 100), ... % [nRoi, nT]
'data_unit', 'lumens', ...
'starting_time_rate', 3.0, ...
'starting_time', 0.0);
To help data analysis and visualization tools know that this RoiResponseSeries object represents fluorescence data, we will store the RoiResponseSeries object inside of a Fluorescence object. Then we add the Fluorescence object into the same ProcessingModule named "ophys" that we created earlier.
fluorescence = types.core.Fluorescence();
fluorescence.roiresponseseries.set('RoiResponseSeries', roi_response_series);
 
ophys_module.nwbdatainterface.set('Fluorescence', fluorescence);
Tip: If you want to store dF/F data instead of fluorescence data, then store the RoiResponseSeries object in a DfOverF object, which works the same way as the Fluorescence class.

Writing the NWB file

nwb_file_name = 'ophys_tutorial.nwb';
if isfile(nwb_file_name); delete(nwb_file_name); end
nwbExport(nwb, nwb_file_name);
Warning: The property "grid_spacing_unit" of type "types.core.ImagingPlane" was not exported to file location "/general/optophysiology/imaging_plane" because it depends on the property "grid_spacing" which is unset.
Warning: The property "origin_coords_unit" of type "types.core.ImagingPlane" was not exported to file location "/general/optophysiology/imaging_plane" because it depends on the property "origin_coords" which is unset.

Reading the NWB file

read_nwb = nwbRead(nwb_file_name, 'ignorecache');
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.
read_nwb.processing.get('ophys').nwbdatainterface.get('Fluorescence')...
.roiresponseseries.get('RoiResponseSeries').data
ans =
DataStub with properties: filename: 'ophys_tutorial.nwb' path: '/processing/ophys/Fluorescence/RoiResponseSeries/data' dims: [20 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 the data in the matrix using the load method.
load with no input arguments reads the entire dataset:
read_nwb.processing.get('ophys').nwbdatainterface.get('Fluorescence'). ...
roiresponseseries.get('RoiResponseSeries').data.load
ans = 20×100
NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN +
This allows you to conveniently work with datasets that are too large to fit in RAM all at once. Access the data in the matrix using the load method.
load with no input arguments reads the entire dataset:
read_nwb.processing.get('ophys').nwbdatainterface.get('Fluorescence'). ...
roiresponseseries.get('RoiResponseSeries').data.load
ans = 20×100
NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN @@ -159,12 +155,12 @@ NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN -
If all you need is 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_nwb.processing.get('ophys'). ...
nwbdatainterface.get('Fluorescence'). ...
roiresponseseries.get('RoiResponseSeries'). ...
data(1:5, 1:10)
ans = 5×10
NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN +
If all you need is 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_nwb.processing.get('ophys'). ...
nwbdatainterface.get('Fluorescence'). ...
roiresponseseries.get('RoiResponseSeries'). ...
data(1:5, 1:10)
ans = 5×10
NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN -
% read back the image/pixel masks and display the first roi
plane_segmentation = read_nwb.processing.get('ophys'). ...
nwbdatainterface.get('ImageSegmentation'). ...
planesegmentation.get('PlaneSegmentation');
 
if ~isempty(plane_segmentation.image_mask)
roi_mask = plane_segmentation.image_mask.data(:,:,1);
elseif ~isempty(plane_segmentation.pixel_mask)
row = plane_segmentation.getRow(1, 'columns', {'pixel_mask'});
pixel_mask = row.pixel_mask{1};
roi_mask = zeros(imaging_shape);
ind = sub2ind(imaging_shape, pixel_mask.y, pixel_mask.x);
roi_mask(ind) = pixel_mask.weight;
end
imshow(roi_mask)

Learn more!

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

Other MatNWB tutorials

Python tutorials

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

+
% read back the image/pixel masks and display the first roi
plane_segmentation = read_nwb.processing.get('ophys'). ...
nwbdatainterface.get('ImageSegmentation'). ...
planesegmentation.get('PlaneSegmentation');
 
if ~isempty(plane_segmentation.image_mask)
roi_mask = plane_segmentation.image_mask.data(:,:,1);
elseif ~isempty(plane_segmentation.pixel_mask)
row = plane_segmentation.getRow(1, 'columns', {'pixel_mask'});
pixel_mask = row.pixel_mask{1};
roi_mask = zeros(imaging_shape);
ind = sub2ind(imaging_shape, pixel_mask.y, pixel_mask.x);
roi_mask(ind) = pixel_mask.weight;
end
imshow(roi_mask)

Learn more!

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

Other MatNWB tutorials

Python tutorials

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


\ 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 From 63a7c4eac97b75695b6a49656e46435fb22d7d12 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Mon, 25 Nov 2024 14:51:18 +0100 Subject: [PATCH 13/16] Add DecompositionSeries example to ecephys tutorial (#623) * Fix bug in table2nwb * Add DecompositionSeries to ecephys tutorial * Merge ecepys tutorial * Revert changes committed by mistake * Add band_mean and stdev to bands in DecompositionSeries * Properly incorporated changes from main * Suppress output of feature variable + minor fix of TutorialTest --- +tests/+unit/TutorialTest.m | 3 +- +util/table2nwb.m | 4 +- tutorials/ecephys.mlx | Bin 359611 -> 360288 bytes tutorials/html/ecephys.html | 1743 +++++++++++++++-------------- tutorials/private/mcode/ecephys.m | 60 +- 5 files changed, 947 insertions(+), 863 deletions(-) diff --git a/+tests/+unit/TutorialTest.m b/+tests/+unit/TutorialTest.m index 089a55b7..554c0cb2 100644 --- a/+tests/+unit/TutorialTest.m +++ b/+tests/+unit/TutorialTest.m @@ -120,7 +120,8 @@ function inspectTutorialFileWithNwbInspector(testCase) results = py.list(py.nwbinspector.inspect_nwbfile(nwbfile_path=nwbFilename)); results = testCase.convertNwbInspectorResultsToStruct(results); elseif testCase.NWBInspectorMode == "CLI" - [~, m] = system(sprintf('nwbinspector %s --levels importance', nwbFilename)); + [s, m] = system(sprintf('nwbinspector %s --levels importance', nwbFilename)); + testCase.assertEqual(s,0, 'Failed to run NWB Inspector using system command.') results = testCase.parseNWBInspectorTextOutput(m); end 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'; diff --git a/tutorials/ecephys.mlx b/tutorials/ecephys.mlx index accdd482e6329ceb7bec72d2280164032d2ce92f..e8f63aab27defbffb32a3e556379b6c65506fe26 100644 GIT binary patch delta 21663 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 n#J?IW6⪚1bhkn55$-%7YvdBjAQ?+r38am!DB(7+hG3#6spfH delta 20999 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 z≠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

Neurodata Without Borders Extracellular Electrophysiology Tutorial

Table of Contents
Neurodata Without Borders Extracellular Electrophysiology Tutorial +.S22 { 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

About This Tutorial

This tutorial describes storage of hypothetical data from extracellular electrophysiology experiments in NWB for the following data categories:
  • Raw voltage recording
  • Local field potential (LFP) and filtered electrical signals
  • Spike times

Before You Begin

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.
Important: The dimensions of timeseries data in MatNWB should be defined in the opposite order of how it is defined in the nwb-schemas. In NWB, time is always stored in the first dimension of the data, whereas in MatNWB time should be stored in the last dimension of the data. This is explained in more detail here: MatNWB <-> HDF5 Dimension Mapping.

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 these required fields along with any additional metadata. In MatNWB, arguments are specified using MATLAB's keyword argument pair convention, where each argument name is followed by its 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

About This Tutorial

This tutorial describes storage of hypothetical data from extracellular electrophysiology experiments in NWB for the following data categories:
  • Raw voltage recording
  • Local field potential (LFP) and filtered electrical signals
  • Spike times

Before You Begin

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.
Important: The dimensions of timeseries data in MatNWB should be defined in the opposite order of how it is defined in the nwb-schemas. In NWB, time is always stored in the first dimension of the data, whereas in MatNWB time should be stored in the last dimension of the data. This is explained in more detail here: MatNWB <-> HDF5 Dimension Mapping.

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 these required fields along with any additional metadata. In MatNWB, arguments are specified using MATLAB's keyword argument pair convention, where each argument name is followed by its 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.7.0' file_create_date: [] @@ -154,922 +155,900 @@ stimulus_presentation: [0×1 types.untyped.Set] stimulus_templates: [0×1 types.untyped.Set] units: [] -

Electrode Information

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, impedance, 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.
numShanks = 4;
numChannelsPerShank = 3;
numChannels = numShanks * numChannelsPerShank;
 
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);
electrodeGroup = types.core.ElectrodeGroup( ...
'description', sprintf('electrode group for %s', shankGroupName), ...
'location', 'brain area', ...
'device', types.untyped.SoftLink(device) ...
);
nwb.general_extracellular_ephys.set(shankGroupName, electrodeGroup);
for iElectrode = 1:numChannelsPerShank
electrodesDynamicTable.addRow( ...
'location', 'unknown', ...
'group', types.untyped.ObjectView(electrodeGroup), ...
'group_name', shankGroupName, ...
'label', sprintf('%s-electrode%d', shankGroupName, iElectrode));
end
end
electrodesDynamicTable.toTable() % Display the table
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 a construct that enables linking one neurodata type to another, allowing a neurodata type to reference another within the NWB file.

Recorded Extracellular Signals

Voltage data are stored using the ElectricalSeries class, a subclass of the TimeSeries class specialized for voltage data.

Referencing Electrodes

In order to create our ElectricalSeries object, we first need to reference a set of rows in the electrodes table to indicate which electrode (channel) each entry in the electrical series were recorded from. 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, using 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)');

Raw Voltage Data

Now create an ElectricalSeries object to hold acquisition data collected during the experiment.
raw_electrical_series = types.core.ElectricalSeries( ...
'starting_time', 0.0, ... % seconds
'starting_time_rate', 30000., ... % Hz
'data', randn(numChannels, 3000), ... % nChannels x nTime
'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', raw_electrical_series);

Processed Extracellular Electrical Signals

LFP

LFP refers to data that has been low-pass filtered, typically below 300 Hz. This data may also be downsampled. Because it is filtered and potentially resampled, it is categorized as processed data. LFP data would also be stored in an ElectricalSeries. To help data analysis and visualization tools know that this ElectricalSeries object represents LFP data, we store it inside an LFP object and 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' in the behavior tutorial
lfp_electrical_series = types.core.ElectricalSeries( ...
'starting_time', 0.0, ... % seconds
'starting_time_rate', 1000., ... % Hz
'data', randn(numChannels, 100), ... nChannels x nTime
'filtering', 'Low-pass filter at 300 Hz', ...
'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);

Other Types of Filtered Electrical Signals

If your derived data is filtered for frequency ranges other than LFP—such as Gamma or Theta—you should store it in an ElectricalSeries and encapsulate it within a FilteredEphys object instead of the LFP object.
% Generate filtered data
filtered_data = randn(50, 12); % 50 time points, 12 channels
filtered_data = permute(filtered_data, [2, 1]); % permute timeseries for matnwb
 
% Create an ElectricalSeries object
filtered_electrical_series = types.core.ElectricalSeries( ...
'description', 'Data filtered in the Theta range', ...
'data', filtered_data, ...
'electrodes', electrode_table_region, ...
'filtering', 'Band-pass filtered between 4 and 8 Hz', ...
'starting_time', 0.0, ...
'starting_time_rate', 200.0 ...
);
 
% Create a FilteredEphys object and add the filtered electrical series
filtered_ephys = types.core.FilteredEphys();
filtered_ephys.electricalseries.set('FilteredElectricalSeries', filtered_electrical_series);
 
% Add the FilteredEphys object to the ecephys module
ecephys_module.nwbdatainterface.set('FilteredEphys', filtered_ephys);

Spike Times and Extracellular Events

Sorted Spike Times

Spike times are stored in a Units table, a specialization of the DynamicTable class. The default Units table is located at /units in the HDF5 file. You can add columns to the Units table just like you did for electrodes and trials (see convertTrials). Here, we generate some random spike data and populate the table.
num_cells = 10;
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×23 double1×24 double1×25 double1×27 double1×23 double1×21 double1×27 double1×27 double1×28 double1×28 double

Ragged Arrays

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 table. These indexed columns have two components, the VectorData object that holds the data and the VectorIndex 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. For more information about ragged arrays, we refer you to the "Ragged Array Columns" section of the dynamic table tutorial.
[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 ...
);
 
nwb.units.toTable
ans = 10×2 table
 idspike_times
1123×1 double
2224×1 double
3325×1 double
4427×1 double
5523×1 double
6621×1 double
7727×1 double
8827×1 double
9928×1 double
101028×1 double

Unsorted Spike Times

While the Units table is used to store spike times and waveform data for spike-sorted, single-unit activity, you may also want to store spike times and waveform snippets of unsorted spiking activity. This is useful for recording multi-unit activity detected via threshold crossings during data acquisition. Such information can be stored using SpikeEventSeries objects.
% In the SpikeEventSeries the dimensions should be ordered as
% [num_events, num_channels, num_samples].
% Define spike snippets: 20 events, 3 channels, 40 samples per event.
spike_snippets = rand(20, 3, 40);
% Permute spike snippets (See dimensionMapNoDataPipes tutorial)
spike_snippets = permute(spike_snippets, [3,2,1])
spike_snippets =
spike_snippets(:,:,1) = - - 0.2780 0.0148 0.4113 - 0.9089 0.2536 0.2512 - 0.2961 0.4550 0.8541 - 0.5709 0.1663 0.1813 - 0.6639 0.8085 0.9136 - 0.1532 0.5275 0.1305 - 0.5169 0.6597 0.9037 - 0.6069 0.9045 0.9244 - 0.9657 0.3104 0.8828 - 0.4293 0.2333 0.9000 - 0.3021 0.8831 0.9400 - 0.1023 0.8102 0.2832 - 0.5171 0.9477 0.6370 - 0.6029 0.9545 0.8187 - 0.5206 0.4737 0.8799 - 0.3451 0.1380 0.5775 - 0.0495 0.1734 0.9920 - 0.8352 0.1176 0.1876 - 0.8302 0.8044 0.6006 - 0.3333 0.6354 0.1640 - 0.1174 0.6743 0.8681 - 0.3170 0.6718 0.1311 - 0.6166 0.2288 0.7316 - 0.8783 0.7603 0.0001 - 0.2442 0.5209 0.2155 - 0.3402 0.2235 0.5329 - 0.5665 0.0798 0.7609 - 0.5323 0.5660 0.5499 - 0.7377 0.0673 0.3152 - 0.2693 0.4380 0.2733 - 0.9295 0.5399 0.4471 - 0.2433 0.3025 0.8800 - 0.5839 0.5657 0.5774 - 0.9038 0.2999 0.9696 - 0.6991 0.2018 0.5173 - 0.2515 0.0586 0.2742 - 0.4466 0.4422 0.5018 - 0.9849 0.9096 0.8287 - 0.1720 0.6847 0.6424 - 0.1289 0.0637 0.0174 +

Electrode Information

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, impedance, 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.
numShanks = 4;
numChannelsPerShank = 3;
numChannels = numShanks * numChannelsPerShank;
 
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);
electrodeGroup = types.core.ElectrodeGroup( ...
'description', sprintf('electrode group for %s', shankGroupName), ...
'location', 'brain area', ...
'device', types.untyped.SoftLink(device) ...
);
nwb.general_extracellular_ephys.set(shankGroupName, electrodeGroup);
for iElectrode = 1:numChannelsPerShank
electrodesDynamicTable.addRow( ...
'location', 'unknown', ...
'group', types.untyped.ObjectView(electrodeGroup), ...
'group_name', shankGroupName, ...
'label', sprintf('%s-electrode%d', shankGroupName, iElectrode));
end
end
electrodesDynamicTable.toTable() % Display the table
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 a construct that enables linking one neurodata type to another, allowing a neurodata type to reference another within the NWB file.

Recorded Extracellular Signals

Voltage data are stored using the ElectricalSeries class, a subclass of the TimeSeries class specialized for voltage data.

Referencing Electrodes

In order to create our ElectricalSeries object, we first need to reference a set of rows in the electrodes table to indicate which electrode (channel) each entry in the electrical series were recorded from. 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, using 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)');

Raw Voltage Data

Now create an ElectricalSeries object to hold acquisition data collected during the experiment.
raw_electrical_series = types.core.ElectricalSeries( ...
'starting_time', 0.0, ... % seconds
'starting_time_rate', 30000., ... % Hz
'data', randn(numChannels, 3000), ... % nChannels x nTime
'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', raw_electrical_series);

Processed Extracellular Electrical Signals

LFP

LFP refers to data that has been low-pass filtered, typically below 300 Hz. This data may also be downsampled. Because it is filtered and potentially resampled, it is categorized as processed data. LFP data would also be stored in an ElectricalSeries. To help data analysis and visualization tools know that this ElectricalSeries object represents LFP data, we store it inside an LFP object and 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' in the behavior tutorial
lfp_electrical_series = types.core.ElectricalSeries( ...
'starting_time', 0.0, ... % seconds
'starting_time_rate', 1000., ... % Hz
'data', randn(numChannels, 100), ... nChannels x nTime
'filtering', 'Low-pass filter at 300 Hz', ...
'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);

Other Types of Filtered Electrical Signals

If your acquired data is filtered for frequency ranges other than LFP—such as Gamma or Theta—you can store the result in an ElectricalSeries and encapsulate it within a FilteredEphys object instead of the LFP object.
% Generate filtered data
filtered_data = randn(50, 12); % 50 time points, 12 channels
filtered_data = permute(filtered_data, [2, 1]); % permute timeseries for matnwb
 
% Create an ElectricalSeries object
filtered_electrical_series = types.core.ElectricalSeries( ...
'description', 'Data filtered in the theta range', ...
'data', filtered_data, ...
'electrodes', electrode_table_region, ...
'filtering', 'Band-pass filtered between 4 and 8 Hz', ...
'starting_time', 0.0, ...
'starting_time_rate', 200.0 ...
);
 
% Create a FilteredEphys object and add the filtered electrical series
filtered_ephys = types.core.FilteredEphys();
filtered_ephys.electricalseries.set('FilteredElectricalSeries', filtered_electrical_series);
 
% Add the FilteredEphys object to the ecephys module
ecephys_module.nwbdatainterface.set('FilteredEphys', filtered_ephys);

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 then 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.
In this tutorial, the examples for FilteredEphys and DecompositionSeries may appear similar. However, the key difference is that DecompositionSeries is specialized for storing the results of spectral analyses of timeseries data in general, whereas FilteredEphys is defined specifically as a container for filtered electrical signals.
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. As mentioned in the beginning of this tutorial, 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_mean = [8; 21; 55];
band_stdev = [2; 4.5; 12.5];
band_limits = [band_mean - 2*band_stdev, band_mean + 2*band_stdev];
 
% The bands should be added to the DecompositionSeries as a dynamic table
bands = table(band_names, band_mean, band_stdev, band_limits, ...
'VariableNames', {'band_names', 'band_mean', 'band_stdev', 'band_limits'})
bands = 3×4 table
 band_namesband_meanband_stdevband_limits
12
1'theta'82412
2'beta'214.50001230
3'gamma'5512.50003080
 
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 and Extracellular Events

Sorted Spike Times

Spike times are stored in a Units table, a specialization of the DynamicTable class. The default Units table is located at /units in the HDF5 file. You can add columns to the Units table just like you did for electrodes and trials (see convertTrials). Here, we generate some random spike data and populate the table.
num_cells = 10;
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×24 double1×28 double1×22 double1×28 double1×21 double1×26 double1×16 double1×18 double1×24 double1×24 double

Ragged Arrays

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 table. These indexed columns have two components, the VectorData object that holds the data and the VectorIndex 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. For more information about ragged arrays, we refer you to the "Ragged Array Columns" section of the dynamic table tutorial.
[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 ...
);
 
nwb.units.toTable
ans = 10×2 table
 idspike_times
1124×1 double
2228×1 double
3322×1 double
4428×1 double
5521×1 double
6626×1 double
7716×1 double
8818×1 double
9924×1 double
101024×1 double

Unsorted Spike Times

While the Units table is used to store spike times and waveform data for spike-sorted, single-unit activity, you may also want to store spike times and waveform snippets of unsorted spiking activity. This is useful for recording multi-unit activity detected via threshold crossings during data acquisition. Such information can be stored using SpikeEventSeries objects.
% In the SpikeEventSeries the dimensions should be ordered as
% [num_events, num_channels, num_samples].
% Define spike snippets: 20 events, 3 channels, 40 samples per event.
spike_snippets = rand(20, 3, 40);
% Permute spike snippets (See dimensionMapNoDataPipes tutorial)
spike_snippets = permute(spike_snippets, [3,2,1])
spike_snippets =
spike_snippets(:,:,1) = + + 0.7910 0.0213 0.8335 + 0.8083 0.5109 0.0694 + 0.0414 0.1315 0.9237 + 0.6840 0.0158 0.8003 + 0.9652 0.0518 0.6909 + 0.4137 0.6962 0.6477 + 0.7065 0.4276 0.1037 + 0.0327 0.2995 0.3123 + 0.5643 0.7282 0.7042 + 0.3009 0.8131 0.9222 + 0.1623 0.8546 0.4080 + 0.3810 0.7375 0.5178 + 0.1005 0.8742 0.2635 + 0.9530 0.3118 0.5444 + 0.0115 0.0973 0.5146 + 0.1436 0.2857 0.3713 + 0.5966 0.3174 0.4652 + 0.0131 0.8012 0.6369 + 0.4585 0.6484 0.6865 + 0.5535 0.1510 0.3442 + 0.0254 0.5062 0.8555 + 0.7063 0.5182 0.6404 + 0.3815 0.1169 0.1685 + 0.8255 0.1120 0.3037 + 0.1461 0.4718 0.1424 + 0.8712 0.8092 0.7453 + 0.3177 0.3974 0.9020 + 0.4894 0.6920 0.5508 + 0.1267 0.0666 0.8641 + 0.9720 0.7211 0.6973 + 0.8166 0.9120 0.1665 + 0.4227 0.3168 0.6086 + 0.0780 0.9907 0.1200 + 0.0661 0.3271 0.7940 + 0.8019 0.9782 0.7975 + 0.4981 0.6964 0.9912 + 0.5068 0.4475 0.9353 + 0.4478 0.6194 0.5117 + 0.8898 0.5428 0.7175 + 0.8606 0.2924 0.9609 spike_snippets(:,:,2) = - 0.1356 0.8906 0.1356 - 0.8359 0.4499 0.4325 - 0.5334 0.7425 0.3893 - 0.7806 0.4774 0.1446 - 0.7090 0.6839 0.8727 - 0.6276 0.3848 0.9937 - 0.6387 0.0727 0.0036 - 0.2166 0.6525 0.1900 - 0.7040 0.5891 0.2017 - 0.3564 0.7157 0.4724 - 0.1167 0.6595 0.5219 - 0.2402 0.7465 0.9879 - 0.7433 0.5308 0.0752 - 0.5490 0.8776 0.2981 - 0.0620 0.7035 0.5912 - 0.1362 0.5743 0.8116 - 0.8572 0.4815 0.6027 - 0.9946 0.4654 0.4843 - 0.0120 0.4187 0.2885 - 0.4056 0.3358 0.7233 - 0.9679 0.1308 0.8167 - 0.2359 0.1989 0.8837 - 0.5314 0.6617 0.3106 - 0.0073 0.3747 0.3369 - 0.4491 0.5629 0.3621 - 0.3106 0.3202 0.3869 - 0.6711 0.7039 0.4220 - 0.5631 0.3319 0.3008 - 0.1683 0.4825 0.1346 - 0.2136 0.7076 0.7932 - 0.8566 0.7091 0.5236 - 0.0288 0.6919 0.5200 - 0.7006 0.1266 0.5687 - 0.6473 0.8367 0.3659 - 0.6176 0.6711 0.9105 - 0.9251 0.7086 0.8938 - 0.1278 0.7159 0.8852 - 0.0841 0.6990 0.3079 - 0.0080 0.0005 0.4075 - 0.7984 0.6617 0.3013 + 0.5338 0.3753 0.6193 + 0.1517 0.6080 0.1663 + 0.1934 0.6342 0.9882 + 0.9244 0.8932 0.3000 + 0.8507 0.4194 0.6482 + 0.5106 0.1159 0.7561 + 0.1162 0.3095 0.9177 + 0.9806 0.6443 0.8760 + 0.0513 0.4850 0.9481 + 0.8886 0.2191 0.6419 + 0.6883 0.4286 0.8974 + 0.8499 0.5069 0.4643 + 0.9019 0.3152 0.4006 + 0.2973 0.4501 0.0411 + 0.2026 0.1939 0.9824 + 0.6041 0.2842 0.5462 + 0.9371 0.5834 0.8635 + 0.4799 0.6260 0.2953 + 0.5238 0.7532 0.8461 + 0.6517 0.7502 0.3512 + 0.4229 0.1752 0.1634 + 0.7366 0.7801 0.9180 + 0.1957 0.2408 0.3131 + 0.9544 0.5748 0.1483 + 0.8544 0.0117 0.7080 + 0.7467 0.9576 0.2643 + 0.5438 0.0537 0.0843 + 0.9563 0.6243 0.4454 + 0.5092 0.1294 0.3496 + 0.4297 0.4393 0.2485 + 0.4890 0.8836 0.6625 + 0.1389 0.4318 0.3081 + 0.1869 0.0011 0.4418 + 0.9201 0.4152 0.0008 + 0.4235 0.6870 0.1545 + 0.9545 0.0976 0.9682 + 0.7207 0.6316 0.7971 + 0.9008 0.5584 0.7311 + 0.1453 0.9232 0.3198 + 0.5304 0.4056 0.1549 spike_snippets(:,:,3) = - 0.3783 0.7965 0.1050 - 0.7297 0.1329 0.8280 - 0.0421 0.7440 0.5148 - 0.0433 0.1966 0.3639 - 0.3937 0.0728 0.1104 - 0.5506 0.7925 0.4094 - 0.3709 0.7109 0.0431 - 0.5237 0.6674 0.2444 - 0.1808 0.7575 0.3823 - 0.8111 0.5852 0.2838 - 0.3822 0.9055 0.2303 - 0.0973 0.3953 0.7989 - 0.0419 0.8453 0.6505 - 0.4524 0.3664 0.2062 - 0.4113 0.1807 0.1150 - 0.0886 0.8685 0.3013 - 0.3449 0.5180 0.2821 - 0.3773 0.6463 0.4354 - 0.7497 0.3262 0.9738 - 0.3253 0.4987 0.7777 - 0.8843 0.4842 0.8025 - 0.6655 0.3403 0.4367 - 0.6173 0.6862 0.7745 - 0.3096 0.4351 0.8634 - 0.9581 0.5508 0.9057 - 0.9796 0.8453 0.2989 - 0.4616 0.3215 0.5217 - 0.6465 0.2757 0.8933 - 0.6273 0.7627 0.3487 - 0.7493 0.0711 0.7441 - 0.6469 0.6425 0.3223 - 0.2125 0.5889 0.7047 - 0.3090 0.2534 0.4767 - 0.2812 0.5091 0.6808 - 0.1553 0.4004 0.3360 - 0.5689 0.5763 0.1933 - 0.4353 0.9890 0.3021 - 0.4081 0.5490 0.2611 - 0.0308 0.0775 0.0438 - 0.2588 0.8739 0.3807 + 0.5092 0.9115 0.6848 + 0.0370 0.3598 0.5459 + 0.8909 0.2320 0.6210 + 0.1415 0.3598 0.3532 + 0.6650 0.7777 0.2036 + 0.0845 0.7893 0.6989 + 0.8263 0.5907 0.7732 + 0.7758 0.9209 0.6862 + 0.6141 0.4780 0.6086 + 0.4845 0.5522 0.5599 + 0.9572 0.1676 0.3414 + 0.1539 0.5025 0.8494 + 0.6809 0.5092 0.8636 + 0.9649 0.6492 0.5732 + 0.3245 0.6510 0.9502 + 0.3759 0.3767 0.4420 + 0.7450 0.5653 0.0341 + 0.8066 0.5285 0.6691 + 0.7006 0.0285 0.2575 + 0.4817 0.5484 0.8613 + 0.2185 0.3218 0.5705 + 0.4282 0.1795 0.8811 + 0.3090 0.8398 0.6193 + 0.0477 0.4053 0.8866 + 0.1156 0.5851 0.2542 + 0.8462 0.0158 0.3698 + 0.4941 0.9994 0.2657 + 0.6635 0.6725 0.1791 + 0.8944 0.2112 0.0403 + 0.3573 0.8075 0.2363 + 0.0931 0.0854 0.2321 + 0.4501 0.9337 0.2882 + 0.5619 0.6824 0.8621 + 0.5455 0.0936 0.0346 + 0.4649 0.7467 0.8067 + 0.0441 0.9981 0.1671 + 0.1436 0.4552 0.5099 + 0.9111 0.4916 0.9271 + 0.1548 0.1222 0.4906 + 0.3945 0.8146 0.5500 spike_snippets(:,:,4) = - 0.2649 0.6259 0.3227 - 0.2491 0.7486 0.8558 - 0.3198 0.5868 0.3054 - 0.9232 0.9241 0.1926 - 0.8567 0.2033 0.2909 - 0.9502 0.7225 0.7050 - 0.3448 0.9984 0.8057 - 0.6264 0.9101 0.6463 - 0.5162 0.6860 0.4762 - 0.3276 0.2264 0.5250 - 0.5039 0.8624 0.7269 - 0.7390 0.1356 0.6052 - 0.9651 0.2247 0.4177 - 0.1197 0.2531 0.0582 - 0.0431 0.1862 0.8068 - 0.5937 0.9858 0.4938 - 0.8370 0.3590 0.5061 - 0.0589 0.0558 0.9941 - 0.4473 0.9498 0.0472 - 0.8378 0.1685 0.4784 - 0.7923 0.4368 0.6926 - 0.5336 0.0716 0.9173 - 0.8650 0.4142 0.8525 - 0.0634 0.7115 0.9690 - 0.1277 0.2495 0.9790 - 0.2543 0.5766 0.0703 - 0.8826 0.7350 0.1136 - 0.6198 0.1620 0.6696 - 0.5354 0.5791 0.6978 - 0.8119 0.8240 0.6792 - 0.4559 0.7728 0.2349 - 0.1032 0.8007 0.3118 - 0.1165 0.3766 0.7736 - 0.2743 0.6713 0.0053 - 0.8355 0.9312 0.8363 - 0.8097 0.4272 0.2623 - 0.3977 0.9368 0.4354 - 0.4137 0.5146 0.7045 - 0.5012 0.6424 0.9537 - 0.6439 0.1141 0.0752 + 0.8651 0.0729 0.5703 + 0.7056 0.7361 0.0422 + 0.7220 0.4867 0.8848 + 0.7563 0.2299 0.4345 + 0.1388 0.6244 0.1856 + 0.4175 0.1199 0.3165 + 0.0970 0.6949 0.4674 + 0.6304 0.8521 0.2420 + 0.5424 0.2486 0.6067 + 0.1731 0.3253 0.6955 + 0.6922 0.3908 0.0253 + 0.3011 0.2044 0.2150 + 0.6281 0.9983 0.8887 + 0.5463 0.3738 0.2357 + 0.1263 0.2581 0.0286 + 0.5020 0.1082 0.0744 + 0.2669 0.0954 0.5680 + 0.3439 0.2201 0.8495 + 0.6176 0.9906 0.6916 + 0.2697 0.4133 0.1197 + 0.5944 0.7802 0.2287 + 0.4447 0.7085 0.0679 + 0.1947 0.6754 0.9432 + 0.4714 0.0661 0.0817 + 0.5271 0.9569 0.9435 + 0.8597 0.6655 0.0351 + 0.8275 0.0531 0.2852 + 0.2070 0.3484 0.4845 + 0.9358 0.0522 0.7680 + 0.6787 0.3460 0.3718 + 0.2934 0.3135 0.0939 + 0.9152 0.4345 0.1295 + 0.3650 0.2472 0.3402 + 0.1528 0.6372 0.5485 + 0.5785 0.9504 0.9111 + 0.5069 0.9808 0.3079 + 0.3256 0.3353 0.1383 + 0.5329 0.4452 0.8509 + 0.1127 0.6844 0.8668 + 0.7811 0.1499 0.4629 spike_snippets(:,:,5) = - 0.0353 0.5785 0.1052 - 0.9667 0.9307 0.7775 - 0.0724 0.7375 0.9376 - 0.2985 0.0157 0.5220 - 0.2656 0.4422 0.7409 - 0.1663 0.4812 0.8922 - 0.2181 0.4709 0.0764 - 0.2997 0.1134 0.7770 - 0.4091 0.1724 0.0672 - 0.3649 0.4570 0.8933 - 0.0467 0.9205 0.5342 - 0.2600 0.4604 0.6358 - 0.3632 0.4091 0.5228 - 0.3142 0.5238 0.8015 - 0.9972 0.8749 0.1874 - 0.2001 0.8972 0.8003 - 0.6966 0.7425 0.8926 - 0.2572 0.2725 0.3606 - 0.2437 0.2278 0.0985 - 0.5260 0.2972 0.9588 - 0.3713 0.6128 0.5907 - 0.5425 0.4280 0.9567 - 0.2313 0.8796 0.2725 - 0.8410 0.8233 0.3227 - 0.2570 0.2153 0.0822 - 0.6232 0.8143 0.8897 - 0.5503 0.2705 0.4599 - 0.4243 0.9212 0.8713 - 0.6731 0.7431 0.2173 - 0.7223 0.2741 0.7489 - 0.0597 0.2518 0.3761 - 0.9505 0.4155 0.3440 - 0.0127 0.5266 0.2639 - 0.9038 0.9368 0.6245 - 0.2475 0.5880 0.0437 - 0.8818 0.2390 0.7275 - 0.8650 0.2695 0.5546 - 0.5279 0.0313 0.2543 - 0.3718 0.3389 0.7586 - 0.9491 0.6410 0.0260 + 0.9833 0.1306 0.1439 + 0.2656 0.5451 0.9737 + 0.5678 0.3446 0.3483 + 0.0967 0.0733 0.2677 + 0.2653 0.9260 0.8876 + 0.3652 0.3403 0.3428 + 0.7242 0.2202 0.3057 + 0.8023 0.8464 0.9116 + 0.2288 0.7517 0.9695 + 0.7929 0.6289 0.9108 + 0.5254 0.5371 0.8497 + 0.9644 0.9208 0.5796 + 0.3907 0.0955 0.7351 + 0.8862 0.8287 0.5880 + 0.3508 0.8420 0.7759 + 0.9350 0.8327 0.3354 + 0.3336 0.4643 0.3187 + 0.3489 0.0708 0.8605 + 0.6006 0.7239 0.3991 + 0.3062 0.9719 0.1192 + 0.6141 0.9838 0.1967 + 0.0040 0.5215 0.8946 + 0.2364 0.0775 0.0065 + 0.7518 0.9408 0.8491 + 0.7013 0.8655 0.8282 + 0.9326 0.1311 0.8476 + 0.1252 0.0149 0.3416 + 0.9947 0.6125 0.6894 + 0.1763 0.6221 0.2775 + 0.7586 0.2494 0.6819 + 0.9048 0.7648 0.0505 + 0.2789 0.3761 0.7825 + 0.1195 0.3080 0.1136 + 0.3286 0.8026 0.9635 + 0.5285 0.2380 0.6579 + 0.4777 0.2527 0.4029 + 0.6137 0.4510 0.4112 + 0.3054 0.5817 0.7277 + 0.5103 0.9052 0.9271 + 0.8229 0.1610 0.4064 spike_snippets(:,:,6) = - 0.8353 0.8842 0.1327 - 0.0023 0.0828 0.0155 - 0.4521 0.2627 0.3135 - 0.6089 0.9924 0.1857 - 0.2771 0.0145 0.5243 - 0.0054 0.6352 0.6397 - 0.9503 0.6881 0.6905 - 0.5268 0.6878 0.0469 - 0.3679 0.0870 0.0940 - 0.7434 0.2069 0.6242 - 0.7756 0.0147 0.1339 - 0.3013 0.4838 0.5379 - 0.7562 0.3814 0.9576 - 0.5539 0.7027 0.2851 - 0.4565 0.4701 0.4139 - 0.9140 0.5460 0.2079 - 0.3755 0.6503 0.2046 - 0.1380 0.9547 0.2964 - 0.9064 0.5506 0.4604 - 0.4459 0.0249 0.1004 - 0.1336 0.8181 0.9869 - 0.7424 0.1500 0.2491 - 0.5502 0.8456 0.8847 - 0.2056 0.3975 0.2799 - 0.3125 0.9756 0.9344 - 0.1778 0.6799 0.5414 - 0.6171 0.7186 0.0913 - 0.5498 0.4818 0.1065 - 0.2924 0.6595 0.9587 - 0.0464 0.5249 0.9911 - 0.6492 0.7509 0.9557 - 0.3380 0.2306 0.6588 - 0.2239 0.2056 0.6502 - 0.0666 0.7243 0.5550 - 0.8463 0.7816 0.2333 - 0.1221 0.9336 0.6374 - 0.2198 0.0172 0.9353 - 0.2697 0.7625 0.7616 - 0.5676 0.3180 0.2653 - 0.7473 0.3677 0.3685 + 0.3054 0.2779 0.6491 + 0.5022 0.0909 0.5466 + 0.2294 0.5000 0.8570 + 0.4042 0.5115 0.9431 + 0.4964 0.2149 0.5783 + 0.4703 0.1227 0.4063 + 0.7156 0.5084 0.0977 + 0.1685 0.6039 0.9440 + 0.5246 0.4176 0.6435 + 0.1973 0.8868 0.0968 + 0.3190 0.5694 0.8356 + 0.9508 0.3955 0.3049 + 0.7104 0.8199 0.1919 + 0.5505 0.9165 0.1106 + 0.5106 0.9105 0.6328 + 0.1722 0.1372 0.2181 + 0.4067 0.5448 0.6478 + 0.9696 0.4703 0.5263 + 0.1818 0.4216 0.7169 + 0.2094 0.2752 0.8469 + 0.4900 0.4156 0.5015 + 0.9689 0.3503 0.3427 + 0.4425 0.7213 0.7925 + 0.1208 0.9218 0.1758 + 0.5253 0.4974 0.9133 + 0.5050 0.1665 0.3859 + 0.3676 0.0337 0.0437 + 0.9110 0.8914 0.5112 + 0.4501 0.4358 0.3669 + 0.8307 0.5862 0.6017 + 0.2800 0.0028 0.3824 + 0.9934 0.6645 0.1911 + 0.1871 0.5665 0.4545 + 0.5104 0.4363 0.3587 + 0.4477 0.1140 0.2533 + 0.0078 0.4282 0.6347 + 0.6320 0.1487 0.7152 + 0.4901 0.3613 0.0225 + 0.0841 0.3465 0.2337 + 0.2430 0.2622 0.4275 spike_snippets(:,:,7) = - 0.2717 0.8437 0.0614 - 0.9846 0.1554 0.6485 - 0.4405 0.5486 0.9861 - 0.2992 0.2502 0.0416 - 0.7311 0.8385 0.7856 - 0.5447 0.1100 0.3637 - 0.4743 0.6841 0.0385 - 0.7385 0.9559 0.6803 - 0.3589 0.2038 0.8423 - 0.6803 0.3435 0.9457 - 0.2223 0.2174 0.5204 - 0.5181 0.9136 0.4587 - 0.5111 0.5251 0.6280 - 0.1176 0.2497 0.5422 - 0.5466 0.6819 0.2333 - 0.5011 0.4795 0.9527 - 0.8456 0.4351 0.4552 - 0.5045 0.9980 0.1937 - 0.3748 0.1342 0.8555 - 0.6774 0.2389 0.2832 - 0.4197 0.1265 0.4677 - 0.9821 0.5483 0.3190 - 0.0633 0.8405 0.1184 - 0.8376 0.0095 0.9551 - 0.1982 0.8255 0.1118 - 0.9934 0.3247 0.2800 - 0.2720 0.7457 0.5024 - 0.4922 0.3824 0.4749 - 0.7537 0.4682 0.0557 - 0.5107 0.6124 0.4445 - 0.5398 0.3796 0.4417 - 0.0725 0.2940 0.3620 - 0.9727 0.1012 0.8457 - 0.6401 0.9106 0.6999 - 0.2930 0.8806 0.3215 - 0.4818 0.7571 0.2296 - 0.5412 0.0074 0.0305 - 0.4098 0.9592 0.5943 - 0.2615 0.6385 0.9016 - 0.0142 0.0078 0.2939 + 0.5203 0.4307 0.6524 + 0.7971 0.7307 0.9649 + 0.5900 0.5683 0.7583 + 0.4517 0.9439 0.4560 + 0.7591 0.4558 0.7293 + 0.5660 0.1464 0.6991 + 0.4934 0.6237 0.6178 + 0.8066 0.6757 0.4065 + 0.1988 0.2131 0.3927 + 0.9253 0.1590 0.2406 + 0.4513 0.2735 0.8884 + 0.3209 0.9513 0.4656 + 0.1497 0.7329 0.8223 + 0.8818 0.5569 0.8199 + 0.1966 0.0080 0.8479 + 0.2544 0.0254 0.1438 + 0.9411 0.2648 0.6022 + 0.9904 0.3270 0.8801 + 0.5011 0.0608 0.7151 + 0.4306 0.5025 0.6970 + 0.7600 0.0648 0.6352 + 0.8428 0.1614 0.4374 + 0.7337 0.1432 0.7528 + 0.7799 0.2674 0.2795 + 0.1125 0.0389 0.7427 + 0.6235 0.6113 0.4224 + 0.8652 0.3020 0.7012 + 0.0585 0.2901 0.6066 + 0.2551 0.6485 0.6733 + 0.3578 0.4431 0.8841 + 0.3194 0.8011 0.5885 + 0.8658 0.1362 0.9775 + 0.7267 0.8782 0.1815 + 0.9021 0.1424 0.9340 + 0.1541 0.2204 0.2235 + 0.3263 0.9738 0.1737 + 0.8442 0.1610 0.1785 + 0.0902 0.4908 0.3965 + 0.9395 0.1451 0.8357 + 0.5820 0.4252 0.1027 spike_snippets(:,:,8) = - 0.5593 0.0664 0.1353 - 0.3706 0.7314 0.5002 - 0.2348 0.4361 0.8898 - 0.3238 0.8773 0.3457 - 0.7999 0.0080 0.0955 - 0.1456 0.2914 0.5586 - 0.8459 0.2323 0.0713 - 0.8664 0.9928 0.3438 - 0.1765 0.6375 0.2839 - 0.0213 0.9495 0.2103 - 0.9784 0.6562 0.7590 - 0.4871 0.4380 0.3342 - 0.3254 0.4880 0.2579 - 0.7349 0.5574 0.3016 - 0.5836 0.4425 0.1105 - 0.6315 0.4373 0.2164 - 0.9936 0.1000 0.7040 - 0.0706 0.2114 0.4139 - 0.3899 0.1189 0.6065 - 0.8679 0.0302 0.9512 - 0.2909 0.3544 0.1529 - 0.2939 0.4527 0.7157 - 0.8946 0.0451 0.3767 - 0.8676 0.5813 0.1104 - 0.0709 0.2271 0.6175 - 0.8726 0.2966 0.0629 - 0.8273 0.7022 0.3356 - 0.0928 0.5537 0.5731 - 0.2954 0.0095 0.0852 - 0.1539 0.0523 0.0867 - 0.9804 0.0772 0.3589 - 0.8008 0.8144 0.7733 - 0.5719 0.3287 0.5242 - 0.7745 0.6716 0.0099 - 0.0604 0.9060 0.4542 - 0.3181 0.2407 0.6370 - 0.1010 0.6173 0.8799 - 0.3902 0.9332 0.4848 - 0.4473 0.5347 0.4481 - 0.8056 0.4465 0.1206 + 0.1236 0.6511 0.1434 + 0.6919 0.8788 0.5315 + 0.0110 0.2866 0.2883 + 0.6653 0.3658 0.7295 + 0.3830 0.3836 0.1894 + 0.0792 0.4844 0.6232 + 0.0429 0.0815 0.0893 + 0.9189 0.4935 0.6753 + 0.9430 0.6922 0.1165 + 0.6600 0.5560 0.4981 + 0.7278 0.3105 0.6180 + 0.2455 0.8558 0.8068 + 0.1648 0.5900 0.8158 + 0.7264 0.2944 0.9467 + 0.5387 0.3136 0.4850 + 0.2455 0.0724 0.9675 + 0.9792 0.7856 0.1285 + 0.0151 0.3191 0.4051 + 0.7201 0.2710 0.8689 + 0.6860 0.1948 0.5062 + 0.8377 0.2674 0.8979 + 0.3805 0.7267 0.2665 + 0.2113 0.5110 0.2891 + 0.2075 0.1331 0.1577 + 0.5781 0.8387 0.8078 + 0.0374 0.8755 0.9759 + 0.0858 0.7683 0.4930 + 0.9413 0.4375 0.2516 + 0.4598 0.0071 0.5345 + 0.3385 0.2310 0.6649 + 0.8964 0.7416 0.8791 + 0.9074 0.5103 0.8058 + 0.0780 0.2104 0.6698 + 0.1535 0.9910 0.7050 + 0.2712 0.3049 0.6195 + 0.0698 0.8990 0.2976 + 0.5451 0.3134 0.1452 + 0.6226 0.0632 0.5848 + 0.3860 0.4139 0.0877 + 0.2459 0.3314 0.7027 spike_snippets(:,:,9) = - 0.9006 0.9767 0.4546 - 0.4900 0.1118 0.3742 - 0.6286 0.1657 0.4895 - 0.0315 0.7151 0.8266 - 0.5473 0.4190 0.4702 - 0.5753 0.7077 0.3470 - 0.9155 0.6868 0.1367 - 0.0095 0.6625 0.5484 - 0.7689 0.2045 0.2163 - 0.4798 0.6163 0.2810 - 0.1679 0.4193 0.0015 - 0.3575 0.4011 0.3735 - 0.0642 0.3644 0.1549 - 0.9379 0.0651 0.6980 - 0.4819 0.6627 0.9033 - 0.8420 0.3811 0.3017 - 0.0722 0.2149 0.7973 - 0.4157 0.9105 0.2724 - 0.0533 0.0427 0.8246 - 0.6276 0.0139 0.2806 - 0.6060 0.0471 0.3017 - 0.5282 0.1674 0.4363 - 0.3956 0.7580 0.4897 - 0.6309 0.0353 0.6656 - 0.1861 0.6520 0.8533 - 0.0611 0.9159 0.1356 - 0.6147 0.7900 0.1875 - 0.2017 0.7556 0.4291 - 0.4022 0.2524 0.1622 - 0.5871 0.7990 0.1337 - 0.1497 0.4101 0.9119 - 0.0517 0.4808 0.4061 - 0.0749 0.3774 0.7091 - 0.2355 0.2938 0.9197 - 0.6699 0.6678 0.5774 - 0.3838 0.9774 0.6441 - 0.0487 0.8784 0.1452 - 0.7651 0.1562 0.2282 - 0.4379 0.5744 0.9057 - 0.9151 0.5201 0.2940 + 0.1862 0.3166 0.5625 + 0.6883 0.8583 0.6659 + 0.8207 0.2343 0.7708 + 0.4771 0.9836 0.6667 + 0.9927 0.8793 0.5623 + 0.5524 0.3897 0.6021 + 0.0158 0.0147 0.6756 + 0.5509 0.3765 0.1045 + 0.0319 0.8561 0.6120 + 0.6078 0.8196 0.1573 + 0.8092 0.9058 0.8133 + 0.1753 0.4625 0.3167 + 0.7093 0.0153 0.8247 + 0.0031 0.9350 0.7074 + 0.7463 0.7795 0.7193 + 0.6112 0.7431 0.8335 + 0.1113 0.7544 0.5980 + 0.1001 0.0113 0.5878 + 0.7874 0.3017 0.8858 + 0.3490 0.6528 0.4592 + 0.2282 0.9735 0.8830 + 0.5118 0.8896 0.5627 + 0.8384 0.6445 0.6133 + 0.7058 0.5834 0.9355 + 0.8892 0.4049 0.2107 + 0.7051 0.1832 0.5109 + 0.9029 0.6395 0.3890 + 0.2350 0.2461 0.1438 + 0.4924 0.8447 0.9080 + 0.3103 0.9046 0.0424 + 0.0415 0.4561 0.1136 + 0.9019 0.4703 0.8842 + 0.5687 0.0371 0.4397 + 0.1593 0.0056 0.3861 + 0.6538 0.0122 0.3605 + 0.1895 0.2687 0.9563 + 0.4757 0.9230 0.5992 + 0.5058 0.0182 0.8989 + 0.7973 0.6793 0.1926 + 0.9365 0.5756 0.1470 spike_snippets(:,:,10) = - 0.8282 0.2138 0.4285 - 0.1261 0.4883 0.5792 - 0.2228 0.6247 0.6779 - 0.5449 0.8480 0.6369 - 0.4660 0.2117 0.6570 - 0.3096 0.5601 0.1036 - 0.7891 0.2303 0.9913 - 0.4926 0.8584 0.9990 - 0.3495 0.0570 0.5718 - 0.9466 0.2449 0.6204 - 0.0511 0.9682 0.8855 - 0.3725 0.5535 0.6946 - 0.3412 0.3071 0.8087 - 0.5381 0.5668 0.9207 - 0.1865 0.0673 0.4772 - 0.3406 0.2523 0.8170 - 0.5898 0.5944 0.8017 - 0.8600 0.7295 0.3962 - 0.1895 0.6133 0.0434 - 0.0279 0.2701 0.1114 - 0.0769 0.4293 0.3671 - 0.7910 0.9349 0.3867 - 0.8657 0.1144 0.5284 - 0.8548 0.8806 0.7524 - 0.4823 0.9414 0.3000 - 0.9317 0.7896 0.5841 - 0.0128 0.5571 0.6863 - 0.8671 0.7001 0.9361 - 0.5764 0.2295 0.1059 - 0.8178 0.5535 0.1238 - 0.8806 0.7218 0.9090 - 0.0153 0.7562 0.7664 - 0.2824 0.6593 0.8112 - 0.3260 0.6340 0.0707 - 0.7501 0.4089 0.5904 - 0.5228 0.3117 0.3652 - 0.4952 0.8159 0.5059 - 0.0382 0.4136 0.9324 - 0.8208 0.4944 0.5800 - 0.2682 0.8801 0.1464 + 0.6774 0.8672 0.3929 + 0.7508 0.8390 0.1819 + 0.8385 0.0982 0.1049 + 0.8561 0.8053 0.5929 + 0.6299 0.8493 0.1438 + 0.4487 0.6291 0.9145 + 0.3847 0.4643 0.3373 + 0.5404 0.7072 0.0958 + 0.4907 0.5549 0.8605 + 0.7538 0.9238 0.0707 + 0.6480 0.6550 0.8500 + 0.2106 0.1632 0.5082 + 0.2997 0.4515 0.2181 + 0.5225 0.6085 0.4031 + 0.7164 0.0671 0.2393 + 0.4219 0.8559 0.8960 + 0.2255 0.7409 0.2879 + 0.1748 0.2214 0.3755 + 0.6444 0.8154 0.7101 + 0.8884 0.4719 0.3015 + 0.9329 0.2244 0.2333 + 0.9767 0.3629 0.4684 + 0.8712 0.7563 0.3301 + 0.1827 0.6614 0.8416 + 0.7168 0.9171 0.7254 + 0.0646 0.5612 0.5675 + 0.5051 0.9097 0.6379 + 0.6441 0.4000 0.2584 + 0.3448 0.8788 0.3373 + 0.7757 0.0176 0.1728 + 0.7208 0.7215 0.9140 + 0.2416 0.7711 0.6687 + 0.9054 0.5785 0.7456 + 0.0335 0.1418 0.4057 + 0.9318 0.0209 0.8280 + 0.4995 0.5503 0.6516 + 0.5591 0.8762 0.9773 + 0.4354 0.9011 0.0860 + 0.2329 0.4244 0.1117 + 0.0895 0.3846 0.8654 spike_snippets(:,:,11) = - 0.8236 0.0540 0.7995 - 0.0204 0.5714 0.1848 - 0.5037 0.2359 0.6157 - 0.1659 0.0862 0.1732 - 0.6297 0.3077 0.5853 - 0.5058 0.9417 0.8461 - 0.9337 0.4265 0.6859 - 0.8281 0.2999 0.5419 - 0.8863 0.3362 0.6720 - 0.7906 0.5052 0.6274 - 0.9557 0.0771 0.3880 - 0.5038 0.6242 0.7661 - 0.8903 0.7702 0.3272 - 0.4220 0.8902 0.2522 - 0.0672 0.8752 0.0396 - 0.9442 0.2209 0.3697 - 0.5215 0.2116 0.3633 - 0.3515 0.4938 0.7357 - 0.2163 0.7856 0.7170 - 0.9434 0.9972 0.2783 - 0.9765 0.7021 0.0121 - 0.4874 0.4950 0.5714 - 0.1665 0.2588 0.9994 - 0.9151 0.8206 0.1286 - 0.9721 0.8228 0.6599 - 0.1537 0.0543 0.0633 - 0.9924 0.5272 0.3047 - 0.6047 0.3395 0.0708 - 0.6732 0.5972 0.7567 - 0.9690 0.4308 0.0410 - 0.8854 0.6984 0.5455 - 0.4442 0.3944 0.5872 - 0.4572 0.4874 0.3204 - 0.2759 0.2410 0.6748 - 0.6670 0.9343 0.9368 - 0.1688 0.9325 0.1636 - 0.9597 0.4930 0.4160 - 0.7391 0.2095 0.5453 - 0.6546 0.9712 0.6962 - 0.6566 0.4447 0.1735 + 0.8347 0.4022 0.6120 + 0.3468 0.5189 0.4033 + 0.2546 0.6819 0.0986 + 0.7404 0.1464 0.7471 + 0.6433 0.2989 0.8645 + 0.9926 0.0527 0.3949 + 0.9637 0.2744 0.2190 + 0.7971 0.7036 0.4778 + 0.4520 0.5440 0.6679 + 0.3244 0.4465 0.5489 + 0.9640 0.3327 0.7965 + 0.5527 0.1972 0.7607 + 0.2490 0.6106 0.3055 + 0.8834 0.5081 0.0499 + 0.2723 0.1501 0.9294 + 0.2521 0.5971 0.5519 + 0.7322 0.9338 0.7382 + 0.0855 0.8082 0.5566 + 0.7416 0.1892 0.5572 + 0.2958 0.9147 0.6783 + 0.0851 0.4890 0.4612 + 0.3550 0.0450 0.2096 + 0.1819 0.2133 0.5934 + 0.9579 0.1414 0.7223 + 0.4275 0.6274 0.9427 + 0.0523 0.0421 0.6497 + 0.0685 0.9755 0.6641 + 0.5790 0.6845 0.7101 + 0.7696 0.1755 0.2687 + 0.7507 0.4235 0.1038 + 0.6559 0.8909 0.1183 + 0.6514 0.9716 0.1036 + 0.9478 0.7154 0.0593 + 0.7043 0.2236 0.8412 + 0.3663 0.5732 0.0736 + 0.1300 0.3943 0.1802 + 0.6451 0.1573 0.4161 + 0.8897 0.7446 0.7855 + 0.3715 0.4903 0.6490 + 0.5015 0.5459 0.3932 spike_snippets(:,:,12) = - 0.1184 0.1251 0.0214 - 0.5802 0.0419 0.5646 - 0.7721 0.2065 0.3439 - 0.8848 0.7671 0.2095 - 0.5418 0.3479 0.6222 - 0.8559 0.5945 0.7391 - 0.6337 0.6223 0.5492 - 0.0702 0.5033 0.6576 - 0.0921 0.6129 0.9543 - 0.8458 0.6076 0.3383 - 0.4057 0.1879 0.5339 - 0.0289 0.5740 0.8159 - 0.2173 0.5540 0.1329 - 0.5727 0.7952 0.9536 - 0.5524 0.2359 0.5984 - 0.4675 0.2840 0.3707 - 0.8868 0.5361 0.0724 - 0.9720 0.0704 0.1437 - 0.4901 0.2043 0.1668 - 0.4051 0.5348 0.8620 - 0.4173 0.5441 0.1257 - 0.1043 0.2794 0.7575 - 0.5445 0.8990 0.4524 - 0.1090 0.8750 0.9088 - 0.8141 0.4295 0.4942 - 0.0896 0.5689 0.9763 - 0.4692 0.4561 0.9672 - 0.5486 0.5594 0.7761 - 0.5805 0.8100 0.1513 - 0.8436 0.7339 0.5115 - 0.0133 0.0170 0.3654 - 0.2173 0.4141 0.6722 - 0.1264 0.5418 0.5239 - 0.2281 0.1424 0.2084 - 0.3829 0.8320 0.6373 - 0.1969 0.7664 0.5257 - 0.4521 0.7498 0.0649 - 0.2503 0.4570 0.1978 - 0.2399 0.7075 0.6893 - 0.4851 0.6479 0.6504 + 0.8950 0.7784 0.3370 + 0.3776 0.8958 0.1258 + 0.2153 0.5221 0.4411 + 0.9188 0.6917 0.9823 + 0.0532 0.3541 0.2336 + 0.6066 0.7796 0.6442 + 0.7270 0.2461 0.5945 + 0.2519 0.4929 0.5494 + 0.0581 0.7809 0.5255 + 0.2975 0.1215 0.7106 + 0.8214 0.1294 0.1760 + 0.4632 0.9377 0.6688 + 0.2908 0.8853 0.7457 + 0.1076 0.5625 0.0337 + 0.0972 0.5536 0.1822 + 0.1280 0.7994 0.4142 + 0.1412 0.1745 0.7949 + 0.3290 0.2348 0.6860 + 0.7861 0.2603 0.6156 + 0.4999 0.3998 0.9619 + 0.2821 0.8703 0.1428 + 0.9904 0.9902 0.9319 + 0.7028 0.0983 0.1120 + 0.8319 0.1780 0.2330 + 0.2656 0.7303 0.3022 + 0.9044 0.4930 0.4198 + 0.6919 0.8411 0.3521 + 0.1977 0.5059 0.4959 + 0.8080 0.3110 0.4749 + 0.7690 0.1746 0.0756 + 0.5096 0.2501 0.1510 + 0.9853 0.4394 0.0432 + 0.2593 0.5565 0.1624 + 0.9890 0.5731 0.9597 + 0.9599 0.0753 0.2030 + 0.5933 0.8058 0.7752 + 0.6314 0.7114 0.8213 + 0.8363 0.0528 0.3939 + 0.4076 0.7217 0.5043 + 0.1097 0.2343 0.2508 spike_snippets(:,:,13) = - 0.1851 0.2148 0.3096 - 0.1530 0.6928 0.4706 - 0.5957 0.5997 0.6436 - 0.5111 0.9477 0.0237 - 0.2794 0.8481 0.2508 - 0.8733 0.8629 0.9143 - 0.8843 0.5084 0.3684 - 0.2139 0.3135 0.3625 - 0.5994 0.2873 0.0790 - 0.9182 0.7084 0.1474 - 0.0354 0.0425 0.9387 - 0.2318 0.1590 0.8480 - 0.3196 0.2009 0.7093 - 0.1199 0.3389 0.8795 - 0.0257 0.4380 0.0876 - 0.3023 0.9498 0.4555 - 0.6875 0.8848 0.0010 - 0.5839 0.9727 0.6120 - 0.5932 0.3215 0.9277 - 0.7070 0.6221 0.4235 - 0.4214 0.4862 0.8240 - 0.3256 0.7700 0.4219 - 0.8684 0.9093 0.2151 - 0.1271 0.2716 0.2960 - 0.8478 0.4911 0.6642 - 0.1270 0.8443 0.8821 - 0.8903 0.9322 0.1781 - 0.0355 0.4999 0.5528 - 0.8283 0.6655 0.4869 - 0.9501 0.6728 0.0283 - 0.0597 0.0752 0.5528 - 0.0772 0.8877 0.5799 - 0.0882 0.7317 0.1380 - 0.6245 0.6600 0.9433 - 0.4312 0.1948 0.2695 - 0.2354 0.4766 0.3604 - 0.9902 0.3074 0.5376 - 0.7988 0.9897 0.9941 - 0.8217 0.5316 0.8525 - 0.2351 0.7831 0.0319 + 0.2949 0.5202 0.1211 + 0.5566 0.5845 0.5552 + 0.0722 0.6373 0.8406 + 0.9252 0.0517 0.5231 + 0.4902 0.5120 0.9631 + 0.2890 0.4406 0.8756 + 0.8333 0.0660 0.7129 + 0.7859 0.1514 0.2913 + 0.0624 0.2431 0.4692 + 0.9129 0.6801 0.7586 + 0.9633 0.8651 0.4898 + 0.9800 0.8840 0.2424 + 0.1020 0.8982 0.5261 + 0.6766 0.0988 0.9697 + 0.2313 0.1582 0.4006 + 0.0004 0.3593 0.5432 + 0.7084 0.5084 0.4525 + 0.7881 0.0612 0.4665 + 0.9016 0.5683 0.2416 + 0.9231 0.7142 0.3431 + 0.3189 0.6186 0.6160 + 0.5037 0.2760 0.5419 + 0.0237 0.7551 0.1368 + 0.6520 0.7281 0.4271 + 0.2760 0.1216 0.5721 + 0.8475 0.6912 0.3738 + 0.7810 0.0934 0.3155 + 0.6271 0.8286 0.1854 + 0.1149 0.9365 0.5787 + 0.7695 0.1150 0.0557 + 0.5477 0.5701 0.2503 + 0.4269 0.0165 0.1992 + 0.4939 0.9138 0.8500 + 0.9287 0.8734 0.5062 + 0.7151 0.2187 0.0008 + 0.2543 0.6974 0.4274 + 0.9483 0.7896 0.6908 + 0.7255 0.8452 0.2864 + 0.0348 0.5217 0.1593 + 0.5899 0.3122 0.1772 spike_snippets(:,:,14) = - 0.2230 0.1351 0.7886 - 0.4238 0.2492 0.9146 - 0.1847 0.5595 0.5707 - 0.8921 0.0787 0.4474 - 0.7938 0.3050 0.6435 - 0.0043 0.8448 0.9762 - 0.9224 0.5257 0.8517 - 0.0390 0.3533 0.2300 - 0.1118 0.7620 0.4621 - 0.2636 0.3288 0.9287 - 0.4195 0.1997 0.1947 - 0.6326 0.5843 0.1339 - 0.9864 0.6860 0.5085 - 0.3759 0.6882 0.3052 - 0.2147 0.6437 0.8457 - 0.8211 0.9626 0.7382 - 0.8825 0.5314 0.3377 - 0.2341 0.1485 0.8010 - 0.3810 0.8068 0.4227 - 0.4321 0.4115 0.7880 - 0.9936 0.1828 0.3977 - 0.7010 0.2326 0.6093 - 0.1119 0.4843 0.9668 - 0.0840 0.3081 0.2195 - 0.8017 0.3575 0.2703 - 0.6757 0.3959 0.3937 - 0.0061 0.4527 0.3682 - 0.1994 0.8625 0.6531 - 0.0574 0.4723 0.1477 - 0.0919 0.6493 0.7246 - 0.6749 0.4246 0.8361 - 0.1309 0.4423 0.8770 - 0.2955 0.5541 0.6218 - 0.3190 0.6917 0.7228 - 0.6028 0.3446 0.4859 - 0.6943 0.8714 0.3217 - 0.6263 0.6614 0.6188 - 0.5735 0.2745 0.5193 - 0.4774 0.9703 0.1984 - 0.5386 0.6275 0.1304 + 0.9771 0.4050 0.7480 + 0.0164 0.7490 0.3865 + 0.3992 0.3380 0.2018 + 0.4467 0.0394 0.6368 + 0.7589 0.7476 0.6173 + 0.3515 0.3967 0.4286 + 0.2178 0.9765 0.7002 + 0.8223 0.9313 0.6389 + 0.5271 0.3603 0.2278 + 0.6464 0.5478 0.1317 + 0.4060 0.4718 0.9555 + 0.0270 0.4830 0.8491 + 0.0169 0.4543 0.9645 + 0.3327 0.7379 0.5937 + 0.9158 0.2060 0.9178 + 0.7765 0.1282 0.4284 + 0.3668 0.5960 0.5725 + 0.8509 0.0220 0.6117 + 0.3111 0.9309 0.9131 + 0.6934 0.8826 0.2040 + 0.2483 0.4454 0.1425 + 0.5874 0.7093 0.7173 + 0.8735 0.5988 0.0476 + 0.0763 0.7666 0.8454 + 0.1493 0.6608 0.4174 + 0.5494 0.2831 0.4819 + 0.4071 0.2351 0.8550 + 0.7085 0.9237 0.4157 + 0.7682 0.6375 0.0323 + 0.0196 0.1156 0.2325 + 1.0000 0.7195 0.4628 + 0.1514 0.4361 0.4644 + 0.9657 0.9117 0.4204 + 0.6568 0.0403 0.5068 + 0.8954 0.0092 0.2470 + 0.0665 1.0000 0.5583 + 0.8023 0.3161 0.5107 + 0.4888 0.3307 0.9941 + 0.6054 0.8640 0.4672 + 0.3619 0.9700 0.5930 spike_snippets(:,:,15) = - 0.2262 0.2766 0.5907 - 0.2056 0.1937 0.8867 - 0.1219 0.6344 0.5519 - 0.5776 0.6414 0.7407 - 0.4338 0.4308 0.8669 - 0.4408 0.5366 0.0469 - 0.4334 0.6519 0.2752 - 0.0031 0.3424 0.6518 - 0.0089 0.9570 0.5937 - 0.3908 0.1357 0.6025 - 0.7333 0.2195 0.2525 - 0.1027 0.9820 0.3862 - 0.8368 0.5239 0.1034 - 0.5086 0.8720 0.9551 - 0.6295 0.4330 0.9450 - 0.8806 0.3041 0.5778 - 0.8743 0.3345 0.8624 - 0.9921 0.4705 0.1314 - 0.4447 0.1070 0.5149 - 0.7418 0.5089 0.6865 - 0.5510 0.5950 0.6378 - 0.5892 0.7082 0.6455 - 0.9912 0.5156 0.8352 - 0.3359 0.6611 0.4911 - 0.3669 0.8796 0.2214 - 0.1394 0.4076 0.7643 - 0.9438 0.9816 0.3084 - 0.3012 0.6891 0.7769 - 0.4734 0.3384 0.8854 - 0.6214 0.6087 0.1137 - 0.0905 0.7920 0.2819 - 0.1783 0.8610 0.3101 - 0.1026 0.2972 0.1394 - 0.9566 0.4932 0.2185 - 0.1568 0.0195 0.2752 - 0.1294 0.8462 0.1243 - 0.5607 0.1783 0.9560 - 0.6664 0.4109 0.7201 - 0.9564 0.1284 0.8795 - 0.3678 0.5102 0.1463 + 0.8285 0.2332 0.5170 + 0.8534 0.5185 0.1475 + 0.1406 0.4439 0.6468 + 0.6271 0.7734 0.2874 + 0.7247 0.1307 0.7292 + 0.8879 0.9477 0.5567 + 0.2379 0.0284 0.5833 + 0.1076 0.1909 0.3181 + 0.9581 0.8145 0.8141 + 0.0705 0.4884 0.9800 + 0.1540 0.4450 0.7869 + 0.8214 0.2924 0.8107 + 0.3680 0.9230 0.7764 + 0.6458 0.1220 0.8034 + 0.0179 0.1152 0.1233 + 0.6552 0.0921 0.4741 + 0.4289 0.4675 0.4627 + 0.5957 0.6017 0.0530 + 0.7899 0.0406 0.7737 + 0.2715 0.5865 0.3788 + 0.3688 0.0015 0.2919 + 0.8261 0.6455 0.1661 + 0.9652 0.1234 0.2829 + 0.9641 0.1551 0.6033 + 0.0687 0.3860 0.3001 + 0.0792 0.8179 0.7818 + 0.7146 0.3863 0.2066 + 0.1720 0.3922 0.6165 + 0.0660 0.6977 0.0619 + 0.3606 0.8087 0.4434 + 0.0506 0.2943 0.3097 + 0.8358 0.0611 0.0026 + 0.1669 0.6148 0.0415 + 0.5499 0.1266 0.6509 + 0.0248 0.1344 0.8819 + 0.4887 0.2018 0.2192 + 0.6738 0.0505 0.3670 + 0.9817 0.2910 0.5394 + 0.0308 0.4956 0.9864 + 0.4028 0.5162 0.1998 spike_snippets(:,:,16) = - 0.1927 0.6688 0.6547 - 0.1281 0.6194 0.0771 - 0.2197 0.5600 0.5198 - 0.4552 0.9523 0.6863 - 0.3747 0.6933 0.0655 - 0.0005 0.2037 0.3981 - 0.6358 0.3771 0.3128 - 0.9756 0.2283 0.1688 - 0.2239 0.9016 0.6976 - 0.6167 0.2771 0.7410 - 0.8370 0.4043 0.8922 - 0.1644 0.5570 0.8032 - 0.1674 0.9934 0.9522 - 0.3557 0.8356 0.3211 - 0.9197 0.0929 0.0852 - 0.5803 0.2539 0.5705 - 0.5259 0.0673 0.3024 - 0.6417 0.3843 0.7508 - 0.8997 0.1641 0.3253 - 0.5032 0.4427 0.6970 - 0.0421 0.0260 0.5815 - 0.2497 0.6524 0.2077 - 0.0868 0.2699 0.6739 - 0.7045 0.2186 0.7132 - 0.2521 0.5041 0.4006 - 0.4592 0.3762 0.8459 - 0.3910 0.6362 0.8886 - 0.9811 0.0518 0.6936 - 0.9311 0.1824 0.4904 - 0.3803 0.1296 0.3489 - 0.2317 0.9686 0.2949 - 0.7826 0.0530 0.1981 - 0.9760 0.7078 0.9084 - 0.1940 0.3259 0.6837 - 0.4076 0.6466 0.9943 - 0.0136 0.9390 0.2555 - 0.3636 0.5421 0.9818 - 0.9103 0.4774 0.2366 - 0.8823 0.6284 0.0572 - 0.3817 0.4467 0.2317 + 0.0632 0.4119 0.1498 + 0.7862 0.8745 0.7497 + 0.3152 0.5970 0.2680 + 0.9582 0.5279 0.0675 + 0.8466 0.0177 0.1343 + 0.0575 0.4940 0.0601 + 0.9653 0.0421 0.6708 + 0.3656 0.9654 0.3555 + 0.6427 0.6600 0.8240 + 0.5254 0.5350 0.7614 + 0.8651 0.4518 0.1316 + 0.3772 0.3970 0.1697 + 0.9844 0.6259 0.4263 + 0.7779 0.2904 0.9443 + 0.4432 0.5715 0.7672 + 0.4826 0.1578 0.9774 + 0.8992 0.2390 0.9241 + 0.5520 0.7640 0.5236 + 0.6074 0.4966 0.5190 + 0.8831 0.8285 0.8182 + 0.0123 0.3464 0.0066 + 0.8589 0.9798 0.5752 + 0.6037 0.3889 0.2671 + 0.4787 0.5672 0.1828 + 0.9966 0.1551 0.4506 + 0.4607 0.6693 0.8682 + 0.1410 0.9311 0.4041 + 0.8505 0.8780 0.9635 + 0.8570 0.6529 0.0714 + 0.3587 0.9783 0.4064 + 0.4781 0.3078 0.8497 + 0.4610 0.3308 0.1232 + 0.8942 0.0222 0.8837 + 0.2707 0.6125 0.0489 + 0.8865 0.2327 0.1457 + 0.5464 0.8523 0.6175 + 0.4665 0.8757 0.5159 + 0.1181 0.2201 0.9281 + 0.8407 0.2243 0.4280 + 0.7371 0.5195 0.5242 spike_snippets(:,:,17) = - 0.6173 0.6918 0.6871 - 0.7990 0.7099 0.8378 - 0.8449 0.0978 0.9949 - 0.5732 0.7979 0.6361 - 0.7057 0.3483 0.5367 - 0.2349 0.2924 0.7952 - 0.1477 0.4052 0.9149 - 0.9360 0.5117 0.0062 - 0.4026 0.8478 0.4686 - 0.9453 0.4626 0.3981 - 0.8858 0.2622 0.2753 - 0.5764 0.1099 0.3039 - 0.3797 0.8276 0.6748 - 0.1666 0.9840 0.7505 - 0.5156 0.3978 0.2766 - 0.0482 0.8001 0.9245 - 0.2122 0.5472 0.2862 - 0.3097 0.3191 0.8146 - 0.3381 0.4056 0.7843 - 0.1068 0.2273 0.6883 - 0.7219 0.8860 0.6370 - 0.2746 0.9846 0.0249 - 0.3418 0.3116 0.4372 - 0.1499 0.1693 0.3565 - 0.7936 0.9327 0.5851 - 0.0315 0.7144 0.9622 - 0.0995 0.4372 0.1286 - 0.8053 0.0565 0.4768 - 0.7469 0.6741 0.1784 - 0.3801 0.1842 0.0446 - 0.9876 0.0459 0.6812 - 0.3980 0.6965 0.4432 - 0.8502 0.9950 0.0137 - 0.7209 0.4370 0.6352 - 0.8724 0.0388 0.8579 - 0.2434 0.4660 0.3433 - 0.9861 0.0908 0.2780 - 0.4661 0.4741 0.7048 - 0.5965 0.5980 0.7120 - 0.9756 0.2825 0.6311 + 0.2248 0.9454 0.3343 + 0.2661 0.2714 0.5529 + 0.6409 0.5257 0.2509 + 0.9720 0.6149 0.1317 + 0.4660 0.5646 0.0069 + 0.6395 0.8293 0.7489 + 0.0363 0.0109 0.0759 + 0.1006 0.2261 0.0331 + 0.4483 0.7455 0.8873 + 0.4724 0.9053 0.7795 + 0.6163 0.1059 0.4694 + 0.5038 0.8689 0.7092 + 0.4000 0.2480 0.9829 + 0.2566 0.8716 0.0221 + 0.1963 0.2623 0.1946 + 0.8754 0.8960 0.0683 + 0.3214 0.5532 0.3871 + 0.3090 0.4612 0.3692 + 0.0949 0.9036 0.7243 + 0.5785 0.0853 0.4553 + 0.4080 0.4273 0.1826 + 0.2397 0.1025 0.2676 + 0.0112 0.4233 0.8566 + 0.6912 0.5644 0.1537 + 0.5037 0.1292 0.5835 + 0.8740 0.9701 0.2860 + 0.1776 0.8694 0.4239 + 0.0146 0.2931 0.1575 + 0.1438 0.2192 0.0534 + 0.1210 0.6226 0.3310 + 0.6808 0.5873 0.8866 + 0.0166 0.4711 0.0265 + 0.2377 0.0932 0.4810 + 0.4095 0.5646 0.3677 + 0.7206 0.4269 0.0106 + 0.4908 0.5084 0.7710 + 0.9168 0.9261 0.2838 + 0.0650 0.3080 0.7490 + 0.9420 0.4491 0.0547 + 0.6323 0.8976 0.8351 spike_snippets(:,:,18) = - 0.3776 0.7823 0.0801 - 0.8622 0.9565 0.5796 - 0.1562 0.4375 0.9515 - 0.6307 0.1624 0.2088 - 0.4483 0.3889 0.3454 - 0.2331 0.8179 0.4931 - 0.7838 0.0012 0.3175 - 0.1697 0.1593 0.4797 - 0.2247 0.6018 0.8109 - 0.0227 0.7476 0.2138 - 0.6950 0.5190 0.2881 - 0.4064 0.3057 0.6401 - 0.2390 0.8704 0.0040 - 0.6227 0.1564 0.0455 - 0.0884 0.0660 0.4039 - 0.2207 0.2480 0.9150 - 0.4498 0.5628 0.7846 - 0.0646 0.8577 0.3313 - 0.7398 0.3892 0.9641 - 0.4970 0.0113 0.2510 - 0.5149 0.6946 0.4709 - 0.3363 0.4596 0.7754 - 0.1217 0.6060 0.3198 - 0.2941 0.8403 0.3636 - 0.8140 0.8582 0.7648 - 0.9589 0.5882 0.1668 - 0.2749 0.3104 0.1562 - 0.9278 0.1316 0.6444 - 0.8572 0.8799 0.8106 - 0.0872 0.1756 0.3494 - 0.8922 0.9633 0.0120 - 0.2752 0.6267 0.2324 - 0.0307 0.7052 0.7999 - 0.0839 0.3741 0.6110 - 0.4028 0.6985 0.5585 - 0.6630 0.8418 0.7399 - 0.1706 0.5353 0.9734 - 0.6713 0.3043 0.4227 - 0.6851 0.4996 0.0506 - 0.3057 0.4802 0.9650 + 0.8528 0.8267 0.3742 + 0.7533 0.1331 0.9552 + 0.4917 0.0555 0.7208 + 0.8872 0.2577 0.6193 + 0.5457 0.5930 0.0307 + 0.1417 0.9495 0.2944 + 0.3786 0.4841 0.4944 + 0.8947 0.1513 0.0882 + 0.5805 0.2407 0.3165 + 0.4782 0.4752 0.6826 + 0.3881 0.8809 0.0212 + 0.1409 0.6620 0.2262 + 0.7749 0.3939 0.4166 + 0.7476 0.2595 0.6855 + 0.8465 0.2595 0.1188 + 0.5590 0.4504 0.5791 + 0.8377 0.9104 0.6629 + 0.6318 0.3158 0.3771 + 0.8022 0.9276 0.2942 + 0.2505 0.8024 0.0318 + 0.9150 0.5174 0.6685 + 0.0144 0.9595 0.8606 + 0.4961 0.8575 0.2233 + 0.5635 0.4358 0.9664 + 0.3172 0.9393 0.1256 + 0.1253 0.8188 0.1029 + 0.3440 0.3788 0.0650 + 0.7378 0.9603 0.1648 + 0.4810 0.1027 0.5597 + 0.0185 0.2453 0.5999 + 0.8983 0.2843 0.7902 + 0.9571 0.6531 0.9934 + 0.3729 0.3625 0.7644 + 0.8940 0.6779 0.7209 + 0.0921 0.6594 0.9088 + 0.1161 0.6699 0.5187 + 0.6088 0.3770 0.6351 + 0.6902 0.4612 0.4053 + 0.1904 0.8284 0.8358 + 0.0138 0.0373 0.1868 spike_snippets(:,:,19) = - 0.5319 0.4611 0.0238 - 0.0107 0.4021 0.9675 - 0.2670 0.4292 0.2256 - 0.0075 0.2988 0.7416 - 0.6676 0.3036 0.8923 - 0.2578 0.9139 0.3128 - 0.4729 0.1512 0.3253 - 0.0726 0.7248 0.6174 - 0.1111 0.1281 0.6482 - 0.1313 0.5600 0.3097 - 0.6213 0.9661 0.2627 - 0.2338 0.6094 0.3207 - 0.1518 0.5683 0.1826 - 0.4942 0.6299 0.3986 - 0.7793 0.3064 0.4732 - 0.6928 0.7628 0.8839 - 0.6003 0.6119 0.9674 - 0.2245 0.4440 0.3893 - 0.3195 0.2501 0.3615 - 0.9588 0.0686 0.9949 - 0.1095 0.7686 0.3792 - 0.6823 0.8999 0.1835 - 0.4789 0.4263 0.1516 - 0.7116 0.3164 0.5941 - 0.1937 0.2194 0.6845 - 0.6531 0.7972 0.8251 - 0.8248 0.3564 0.6396 - 0.9730 0.2390 0.7955 - 0.7474 0.6799 0.7575 - 0.9625 0.1981 0.9859 - 0.2182 0.2432 0.8913 - 0.6330 0.5699 0.0269 - 0.6607 0.3176 0.4056 - 0.1300 0.8386 0.4653 - 0.5340 0.0663 0.1382 - 0.6349 0.7329 0.6797 - 0.0701 0.4965 0.6668 - 0.5020 0.1398 0.9634 - 0.5295 0.0102 0.9432 - 0.4737 0.2624 0.7338 + 0.7833 0.3800 0.2596 + 0.4774 0.3225 0.9132 + 0.3767 0.5766 0.7866 + 0.5167 0.7333 0.2876 + 0.3313 0.6348 0.4921 + 0.9909 0.7142 0.0040 + 0.7992 0.8899 0.7669 + 0.1438 0.3450 0.1557 + 0.8615 0.5972 0.6849 + 0.2221 0.2920 0.8230 + 0.9428 0.8654 0.7775 + 0.5121 0.7407 0.5810 + 0.6163 0.4000 0.3330 + 0.3748 0.9231 0.6470 + 0.4877 0.7523 0.8188 + 0.4864 0.9930 0.0859 + 0.0486 0.4711 0.6532 + 0.3978 0.0564 0.0195 + 0.9749 0.1345 0.4348 + 0.4293 0.0039 0.9382 + 0.5667 0.6412 0.2270 + 0.5779 0.7878 0.6903 + 0.4924 0.9922 0.8492 + 0.6379 0.1296 0.5580 + 0.4197 0.5457 0.1361 + 0.4808 0.5291 0.6389 + 0.6873 0.0123 0.3525 + 0.9886 0.1202 0.7538 + 0.7048 0.8567 0.9505 + 0.3475 0.1045 0.1739 + 0.8209 0.6846 0.5388 + 0.0525 0.3191 0.7253 + 0.1306 0.5241 0.2333 + 0.6714 0.2183 0.1283 + 0.8935 0.1168 0.5805 + 0.8253 0.1419 0.3755 + 0.6320 0.2447 0.7466 + 0.0071 0.0700 0.8273 + 0.3107 0.5529 0.8195 + 0.2268 0.3851 0.4185 spike_snippets(:,:,20) = - 0.6584 0.9637 0.6694 - 0.6882 0.9234 0.5334 - 0.9451 0.2206 0.1100 - 0.1020 0.7264 0.1427 - 0.8448 0.6778 0.2754 - 0.1440 0.4233 0.8132 - 0.5736 0.6490 0.4518 - 0.2343 0.2426 0.4747 - 0.0774 0.1005 0.0246 - 0.9198 0.3224 0.4104 - 0.4359 0.3599 0.1246 - 0.5686 0.0664 0.3270 - 0.2882 0.4851 0.5189 - 0.7017 0.9357 0.8863 - 0.3291 0.3256 0.1743 - 0.7455 0.4708 0.5486 - 0.6446 0.0108 0.8551 - 0.4382 0.1401 0.4226 - 0.2296 0.6263 0.9103 - 0.2811 0.5259 0.3679 - 0.6857 0.9481 0.0568 - 0.2926 0.8703 0.7404 - 0.8050 0.0736 0.1273 - 0.7048 0.6245 0.4469 - 0.9038 0.0299 0.8430 - 0.5372 0.8920 0.6377 - 0.9344 0.5897 0.1500 - 0.5390 0.3147 0.9303 - 0.6639 0.7378 0.5821 - 0.7281 0.8026 0.4448 - 0.2334 0.8817 0.7433 - 0.4686 0.5002 0.2370 - 0.8984 0.6981 0.5906 - 0.4098 0.2956 0.0880 - 0.2328 0.6203 0.4738 - 0.6246 0.7486 0.9808 - 0.2156 0.1182 0.3692 - 0.6777 0.0449 0.6831 - 0.3080 0.6611 0.3553 - 0.0171 0.1520 0.3349 -
 
% Create electrode table region referencing electrodes 0, 1, and 2
shank0_table_region = types.hdmf_common.DynamicTableRegion( ...
'table', types.untyped.ObjectView(electrodesDynamicTable), ...
'description', 'shank0', ...
'data', (0:2)');
 
% Define spike event series for unsorted spike times
spike_events = types.core.SpikeEventSeries( ...
'data', spike_snippets, ...
'timestamps', (0:19)', ... % Timestamps for each event
'description', 'events detected with 100uV threshold', ...
'electrodes', shank0_table_region ...
);
 
% Add spike event series to NWB file acquisition
nwb.acquisition.set('SpikeEvents_Shank0', spike_events);

Detected Events

If you need to store the complete, continuous raw voltage traces, along with unsorted spike times, you should store the traces in ElectricalSeries objects in the acquisition group, and use the EventDetection class to identify the spike events in your raw traces.
% Create the EventDetection object
event_detection = types.core.EventDetection( ...
'detection_method', 'thresholding, 1.5 * std', ...
'source_electricalseries', types.untyped.SoftLink(raw_electrical_series), ...
'source_idx', [1000; 2000; 3000], ...
'times', [.033, .066, .099] ...
);
 
% Add the EventDetection object to the ecephys module
ecephys_module.nwbdatainterface.set('ThresholdEvents', event_detection);

Storing Spike Features (e.g Principal Components)

NWB also provides a way to store features of spikes, such as principal components, using the FeatureExtraction class.
% Generate random feature data (time x channel x feature)
features = rand(3, 12, 4); % 3 time points, 12 channels, 4 features
features = permute(features, [3,2,1]) % reverse dimension order for matnwb
features =
features(:,:,1) = - - 0.9316 0.4439 0.1664 0.8142 0.2697 0.6258 0.4445 0.5889 0.5936 0.5510 0.3843 0.0928 - 0.6321 0.6096 0.2896 0.5378 0.2169 0.2326 0.6727 0.3081 0.5352 0.3272 0.3730 0.7105 - 0.5674 0.5495 0.8397 0.5804 0.3298 0.0668 0.6826 0.1170 0.2237 0.8309 0.8602 0.1544 - 0.4657 0.4697 0.6416 0.8899 0.2070 0.2748 0.1939 0.7873 0.9721 0.7856 0.0667 0.1039 - - -features(:,:,2) = - - 0.9188 0.8798 0.8666 0.2478 0.7362 0.0880 0.0963 0.1304 0.5751 0.4159 0.8664 0.6476 - 0.9597 0.7697 0.0909 0.7685 0.0993 0.9162 0.8964 0.5880 0.4342 0.1795 0.7080 0.4490 - 0.5198 0.6699 0.2718 0.2714 0.3393 0.5473 0.2908 0.5508 0.2389 0.9618 0.6670 0.1449 - 0.1393 0.5076 0.7794 0.9437 0.9352 0.1637 0.5838 0.1138 0.7266 0.7907 0.8624 0.1127 - - -features(:,:,3) = - - 0.8750 0.5151 0.1783 0.6123 0.1882 0.2455 0.3716 0.9768 0.1423 0.1525 0.3370 0.2588 - 0.4765 0.7175 0.5126 0.4949 0.2113 0.9030 0.0051 0.3652 0.7116 0.9779 0.8704 0.7190 - 0.6226 0.7981 0.6252 0.9443 0.8116 0.8497 0.9575 0.3736 0.2381 0.6094 0.1856 0.6485 - 0.8744 0.2761 0.6534 0.0308 0.2033 0.1817 0.1606 0.0353 0.8287 0.1804 0.5846 0.6030 -
 
% Create the FeatureExtraction object
feature_extraction = types.core.FeatureExtraction( ...
'description', {'PC1', 'PC2', 'PC3', 'PC4'}, ... % Feature descriptions
'electrodes', electrode_table_region, ... % DynamicTableRegion referencing the electrodes table
'times', [.033; .066; .099], ... % Column vector for times
'features', features ...
);
 
% Add the FeatureExtraction object to the ecephys module (if required)
ecephys_module.nwbdatainterface.set('PCA_features', feature_extraction);

Choosing NWB-Types for Electrophysiology Data (A Summary)

As mentioned above, ElectricalSeries objects are meant for storing electrical timeseries data like raw voltage signals or processed signals like LFP or other filtered signals. In addition to the ElectricalSeries class, NWB provides some more classes for storing event-based electropysiological data. We will briefly discuss them here, and refer the reader to the API documentation and the section on Extracellular Physiology in the "NWB Format Specification" for more details on using these objects.
For storing unsorted spiking 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 entire raw voltage traces, only the waveform ‘snippets’ surrounding spike events, you should use SpikeEventSeries objects.
The results of spike sorting (or clustering) should be stored in the top-level Units table. The Units table can hold just the spike times of sorted units or, optionally, include additional waveform information. You can use the optional predefined columns waveform_mean, waveform_sd, and waveforms in the Units table to store individual and mean waveform data.

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.0003 -1.3106 2.3180 0.0008 1.9394 1.7373 -1.1235 -0.1723 -0.7690 0.3233 - -2.7453 0.6558 0.7985 -0.7032 -0.1310 0.7679 -1.0497 2.1285 0.8362 0.3742 - 0.7983 1.4374 0.1157 0.9797 0.2739 -1.4714 -2.2506 1.2310 -0.5585 0.0383 - -0.3854 -1.5432 1.3848 0.9771 1.1799 -0.3393 -1.3353 -1.6190 2.2322 -1.1545 - -0.9029 -1.5846 -0.5733 -0.1312 -1.0385 -0.9423 -0.2002 -0.9642 -0.4652 -1.5418 -
 
% 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 = 23×1
0.7889 - 0.1248 - 0.0928 - 0.2707 - 0.1974 - 0.6140 - 0.5269 - 0.8578 - 0.2521 - 0.3357 -

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:
+ 0.0803 0.5497 0.8134 + 0.2016 0.7139 0.9475 + 0.2989 0.6889 0.9147 + 0.4506 0.2072 0.5099 + 0.6934 0.8924 0.4041 + 0.9842 0.2183 0.5916 + 0.9099 0.1014 0.2588 + 0.3646 0.2869 0.1107 + 0.7892 0.5949 0.2686 + 0.8226 0.5505 0.3111 + 0.0764 0.4849 0.1924 + 0.2240 0.8471 0.8423 + 0.1020 0.2254 0.4194 + 0.8691 0.5801 0.1725 + 0.8217 0.0235 0.0745 + 0.6285 0.1380 0.2667 + 0.6437 0.4091 0.5609 + 0.6716 0.2623 0.3741 + 0.7291 0.8411 0.0698 + 0.4717 0.0125 0.4050 + 0.1205 0.4536 0.2174 + 0.1712 0.4339 0.4282 + 0.2948 0.1448 0.0394 + 0.8869 0.1695 0.8341 + 0.0986 0.4590 0.6105 + 0.2961 0.1109 0.6225 + 0.2111 0.6054 0.6792 + 0.9609 0.6630 0.4239 + 0.8231 0.0917 0.1793 + 0.9510 0.1655 0.7910 + 0.8841 0.3304 0.3527 + 0.7403 0.3570 0.1069 + 0.6682 0.3612 0.9556 + 0.7742 0.0715 0.6476 + 0.8029 0.4781 0.8936 + 0.8517 0.2079 0.6216 + 0.8403 0.8003 0.7215 + 0.5677 0.7792 0.8469 + 0.1805 0.0260 0.8686 + 0.4809 0.8847 0.0151 +
 
% Create electrode table region referencing electrodes 0, 1, and 2
shank0_table_region = types.hdmf_common.DynamicTableRegion( ...
'table', types.untyped.ObjectView(electrodesDynamicTable), ...
'description', 'shank0', ...
'data', (0:2)');
 
% Define spike event series for unsorted spike times
spike_events = types.core.SpikeEventSeries( ...
'data', spike_snippets, ...
'timestamps', (0:19)', ... % Timestamps for each event
'description', 'events detected with 100uV threshold', ...
'electrodes', shank0_table_region ...
);
 
% Add spike event series to NWB file acquisition
nwb.acquisition.set('SpikeEvents_Shank0', spike_events);

Detected Events

If you need to store the complete, continuous raw voltage traces, along with unsorted spike times, you should store the traces in ElectricalSeries objects in the acquisition group, and use the EventDetection class to identify the spike events in your raw traces.
% Create the EventDetection object
event_detection = types.core.EventDetection( ...
'detection_method', 'thresholding, 1.5 * std', ...
'source_electricalseries', types.untyped.SoftLink(raw_electrical_series), ...
'source_idx', [1000; 2000; 3000], ...
'times', [.033, .066, .099] ...
);
 
% Add the EventDetection object to the ecephys module
ecephys_module.nwbdatainterface.set('ThresholdEvents', event_detection);

Storing Spike Features (e.g Principal Components)

NWB also provides a way to store features of spikes, such as principal components, using the FeatureExtraction class.
% Generate random feature data (time x channel x feature)
features = rand(3, 12, 4); % 3 time points, 12 channels, 4 features
features = permute(features, [3,2,1]); % reverse dimension order for matnwb
 
% Create the FeatureExtraction object
feature_extraction = types.core.FeatureExtraction( ...
'description', {'PC1', 'PC2', 'PC3', 'PC4'}, ... % Feature descriptions
'electrodes', electrode_table_region, ... % DynamicTableRegion referencing the electrodes table
'times', [.033; .066; .099], ... % Column vector for times
'features', features ...
);
 
% Add the FeatureExtraction object to the ecephys module (if required)
ecephys_module.nwbdatainterface.set('PCA_features', feature_extraction);

Choosing NWB-Types for Electrophysiology Data (A Summary)

As mentioned above, ElectricalSeries objects are meant for storing electrical timeseries data like raw voltage signals or processed signals like LFP or other filtered signals. In addition to the ElectricalSeries class, NWB provides some more classes for storing event-based electropysiological data. We will briefly discuss them here, and refer the reader to the API documentation and the section on Extracellular Physiology in the "NWB Format Specification" for more details on using these objects.
For storing unsorted spiking 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 entire raw voltage traces, only the waveform ‘snippets’ surrounding spike events, you should use SpikeEventSeries objects.
The results of spike sorting (or clustering) should be stored in the top-level Units table. The Units table can hold just the spike times of sorted units or, optionally, include additional waveform information. You can use the optional predefined columns waveform_mean, waveform_sd, and waveforms in the Units table to store individual and mean waveform data.

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
2.0421 -1.9417 0.3559 0.4354 0.6993 -1.4009 0.5222 0.0893 0.1243 1.2460 + 0.2329 0.4688 2.1159 1.2094 0.1735 -0.3315 0.1403 -2.0881 0.2840 -0.4077 + -0.0943 1.3933 -0.0871 -0.5193 -0.0920 -1.1307 0.8399 0.0975 -0.6912 -0.4536 + -1.4224 2.7602 1.1832 0.0075 0.6687 0.2074 0.5432 0.4366 0.0113 0.2925 + 0.9660 0.9444 -0.8471 1.0362 0.0652 -0.2155 0.6006 0.1602 0.7417 -1.3644 +
 
% 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 = 24×1
0.1312 + 0.4211 + 0.2037 + 0.1021 + 0.1315 + 0.8392 + 0.9195 + 0.7023 + 0.0588 + 0.7331 +

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:

SJza#O~Qe> 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 more! See the API documentation to learn what data types are available. MATLAB tutorials - Python tutorials

About This Tutorial

This tutorial describes storage of hypothetical data from extracellular electrophysiology experiments in NWB for the following data categories:
  • Raw voltage recording
  • Local field potential (LFP) and filtered electrical signals
  • Spike times

Before You Begin

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.
Important: The dimensions of timeseries data in MatNWB should be defined in the opposite order of how it is defined in the nwb-schemas. In NWB, time is always stored in the first dimension of the data, whereas in MatNWB time should be stored in the last dimension of the data. This is explained in more detail here: MatNWB <-> HDF5 Dimension Mapping.

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 these required fields along with any additional metadata. In MatNWB, arguments are specified using MATLAB's keyword argument pair convention, where each argument name is followed by its 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

About This Tutorial

This tutorial describes storage of hypothetical data from extracellular electrophysiology experiments in NWB for the following data categories:
  • Raw voltage recording
  • Local field potential (LFP) and filtered electrical signals
  • Spike times

Before You Begin

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.
Important: The dimensions of timeseries data in MatNWB should be defined in the opposite order of how it is defined in the nwb-schemas. In NWB, time is always stored in the first dimension of the data, whereas in MatNWB time should be stored in the last dimension of the data. This is explained in more detail here: MatNWB <-> HDF5 Dimension Mapping.

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 these required fields along with any additional metadata. In MatNWB, arguments are specified using MATLAB's keyword argument pair convention, where each argument name is followed by its 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.7.0' file_create_date: [] @@ -155,899 +155,899 @@ stimulus_presentation: [0×1 types.untyped.Set] stimulus_templates: [0×1 types.untyped.Set] units: [] -

Electrode Information

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, impedance, 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.
numShanks = 4;
numChannelsPerShank = 3;
numChannels = numShanks * numChannelsPerShank;
 
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);
electrodeGroup = types.core.ElectrodeGroup( ...
'description', sprintf('electrode group for %s', shankGroupName), ...
'location', 'brain area', ...
'device', types.untyped.SoftLink(device) ...
);
nwb.general_extracellular_ephys.set(shankGroupName, electrodeGroup);
for iElectrode = 1:numChannelsPerShank
electrodesDynamicTable.addRow( ...
'location', 'unknown', ...
'group', types.untyped.ObjectView(electrodeGroup), ...
'group_name', shankGroupName, ...
'label', sprintf('%s-electrode%d', shankGroupName, iElectrode));
end
end
electrodesDynamicTable.toTable() % Display the table
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 a construct that enables linking one neurodata type to another, allowing a neurodata type to reference another within the NWB file.

Recorded Extracellular Signals

Voltage data are stored using the ElectricalSeries class, a subclass of the TimeSeries class specialized for voltage data.

Referencing Electrodes

In order to create our ElectricalSeries object, we first need to reference a set of rows in the electrodes table to indicate which electrode (channel) each entry in the electrical series were recorded from. 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, using 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)');

Raw Voltage Data

Now create an ElectricalSeries object to hold acquisition data collected during the experiment.
raw_electrical_series = types.core.ElectricalSeries( ...
'starting_time', 0.0, ... % seconds
'starting_time_rate', 30000., ... % Hz
'data', randn(numChannels, 3000), ... % nChannels x nTime
'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', raw_electrical_series);

Processed Extracellular Electrical Signals

LFP

LFP refers to data that has been low-pass filtered, typically below 300 Hz. This data may also be downsampled. Because it is filtered and potentially resampled, it is categorized as processed data. LFP data would also be stored in an ElectricalSeries. To help data analysis and visualization tools know that this ElectricalSeries object represents LFP data, we store it inside an LFP object and 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' in the behavior tutorial
lfp_electrical_series = types.core.ElectricalSeries( ...
'starting_time', 0.0, ... % seconds
'starting_time_rate', 1000., ... % Hz
'data', randn(numChannels, 100), ... nChannels x nTime
'filtering', 'Low-pass filter at 300 Hz', ...
'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);

Other Types of Filtered Electrical Signals

If your acquired data is filtered for frequency ranges other than LFP—such as Gamma or Theta—you can store the result in an ElectricalSeries and encapsulate it within a FilteredEphys object instead of the LFP object.
% Generate filtered data
filtered_data = randn(50, 12); % 50 time points, 12 channels
filtered_data = permute(filtered_data, [2, 1]); % permute timeseries for matnwb
 
% Create an ElectricalSeries object
filtered_electrical_series = types.core.ElectricalSeries( ...
'description', 'Data filtered in the theta range', ...
'data', filtered_data, ...
'electrodes', electrode_table_region, ...
'filtering', 'Band-pass filtered between 4 and 8 Hz', ...
'starting_time', 0.0, ...
'starting_time_rate', 200.0 ...
);
 
% Create a FilteredEphys object and add the filtered electrical series
filtered_ephys = types.core.FilteredEphys();
filtered_ephys.electricalseries.set('FilteredElectricalSeries', filtered_electrical_series);
 
% Add the FilteredEphys object to the ecephys module
ecephys_module.nwbdatainterface.set('FilteredEphys', filtered_ephys);

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 then 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.
In this tutorial, the examples for FilteredEphys and DecompositionSeries may appear similar. However, the key difference is that DecompositionSeries is specialized for storing the results of spectral analyses of timeseries data in general, whereas FilteredEphys is defined specifically as a container for filtered electrical signals.
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. As mentioned in the beginning of this tutorial, 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_mean = [8; 21; 55];
band_stdev = [2; 4.5; 12.5];
band_limits = [band_mean - 2*band_stdev, band_mean + 2*band_stdev];
 
% The bands should be added to the DecompositionSeries as a dynamic table
bands = table(band_names, band_mean, band_stdev, band_limits, ...
'VariableNames', {'band_names', 'band_mean', 'band_stdev', 'band_limits'})
bands = 3×4 table
 band_namesband_meanband_stdevband_limits
12
1'theta'82412
2'beta'214.50001230
3'gamma'5512.50003080
 
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 and Extracellular Events

Sorted Spike Times

Spike times are stored in a Units table, a specialization of the DynamicTable class. The default Units table is located at /units in the HDF5 file. You can add columns to the Units table just like you did for electrodes and trials (see convertTrials). Here, we generate some random spike data and populate the table.
num_cells = 10;
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×24 double1×28 double1×22 double1×28 double1×21 double1×26 double1×16 double1×18 double1×24 double1×24 double

Ragged Arrays

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 table. These indexed columns have two components, the VectorData object that holds the data and the VectorIndex 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. For more information about ragged arrays, we refer you to the "Ragged Array Columns" section of the dynamic table tutorial.
[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 ...
);
 
nwb.units.toTable
ans = 10×2 table
 idspike_times
1124×1 double
2228×1 double
3322×1 double
4428×1 double
5521×1 double
6626×1 double
7716×1 double
8818×1 double
9924×1 double
101024×1 double

Unsorted Spike Times

While the Units table is used to store spike times and waveform data for spike-sorted, single-unit activity, you may also want to store spike times and waveform snippets of unsorted spiking activity. This is useful for recording multi-unit activity detected via threshold crossings during data acquisition. Such information can be stored using SpikeEventSeries objects.
% In the SpikeEventSeries the dimensions should be ordered as
% [num_events, num_channels, num_samples].
% Define spike snippets: 20 events, 3 channels, 40 samples per event.
spike_snippets = rand(20, 3, 40);
% Permute spike snippets (See dimensionMapNoDataPipes tutorial)
spike_snippets = permute(spike_snippets, [3,2,1])
spike_snippets =
spike_snippets(:,:,1) = - - 0.7910 0.0213 0.8335 - 0.8083 0.5109 0.0694 - 0.0414 0.1315 0.9237 - 0.6840 0.0158 0.8003 - 0.9652 0.0518 0.6909 - 0.4137 0.6962 0.6477 - 0.7065 0.4276 0.1037 - 0.0327 0.2995 0.3123 - 0.5643 0.7282 0.7042 - 0.3009 0.8131 0.9222 - 0.1623 0.8546 0.4080 - 0.3810 0.7375 0.5178 - 0.1005 0.8742 0.2635 - 0.9530 0.3118 0.5444 - 0.0115 0.0973 0.5146 - 0.1436 0.2857 0.3713 - 0.5966 0.3174 0.4652 - 0.0131 0.8012 0.6369 - 0.4585 0.6484 0.6865 - 0.5535 0.1510 0.3442 - 0.0254 0.5062 0.8555 - 0.7063 0.5182 0.6404 - 0.3815 0.1169 0.1685 - 0.8255 0.1120 0.3037 - 0.1461 0.4718 0.1424 - 0.8712 0.8092 0.7453 - 0.3177 0.3974 0.9020 - 0.4894 0.6920 0.5508 - 0.1267 0.0666 0.8641 - 0.9720 0.7211 0.6973 - 0.8166 0.9120 0.1665 - 0.4227 0.3168 0.6086 - 0.0780 0.9907 0.1200 - 0.0661 0.3271 0.7940 - 0.8019 0.9782 0.7975 - 0.4981 0.6964 0.9912 - 0.5068 0.4475 0.9353 - 0.4478 0.6194 0.5117 - 0.8898 0.5428 0.7175 - 0.8606 0.2924 0.9609 +

Electrode Information

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, impedance, 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.
numShanks = 4;
numChannelsPerShank = 3;
numChannels = numShanks * numChannelsPerShank;
 
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);
electrodeGroup = types.core.ElectrodeGroup( ...
'description', sprintf('electrode group for %s', shankGroupName), ...
'location', 'brain area', ...
'device', types.untyped.SoftLink(device) ...
);
nwb.general_extracellular_ephys.set(shankGroupName, electrodeGroup);
for iElectrode = 1:numChannelsPerShank
electrodesDynamicTable.addRow( ...
'location', 'unknown', ...
'group', types.untyped.ObjectView(electrodeGroup), ...
'group_name', shankGroupName, ...
'label', sprintf('%s-electrode%d', shankGroupName, iElectrode));
end
end
electrodesDynamicTable.toTable() % Display the table
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 a construct that enables linking one neurodata type to another, allowing a neurodata type to reference another within the NWB file.

Recorded Extracellular Signals

Voltage data are stored using the ElectricalSeries class, a subclass of the TimeSeries class specialized for voltage data.

Referencing Electrodes

In order to create our ElectricalSeries object, we first need to reference a set of rows in the electrodes table to indicate which electrode (channel) each entry in the electrical series were recorded from. 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, using 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)');

Raw Voltage Data

Now create an ElectricalSeries object to hold acquisition data collected during the experiment.
raw_electrical_series = types.core.ElectricalSeries( ...
'starting_time', 0.0, ... % seconds
'starting_time_rate', 30000., ... % Hz
'data', randn(numChannels, 3000), ... % nChannels x nTime
'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', raw_electrical_series);

Processed Extracellular Electrical Signals

LFP

LFP refers to data that has been low-pass filtered, typically below 300 Hz. This data may also be downsampled. Because it is filtered and potentially resampled, it is categorized as processed data. LFP data would also be stored in an ElectricalSeries. To help data analysis and visualization tools know that this ElectricalSeries object represents LFP data, we store it inside an LFP object and 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' in the behavior tutorial
lfp_electrical_series = types.core.ElectricalSeries( ...
'starting_time', 0.0, ... % seconds
'starting_time_rate', 1000., ... % Hz
'data', randn(numChannels, 100), ... nChannels x nTime
'filtering', 'Low-pass filter at 300 Hz', ...
'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);

Other Types of Filtered Electrical Signals

If your acquired data is filtered for frequency ranges other than LFP—such as Gamma or Theta—you can store the result in an ElectricalSeries and encapsulate it within a FilteredEphys object instead of the LFP object.
% Generate filtered data
filtered_data = randn(50, 12); % 50 time points, 12 channels
filtered_data = permute(filtered_data, [2, 1]); % permute timeseries for matnwb
 
% Create an ElectricalSeries object
filtered_electrical_series = types.core.ElectricalSeries( ...
'description', 'Data filtered in the theta range', ...
'data', filtered_data, ...
'electrodes', electrode_table_region, ...
'filtering', 'Band-pass filtered between 4 and 8 Hz', ...
'starting_time', 0.0, ...
'starting_time_rate', 200.0 ...
);
 
% Create a FilteredEphys object and add the filtered electrical series
filtered_ephys = types.core.FilteredEphys();
filtered_ephys.electricalseries.set('FilteredElectricalSeries', filtered_electrical_series);
 
% Add the FilteredEphys object to the ecephys module
ecephys_module.nwbdatainterface.set('FilteredEphys', filtered_ephys);

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 then 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.
In this tutorial, the examples for FilteredEphys and DecompositionSeries may appear similar. However, the key difference is that DecompositionSeries is specialized for storing the results of spectral analyses of timeseries data in general, whereas FilteredEphys is defined specifically as a container for filtered electrical signals.
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. As mentioned in the beginning of this tutorial, 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_mean = [8; 21; 55];
band_stdev = [2; 4.5; 12.5];
band_limits = [band_mean - 2*band_stdev, band_mean + 2*band_stdev];
 
% The bands should be added to the DecompositionSeries as a dynamic table
bands = table(band_names, band_mean, band_stdev, band_limits, ...
'VariableNames', {'band_name', 'band_mean', 'band_stdev', 'band_limits'})
bands = 3×4 table
 band_nameband_meanband_stdevband_limits
12
1'theta'82412
2'beta'214.50001230
3'gamma'5512.50003080
 
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 and Extracellular Events

Sorted Spike Times

Spike times are stored in a Units table, a specialization of the DynamicTable class. The default Units table is located at /units in the HDF5 file. You can add columns to the Units table just like you did for electrodes and trials (see convertTrials). Here, we generate some random spike data and populate the table.
num_cells = 10;
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×24 double1×19 double1×20 double1×25 double1×22 double1×20 double1×22 double1×17 double1×21 double1×25 double

Ragged Arrays

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 table. These indexed columns have two components, the VectorData object that holds the data and the VectorIndex 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. For more information about ragged arrays, we refer you to the "Ragged Array Columns" section of the dynamic table tutorial.
[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 ...
);
 
nwb.units.toTable
ans = 10×2 table
 idspike_times
1124×1 double
2219×1 double
3320×1 double
4425×1 double
5522×1 double
6620×1 double
7722×1 double
8817×1 double
9921×1 double
101025×1 double

Unsorted Spike Times

While the Units table is used to store spike times and waveform data for spike-sorted, single-unit activity, you may also want to store spike times and waveform snippets of unsorted spiking activity. This is useful for recording multi-unit activity detected via threshold crossings during data acquisition. Such information can be stored using SpikeEventSeries objects.
% In the SpikeEventSeries the dimensions should be ordered as
% [num_events, num_channels, num_samples].
% Define spike snippets: 20 events, 3 channels, 40 samples per event.
spike_snippets = rand(20, 3, 40);
% Permute spike snippets (See dimensionMapNoDataPipes tutorial)
spike_snippets = permute(spike_snippets, [3,2,1])
spike_snippets =
spike_snippets(:,:,1) = + + 0.3732 0.7791 0.5937 + 0.7586 0.8887 0.6501 + 0.3051 0.4772 0.4288 + 0.4012 0.5913 0.3957 + 0.3939 0.3286 0.1415 + 0.5662 0.5443 0.2179 + 0.9869 0.4951 0.4386 + 0.7861 0.9518 0.4311 + 0.6751 0.4070 0.1629 + 0.4426 0.5435 0.8580 + 0.0396 0.6455 0.9119 + 0.9309 0.4391 0.6281 + 0.2086 0.5926 0.6285 + 0.9404 0.2170 0.2241 + 0.5310 0.3074 0.9547 + 0.3391 0.8501 0.5889 + 0.0462 0.7716 0.8651 + 0.0262 0.6235 0.9419 + 0.8849 0.0555 0.4975 + 0.4348 0.8429 0.5563 + 0.3528 0.4937 0.8508 + 0.5342 0.9424 0.9996 + 0.5746 0.0628 0.8755 + 0.4744 0.8553 0.9244 + 0.7230 0.6936 0.2125 + 0.4087 0.2393 0.7200 + 0.2982 0.4357 0.2804 + 0.1524 0.7107 0.3372 + 0.9375 0.9103 0.1958 + 0.7425 0.0687 0.4879 + 0.2087 0.0370 0.0393 + 0.1920 0.2053 0.4417 + 0.1422 0.4232 0.1870 + 0.8810 0.7692 0.7563 + 0.1456 0.9029 0.5702 + 0.2523 0.1514 0.7324 + 0.4195 0.4896 0.2667 + 0.3307 0.6090 0.6230 + 0.1701 0.9241 0.5880 + 0.5319 0.0420 0.6809 spike_snippets(:,:,2) = - 0.5338 0.3753 0.6193 - 0.1517 0.6080 0.1663 - 0.1934 0.6342 0.9882 - 0.9244 0.8932 0.3000 - 0.8507 0.4194 0.6482 - 0.5106 0.1159 0.7561 - 0.1162 0.3095 0.9177 - 0.9806 0.6443 0.8760 - 0.0513 0.4850 0.9481 - 0.8886 0.2191 0.6419 - 0.6883 0.4286 0.8974 - 0.8499 0.5069 0.4643 - 0.9019 0.3152 0.4006 - 0.2973 0.4501 0.0411 - 0.2026 0.1939 0.9824 - 0.6041 0.2842 0.5462 - 0.9371 0.5834 0.8635 - 0.4799 0.6260 0.2953 - 0.5238 0.7532 0.8461 - 0.6517 0.7502 0.3512 - 0.4229 0.1752 0.1634 - 0.7366 0.7801 0.9180 - 0.1957 0.2408 0.3131 - 0.9544 0.5748 0.1483 - 0.8544 0.0117 0.7080 - 0.7467 0.9576 0.2643 - 0.5438 0.0537 0.0843 - 0.9563 0.6243 0.4454 - 0.5092 0.1294 0.3496 - 0.4297 0.4393 0.2485 - 0.4890 0.8836 0.6625 - 0.1389 0.4318 0.3081 - 0.1869 0.0011 0.4418 - 0.9201 0.4152 0.0008 - 0.4235 0.6870 0.1545 - 0.9545 0.0976 0.9682 - 0.7207 0.6316 0.7971 - 0.9008 0.5584 0.7311 - 0.1453 0.9232 0.3198 - 0.5304 0.4056 0.1549 + 0.6946 0.9867 0.1621 + 0.2072 0.7996 0.6578 + 0.7396 0.9594 0.2076 + 0.5369 0.8403 0.3706 + 0.3963 0.8120 0.9353 + 0.0532 0.0624 0.7060 + 0.1420 0.1284 0.4933 + 0.9286 0.5055 0.6835 + 0.3156 0.7733 0.3820 + 0.4400 0.2803 0.6084 + 0.2611 0.6750 0.0324 + 0.0603 0.6302 0.9327 + 0.3091 0.1783 0.3724 + 0.5451 0.6656 0.4110 + 0.1974 0.1752 0.6761 + 0.9598 0.3204 0.7998 + 0.7463 0.9291 0.1296 + 0.1828 0.4812 0.6270 + 0.7885 0.1908 0.7519 + 0.1154 0.7749 0.6845 + 0.5216 0.3994 0.9655 + 0.2219 0.3672 0.2583 + 0.4704 0.8092 0.4067 + 0.9942 0.0751 0.1858 + 0.2373 0.5807 0.9973 + 0.6321 0.7136 0.1707 + 0.4648 0.7071 0.1290 + 0.6359 0.6416 0.3091 + 0.1669 0.8553 0.1380 + 0.7639 0.3221 0.2149 + 0.0145 0.3226 0.4020 + 0.5560 0.0633 0.2573 + 0.6934 0.7846 0.9890 + 0.5472 0.7723 0.0297 + 0.7413 0.4124 0.4747 + 0.9513 0.8615 0.3798 + 0.8774 0.0074 0.0822 + 0.2707 0.2008 0.6769 + 0.9066 0.2487 0.3473 + 0.9031 0.5947 0.2998 spike_snippets(:,:,3) = - 0.5092 0.9115 0.6848 - 0.0370 0.3598 0.5459 - 0.8909 0.2320 0.6210 - 0.1415 0.3598 0.3532 - 0.6650 0.7777 0.2036 - 0.0845 0.7893 0.6989 - 0.8263 0.5907 0.7732 - 0.7758 0.9209 0.6862 - 0.6141 0.4780 0.6086 - 0.4845 0.5522 0.5599 - 0.9572 0.1676 0.3414 - 0.1539 0.5025 0.8494 - 0.6809 0.5092 0.8636 - 0.9649 0.6492 0.5732 - 0.3245 0.6510 0.9502 - 0.3759 0.3767 0.4420 - 0.7450 0.5653 0.0341 - 0.8066 0.5285 0.6691 - 0.7006 0.0285 0.2575 - 0.4817 0.5484 0.8613 - 0.2185 0.3218 0.5705 - 0.4282 0.1795 0.8811 - 0.3090 0.8398 0.6193 - 0.0477 0.4053 0.8866 - 0.1156 0.5851 0.2542 - 0.8462 0.0158 0.3698 - 0.4941 0.9994 0.2657 - 0.6635 0.6725 0.1791 - 0.8944 0.2112 0.0403 - 0.3573 0.8075 0.2363 - 0.0931 0.0854 0.2321 - 0.4501 0.9337 0.2882 - 0.5619 0.6824 0.8621 - 0.5455 0.0936 0.0346 - 0.4649 0.7467 0.8067 - 0.0441 0.9981 0.1671 - 0.1436 0.4552 0.5099 - 0.9111 0.4916 0.9271 - 0.1548 0.1222 0.4906 - 0.3945 0.8146 0.5500 + 0.5451 0.8070 0.3206 + 0.6928 0.4280 0.8321 + 0.6805 0.9535 0.2361 + 0.2666 0.1868 0.4845 + 0.9815 0.7233 0.8900 + 0.7260 0.8210 0.3314 + 0.3164 0.7107 0.6713 + 0.4181 0.4670 0.0010 + 0.9741 0.2856 0.7074 + 0.4238 0.8375 0.8412 + 0.9819 0.8191 0.0949 + 0.4364 0.9326 0.4354 + 0.7310 0.9098 0.4869 + 0.4943 0.9199 0.3313 + 0.6452 0.6932 0.7494 + 0.0505 0.9546 0.9591 + 0.1264 0.4607 0.1279 + 0.9333 0.0234 0.7867 + 0.3341 0.3857 0.6957 + 0.7642 0.1095 0.0429 + 0.9580 0.4884 0.6948 + 0.6084 0.5523 0.8079 + 0.5066 0.6660 0.3920 + 0.0327 0.7744 0.8886 + 0.9240 0.5647 0.0658 + 0.5356 0.0641 0.7811 + 0.4054 0.5640 0.4154 + 0.9789 0.5963 0.8608 + 0.5668 0.1509 0.3670 + 0.7025 0.9988 0.9933 + 0.8613 0.4405 0.1464 + 0.1041 0.7715 0.3392 + 0.3661 0.3882 0.5087 + 0.7465 0.0436 0.0087 + 0.3572 0.4768 0.0233 + 0.9081 0.2854 0.6945 + 0.2392 0.3414 0.6361 + 0.6355 0.0123 0.6029 + 0.8906 0.9561 0.0998 + 0.8724 0.4231 0.8791 spike_snippets(:,:,4) = - 0.8651 0.0729 0.5703 - 0.7056 0.7361 0.0422 - 0.7220 0.4867 0.8848 - 0.7563 0.2299 0.4345 - 0.1388 0.6244 0.1856 - 0.4175 0.1199 0.3165 - 0.0970 0.6949 0.4674 - 0.6304 0.8521 0.2420 - 0.5424 0.2486 0.6067 - 0.1731 0.3253 0.6955 - 0.6922 0.3908 0.0253 - 0.3011 0.2044 0.2150 - 0.6281 0.9983 0.8887 - 0.5463 0.3738 0.2357 - 0.1263 0.2581 0.0286 - 0.5020 0.1082 0.0744 - 0.2669 0.0954 0.5680 - 0.3439 0.2201 0.8495 - 0.6176 0.9906 0.6916 - 0.2697 0.4133 0.1197 - 0.5944 0.7802 0.2287 - 0.4447 0.7085 0.0679 - 0.1947 0.6754 0.9432 - 0.4714 0.0661 0.0817 - 0.5271 0.9569 0.9435 - 0.8597 0.6655 0.0351 - 0.8275 0.0531 0.2852 - 0.2070 0.3484 0.4845 - 0.9358 0.0522 0.7680 - 0.6787 0.3460 0.3718 - 0.2934 0.3135 0.0939 - 0.9152 0.4345 0.1295 - 0.3650 0.2472 0.3402 - 0.1528 0.6372 0.5485 - 0.5785 0.9504 0.9111 - 0.5069 0.9808 0.3079 - 0.3256 0.3353 0.1383 - 0.5329 0.4452 0.8509 - 0.1127 0.6844 0.8668 - 0.7811 0.1499 0.4629 + 0.4854 0.2021 0.4178 + 0.1042 0.0572 0.5235 + 0.7787 0.8715 0.3109 + 0.0947 0.2513 0.3742 + 0.4724 0.0945 0.4348 + 0.7312 0.1811 0.7789 + 0.1908 0.4436 0.7879 + 0.3579 0.8552 0.1141 + 0.4405 0.4878 0.4577 + 0.7250 0.3002 0.0478 + 0.4961 0.2143 0.2460 + 0.3935 0.5856 0.3734 + 0.4304 0.8539 0.6590 + 0.4677 0.2888 0.9005 + 0.6995 0.0156 0.3576 + 0.8153 0.7794 0.9313 + 0.6333 0.6757 0.7350 + 0.5318 0.6517 0.2361 + 0.8009 0.7342 0.3832 + 0.2961 0.9837 0.3576 + 0.2021 0.9876 0.7297 + 0.9424 0.2764 0.0504 + 0.1479 0.1211 0.1844 + 0.4527 0.4286 0.2950 + 0.4787 0.1957 0.8230 + 0.4661 0.1299 0.0478 + 0.4762 0.0754 0.6567 + 0.4106 0.8626 0.7855 + 0.2155 0.7685 0.4191 + 0.8953 0.0073 0.6560 + 0.2029 0.2695 0.3976 + 0.1809 0.4246 0.5822 + 0.9363 0.5071 0.9302 + 0.3073 0.1328 0.5443 + 0.6552 0.0491 0.8480 + 0.3227 0.3559 0.1201 + 0.0361 0.0332 0.5380 + 0.0322 0.5357 0.4574 + 0.8864 0.4393 0.4757 + 0.3848 0.2086 0.7510 spike_snippets(:,:,5) = - 0.9833 0.1306 0.1439 - 0.2656 0.5451 0.9737 - 0.5678 0.3446 0.3483 - 0.0967 0.0733 0.2677 - 0.2653 0.9260 0.8876 - 0.3652 0.3403 0.3428 - 0.7242 0.2202 0.3057 - 0.8023 0.8464 0.9116 - 0.2288 0.7517 0.9695 - 0.7929 0.6289 0.9108 - 0.5254 0.5371 0.8497 - 0.9644 0.9208 0.5796 - 0.3907 0.0955 0.7351 - 0.8862 0.8287 0.5880 - 0.3508 0.8420 0.7759 - 0.9350 0.8327 0.3354 - 0.3336 0.4643 0.3187 - 0.3489 0.0708 0.8605 - 0.6006 0.7239 0.3991 - 0.3062 0.9719 0.1192 - 0.6141 0.9838 0.1967 - 0.0040 0.5215 0.8946 - 0.2364 0.0775 0.0065 - 0.7518 0.9408 0.8491 - 0.7013 0.8655 0.8282 - 0.9326 0.1311 0.8476 - 0.1252 0.0149 0.3416 - 0.9947 0.6125 0.6894 - 0.1763 0.6221 0.2775 - 0.7586 0.2494 0.6819 - 0.9048 0.7648 0.0505 - 0.2789 0.3761 0.7825 - 0.1195 0.3080 0.1136 - 0.3286 0.8026 0.9635 - 0.5285 0.2380 0.6579 - 0.4777 0.2527 0.4029 - 0.6137 0.4510 0.4112 - 0.3054 0.5817 0.7277 - 0.5103 0.9052 0.9271 - 0.8229 0.1610 0.4064 + 0.1860 0.0536 0.5940 + 0.2376 0.8684 0.8130 + 0.1716 0.3393 0.4770 + 0.0312 0.1379 0.6187 + 0.3256 0.6297 0.1215 + 0.8935 0.3748 0.4845 + 0.6420 0.1018 0.4572 + 0.1572 0.6465 0.1285 + 0.7944 0.8081 0.1087 + 0.3353 0.1870 0.9674 + 0.3222 0.3932 0.3531 + 0.8497 0.5727 0.3463 + 0.6986 0.6426 0.5585 + 0.7362 0.5711 0.3389 + 0.1066 0.8356 0.3358 + 0.9519 0.5272 0.1312 + 0.0989 0.9641 0.3374 + 0.5441 0.7474 0.6270 + 0.9346 0.6895 0.6218 + 0.7645 0.7542 0.8233 + 0.0604 0.9699 0.0417 + 0.1195 0.0637 0.8807 + 0.4555 0.7875 0.7380 + 0.3830 0.6953 0.0521 + 0.0568 0.6954 0.6580 + 0.3506 0.4798 0.0857 + 0.2994 0.7058 0.4222 + 0.3651 0.4766 0.6714 + 0.6819 0.1449 0.8841 + 0.7518 0.6569 0.8799 + 0.4554 0.0071 0.1870 + 0.1671 0.5957 0.8323 + 0.7819 0.1208 0.0767 + 0.5283 0.8852 0.1754 + 0.4018 0.9644 0.2548 + 0.3075 0.7336 0.8771 + 0.8817 0.0490 0.6314 + 0.9053 0.7864 0.7232 + 0.3306 0.1048 0.3259 + 0.4217 0.0060 0.3249 spike_snippets(:,:,6) = - 0.3054 0.2779 0.6491 - 0.5022 0.0909 0.5466 - 0.2294 0.5000 0.8570 - 0.4042 0.5115 0.9431 - 0.4964 0.2149 0.5783 - 0.4703 0.1227 0.4063 - 0.7156 0.5084 0.0977 - 0.1685 0.6039 0.9440 - 0.5246 0.4176 0.6435 - 0.1973 0.8868 0.0968 - 0.3190 0.5694 0.8356 - 0.9508 0.3955 0.3049 - 0.7104 0.8199 0.1919 - 0.5505 0.9165 0.1106 - 0.5106 0.9105 0.6328 - 0.1722 0.1372 0.2181 - 0.4067 0.5448 0.6478 - 0.9696 0.4703 0.5263 - 0.1818 0.4216 0.7169 - 0.2094 0.2752 0.8469 - 0.4900 0.4156 0.5015 - 0.9689 0.3503 0.3427 - 0.4425 0.7213 0.7925 - 0.1208 0.9218 0.1758 - 0.5253 0.4974 0.9133 - 0.5050 0.1665 0.3859 - 0.3676 0.0337 0.0437 - 0.9110 0.8914 0.5112 - 0.4501 0.4358 0.3669 - 0.8307 0.5862 0.6017 - 0.2800 0.0028 0.3824 - 0.9934 0.6645 0.1911 - 0.1871 0.5665 0.4545 - 0.5104 0.4363 0.3587 - 0.4477 0.1140 0.2533 - 0.0078 0.4282 0.6347 - 0.6320 0.1487 0.7152 - 0.4901 0.3613 0.0225 - 0.0841 0.3465 0.2337 - 0.2430 0.2622 0.4275 + 0.3607 0.6747 0.2823 + 0.8019 0.7234 0.6816 + 0.9769 0.9206 0.9695 + 0.8595 0.1186 0.5267 + 0.8213 0.5041 0.7992 + 0.6864 0.4441 0.3328 + 0.4342 0.7906 0.7966 + 0.2791 0.6789 0.6589 + 0.0677 0.1800 0.8605 + 0.9977 0.6627 0.8032 + 0.6905 0.0662 0.9176 + 0.6056 0.3628 0.2537 + 0.3349 0.6504 0.4738 + 0.3405 0.3380 0.9896 + 0.9532 0.6461 0.2478 + 0.6151 0.3351 0.3112 + 0.9391 0.4259 0.4215 + 0.1135 0.2030 0.1276 + 0.6935 0.5926 0.5615 + 0.9860 0.1078 0.4081 + 0.8066 0.0918 0.1500 + 0.3176 0.4185 0.3038 + 0.5564 0.0260 0.1120 + 0.2822 0.2521 0.1910 + 0.5111 0.1336 0.9303 + 0.9938 0.0073 0.2631 + 0.9386 0.2338 0.9264 + 0.8758 0.3476 0.6830 + 0.0299 0.3936 0.0800 + 0.5553 0.8059 0.5107 + 0.5919 0.3904 0.7407 + 0.3952 0.6322 0.8418 + 0.0015 0.1364 0.9954 + 0.6989 0.2713 0.1460 + 0.1746 0.6716 0.0101 + 0.3299 0.8212 0.8991 + 0.2970 0.9432 0.8047 + 0.9798 0.9383 0.8505 + 0.4682 0.5601 0.8434 + 0.4084 0.6759 0.9916 spike_snippets(:,:,7) = - 0.5203 0.4307 0.6524 - 0.7971 0.7307 0.9649 - 0.5900 0.5683 0.7583 - 0.4517 0.9439 0.4560 - 0.7591 0.4558 0.7293 - 0.5660 0.1464 0.6991 - 0.4934 0.6237 0.6178 - 0.8066 0.6757 0.4065 - 0.1988 0.2131 0.3927 - 0.9253 0.1590 0.2406 - 0.4513 0.2735 0.8884 - 0.3209 0.9513 0.4656 - 0.1497 0.7329 0.8223 - 0.8818 0.5569 0.8199 - 0.1966 0.0080 0.8479 - 0.2544 0.0254 0.1438 - 0.9411 0.2648 0.6022 - 0.9904 0.3270 0.8801 - 0.5011 0.0608 0.7151 - 0.4306 0.5025 0.6970 - 0.7600 0.0648 0.6352 - 0.8428 0.1614 0.4374 - 0.7337 0.1432 0.7528 - 0.7799 0.2674 0.2795 - 0.1125 0.0389 0.7427 - 0.6235 0.6113 0.4224 - 0.8652 0.3020 0.7012 - 0.0585 0.2901 0.6066 - 0.2551 0.6485 0.6733 - 0.3578 0.4431 0.8841 - 0.3194 0.8011 0.5885 - 0.8658 0.1362 0.9775 - 0.7267 0.8782 0.1815 - 0.9021 0.1424 0.9340 - 0.1541 0.2204 0.2235 - 0.3263 0.9738 0.1737 - 0.8442 0.1610 0.1785 - 0.0902 0.4908 0.3965 - 0.9395 0.1451 0.8357 - 0.5820 0.4252 0.1027 + 0.0571 0.8938 0.2892 + 0.3651 0.2947 0.0883 + 0.0048 0.5505 0.2311 + 0.0752 0.5008 0.0071 + 0.1115 0.1171 0.3412 + 0.2859 0.2660 0.7362 + 0.8617 0.7911 0.6713 + 0.7250 0.9561 0.2437 + 0.3928 0.7430 0.5275 + 0.4087 0.4779 0.9706 + 0.1443 0.3129 0.7893 + 0.5913 0.3437 0.1562 + 0.4039 0.9153 0.6639 + 0.1441 0.5124 0.6560 + 0.8561 0.5080 0.9686 + 0.4229 0.9691 0.2507 + 0.2713 0.5633 0.6792 + 0.0239 0.2525 0.9951 + 0.4989 0.3135 0.1867 + 0.6718 0.8556 0.8071 + 0.8666 0.1969 0.1976 + 0.1018 0.3002 0.1270 + 0.3555 0.5716 0.8467 + 0.7235 0.7092 0.0841 + 0.1264 0.0393 0.0440 + 0.7326 0.2412 0.5221 + 0.0684 0.3701 0.9379 + 0.3814 0.2398 0.9121 + 0.8694 0.0473 0.1581 + 0.5230 0.8057 0.0546 + 0.5274 0.3905 0.7978 + 0.2959 0.0482 0.5761 + 0.3504 0.3914 0.5200 + 0.8993 0.2229 0.4167 + 0.0838 0.4613 0.7469 + 0.8273 0.6824 0.5753 + 0.8421 0.8724 0.1875 + 0.3069 0.7149 0.9203 + 0.8881 0.0453 0.7021 + 0.8480 0.5593 0.0597 spike_snippets(:,:,8) = - 0.1236 0.6511 0.1434 - 0.6919 0.8788 0.5315 - 0.0110 0.2866 0.2883 - 0.6653 0.3658 0.7295 - 0.3830 0.3836 0.1894 - 0.0792 0.4844 0.6232 - 0.0429 0.0815 0.0893 - 0.9189 0.4935 0.6753 - 0.9430 0.6922 0.1165 - 0.6600 0.5560 0.4981 - 0.7278 0.3105 0.6180 - 0.2455 0.8558 0.8068 - 0.1648 0.5900 0.8158 - 0.7264 0.2944 0.9467 - 0.5387 0.3136 0.4850 - 0.2455 0.0724 0.9675 - 0.9792 0.7856 0.1285 - 0.0151 0.3191 0.4051 - 0.7201 0.2710 0.8689 - 0.6860 0.1948 0.5062 - 0.8377 0.2674 0.8979 - 0.3805 0.7267 0.2665 - 0.2113 0.5110 0.2891 - 0.2075 0.1331 0.1577 - 0.5781 0.8387 0.8078 - 0.0374 0.8755 0.9759 - 0.0858 0.7683 0.4930 - 0.9413 0.4375 0.2516 - 0.4598 0.0071 0.5345 - 0.3385 0.2310 0.6649 - 0.8964 0.7416 0.8791 - 0.9074 0.5103 0.8058 - 0.0780 0.2104 0.6698 - 0.1535 0.9910 0.7050 - 0.2712 0.3049 0.6195 - 0.0698 0.8990 0.2976 - 0.5451 0.3134 0.1452 - 0.6226 0.0632 0.5848 - 0.3860 0.4139 0.0877 - 0.2459 0.3314 0.7027 + 0.5469 0.2381 0.3206 + 0.1442 0.0464 0.9169 + 0.8726 0.4046 0.1317 + 0.3666 0.0378 0.7042 + 0.1399 0.5182 0.1363 + 0.9368 0.7287 0.7152 + 0.7624 0.0543 0.3947 + 0.7231 0.4113 0.6290 + 0.4949 0.3340 0.8478 + 0.6423 0.7035 0.9309 + 0.6111 0.9467 0.7805 + 0.0830 0.3386 0.1138 + 0.6171 0.0343 0.6772 + 0.5062 0.2758 0.3346 + 0.3766 0.5560 0.6250 + 0.1964 0.1594 0.8136 + 0.2617 0.4583 0.1110 + 0.0918 0.6463 0.7804 + 0.9457 0.4661 0.8166 + 0.1252 0.4231 0.8606 + 0.7374 0.7485 0.3757 + 0.1418 0.2033 0.9158 + 0.4184 0.0849 0.9580 + 0.4020 0.1756 0.7954 + 0.3852 0.2716 0.4669 + 0.4989 0.4680 0.5607 + 0.2454 0.1776 0.6862 + 0.0040 0.4639 0.3231 + 0.0427 0.0131 0.4277 + 0.3237 0.7697 0.2445 + 0.2296 0.8780 0.5891 + 0.4673 0.3589 0.6978 + 0.9605 0.5383 0.8642 + 0.6235 0.9730 0.8974 + 0.8655 0.3656 0.7443 + 0.2165 0.9781 0.7558 + 0.8414 0.3375 0.1513 + 0.6390 0.4023 0.5663 + 0.2621 0.5162 0.0405 + 0.8289 0.3021 0.8460 spike_snippets(:,:,9) = - 0.1862 0.3166 0.5625 - 0.6883 0.8583 0.6659 - 0.8207 0.2343 0.7708 - 0.4771 0.9836 0.6667 - 0.9927 0.8793 0.5623 - 0.5524 0.3897 0.6021 - 0.0158 0.0147 0.6756 - 0.5509 0.3765 0.1045 - 0.0319 0.8561 0.6120 - 0.6078 0.8196 0.1573 - 0.8092 0.9058 0.8133 - 0.1753 0.4625 0.3167 - 0.7093 0.0153 0.8247 - 0.0031 0.9350 0.7074 - 0.7463 0.7795 0.7193 - 0.6112 0.7431 0.8335 - 0.1113 0.7544 0.5980 - 0.1001 0.0113 0.5878 - 0.7874 0.3017 0.8858 - 0.3490 0.6528 0.4592 - 0.2282 0.9735 0.8830 - 0.5118 0.8896 0.5627 - 0.8384 0.6445 0.6133 - 0.7058 0.5834 0.9355 - 0.8892 0.4049 0.2107 - 0.7051 0.1832 0.5109 - 0.9029 0.6395 0.3890 - 0.2350 0.2461 0.1438 - 0.4924 0.8447 0.9080 - 0.3103 0.9046 0.0424 - 0.0415 0.4561 0.1136 - 0.9019 0.4703 0.8842 - 0.5687 0.0371 0.4397 - 0.1593 0.0056 0.3861 - 0.6538 0.0122 0.3605 - 0.1895 0.2687 0.9563 - 0.4757 0.9230 0.5992 - 0.5058 0.0182 0.8989 - 0.7973 0.6793 0.1926 - 0.9365 0.5756 0.1470 + 0.4691 0.7988 0.0529 + 0.9929 0.6395 0.6071 + 0.7489 0.2541 0.8289 + 0.0846 0.9819 0.9793 + 0.9771 0.4078 0.9797 + 0.8538 0.5584 0.8525 + 0.4623 0.2815 0.8258 + 0.6717 0.7234 0.3825 + 0.3005 0.7018 0.5217 + 0.0373 0.3626 0.3260 + 0.2694 0.0014 0.9328 + 0.0935 0.4851 0.9782 + 0.9277 0.7393 0.8915 + 0.4584 0.3471 0.7444 + 0.6188 0.9747 0.5640 + 0.9186 0.2859 0.3740 + 0.5948 0.5343 0.3207 + 0.9418 0.8765 0.9253 + 0.8842 0.1309 0.4301 + 0.3234 0.3499 0.1663 + 0.2283 0.5276 0.5893 + 0.3362 0.4110 0.4571 + 0.1672 0.0150 0.1694 + 0.5063 0.6239 0.0191 + 0.9739 0.8482 0.7000 + 0.9642 0.0519 0.1061 + 0.3678 0.6110 0.6649 + 0.2460 0.7439 0.6566 + 0.0136 0.4706 0.7010 + 0.8887 0.1154 0.8915 + 0.4598 0.1088 0.0265 + 0.4780 0.0377 0.1051 + 0.6038 0.4467 0.5940 + 0.5292 0.5928 0.9708 + 0.4460 0.6881 0.8970 + 0.3750 0.5452 0.6821 + 0.7657 0.2439 0.3457 + 0.9692 0.2747 0.4023 + 0.5081 0.2458 0.8449 + 0.2699 0.1724 0.6493 spike_snippets(:,:,10) = - 0.6774 0.8672 0.3929 - 0.7508 0.8390 0.1819 - 0.8385 0.0982 0.1049 - 0.8561 0.8053 0.5929 - 0.6299 0.8493 0.1438 - 0.4487 0.6291 0.9145 - 0.3847 0.4643 0.3373 - 0.5404 0.7072 0.0958 - 0.4907 0.5549 0.8605 - 0.7538 0.9238 0.0707 - 0.6480 0.6550 0.8500 - 0.2106 0.1632 0.5082 - 0.2997 0.4515 0.2181 - 0.5225 0.6085 0.4031 - 0.7164 0.0671 0.2393 - 0.4219 0.8559 0.8960 - 0.2255 0.7409 0.2879 - 0.1748 0.2214 0.3755 - 0.6444 0.8154 0.7101 - 0.8884 0.4719 0.3015 - 0.9329 0.2244 0.2333 - 0.9767 0.3629 0.4684 - 0.8712 0.7563 0.3301 - 0.1827 0.6614 0.8416 - 0.7168 0.9171 0.7254 - 0.0646 0.5612 0.5675 - 0.5051 0.9097 0.6379 - 0.6441 0.4000 0.2584 - 0.3448 0.8788 0.3373 - 0.7757 0.0176 0.1728 - 0.7208 0.7215 0.9140 - 0.2416 0.7711 0.6687 - 0.9054 0.5785 0.7456 - 0.0335 0.1418 0.4057 - 0.9318 0.0209 0.8280 - 0.4995 0.5503 0.6516 - 0.5591 0.8762 0.9773 - 0.4354 0.9011 0.0860 - 0.2329 0.4244 0.1117 - 0.0895 0.3846 0.8654 + 0.2646 0.4643 0.9420 + 0.2981 0.8982 0.9420 + 0.9444 0.8127 0.2281 + 0.0019 0.9399 0.6619 + 0.8383 0.3594 0.0589 + 0.3812 0.7412 0.3856 + 0.0461 0.3143 0.6805 + 0.2631 0.5349 0.8362 + 0.6679 0.2794 0.0747 + 0.6365 0.1144 0.1717 + 0.9935 0.8025 0.8176 + 0.2022 0.7851 0.6757 + 0.4380 0.6696 0.4313 + 0.6085 0.2422 0.5570 + 0.1107 0.3251 0.0433 + 0.9562 0.5477 0.2331 + 0.7087 0.1371 0.4938 + 0.7187 0.5549 0.4971 + 0.5788 0.5722 0.0299 + 0.6707 0.4923 0.2003 + 0.6433 0.4343 0.4186 + 0.4536 0.4802 0.5284 + 0.8884 0.5167 0.3753 + 0.7600 0.1937 0.3257 + 0.2230 0.4037 0.1190 + 0.6195 0.8798 0.7132 + 0.6905 0.4647 0.3077 + 0.2035 0.4971 0.3984 + 0.2586 0.8970 0.0824 + 0.3915 0.7657 0.8055 + 0.1682 0.0791 0.8345 + 0.0808 0.8050 0.3915 + 0.2290 0.9915 0.2723 + 0.1630 0.0845 0.3458 + 0.2780 0.0904 0.4128 + 0.2752 0.0924 0.5610 + 0.1138 0.8468 0.0026 + 0.1058 0.0355 0.9058 + 0.7652 0.2477 0.4562 + 0.9184 0.5650 0.4295 spike_snippets(:,:,11) = - 0.8347 0.4022 0.6120 - 0.3468 0.5189 0.4033 - 0.2546 0.6819 0.0986 - 0.7404 0.1464 0.7471 - 0.6433 0.2989 0.8645 - 0.9926 0.0527 0.3949 - 0.9637 0.2744 0.2190 - 0.7971 0.7036 0.4778 - 0.4520 0.5440 0.6679 - 0.3244 0.4465 0.5489 - 0.9640 0.3327 0.7965 - 0.5527 0.1972 0.7607 - 0.2490 0.6106 0.3055 - 0.8834 0.5081 0.0499 - 0.2723 0.1501 0.9294 - 0.2521 0.5971 0.5519 - 0.7322 0.9338 0.7382 - 0.0855 0.8082 0.5566 - 0.7416 0.1892 0.5572 - 0.2958 0.9147 0.6783 - 0.0851 0.4890 0.4612 - 0.3550 0.0450 0.2096 - 0.1819 0.2133 0.5934 - 0.9579 0.1414 0.7223 - 0.4275 0.6274 0.9427 - 0.0523 0.0421 0.6497 - 0.0685 0.9755 0.6641 - 0.5790 0.6845 0.7101 - 0.7696 0.1755 0.2687 - 0.7507 0.4235 0.1038 - 0.6559 0.8909 0.1183 - 0.6514 0.9716 0.1036 - 0.9478 0.7154 0.0593 - 0.7043 0.2236 0.8412 - 0.3663 0.5732 0.0736 - 0.1300 0.3943 0.1802 - 0.6451 0.1573 0.4161 - 0.8897 0.7446 0.7855 - 0.3715 0.4903 0.6490 - 0.5015 0.5459 0.3932 + 0.3360 0.2768 0.2706 + 0.5883 0.5018 0.6218 + 0.5947 0.7284 0.9354 + 0.5599 0.6084 0.1978 + 0.9211 0.8140 0.0545 + 0.7966 0.8976 0.2581 + 0.3007 0.5282 0.5474 + 0.8500 0.4486 0.2071 + 0.0512 0.4022 0.9222 + 0.6838 0.6349 0.0706 + 0.9257 0.6698 0.5748 + 0.2152 0.5339 0.8961 + 0.6102 0.5816 0.3377 + 0.3648 0.6726 0.8777 + 0.2972 0.3565 0.8838 + 0.2829 0.7476 0.3734 + 0.9157 0.7164 0.6893 + 0.8746 0.0111 0.0486 + 0.1815 0.0744 0.1274 + 0.8996 0.0554 0.3310 + 0.9089 0.8892 0.4710 + 0.2577 0.9764 0.5696 + 0.9031 0.9686 0.5731 + 0.7543 0.8753 0.5673 + 0.0123 0.9955 0.3922 + 0.5084 0.7922 0.4345 + 0.4505 0.1746 0.2203 + 0.7865 0.5427 0.2527 + 0.5117 0.9445 0.8626 + 0.4887 0.1589 0.7124 + 0.2850 0.8352 0.5718 + 0.0180 0.3309 0.5588 + 0.5399 0.5011 0.8363 + 0.4116 0.7307 0.3184 + 0.5524 0.4636 0.5660 + 0.7529 0.1027 0.1108 + 0.6302 0.5957 0.6093 + 0.0836 0.9445 0.1758 + 0.0951 0.9562 0.2351 + 0.0849 0.0117 0.7479 spike_snippets(:,:,12) = - 0.8950 0.7784 0.3370 - 0.3776 0.8958 0.1258 - 0.2153 0.5221 0.4411 - 0.9188 0.6917 0.9823 - 0.0532 0.3541 0.2336 - 0.6066 0.7796 0.6442 - 0.7270 0.2461 0.5945 - 0.2519 0.4929 0.5494 - 0.0581 0.7809 0.5255 - 0.2975 0.1215 0.7106 - 0.8214 0.1294 0.1760 - 0.4632 0.9377 0.6688 - 0.2908 0.8853 0.7457 - 0.1076 0.5625 0.0337 - 0.0972 0.5536 0.1822 - 0.1280 0.7994 0.4142 - 0.1412 0.1745 0.7949 - 0.3290 0.2348 0.6860 - 0.7861 0.2603 0.6156 - 0.4999 0.3998 0.9619 - 0.2821 0.8703 0.1428 - 0.9904 0.9902 0.9319 - 0.7028 0.0983 0.1120 - 0.8319 0.1780 0.2330 - 0.2656 0.7303 0.3022 - 0.9044 0.4930 0.4198 - 0.6919 0.8411 0.3521 - 0.1977 0.5059 0.4959 - 0.8080 0.3110 0.4749 - 0.7690 0.1746 0.0756 - 0.5096 0.2501 0.1510 - 0.9853 0.4394 0.0432 - 0.2593 0.5565 0.1624 - 0.9890 0.5731 0.9597 - 0.9599 0.0753 0.2030 - 0.5933 0.8058 0.7752 - 0.6314 0.7114 0.8213 - 0.8363 0.0528 0.3939 - 0.4076 0.7217 0.5043 - 0.1097 0.2343 0.2508 + 0.8269 0.0684 0.4439 + 0.7323 0.1734 0.0751 + 0.6568 0.2218 0.2693 + 0.5686 0.5931 0.7554 + 0.8733 0.5064 0.8141 + 0.6172 0.8727 0.9094 + 0.0136 0.5690 0.1943 + 0.8223 0.9764 0.9327 + 0.0876 0.5534 0.0578 + 0.3906 0.9969 0.1656 + 0.2662 0.9741 0.8474 + 0.2071 0.9988 0.7772 + 0.6265 0.1958 0.3806 + 0.2814 0.7539 0.9439 + 0.6970 0.4425 0.1954 + 0.5497 0.7827 0.0818 + 0.9411 0.2419 0.0642 + 0.3968 0.5786 0.3247 + 0.5858 0.5023 0.4566 + 0.4175 0.2451 0.5167 + 0.3272 0.8763 0.9273 + 0.9850 0.8327 0.0031 + 0.8648 0.6048 0.2515 + 0.7225 0.9099 0.5846 + 0.5928 0.7970 0.9333 + 0.9333 0.4034 0.3905 + 0.7934 0.0362 0.6215 + 0.6854 0.4067 0.6295 + 0.3693 0.6808 0.0589 + 0.2698 0.8693 0.6090 + 0.0236 0.6264 0.6358 + 0.7969 0.6779 0.1526 + 0.1936 1.0000 0.0233 + 0.4508 0.3273 0.7223 + 0.9213 0.7523 0.2685 + 0.3340 0.4351 0.9825 + 0.8270 0.3205 0.1033 + 0.4581 0.7766 0.6554 + 0.1236 0.8837 0.3875 + 0.3160 0.4216 0.8846 spike_snippets(:,:,13) = - 0.2949 0.5202 0.1211 - 0.5566 0.5845 0.5552 - 0.0722 0.6373 0.8406 - 0.9252 0.0517 0.5231 - 0.4902 0.5120 0.9631 - 0.2890 0.4406 0.8756 - 0.8333 0.0660 0.7129 - 0.7859 0.1514 0.2913 - 0.0624 0.2431 0.4692 - 0.9129 0.6801 0.7586 - 0.9633 0.8651 0.4898 - 0.9800 0.8840 0.2424 - 0.1020 0.8982 0.5261 - 0.6766 0.0988 0.9697 - 0.2313 0.1582 0.4006 - 0.0004 0.3593 0.5432 - 0.7084 0.5084 0.4525 - 0.7881 0.0612 0.4665 - 0.9016 0.5683 0.2416 - 0.9231 0.7142 0.3431 - 0.3189 0.6186 0.6160 - 0.5037 0.2760 0.5419 - 0.0237 0.7551 0.1368 - 0.6520 0.7281 0.4271 - 0.2760 0.1216 0.5721 - 0.8475 0.6912 0.3738 - 0.7810 0.0934 0.3155 - 0.6271 0.8286 0.1854 - 0.1149 0.9365 0.5787 - 0.7695 0.1150 0.0557 - 0.5477 0.5701 0.2503 - 0.4269 0.0165 0.1992 - 0.4939 0.9138 0.8500 - 0.9287 0.8734 0.5062 - 0.7151 0.2187 0.0008 - 0.2543 0.6974 0.4274 - 0.9483 0.7896 0.6908 - 0.7255 0.8452 0.2864 - 0.0348 0.5217 0.1593 - 0.5899 0.3122 0.1772 + 0.3545 0.3870 0.0442 + 0.0984 0.6191 0.7319 + 0.4817 0.1129 0.7033 + 0.2747 0.7700 0.5511 + 0.9428 0.1419 0.6388 + 0.4259 0.8899 0.2468 + 0.5116 0.7886 0.0778 + 0.4094 0.0737 0.5975 + 0.5140 0.1224 0.4620 + 0.5372 0.0651 0.4016 + 0.6660 0.9459 0.3132 + 0.4055 0.3468 0.6925 + 0.3096 0.0933 0.2572 + 0.4144 0.0373 0.5043 + 0.9139 0.3029 0.4919 + 0.3319 0.8805 0.7918 + 0.7234 0.9948 0.1098 + 0.0170 0.1829 0.1256 + 0.2830 0.1492 0.3176 + 0.2600 0.7454 0.1790 + 0.9421 0.7018 0.4925 + 0.4013 0.3687 0.5861 + 0.5564 0.1115 0.6931 + 0.5644 0.5759 0.5131 + 0.7829 0.3622 0.5733 + 0.4672 0.1752 0.3412 + 0.6831 0.0186 0.9405 + 0.6730 0.2138 0.3973 + 0.6319 0.7183 0.0820 + 0.5931 0.5956 0.3570 + 0.5988 0.1654 0.1494 + 0.5480 0.1904 0.6582 + 0.9045 0.6122 0.3443 + 0.4664 0.2166 0.3314 + 0.0590 0.6913 0.4432 + 0.6690 0.9421 0.0179 + 0.5997 0.5471 0.8197 + 0.9114 0.1852 0.7625 + 0.7377 0.3112 0.3322 + 0.1051 0.0307 0.9699 spike_snippets(:,:,14) = - 0.9771 0.4050 0.7480 - 0.0164 0.7490 0.3865 - 0.3992 0.3380 0.2018 - 0.4467 0.0394 0.6368 - 0.7589 0.7476 0.6173 - 0.3515 0.3967 0.4286 - 0.2178 0.9765 0.7002 - 0.8223 0.9313 0.6389 - 0.5271 0.3603 0.2278 - 0.6464 0.5478 0.1317 - 0.4060 0.4718 0.9555 - 0.0270 0.4830 0.8491 - 0.0169 0.4543 0.9645 - 0.3327 0.7379 0.5937 - 0.9158 0.2060 0.9178 - 0.7765 0.1282 0.4284 - 0.3668 0.5960 0.5725 - 0.8509 0.0220 0.6117 - 0.3111 0.9309 0.9131 - 0.6934 0.8826 0.2040 - 0.2483 0.4454 0.1425 - 0.5874 0.7093 0.7173 - 0.8735 0.5988 0.0476 - 0.0763 0.7666 0.8454 - 0.1493 0.6608 0.4174 - 0.5494 0.2831 0.4819 - 0.4071 0.2351 0.8550 - 0.7085 0.9237 0.4157 - 0.7682 0.6375 0.0323 - 0.0196 0.1156 0.2325 - 1.0000 0.7195 0.4628 - 0.1514 0.4361 0.4644 - 0.9657 0.9117 0.4204 - 0.6568 0.0403 0.5068 - 0.8954 0.0092 0.2470 - 0.0665 1.0000 0.5583 - 0.8023 0.3161 0.5107 - 0.4888 0.3307 0.9941 - 0.6054 0.8640 0.4672 - 0.3619 0.9700 0.5930 + 0.8738 0.2539 0.5170 + 0.2402 0.9058 0.8826 + 0.5606 0.2673 0.7385 + 0.4901 0.1698 0.0661 + 0.9379 0.7687 0.2550 + 0.3886 0.8274 0.8461 + 0.6457 0.2094 0.0937 + 0.1621 0.3068 0.3773 + 0.5236 0.8581 0.4598 + 0.0713 0.0281 0.4309 + 0.0261 0.3696 0.1999 + 0.2469 0.5367 0.7956 + 0.8040 0.6403 0.4242 + 0.0573 0.7759 0.3112 + 0.6276 0.5760 0.6602 + 0.6207 0.0823 0.4012 + 0.8178 0.4223 0.2240 + 0.4988 0.1640 0.9503 + 0.9282 0.9511 0.7333 + 0.9976 0.8312 0.1382 + 0.3378 0.5846 0.9600 + 0.0158 0.1242 0.9066 + 0.9885 0.3426 0.5408 + 0.0505 0.8228 0.2916 + 0.9758 0.7815 0.6794 + 0.3000 0.9950 0.0827 + 0.9738 0.6339 0.1007 + 0.1853 0.2731 0.0706 + 0.4156 0.1586 0.1189 + 0.6042 0.0076 0.5364 + 0.8413 0.5236 0.2999 + 0.9670 0.2042 0.8950 + 0.0941 0.8300 0.0252 + 0.3518 0.4673 0.2669 + 0.8261 0.7968 0.3462 + 0.2083 0.6459 0.9281 + 0.4527 0.4038 0.3700 + 0.9544 0.9912 0.0818 + 0.8993 0.6790 0.6198 + 0.3285 0.1273 0.1766 spike_snippets(:,:,15) = - 0.8285 0.2332 0.5170 - 0.8534 0.5185 0.1475 - 0.1406 0.4439 0.6468 - 0.6271 0.7734 0.2874 - 0.7247 0.1307 0.7292 - 0.8879 0.9477 0.5567 - 0.2379 0.0284 0.5833 - 0.1076 0.1909 0.3181 - 0.9581 0.8145 0.8141 - 0.0705 0.4884 0.9800 - 0.1540 0.4450 0.7869 - 0.8214 0.2924 0.8107 - 0.3680 0.9230 0.7764 - 0.6458 0.1220 0.8034 - 0.0179 0.1152 0.1233 - 0.6552 0.0921 0.4741 - 0.4289 0.4675 0.4627 - 0.5957 0.6017 0.0530 - 0.7899 0.0406 0.7737 - 0.2715 0.5865 0.3788 - 0.3688 0.0015 0.2919 - 0.8261 0.6455 0.1661 - 0.9652 0.1234 0.2829 - 0.9641 0.1551 0.6033 - 0.0687 0.3860 0.3001 - 0.0792 0.8179 0.7818 - 0.7146 0.3863 0.2066 - 0.1720 0.3922 0.6165 - 0.0660 0.6977 0.0619 - 0.3606 0.8087 0.4434 - 0.0506 0.2943 0.3097 - 0.8358 0.0611 0.0026 - 0.1669 0.6148 0.0415 - 0.5499 0.1266 0.6509 - 0.0248 0.1344 0.8819 - 0.4887 0.2018 0.2192 - 0.6738 0.0505 0.3670 - 0.9817 0.2910 0.5394 - 0.0308 0.4956 0.9864 - 0.4028 0.5162 0.1998 + 0.2518 0.2132 0.6517 + 0.4294 0.2029 0.5901 + 0.1920 0.6816 0.0450 + 0.7183 0.2932 0.3240 + 0.3578 0.0861 0.9851 + 0.3588 0.7868 0.3229 + 0.6010 0.2346 0.3567 + 0.3353 0.8160 0.9498 + 0.6412 0.4145 0.4684 + 0.7884 0.5874 0.5773 + 0.8174 0.3730 0.7253 + 0.6890 0.8826 0.1494 + 0.6320 0.2392 0.3984 + 0.2187 0.1697 0.5931 + 0.4682 0.2170 0.4543 + 0.3698 0.5491 0.1559 + 0.4286 0.7919 0.6831 + 0.6181 0.8617 0.6636 + 0.5057 0.3220 0.4828 + 0.0323 0.0533 0.3387 + 0.8523 0.6696 0.8105 + 0.0593 0.3572 0.5220 + 0.7963 0.7267 0.4698 + 0.1692 0.8722 0.9332 + 0.8412 0.7119 0.8153 + 0.2336 0.3204 0.5797 + 0.5893 0.7290 0.6995 + 0.0408 0.6390 0.3742 + 0.5699 0.7568 0.5696 + 0.1617 0.6294 0.5978 + 0.8506 0.3235 0.0180 + 0.5276 0.7121 0.4807 + 0.0257 0.5351 0.0431 + 0.8287 0.6561 0.6653 + 0.7459 0.8562 0.6875 + 0.3030 0.9366 0.7649 + 0.3332 0.1334 0.1270 + 0.9961 0.1659 0.6390 + 0.3319 0.7664 0.4069 + 0.5801 0.2167 0.8845 spike_snippets(:,:,16) = - 0.0632 0.4119 0.1498 - 0.7862 0.8745 0.7497 - 0.3152 0.5970 0.2680 - 0.9582 0.5279 0.0675 - 0.8466 0.0177 0.1343 - 0.0575 0.4940 0.0601 - 0.9653 0.0421 0.6708 - 0.3656 0.9654 0.3555 - 0.6427 0.6600 0.8240 - 0.5254 0.5350 0.7614 - 0.8651 0.4518 0.1316 - 0.3772 0.3970 0.1697 - 0.9844 0.6259 0.4263 - 0.7779 0.2904 0.9443 - 0.4432 0.5715 0.7672 - 0.4826 0.1578 0.9774 - 0.8992 0.2390 0.9241 - 0.5520 0.7640 0.5236 - 0.6074 0.4966 0.5190 - 0.8831 0.8285 0.8182 - 0.0123 0.3464 0.0066 - 0.8589 0.9798 0.5752 - 0.6037 0.3889 0.2671 - 0.4787 0.5672 0.1828 - 0.9966 0.1551 0.4506 - 0.4607 0.6693 0.8682 - 0.1410 0.9311 0.4041 - 0.8505 0.8780 0.9635 - 0.8570 0.6529 0.0714 - 0.3587 0.9783 0.4064 - 0.4781 0.3078 0.8497 - 0.4610 0.3308 0.1232 - 0.8942 0.0222 0.8837 - 0.2707 0.6125 0.0489 - 0.8865 0.2327 0.1457 - 0.5464 0.8523 0.6175 - 0.4665 0.8757 0.5159 - 0.1181 0.2201 0.9281 - 0.8407 0.2243 0.4280 - 0.7371 0.5195 0.5242 + 0.3617 0.4796 0.3849 + 0.2697 0.9468 0.2100 + 0.6193 0.7343 0.1739 + 0.9393 0.8904 0.9818 + 0.5152 0.3033 0.4682 + 0.7790 0.8632 0.9662 + 0.4675 0.4774 0.5807 + 0.5060 0.4397 0.1240 + 0.9510 0.8019 0.9094 + 0.0934 0.1602 0.6181 + 0.0969 0.6271 0.8978 + 0.2026 0.4344 0.6037 + 0.6735 0.9694 0.1817 + 0.3916 0.3344 0.9062 + 0.9732 0.7063 0.0107 + 0.2138 0.3680 0.3148 + 0.4871 0.1667 0.1139 + 0.6683 0.2919 0.4264 + 0.3876 0.0255 0.8798 + 0.5643 0.8564 0.2776 + 0.5715 0.9426 0.0235 + 0.6990 0.6762 0.3343 + 0.8093 0.2319 0.1015 + 0.3863 0.8508 0.8312 + 0.1336 0.2826 0.6013 + 0.9609 0.7174 0.9380 + 0.9554 0.7533 0.1771 + 0.4546 0.6906 0.3352 + 0.6678 0.1365 0.7942 + 0.4918 0.9342 0.2363 + 0.5866 0.6735 0.0328 + 0.7620 0.9728 0.0622 + 0.6156 0.4195 0.1368 + 0.8612 0.5185 0.0101 + 0.2808 0.2869 0.7742 + 0.4417 0.8612 0.8285 + 0.7203 0.2428 0.9894 + 0.8703 0.6190 0.7001 + 0.4393 0.1567 0.1823 + 0.7885 0.1806 0.0020 spike_snippets(:,:,17) = - 0.2248 0.9454 0.3343 - 0.2661 0.2714 0.5529 - 0.6409 0.5257 0.2509 - 0.9720 0.6149 0.1317 - 0.4660 0.5646 0.0069 - 0.6395 0.8293 0.7489 - 0.0363 0.0109 0.0759 - 0.1006 0.2261 0.0331 - 0.4483 0.7455 0.8873 - 0.4724 0.9053 0.7795 - 0.6163 0.1059 0.4694 - 0.5038 0.8689 0.7092 - 0.4000 0.2480 0.9829 - 0.2566 0.8716 0.0221 - 0.1963 0.2623 0.1946 - 0.8754 0.8960 0.0683 - 0.3214 0.5532 0.3871 - 0.3090 0.4612 0.3692 - 0.0949 0.9036 0.7243 - 0.5785 0.0853 0.4553 - 0.4080 0.4273 0.1826 - 0.2397 0.1025 0.2676 - 0.0112 0.4233 0.8566 - 0.6912 0.5644 0.1537 - 0.5037 0.1292 0.5835 - 0.8740 0.9701 0.2860 - 0.1776 0.8694 0.4239 - 0.0146 0.2931 0.1575 - 0.1438 0.2192 0.0534 - 0.1210 0.6226 0.3310 - 0.6808 0.5873 0.8866 - 0.0166 0.4711 0.0265 - 0.2377 0.0932 0.4810 - 0.4095 0.5646 0.3677 - 0.7206 0.4269 0.0106 - 0.4908 0.5084 0.7710 - 0.9168 0.9261 0.2838 - 0.0650 0.3080 0.7490 - 0.9420 0.4491 0.0547 - 0.6323 0.8976 0.8351 + 0.1959 0.5420 0.9105 + 0.9073 0.6598 0.9800 + 0.3511 0.7097 0.4335 + 0.6976 0.0432 0.8542 + 0.4499 0.8647 0.0570 + 0.6184 0.1866 0.5088 + 0.1193 0.2679 0.1645 + 0.6003 0.3598 0.0318 + 0.7014 0.7784 0.4366 + 0.2962 0.9476 0.4504 + 0.3308 0.3297 0.9279 + 0.2546 0.0680 0.8303 + 0.9917 0.5504 0.9679 + 0.8936 0.4322 0.1293 + 0.5590 0.5348 0.5241 + 0.4043 0.1695 0.9367 + 0.2596 0.0089 0.7440 + 0.3231 0.3334 0.0031 + 0.2933 0.8405 0.0099 + 0.3029 0.3807 0.3762 + 0.3166 0.8088 0.7310 + 0.4989 0.6476 0.8311 + 0.1168 0.6515 0.7213 + 0.2899 0.3840 0.6481 + 0.1497 0.6184 0.0411 + 0.0861 0.9320 0.1645 + 0.3731 0.4888 0.1075 + 0.3739 0.0245 0.9520 + 0.2799 0.4943 0.5538 + 0.2278 0.6777 0.3999 + 0.2515 0.1443 0.8072 + 0.5196 0.6802 0.8547 + 0.9614 0.4804 0.2351 + 0.3108 0.2935 0.7144 + 0.6170 0.7830 0.1604 + 0.3423 0.2268 0.9950 + 0.0840 0.1316 0.4855 + 0.9375 0.8720 0.7160 + 0.8194 0.4043 0.6775 + 0.2686 0.9371 0.0693 spike_snippets(:,:,18) = - 0.8528 0.8267 0.3742 - 0.7533 0.1331 0.9552 - 0.4917 0.0555 0.7208 - 0.8872 0.2577 0.6193 - 0.5457 0.5930 0.0307 - 0.1417 0.9495 0.2944 - 0.3786 0.4841 0.4944 - 0.8947 0.1513 0.0882 - 0.5805 0.2407 0.3165 - 0.4782 0.4752 0.6826 - 0.3881 0.8809 0.0212 - 0.1409 0.6620 0.2262 - 0.7749 0.3939 0.4166 - 0.7476 0.2595 0.6855 - 0.8465 0.2595 0.1188 - 0.5590 0.4504 0.5791 - 0.8377 0.9104 0.6629 - 0.6318 0.3158 0.3771 - 0.8022 0.9276 0.2942 - 0.2505 0.8024 0.0318 - 0.9150 0.5174 0.6685 - 0.0144 0.9595 0.8606 - 0.4961 0.8575 0.2233 - 0.5635 0.4358 0.9664 - 0.3172 0.9393 0.1256 - 0.1253 0.8188 0.1029 - 0.3440 0.3788 0.0650 - 0.7378 0.9603 0.1648 - 0.4810 0.1027 0.5597 - 0.0185 0.2453 0.5999 - 0.8983 0.2843 0.7902 - 0.9571 0.6531 0.9934 - 0.3729 0.3625 0.7644 - 0.8940 0.6779 0.7209 - 0.0921 0.6594 0.9088 - 0.1161 0.6699 0.5187 - 0.6088 0.3770 0.6351 - 0.6902 0.4612 0.4053 - 0.1904 0.8284 0.8358 - 0.0138 0.0373 0.1868 + 0.2044 0.8060 0.4559 + 0.3989 0.3477 0.0178 + 0.3788 0.1528 0.2253 + 0.4920 0.6866 0.6046 + 0.0173 0.5004 0.6817 + 0.4821 0.8199 0.8309 + 0.2736 0.1016 0.2971 + 0.9217 0.5541 0.4229 + 0.1621 0.0677 0.9017 + 0.5945 0.1912 0.6291 + 0.2916 0.5514 0.5131 + 0.2112 0.8000 0.3688 + 0.8284 0.4171 0.1074 + 0.8941 0.1970 0.8132 + 0.5454 0.1708 0.5964 + 0.0349 0.1684 0.5245 + 0.5870 0.6541 0.5613 + 0.7540 0.6592 0.1215 + 0.1668 0.6609 0.2367 + 0.6786 0.1693 0.0634 + 0.9586 0.3644 0.4699 + 0.3425 0.2172 0.2802 + 0.1701 0.9606 0.7301 + 0.9152 0.3249 0.7825 + 0.8650 0.4647 0.2678 + 0.7262 0.6912 0.9897 + 0.4532 0.6555 0.2157 + 0.7250 0.0509 0.3455 + 0.3804 0.2556 0.8391 + 0.6323 0.8273 0.0621 + 0.7440 0.9607 0.9505 + 0.5396 0.6674 0.5746 + 0.5364 0.2100 0.9133 + 0.5956 0.7204 0.8166 + 0.4304 0.9428 0.1365 + 0.2596 0.5669 0.2789 + 0.1343 0.4623 0.9535 + 0.2614 0.6911 0.2445 + 0.4018 0.0246 0.3004 + 0.0077 0.1523 0.3515 spike_snippets(:,:,19) = - 0.7833 0.3800 0.2596 - 0.4774 0.3225 0.9132 - 0.3767 0.5766 0.7866 - 0.5167 0.7333 0.2876 - 0.3313 0.6348 0.4921 - 0.9909 0.7142 0.0040 - 0.7992 0.8899 0.7669 - 0.1438 0.3450 0.1557 - 0.8615 0.5972 0.6849 - 0.2221 0.2920 0.8230 - 0.9428 0.8654 0.7775 - 0.5121 0.7407 0.5810 - 0.6163 0.4000 0.3330 - 0.3748 0.9231 0.6470 - 0.4877 0.7523 0.8188 - 0.4864 0.9930 0.0859 - 0.0486 0.4711 0.6532 - 0.3978 0.0564 0.0195 - 0.9749 0.1345 0.4348 - 0.4293 0.0039 0.9382 - 0.5667 0.6412 0.2270 - 0.5779 0.7878 0.6903 - 0.4924 0.9922 0.8492 - 0.6379 0.1296 0.5580 - 0.4197 0.5457 0.1361 - 0.4808 0.5291 0.6389 - 0.6873 0.0123 0.3525 - 0.9886 0.1202 0.7538 - 0.7048 0.8567 0.9505 - 0.3475 0.1045 0.1739 - 0.8209 0.6846 0.5388 - 0.0525 0.3191 0.7253 - 0.1306 0.5241 0.2333 - 0.6714 0.2183 0.1283 - 0.8935 0.1168 0.5805 - 0.8253 0.1419 0.3755 - 0.6320 0.2447 0.7466 - 0.0071 0.0700 0.8273 - 0.3107 0.5529 0.8195 - 0.2268 0.3851 0.4185 + 0.4796 0.1469 0.7166 + 0.7013 0.1920 0.6477 + 0.3507 0.7289 0.3165 + 0.8040 0.2512 0.8675 + 0.8645 0.4048 0.6909 + 0.6119 0.1603 0.0853 + 0.3848 0.3168 0.8677 + 0.7272 0.9591 0.4262 + 0.4190 0.5329 0.7629 + 0.8615 0.3420 0.3994 + 0.6660 0.7825 0.5770 + 0.0337 0.5107 0.3806 + 0.2774 0.0544 0.5483 + 0.3664 0.7206 0.4609 + 0.3950 0.5821 0.2499 + 0.8709 0.9255 0.2459 + 0.4484 0.3779 0.7865 + 0.5256 0.7493 0.3957 + 0.3331 0.4078 0.6649 + 0.0315 0.3368 0.5578 + 0.8973 0.6093 0.0103 + 0.8446 0.0363 0.2517 + 0.8092 0.1453 0.8943 + 0.7750 0.4485 0.2392 + 0.8051 0.5641 0.9237 + 0.5442 0.3667 0.5998 + 0.7352 0.7777 0.9312 + 0.4753 0.1060 0.7647 + 0.6985 0.3146 0.1770 + 0.9186 0.0215 0.0437 + 0.8929 0.5599 0.3737 + 0.6774 0.9312 0.5492 + 0.2597 0.3554 0.3777 + 0.3778 0.6063 0.0516 + 0.3313 0.3930 0.4723 + 0.3727 0.7249 0.9840 + 0.7936 0.9136 0.4857 + 0.4638 0.5821 0.0701 + 0.8432 0.2344 0.4754 + 0.5643 0.0735 0.2353 spike_snippets(:,:,20) = - 0.0803 0.5497 0.8134 - 0.2016 0.7139 0.9475 - 0.2989 0.6889 0.9147 - 0.4506 0.2072 0.5099 - 0.6934 0.8924 0.4041 - 0.9842 0.2183 0.5916 - 0.9099 0.1014 0.2588 - 0.3646 0.2869 0.1107 - 0.7892 0.5949 0.2686 - 0.8226 0.5505 0.3111 - 0.0764 0.4849 0.1924 - 0.2240 0.8471 0.8423 - 0.1020 0.2254 0.4194 - 0.8691 0.5801 0.1725 - 0.8217 0.0235 0.0745 - 0.6285 0.1380 0.2667 - 0.6437 0.4091 0.5609 - 0.6716 0.2623 0.3741 - 0.7291 0.8411 0.0698 - 0.4717 0.0125 0.4050 - 0.1205 0.4536 0.2174 - 0.1712 0.4339 0.4282 - 0.2948 0.1448 0.0394 - 0.8869 0.1695 0.8341 - 0.0986 0.4590 0.6105 - 0.2961 0.1109 0.6225 - 0.2111 0.6054 0.6792 - 0.9609 0.6630 0.4239 - 0.8231 0.0917 0.1793 - 0.9510 0.1655 0.7910 - 0.8841 0.3304 0.3527 - 0.7403 0.3570 0.1069 - 0.6682 0.3612 0.9556 - 0.7742 0.0715 0.6476 - 0.8029 0.4781 0.8936 - 0.8517 0.2079 0.6216 - 0.8403 0.8003 0.7215 - 0.5677 0.7792 0.8469 - 0.1805 0.0260 0.8686 - 0.4809 0.8847 0.0151 -
 
% Create electrode table region referencing electrodes 0, 1, and 2
shank0_table_region = types.hdmf_common.DynamicTableRegion( ...
'table', types.untyped.ObjectView(electrodesDynamicTable), ...
'description', 'shank0', ...
'data', (0:2)');
 
% Define spike event series for unsorted spike times
spike_events = types.core.SpikeEventSeries( ...
'data', spike_snippets, ...
'timestamps', (0:19)', ... % Timestamps for each event
'description', 'events detected with 100uV threshold', ...
'electrodes', shank0_table_region ...
);
 
% Add spike event series to NWB file acquisition
nwb.acquisition.set('SpikeEvents_Shank0', spike_events);

Detected Events

If you need to store the complete, continuous raw voltage traces, along with unsorted spike times, you should store the traces in ElectricalSeries objects in the acquisition group, and use the EventDetection class to identify the spike events in your raw traces.
% Create the EventDetection object
event_detection = types.core.EventDetection( ...
'detection_method', 'thresholding, 1.5 * std', ...
'source_electricalseries', types.untyped.SoftLink(raw_electrical_series), ...
'source_idx', [1000; 2000; 3000], ...
'times', [.033, .066, .099] ...
);
 
% Add the EventDetection object to the ecephys module
ecephys_module.nwbdatainterface.set('ThresholdEvents', event_detection);

Storing Spike Features (e.g Principal Components)

NWB also provides a way to store features of spikes, such as principal components, using the FeatureExtraction class.
% Generate random feature data (time x channel x feature)
features = rand(3, 12, 4); % 3 time points, 12 channels, 4 features
features = permute(features, [3,2,1]); % reverse dimension order for matnwb
 
% Create the FeatureExtraction object
feature_extraction = types.core.FeatureExtraction( ...
'description', {'PC1', 'PC2', 'PC3', 'PC4'}, ... % Feature descriptions
'electrodes', electrode_table_region, ... % DynamicTableRegion referencing the electrodes table
'times', [.033; .066; .099], ... % Column vector for times
'features', features ...
);
 
% Add the FeatureExtraction object to the ecephys module (if required)
ecephys_module.nwbdatainterface.set('PCA_features', feature_extraction);

Choosing NWB-Types for Electrophysiology Data (A Summary)

As mentioned above, ElectricalSeries objects are meant for storing electrical timeseries data like raw voltage signals or processed signals like LFP or other filtered signals. In addition to the ElectricalSeries class, NWB provides some more classes for storing event-based electropysiological data. We will briefly discuss them here, and refer the reader to the API documentation and the section on Extracellular Physiology in the "NWB Format Specification" for more details on using these objects.
For storing unsorted spiking 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 entire raw voltage traces, only the waveform ‘snippets’ surrounding spike events, you should use SpikeEventSeries objects.
The results of spike sorting (or clustering) should be stored in the top-level Units table. The Units table can hold just the spike times of sorted units or, optionally, include additional waveform information. You can use the optional predefined columns waveform_mean, waveform_sd, and waveforms in the Units table to store individual and mean waveform data.

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
2.0421 -1.9417 0.3559 0.4354 0.6993 -1.4009 0.5222 0.0893 0.1243 1.2460 - 0.2329 0.4688 2.1159 1.2094 0.1735 -0.3315 0.1403 -2.0881 0.2840 -0.4077 - -0.0943 1.3933 -0.0871 -0.5193 -0.0920 -1.1307 0.8399 0.0975 -0.6912 -0.4536 - -1.4224 2.7602 1.1832 0.0075 0.6687 0.2074 0.5432 0.4366 0.0113 0.2925 - 0.9660 0.9444 -0.8471 1.0362 0.0652 -0.2155 0.6006 0.1602 0.7417 -1.3644 -
 
% 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 = 24×1
0.1312 - 0.4211 - 0.2037 - 0.1021 - 0.1315 - 0.8392 - 0.9195 - 0.7023 - 0.0588 - 0.7331 + 0.6873 0.0605 0.9140 + 0.9657 0.0721 0.8518 + 0.8899 0.1328 0.6940 + 0.0387 0.6798 0.9621 + 0.4656 0.5908 0.2731 + 0.7438 0.5670 0.2736 + 0.3971 0.0360 0.3144 + 0.8050 0.7448 0.6957 + 0.0030 0.4603 0.2473 + 0.2465 0.6805 0.3513 + 0.8086 0.2980 0.2490 + 0.2352 0.3977 0.5605 + 0.2713 0.7731 0.5679 + 0.8310 0.7215 0.5309 + 0.7490 0.9698 0.8578 + 0.5483 0.1271 0.5879 + 0.3070 0.0921 0.7794 + 0.1199 0.8039 0.5579 + 0.9437 0.8100 0.8285 + 0.7219 0.4346 0.4720 + 0.7465 0.6658 0.1583 + 0.5633 0.8219 0.5985 + 0.3870 0.4728 0.3190 + 0.4641 0.8891 0.5325 + 0.8391 0.2668 0.5287 + 0.2010 0.5825 0.9167 + 0.9513 0.0738 0.1681 + 0.2306 0.2030 0.1066 + 0.9499 0.1480 0.6067 + 0.9601 0.6199 0.4368 + 0.9094 0.2456 0.3924 + 0.9030 0.7869 0.9830 + 0.1268 0.3279 0.8036 + 0.4003 0.7065 0.6491 + 0.6768 0.2883 0.4791 + 0.1409 0.1552 0.0926 + 0.7783 0.5634 0.3639 + 0.7050 0.2093 0.0664 + 0.2874 0.9361 0.8809 + 0.5040 0.0051 0.3799 +
 
% Create electrode table region referencing electrodes 0, 1, and 2
shank0_table_region = types.hdmf_common.DynamicTableRegion( ...
'table', types.untyped.ObjectView(electrodesDynamicTable), ...
'description', 'shank0', ...
'data', (0:2)');
 
% Define spike event series for unsorted spike times
spike_events = types.core.SpikeEventSeries( ...
'data', spike_snippets, ...
'timestamps', (0:19)', ... % Timestamps for each event
'description', 'events detected with 100uV threshold', ...
'electrodes', shank0_table_region ...
);
 
% Add spike event series to NWB file acquisition
nwb.acquisition.set('SpikeEvents_Shank0', spike_events);

Detected Events

If you need to store the complete, continuous raw voltage traces, along with unsorted spike times, you should store the traces in ElectricalSeries objects in the acquisition group, and use the EventDetection class to identify the spike events in your raw traces.
% Create the EventDetection object
event_detection = types.core.EventDetection( ...
'detection_method', 'thresholding, 1.5 * std', ...
'source_electricalseries', types.untyped.SoftLink(raw_electrical_series), ...
'source_idx', [1000; 2000; 3000], ...
'times', [.033, .066, .099] ...
);
 
% Add the EventDetection object to the ecephys module
ecephys_module.nwbdatainterface.set('ThresholdEvents', event_detection);

Storing Spike Features (e.g Principal Components)

NWB also provides a way to store features of spikes, such as principal components, using the FeatureExtraction class.
% Generate random feature data (time x channel x feature)
features = rand(3, 12, 4); % 3 time points, 12 channels, 4 features
features = permute(features, [3,2,1]); % reverse dimension order for matnwb
 
% Create the FeatureExtraction object
feature_extraction = types.core.FeatureExtraction( ...
'description', {'PC1', 'PC2', 'PC3', 'PC4'}, ... % Feature descriptions
'electrodes', electrode_table_region, ... % DynamicTableRegion referencing the electrodes table
'times', [.033; .066; .099], ... % Column vector for times
'features', features ...
);
 
% Add the FeatureExtraction object to the ecephys module (if required)
ecephys_module.nwbdatainterface.set('PCA_features', feature_extraction);

Choosing NWB-Types for Electrophysiology Data (A Summary)

As mentioned above, ElectricalSeries objects are meant for storing electrical timeseries data like raw voltage signals or processed signals like LFP or other filtered signals. In addition to the ElectricalSeries class, NWB provides some more classes for storing event-based electropysiological data. We will briefly discuss them here, and refer the reader to the API documentation and the section on Extracellular Physiology in the "NWB Format Specification" for more details on using these objects.
For storing unsorted spiking 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 entire raw voltage traces, only the waveform ‘snippets’ surrounding spike events, you should use SpikeEventSeries objects.
The results of spike sorting (or clustering) should be stored in the top-level Units table. The Units table can hold just the spike times of sorted units or, optionally, include additional waveform information. You can use the optional predefined columns waveform_mean, waveform_sd, and waveforms in the Units table to store individual and mean waveform data.

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.9651 -0.8883 -0.0094 0.8173 -1.0690 0.1127 -1.4322 -1.0934 -1.5671 1.0822 + -0.6077 0.1766 0.1769 0.3961 -0.4618 -0.6057 0.4036 -0.8789 -1.2575 0.5712 + -0.1929 0.5733 -1.4192 -0.2441 1.7385 -0.8977 -0.4516 -0.9052 -0.1145 0.3583 + 0.2470 -0.4401 1.6814 -0.3924 -0.1789 0.1862 -0.4147 -0.1626 -0.4556 -0.1766 + 0.2099 -0.4356 -0.8001 -0.3095 -1.3299 0.1646 0.5666 1.0098 -0.4388 0.7080 +
 
% 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 = 24×1
0.3390 + 0.8821 + 0.0155 + 0.9190 + 0.3082 + 0.1643 + 0.0970 + 0.8547 + 0.1262 + 0.3271

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: