diff --git a/+file/+internal/isPropertyHidden.m b/+file/+internal/isPropertyHidden.m deleted file mode 100644 index c000810ab..000000000 --- a/+file/+internal/isPropertyHidden.m +++ /dev/null @@ -1,15 +0,0 @@ -function result = isPropertyHidden(propertyInfo, className, namespace) -% isPropertyHidden - Determine if a property is hidden - - if isa(propertyInfo, 'file.Attribute') || isa(propertyInfo, 'file.Dataset') - if strcmp(namespace.name, 'hdmf_common') ... - && strcmp(className, 'VectorData') ... - && any(strcmp(propertyInfo.name, {'unit', 'sampling_rate', 'resolution'})) - result = true; - else - result = false; - end - else - result = false; - end -end diff --git a/+file/Attribute.m b/+file/Attribute.m index 2319daafc..90df92642 100644 --- a/+file/Attribute.m +++ b/+file/Attribute.m @@ -8,7 +8,6 @@ dtype; %type of value dependent; %set externally. If the attribute is actually dependent on an untyped dataset/group dependent_fullname; %set externally. This is the full name, including names of potential parent groups separated by underscore. A value will only be present if it would differ from dependent. - promoted_to_container = false; % set externally when promoted from a typed dataset onto the containing class API scalar; %if the value is scalar or an array dimnames; shape; @@ -26,7 +25,6 @@ obj.dtype = ''; obj.dependent = ''; obj.dependent_fullname = ''; - obj.promoted_to_container = false; obj.scalar = true; obj.shape = {}; obj.dimnames = {}; diff --git a/+file/fillClass.m b/+file/fillClass.m index ad10e294f..a00d8f2bd 100644 --- a/+file/fillClass.m +++ b/+file/fillClass.m @@ -11,7 +11,6 @@ readonly = {}; defaults = {}; dependent = {}; - hidden = {}; % special hidden properties for hard-coded workarounds %separate into readonly, required, and optional properties for iGroup = 1:length(allProperties) @@ -65,19 +64,18 @@ dependent = [dependent {propertyName}]; end end - - if strcmp(namespace.name, 'hdmf_common') ... - && strcmp(name, 'VectorData') ... - && any(strcmp(prop.name, {'unit', 'sampling_rate', 'resolution'})) - hidden{end+1} = propertyName; - end end end + + % Each property is emitted in exactly one properties block, with readonly + % taking precedence over required/optional. Restrict each bucket to + % non-inherited names and remove anything already going into the readonly + % block to avoid duplicate property declarations. nonInherited = setdiff(allProperties, inherited); readonly = intersect(readonly, nonInherited); - exclusivePropertyGroups = union(readonly, hidden); - required = setdiff(intersect(required, nonInherited), exclusivePropertyGroups); - optional = setdiff(intersect(optional, nonInherited), exclusivePropertyGroups); + settable = setdiff(nonInherited, readonly); % what's left for required/optional + required = intersect(required, settable); + optional = intersect(optional, settable); %% CLASSDEF superclassNames = {}; @@ -115,25 +113,18 @@ '% Required Properties:', newline, ... sprintf('%% %s', strjoin(allRequiredPropertyNames, ', '))]; - hiddenAndReadonly = intersect(hidden, readonly); - hidden = setdiff(hidden, hiddenAndReadonly); - readonly = setdiff(readonly, hiddenAndReadonly); PropertyGroups = struct(... - 'Function', {... - @()file.fillProps(classprops, hiddenAndReadonly, 'PropertyAttributes', 'Hidden, SetAccess = protected') ... - , @()file.fillProps(classprops, hidden, 'PropertyAttributes', 'Hidden') ... - , @()file.fillProps(classprops, readonly, 'PropertyAttributes', 'SetAccess = protected') ... - , @()file.fillProps(classprops, required, 'IsRequired', true) ... - , @()file.fillProps(classprops, optional)... - } ... - , 'Separator', { ... - '% HIDDEN READONLY PROPERTIES' ... - , '% HIDDEN PROPERTIES' ... - , '% READONLY PROPERTIES' ... - , '% REQUIRED PROPERTIES' ... - , '% OPTIONAL PROPERTIES' ... + 'Function', { ... + @()file.fillProps(classprops, readonly, 'PropertyAttributes', 'SetAccess = protected'), ... + @()file.fillProps(classprops, required, 'IsRequired', true), ... + @()file.fillProps(classprops, optional)... + }, ... + 'Separator', { ... + '% READONLY PROPERTIES', ... + '% REQUIRED PROPERTIES', ... + '% OPTIONAL PROPERTIES' ... } ... - ); + ); fullPropertyDefinition = ''; for iGroup = 1:length(PropertyGroups) @@ -165,7 +156,7 @@ class, ... inherited); setterFcns = file.fillSetters( ... - setdiff(nonInherited, union(readonly, hiddenAndReadonly)), ... + setdiff(nonInherited, readonly), ... classprops, ... name, ... namespace); diff --git a/+file/fillExport.m b/+file/fillExport.m index 87da582e2..02f35caff 100644 --- a/+file/fillExport.m +++ b/+file/fillExport.m @@ -38,15 +38,7 @@ needsElisionGroupWrite = ~isempty(elideProps) ... && all(cellfun('isclass', elideProps, 'file.Group')); - if strcmp(propertyName, 'unit') && strcmp(RawClass.type, 'VectorData') - exportBody{end+1} = fillVectorDataUnitConditional(); - elseif strcmp(propertyName, 'sampling_rate') && strcmp(RawClass.type, 'VectorData') - exportBody{end+1} = fillVectorDataSamplingRateConditional(); - elseif strcmp(propertyName, 'resolution') && strcmp(RawClass.type, 'VectorData') - exportBody{end+1} = fillVectorDataResolutionConditional(); - else - exportBody{end+1} = fillDataExport(propertyName, prop, elisions, required, needsElisionGroupWrite, classprops); - end + exportBody{end+1} = fillDataExport(propertyName, prop, elisions, required, needsElisionGroupWrite, classprops); end festr = strjoin({ ... @@ -56,29 +48,6 @@ }, newline); end -function exportBody = fillVectorDataResolutionConditional() - exportBody = strjoin({... - 'if ~isempty(obj.resolution) && any(endsWith(fullpath, ''units/spike_times''))' ... - , ' writer.writeAttribute([fullpath ''/resolution''], obj.resolution);' ... - , 'end'}, newline); -end - -function exportBody = fillVectorDataUnitConditional() - exportBody = strjoin({... - 'validUnitPaths = strcat(''units/'', {''waveform_mean'', ''waveform_sd'', ''waveforms''});' ... - , 'if ~isempty(obj.unit) && any(endsWith(fullpath, validUnitPaths))' ... - , ' writer.writeAttribute([fullpath ''/unit''], obj.unit);' ... - , 'end'}, newline); -end - -function exportBody = fillVectorDataSamplingRateConditional() - exportBody = strjoin({... - 'validDataSamplingPaths = strcat(''units/'', {''waveform_mean'', ''waveform_sd'', ''waveforms''});' ... - , 'if ~isempty(obj.sampling_rate) && any(endsWith(fullpath, validDataSamplingPaths))' ... - , ' writer.writeAttribute([fullpath ''/sampling_rate''], obj.sampling_rate);' ... - , 'end'}, newline); -end - function path = traverseRaw(propertyName, RawClass) % returns a cell array of named tokens which may or may not indicate an identifier. % these tokens are relative to the Raw class @@ -265,16 +234,6 @@ warnIfMissingRequiredDependentAttributeStr = ... sprintf('obj.throwErrorIfRequiredDependencyMissing(''%s'', ''%s'', fullpath)', name, depPropname); end - - if prop.promoted_to_container - preExportString = sprintf([ ... - 'if %s && %s && isobject(obj.%s) && isprop(obj.%s, ''%s'') && ~isempty(obj.%s.%s)\n' ... - ' obj.%s = obj.%s.%s;\n' ... - 'end'], ... - isCurrentPropertyUnset, isDependentPropertySet, ... - depPropname, depPropname, prop.name, depPropname, prop.name, ... - name, depPropname, prop.name); - end end if ~prop.required @@ -302,10 +261,6 @@ end end - if ~isempty(preExportString) - dataExportString = sprintf('%s\n%s', preExportString, dataExportString); - end - if ~isempty(dependencyCheck) dataExportString = sprintf('%s\nif %s\n%s\nend', ... dataExportString, ... diff --git a/+file/fillSetters.m b/+file/fillSetters.m index 4a22de307..0f18d022e 100644 --- a/+file/fillSetters.m +++ b/+file/fillSetters.m @@ -45,25 +45,10 @@ 'obj.warnIfAttributeDependencyMissing(''%s'', ''%s'')', ... propName, parentname); - syncPromotedDatasetAttributeString = ''; - if prop.promoted_to_container - syncPromotedDatasetAttributeString = sprintf([ ... - 'if ~isempty(obj.%1$s) && isobject(obj.%1$s) && isprop(obj.%1$s, ''%2$s'')\n' ... - ' if ~isempty(obj.%3$s)\n' ... - ' obj.%1$s.%2$s = obj.%3$s;\n' ... - ' elseif ~isempty(obj.%1$s.%2$s)\n' ... - ' obj.%3$s = obj.%1$s.%2$s;\n' ... - ' end\n' ... - 'end'], parentname, prop.name, propName); - end - postsetStatements = [postsetStatements, ... {conditionStr}, ... {file.addSpaces(warnIfDependencyMissingString, 4)}, ... {'end'}]; - if ~isempty(syncPromotedDatasetAttributeString) - postsetStatements{end+1} = syncPromotedDatasetAttributeString; - end end end diff --git a/+file/processClass.m b/+file/processClass.m index 09a418d49..d6771181b 100644 --- a/+file/processClass.m +++ b/+file/processClass.m @@ -28,9 +28,7 @@ error('NWB:FileGen:InvalidClassType',... 'Class type %s is invalid', node('class_type')); end - if strcmp(nodename, 'VectorData') && strcmp(namespace.name, 'hdmf_common') - class = patchVectorData(class); - end + props = class.getProps(); props = markPromotedAttributesForIncludedTypedDatasets(class, props, namespace); @@ -80,10 +78,6 @@ if any(strcmp(attribute.name, schemaAttributeNames)) remove(props, propertyName); - else - promotedAttribute = props(propertyName); - promotedAttribute.promoted_to_container = true; - props(propertyName) = promotedAttribute; end end end @@ -128,47 +122,3 @@ attributeNames = propNames(isAttribute); schemaAttributeNameCache(cacheKey) = attributeNames; end - -function class = patchVectorData(class) - %% Unit Attribute - % derived from schema 2.6.0 - source = containers.Map(); - source('name') = 'unit'; - source('doc') = ['NOTE: this is a special value for compatibility with the Units table and is ' ... - 'only written to file when detected to be in that specific HDF5 Group. ' ... - 'The value must be ''volts''']; - source('dtype') = 'text'; - source('value') = 'volts'; - source('required') = false; - class.attributes(end+1) = file.Attribute(source); - - %% Sampling Rate Attribute - % derived from schema 2.6.0 - - source = containers.Map(); - source('name') = 'sampling_rate'; - source('doc') = ['NOTE: this is a special value for compatibility with the Units table and is ' ... - 'only written to file when detected to be in that specific HDF5 Group. ' ... - 'Must be Hertz']; - source('dtype') = 'float32'; - source('required') = false; - - class.attributes(end+1) = file.Attribute(source); - %% Resolution Attribute - % derived from schema 2.6.0 - - source = containers.Map(); - source('name') = 'resolution'; - source('doc') = ['NOTE: this is a special value for compatibility with the Units table and is ' ... - 'only written to file when detected to be in that specific HDF5 Group. ' ... - 'The smallest possible difference between two spike times. ' ... - 'Usually 1 divided by the acquisition sampling rate from which spike times were extracted, ' ... - 'but could be larger if the acquisition time series was downsampled or smaller if the ' ... - 'acquisition time series was smoothed/interpolated ' ... - 'and it is possible for the spike time to be between samples.' ... - ]; - source('dtype') = 'float64'; - source('required') = false; - - class.attributes(end+1) = file.Attribute(source); -end diff --git a/+schemes/+internal/getRequiredPropsForClass.m b/+schemes/+internal/getRequiredPropsForClass.m index 5d6111834..da6304d25 100644 --- a/+schemes/+internal/getRequiredPropsForClass.m +++ b/+schemes/+internal/getRequiredPropsForClass.m @@ -37,15 +37,14 @@ % Resolve the required properties. For the final list of required properties, % we ignore both hidden and read-only properties. allPropertieNames = keys(classprops); - [isRequired, isReadOnly, isHidden] = deal( false(1, classprops.Count) ); + [isRequired, isReadOnly] = deal( false(1, classprops.Count) ); for iProp = 1:length(allPropertieNames) propertyName = allPropertieNames{iProp}; prop = classprops(propertyName); isRequired(iProp) = file.internal.isPropertyRequired(prop, propertyName, classprops); isReadOnly(iProp) = file.internal.isPropertyReadonly(prop); - isHidden(iProp) = file.internal.isPropertyHidden(prop, className, namespace); end - requiredPropertyNames = allPropertieNames(isRequired & ~isReadOnly & ~isHidden); + requiredPropertyNames = allPropertieNames(isRequired & ~isReadOnly); end diff --git a/+tests/+system/UnitTimesIOTest.m b/+tests/+system/UnitTimesIOTest.m index 3a87b4e24..12ab205df 100644 --- a/+tests/+system/UnitTimesIOTest.m +++ b/+tests/+system/UnitTimesIOTest.m @@ -54,21 +54,4 @@ function addContainer(~, file) c = file.units; end end - - methods (Test) - function testLegacyNestedSpikeTimesResolutionIsPreserved(testCase) - spikeTimes = types.hdmf_common.VectorData( ... - 'data', 11, ... - 'description', 'the spike times for each unit in seconds'); - spikeTimes.resolution = 1/20000; - - units = types.core.Units( ... - 'colnames', {'spike_times'}, ... - 'description', 'data on spiking units', ... - 'spike_times', spikeTimes); - - testCase.verifyEqual(units.spike_times.resolution, 1/20000); - testCase.verifyEqual(units.spike_times_resolution, 1/20000); - end - end end diff --git a/+tests/+unit/+file/VectorDataPatchTest.m b/+tests/+unit/+file/VectorDataPatchTest.m new file mode 100644 index 000000000..6bf53bbbe --- /dev/null +++ b/+tests/+unit/+file/VectorDataPatchTest.m @@ -0,0 +1,31 @@ +classdef VectorDataPatchTest < tests.abstract.NwbTestCase + + methods (Test) + function testVectorDataUnitIsNotInjected(testCase) + vectorDataClass = ?types.hdmf_common.VectorData; + vectorDataPropertyNames = {vectorDataClass.PropertyList.Name}; + + testCase.verifyFalse(ismember('unit', vectorDataPropertyNames)) + testCase.verifyFalse(ismember('sampling_rate', vectorDataPropertyNames)) + testCase.verifyFalse(ismember('resolution', vectorDataPropertyNames)) + + unitsClass = ?types.core.Units; + unitsPropertyNames = {unitsClass.PropertyList.Name}; + + testCase.verifyTrue(ismember('waveform_mean_unit', unitsPropertyNames)) + testCase.verifyTrue(ismember('waveform_sd_unit', unitsPropertyNames)) + testCase.verifyTrue(ismember('waveforms_unit', unitsPropertyNames)) + end + + function testUnitsDoesNotSyncPromotedUnitAttributesFromVectorData(testCase) + unitsFile = fullfile( ... + testCase.getTypesOutputFolder(), ... + '+types', '+core', 'Units.m'); + unitsContents = string(fileread(unitsFile)); + + testCase.verifyFalse(contains(unitsContents, 'obj.waveform_mean.unit')) + testCase.verifyFalse(contains(unitsContents, 'obj.waveform_sd.unit')) + testCase.verifyFalse(contains(unitsContents, 'obj.waveforms.unit')) + end + end +end diff --git a/+types/+core/Units.m b/+types/+core/Units.m index 5242017e6..607e02b26 100644 --- a/+types/+core/Units.m +++ b/+types/+core/Units.m @@ -186,13 +186,6 @@ function postset_spike_times_resolution(obj) if isempty(obj.spike_times) && ~isempty(obj.spike_times_resolution) obj.warnIfAttributeDependencyMissing('spike_times_resolution', 'spike_times') end - if ~isempty(obj.spike_times) && isobject(obj.spike_times) && isprop(obj.spike_times, 'resolution') - if ~isempty(obj.spike_times_resolution) - obj.spike_times.resolution = obj.spike_times_resolution; - elseif ~isempty(obj.spike_times.resolution) - obj.spike_times_resolution = obj.spike_times.resolution; - end - end end function set.waveform_mean(obj, val) obj.waveform_mean = obj.validate_waveform_mean(val); @@ -209,13 +202,6 @@ function postset_waveform_mean_sampling_rate(obj) if isempty(obj.waveform_mean) && ~isempty(obj.waveform_mean_sampling_rate) obj.warnIfAttributeDependencyMissing('waveform_mean_sampling_rate', 'waveform_mean') end - if ~isempty(obj.waveform_mean) && isobject(obj.waveform_mean) && isprop(obj.waveform_mean, 'sampling_rate') - if ~isempty(obj.waveform_mean_sampling_rate) - obj.waveform_mean.sampling_rate = obj.waveform_mean_sampling_rate; - elseif ~isempty(obj.waveform_mean.sampling_rate) - obj.waveform_mean_sampling_rate = obj.waveform_mean.sampling_rate; - end - end end function set.waveform_sd(obj, val) obj.waveform_sd = obj.validate_waveform_sd(val); @@ -232,13 +218,6 @@ function postset_waveform_sd_sampling_rate(obj) if isempty(obj.waveform_sd) && ~isempty(obj.waveform_sd_sampling_rate) obj.warnIfAttributeDependencyMissing('waveform_sd_sampling_rate', 'waveform_sd') end - if ~isempty(obj.waveform_sd) && isobject(obj.waveform_sd) && isprop(obj.waveform_sd, 'sampling_rate') - if ~isempty(obj.waveform_sd_sampling_rate) - obj.waveform_sd.sampling_rate = obj.waveform_sd_sampling_rate; - elseif ~isempty(obj.waveform_sd.sampling_rate) - obj.waveform_sd_sampling_rate = obj.waveform_sd.sampling_rate; - end - end end function set.waveforms(obj, val) obj.waveforms = obj.validate_waveforms(val); @@ -261,13 +240,6 @@ function postset_waveforms_sampling_rate(obj) if isempty(obj.waveforms) && ~isempty(obj.waveforms_sampling_rate) obj.warnIfAttributeDependencyMissing('waveforms_sampling_rate', 'waveforms') end - if ~isempty(obj.waveforms) && isobject(obj.waveforms) && isprop(obj.waveforms, 'sampling_rate') - if ~isempty(obj.waveforms_sampling_rate) - obj.waveforms.sampling_rate = obj.waveforms_sampling_rate; - elseif ~isempty(obj.waveforms.sampling_rate) - obj.waveforms_sampling_rate = obj.waveforms.sampling_rate; - end - end end %% VALIDATORS @@ -385,39 +357,24 @@ function postset_waveforms_sampling_rate(obj) if ~isempty(obj.spike_times_index) refs = obj.spike_times_index.export(writer, [fullpath '/spike_times_index'], refs); end - if isempty(obj.spike_times_resolution) && ~isempty(obj.spike_times) && isobject(obj.spike_times) && isprop(obj.spike_times, 'resolution') && ~isempty(obj.spike_times.resolution) - obj.spike_times_resolution = obj.spike_times.resolution; - end if ~isempty(obj.spike_times) && ~isa(obj.spike_times, 'types.untyped.SoftLink') && ~isa(obj.spike_times, 'types.untyped.ExternalLink') && ~isempty(obj.spike_times_resolution) writer.writeAttribute([fullpath '/spike_times/resolution'], obj.spike_times_resolution); end if ~isempty(obj.waveform_mean) refs = obj.waveform_mean.export(writer, [fullpath '/waveform_mean'], refs); end - if isempty(obj.waveform_mean_sampling_rate) && ~isempty(obj.waveform_mean) && isobject(obj.waveform_mean) && isprop(obj.waveform_mean, 'sampling_rate') && ~isempty(obj.waveform_mean.sampling_rate) - obj.waveform_mean_sampling_rate = obj.waveform_mean.sampling_rate; - end if ~isempty(obj.waveform_mean) && ~isa(obj.waveform_mean, 'types.untyped.SoftLink') && ~isa(obj.waveform_mean, 'types.untyped.ExternalLink') && ~isempty(obj.waveform_mean_sampling_rate) writer.writeAttribute([fullpath '/waveform_mean/sampling_rate'], obj.waveform_mean_sampling_rate); end - if isempty(obj.waveform_mean_unit) && ~isempty(obj.waveform_mean) && isobject(obj.waveform_mean) && isprop(obj.waveform_mean, 'unit') && ~isempty(obj.waveform_mean.unit) - obj.waveform_mean_unit = obj.waveform_mean.unit; - end if ~isempty(obj.waveform_mean) && ~isa(obj.waveform_mean, 'types.untyped.SoftLink') && ~isa(obj.waveform_mean, 'types.untyped.ExternalLink') && ~isempty(obj.waveform_mean_unit) writer.writeAttribute([fullpath '/waveform_mean/unit'], obj.waveform_mean_unit); end if ~isempty(obj.waveform_sd) refs = obj.waveform_sd.export(writer, [fullpath '/waveform_sd'], refs); end - if isempty(obj.waveform_sd_sampling_rate) && ~isempty(obj.waveform_sd) && isobject(obj.waveform_sd) && isprop(obj.waveform_sd, 'sampling_rate') && ~isempty(obj.waveform_sd.sampling_rate) - obj.waveform_sd_sampling_rate = obj.waveform_sd.sampling_rate; - end if ~isempty(obj.waveform_sd) && ~isa(obj.waveform_sd, 'types.untyped.SoftLink') && ~isa(obj.waveform_sd, 'types.untyped.ExternalLink') && ~isempty(obj.waveform_sd_sampling_rate) writer.writeAttribute([fullpath '/waveform_sd/sampling_rate'], obj.waveform_sd_sampling_rate); end - if isempty(obj.waveform_sd_unit) && ~isempty(obj.waveform_sd) && isobject(obj.waveform_sd) && isprop(obj.waveform_sd, 'unit') && ~isempty(obj.waveform_sd.unit) - obj.waveform_sd_unit = obj.waveform_sd.unit; - end if ~isempty(obj.waveform_sd) && ~isa(obj.waveform_sd, 'types.untyped.SoftLink') && ~isa(obj.waveform_sd, 'types.untyped.ExternalLink') && ~isempty(obj.waveform_sd_unit) writer.writeAttribute([fullpath '/waveform_sd/unit'], obj.waveform_sd_unit); end @@ -430,15 +387,9 @@ function postset_waveforms_sampling_rate(obj) if ~isempty(obj.waveforms_index_index) refs = obj.waveforms_index_index.export(writer, [fullpath '/waveforms_index_index'], refs); end - if isempty(obj.waveforms_sampling_rate) && ~isempty(obj.waveforms) && isobject(obj.waveforms) && isprop(obj.waveforms, 'sampling_rate') && ~isempty(obj.waveforms.sampling_rate) - obj.waveforms_sampling_rate = obj.waveforms.sampling_rate; - end if ~isempty(obj.waveforms) && ~isa(obj.waveforms, 'types.untyped.SoftLink') && ~isa(obj.waveforms, 'types.untyped.ExternalLink') && ~isempty(obj.waveforms_sampling_rate) writer.writeAttribute([fullpath '/waveforms/sampling_rate'], obj.waveforms_sampling_rate); end - if isempty(obj.waveforms_unit) && ~isempty(obj.waveforms) && isobject(obj.waveforms) && isprop(obj.waveforms, 'unit') && ~isempty(obj.waveforms.unit) - obj.waveforms_unit = obj.waveforms.unit; - end if ~isempty(obj.waveforms) && ~isa(obj.waveforms, 'types.untyped.SoftLink') && ~isa(obj.waveforms, 'types.untyped.ExternalLink') && ~isempty(obj.waveforms_unit) writer.writeAttribute([fullpath '/waveforms/unit'], obj.waveforms_unit); end diff --git a/+types/+hdmf_common/DynamicTableRegion.m b/+types/+hdmf_common/DynamicTableRegion.m index 3c2127a9c..457276a1b 100644 --- a/+types/+hdmf_common/DynamicTableRegion.m +++ b/+types/+hdmf_common/DynamicTableRegion.m @@ -24,10 +24,6 @@ % % - description (char) - Description of what this table region points to. % - % - resolution (double) - NOTE: this is a special value for compatibility with the Units table and is only written to file when detected to be in that specific HDF5 Group. The smallest possible difference between two spike times. Usually 1 divided by the acquisition sampling rate from which spike times were extracted, but could be larger if the acquisition time series was downsampled or smaller if the acquisition time series was smoothed/interpolated and it is possible for the spike time to be between samples. - % - % - sampling_rate (single) - NOTE: this is a special value for compatibility with the Units table and is only written to file when detected to be in that specific HDF5 Group. Must be Hertz - % % - table (Object reference to DynamicTable) - Reference to the DynamicTable object that this region applies to. % % Output Arguments: diff --git a/+types/+hdmf_common/VectorData.m b/+types/+hdmf_common/VectorData.m index 30ab16381..c84f4307a 100644 --- a/+types/+hdmf_common/VectorData.m +++ b/+types/+hdmf_common/VectorData.m @@ -5,15 +5,6 @@ % data, description -% HIDDEN READONLY PROPERTIES -properties(Hidden, SetAccess = protected) - unit = "volts"; % (char) NOTE: this is a special value for compatibility with the Units table and is only written to file when detected to be in that specific HDF5 Group. The value must be 'volts' -end -% HIDDEN PROPERTIES -properties(Hidden) - resolution; % (double) NOTE: this is a special value for compatibility with the Units table and is only written to file when detected to be in that specific HDF5 Group. The smallest possible difference between two spike times. Usually 1 divided by the acquisition sampling rate from which spike times were extracted, but could be larger if the acquisition time series was downsampled or smaller if the acquisition time series was smoothed/interpolated and it is possible for the spike time to be between samples. - sampling_rate; % (single) NOTE: this is a special value for compatibility with the Units table and is only written to file when detected to be in that specific HDF5 Group. Must be Hertz -end % REQUIRED PROPERTIES properties description; % REQUIRED (char) Description of what these vectors represent. @@ -33,14 +24,9 @@ % % - description (char) - Description of what these vectors represent. % - % - resolution (double) - NOTE: this is a special value for compatibility with the Units table and is only written to file when detected to be in that specific HDF5 Group. The smallest possible difference between two spike times. Usually 1 divided by the acquisition sampling rate from which spike times were extracted, but could be larger if the acquisition time series was downsampled or smaller if the acquisition time series was smoothed/interpolated and it is possible for the spike time to be between samples. - % - % - sampling_rate (single) - NOTE: this is a special value for compatibility with the Units table and is only written to file when detected to be in that specific HDF5 Group. Must be Hertz - % % Output Arguments: % - vectorData (types.hdmf_common.VectorData) - A VectorData object - varargin = [{'unit' 'volts'} varargin]; obj = obj@types.hdmf_common.Data(varargin{:}); @@ -49,14 +35,8 @@ p.PartialMatching = false; p.StructExpand = false; addParameter(p, 'description',[]); - addParameter(p, 'resolution',[]); - addParameter(p, 'sampling_rate',[]); - addParameter(p, 'unit',[]); misc.parseSkipInvalidName(p, varargin); obj.description = p.Results.description; - obj.resolution = p.Results.resolution; - obj.sampling_rate = p.Results.sampling_rate; - obj.unit = p.Results.unit; % Only execute validation/setup code when called directly in this class's % constructor, not when invoked through superclass constructor chain @@ -69,12 +49,6 @@ function set.description(obj, val) obj.description = obj.validate_description(val); end - function set.resolution(obj, val) - obj.resolution = obj.validate_resolution(val); - end - function set.sampling_rate(obj, val) - obj.sampling_rate = obj.validate_sampling_rate(val); - end %% VALIDATORS function val = validate_data(obj, val) @@ -85,14 +59,6 @@ val = types.util.checkDtype('description', 'char', val); types.util.validateShape('description', {[1]}, val) end - function val = validate_resolution(obj, val) - val = types.util.checkDtype('resolution', 'double', val); - types.util.validateShape('resolution', {[1]}, val) - end - function val = validate_sampling_rate(obj, val) - val = types.util.checkDtype('sampling_rate', 'single', val); - types.util.validateShape('sampling_rate', {[1]}, val) - end %% EXPORT function refs = export(obj, writer, fullpath, refs) refs = export@types.hdmf_common.Data(obj, writer, fullpath, refs); @@ -100,17 +66,6 @@ return; end writer.writeAttribute([fullpath '/description'], obj.description); - if ~isempty(obj.resolution) && any(endsWith(fullpath, 'units/spike_times')) - writer.writeAttribute([fullpath '/resolution'], obj.resolution); - end - validDataSamplingPaths = strcat('units/', {'waveform_mean', 'waveform_sd', 'waveforms'}); - if ~isempty(obj.sampling_rate) && any(endsWith(fullpath, validDataSamplingPaths)) - writer.writeAttribute([fullpath '/sampling_rate'], obj.sampling_rate); - end - validUnitPaths = strcat('units/', {'waveform_mean', 'waveform_sd', 'waveforms'}); - if ~isempty(obj.unit) && any(endsWith(fullpath, validUnitPaths)) - writer.writeAttribute([fullpath '/unit'], obj.unit); - end end end diff --git a/+types/+hdmf_common/VectorIndex.m b/+types/+hdmf_common/VectorIndex.m index c26d7d2d8..c72782ab8 100644 --- a/+types/+hdmf_common/VectorIndex.m +++ b/+types/+hdmf_common/VectorIndex.m @@ -24,10 +24,6 @@ % % - description (char) - Description of what these vectors represent. % - % - resolution (double) - NOTE: this is a special value for compatibility with the Units table and is only written to file when detected to be in that specific HDF5 Group. The smallest possible difference between two spike times. Usually 1 divided by the acquisition sampling rate from which spike times were extracted, but could be larger if the acquisition time series was downsampled or smaller if the acquisition time series was smoothed/interpolated and it is possible for the spike time to be between samples. - % - % - sampling_rate (single) - NOTE: this is a special value for compatibility with the Units table and is only written to file when detected to be in that specific HDF5 Group. Must be Hertz - % % - target (Object reference to VectorData) - Reference to the target dataset that this index applies to. % % Output Arguments: diff --git a/tutorials/ecephys.mlx b/tutorials/ecephys.mlx index f111adfdc..526df08b3 100644 Binary files a/tutorials/ecephys.mlx and b/tutorials/ecephys.mlx differ diff --git a/tutorials/private/mcode/ecephys.m b/tutorials/private/mcode/ecephys.m index 5879aeb67..a7b87c29b 100644 --- a/tutorials/private/mcode/ecephys.m +++ b/tutorials/private/mcode/ecephys.m @@ -306,13 +306,14 @@ % [spike_times_vector, spike_times_index] = util.create_indexed_column(spike_times); -spike_times_vector.resolution = 1/20000; % If original sampling rate was 20 kHz +spike_times_resolution = 1/20000; % If original sampling rate was 20 kHz nwb.units = types.core.Units( ... 'colnames', {'spike_times'}, ... 'description', 'units table', ... 'spike_times', spike_times_vector, ... - 'spike_times_index', spike_times_index ... + 'spike_times_index', spike_times_index, ... + 'spike_times_resolution', spike_times_resolution ... ); nwb.units.toTable()