Skip to content
16 changes: 11 additions & 5 deletions +file/fillClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
superclassNames{end+1} = 'types.untyped.DatasetClass';
end

if isa(class, 'file.Group') && class.hasAnonGroups
if isa(class, 'file.Group') && (class.hasAnonGroups || class.hasAnonData)
superclassNames{end+1} = 'matnwb.mixin.HasUnnamedGroups';
end

Expand Down Expand Up @@ -147,7 +147,7 @@
, propertyDefinitionBody ...
}, newline);
end
if isa(class, 'file.Group') && class.hasAnonGroups
if isa(class, 'file.Group') && (class.hasAnonGroups || class.hasAnonData)
mixinPropertyBlock = createPropertyBlockForHasUnnamedGroupMixin(class);

fullPropertyDefinition = strjoin(...
Expand Down Expand Up @@ -182,6 +182,7 @@

fullMethodBody = strjoin({'methods' ...
file.addSpaces(methodBody, 4) 'end'}, newline);

template = strjoin({classDefinitionHeader fullPropertyDefinition fullMethodBody 'end'}, ...
[newline newline]);
end
Expand All @@ -201,10 +202,15 @@

function propertyBlockStr = createPropertyBlockForHasUnnamedGroupMixin(classInfo)
isAnonGroup = arrayfun(@(x) isempty(x.name), classInfo.subgroups, 'uni', true);
anonNames = arrayfun(@(x) lower(x.type), classInfo.subgroups(isAnonGroup), 'uni', false);

isAnonDataset = arrayfun(@(x) isempty(x.name), classInfo.datasets, 'uni', true);

anonNames = [...
arrayfun(@(x) lower(x.type), classInfo.subgroups(isAnonGroup), 'uni', false), ...
arrayfun(@(x) lower(x.type), classInfo.datasets(isAnonDataset), 'uni', false), ...
];

propertyBlockStr = strjoin({...
'properties (Access = protected)', ...
'properties (Constant, Access = private)', ...
sprintf(' GroupPropertyNames = {%s}', strjoin(strcat('''', anonNames, ''''), ', ') ), ...
'end'}, newline);
end
2 changes: 1 addition & 1 deletion +file/fillConstructor.m
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
fullBody = strjoin(fullBody, newline);
bodystr(end+1:end+length(fullBody)+1) = [newline fullBody];

if isa(class, 'file.Group') && class.hasAnonGroups
if isa(class, 'file.Group') && (class.hasAnonGroups || class.hasAnonData)
% Include the setup function for the HasUnnamedGroups mixin
bodystr = [bodystr, newline, 'obj.setupHasUnnamedGroupsMixin()', newline];
end
Expand Down
71 changes: 70 additions & 1 deletion +matnwb/+mixin/HasUnnamedGroups.m
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,14 @@
% are no schemas in NWB where Anon sets are used, and this class therefore
% does not support contained Anon sets.

properties (Abstract, Access = protected, Transient)
properties (Access = private, Dependent, Transient)
% GroupPropertyNames - String array of property names that contain Sets
GroupPropertyNames (1,:) string
end

properties (Access = private)
% GroupPropertyNames_ - Cached value backing the dependent property GroupPropertyNames
GroupPropertyNames_ (1,:) string
end

properties (Access = private, Transient)
Expand Down Expand Up @@ -153,6 +158,17 @@ function setupHasUnnamedGroupsMixin(obj)
obj.assignContainedSetCallbackFunctions()
end
end

methods
function value = get.GroupPropertyNames(obj)
if isempty(obj.GroupPropertyNames_)
className = class(obj);
obj.GroupPropertyNames_ = obj.getGroupPropertyNamesAcrossTypeHierarchy(className);
end
value = obj.GroupPropertyNames_;
end
end

methods (Access = private)
function initializeDynamicProperties(obj)
% initializeDynamicProperties - Init dynamic properties from set entries
Expand Down Expand Up @@ -515,6 +531,59 @@ function displayAliasWarning(obj)
end
end
end

methods (Static, Access = protected)
function groupPropertyNames = getGroupPropertyNamesAcrossTypeHierarchy(nwbTypeName)
% getGroupPropertyNamesAcrossTypeHierarchy - Retrieve property names of unnamed groups for a specific NWB type
%
% Syntax:
% groupTypes = getGroupPropertyNamesAcrossTypeHierarchy(nwbTypeName)
% This function retrieves property names of unnamed groups associated with
% the specified NWB type name, traversing the class hierarchy to also include
% property names of unnamed groups for parent types.
%
% Input Arguments:
% nwbTypeName (1,1) string - The name of the NWB type for which property
% names of unnamed groups are to be retrieved.
%
% Output Arguments:
% groupPropertyNames - An array of property names of unnamed groups
% associated with the specified NWB type.

arguments
nwbTypeName (1,1) string
end

groupPropertyNames = string.empty; % Initialize an empty cell array
currentType = nwbTypeName; % Start with the specific type

% Iterate over class and superclasses to detect property names for
% unnamed groups across the type hierarchy.
while ~strcmp(currentType, 'types.untyped.MetaClass')

% Use MetaClass information to get class information
metaClass = meta.class.fromName(currentType);

% Get value of GroupPropertyNames if this class is a subclass of
% the HasUnnamedGroups subclass.
if any(strcmp({metaClass.SuperclassList.Name}, 'matnwb.mixin.HasUnnamedGroups'))
isProp = strcmp({metaClass.PropertyList.Name}, 'GroupPropertyNames');
if any(isProp)
groupPropertyNames = [groupPropertyNames, ...
string(metaClass.PropertyList(isProp).DefaultValue)]; %#ok<AGROW>
end
end

if isempty(metaClass.SuperclassList)
break; % Reached the base type
end

% Get superclass for next iteration. NWB parent type should
% always be the first superclass in the list
currentType = metaClass.SuperclassList(1).Name;
end
end
end
end

function ME = getNameExistsException(name, typeName)
Expand Down
2 changes: 1 addition & 1 deletion +types/+core/BehavioralEpochs.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
intervalseries; % REQUIRED (IntervalSeries) IntervalSeries object containing start and stop times of epochs.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'intervalseries'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/BehavioralEvents.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
timeseries; % REQUIRED (TimeSeries) TimeSeries object containing behavioral events.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'timeseries'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/BehavioralTimeSeries.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
timeseries; % REQUIRED (TimeSeries) TimeSeries object containing continuous behavioral data.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'timeseries'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/CompassDirection.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
spatialseries; % REQUIRED (SpatialSeries) SpatialSeries object containing direction of gaze travel.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'spatialseries'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/DfOverF.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
roiresponseseries; % REQUIRED (RoiResponseSeries) RoiResponseSeries object(s) containing dF/F for a ROI.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'roiresponseseries'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/EventWaveform.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
spikeeventseries; % REQUIRED (SpikeEventSeries) SpikeEventSeries object(s) containing detected spike event waveforms.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'spikeeventseries'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/EyeTracking.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
spatialseries; % REQUIRED (SpatialSeries) SpatialSeries object containing data measuring direction of gaze.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'spatialseries'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/FilteredEphys.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
electricalseries; % REQUIRED (ElectricalSeries) ElectricalSeries object(s) containing filtered electrophysiology data.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'electricalseries'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/Fluorescence.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
roiresponseseries; % REQUIRED (RoiResponseSeries) RoiResponseSeries object(s) containing fluorescence data for a ROI.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'roiresponseseries'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/ImageSegmentation.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
planesegmentation; % REQUIRED (PlaneSegmentation) Results from image segmentation of a specific imaging plane.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'planesegmentation'}
end

Expand Down
7 changes: 6 additions & 1 deletion +types/+core/Images.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
classdef Images < types.core.NWBDataInterface & types.untyped.GroupClass
classdef Images < types.core.NWBDataInterface & types.untyped.GroupClass & matnwb.mixin.HasUnnamedGroups
% IMAGES - A collection of images with an optional way to specify the order of the images using the "order_of_images" dataset. An order must be specified if the images are referenced by index, e.g., from an IndexSeries.
%
% Required Properties:
Expand All @@ -14,6 +14,9 @@
properties
order_of_images; % (ImageReferences) Ordered dataset of references to BaseImage objects stored in the parent group. Each object in the Images group should be stored once and only once, so the dataset should have the same length as the number of images.
end
properties (Constant, Access = private)
GroupPropertyNames = {'baseimage'}
end

methods
function obj = Images(varargin)
Expand All @@ -38,6 +41,8 @@
[obj.baseimage, ivarargin] = types.util.parseConstrained(obj,'baseimage', 'types.core.BaseImage', varargin{:});
varargin(ivarargin) = [];

obj.setupHasUnnamedGroupsMixin()

p = inputParser;
p.KeepUnmatched = true;
p.PartialMatching = false;
Expand Down
2 changes: 1 addition & 1 deletion +types/+core/ImagingPlane.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
origin_coords_unit = "meters"; % (char) Measurement units for origin_coords. The default value is 'meters'.
reference_frame; % (char) Describes reference frame of origin_coords and grid_spacing. For example, this can be a text description of the anatomical location and orientation of the grid defined by origin_coords and grid_spacing or the vectors needed to transform or rotate the grid to a common anatomical axis (e.g., AP/DV/ML). This field is necessary to interpret origin_coords and grid_spacing. If origin_coords and grid_spacing are not present, then this field is not required. For example, if the microscope takes 10 x 10 x 2 images, where the first value of the data matrix (index (0, 0, 0)) corresponds to (-1.2, -0.6, -2) mm relative to bregma, the spacing between pixels is 0.2 mm in x, 0.2 mm in y and 0.5 mm in z, and larger numbers in x means more anterior, larger numbers in y means more rightward, and larger numbers in z means more ventral, then enter the following -- origin_coords = (-1.2, -0.6, -2) grid_spacing = (0.2, 0.2, 0.5) reference_frame = "Origin coordinates are relative to bregma. First dimension corresponds to anterior-posterior axis (larger index = more anterior). Second dimension corresponds to medial-lateral axis (larger index = more rightward). Third dimension corresponds to dorsal-ventral axis (larger index = more ventral)."
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'opticalchannel'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/LFP.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
electricalseries; % REQUIRED (ElectricalSeries) ElectricalSeries object(s) containing LFP data for one or more channels.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'electricalseries'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/MotionCorrection.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
correctedimagestack; % REQUIRED (CorrectedImageStack) Results from motion correction of an image stack.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'correctedimagestack'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/Position.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
spatialseries; % REQUIRED (SpatialSeries) SpatialSeries object containing position data.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'spatialseries'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/ProcessingModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
dynamictable; % (DynamicTable) Tables stored in this collection.
nwbdatainterface; % (NWBDataInterface) Data objects stored in this collection.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'nwbdatainterface', 'dynamictable'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+core/PupilTracking.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
properties
timeseries; % REQUIRED (TimeSeries) TimeSeries object containing time series data on pupil size.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'timeseries'}
end

Expand Down
2 changes: 1 addition & 1 deletion +types/+hdmf_common/AlignedDynamicTable.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
properties
dynamictable; % (DynamicTable) A DynamicTable representing a particular category for columns in the AlignedDynamicTable parent container. The table MUST be aligned with (i.e., have the same number of rows) as all other DynamicTables stored in the AlignedDynamicTable parent container. The name of the category is given by the name of the DynamicTable and its description by the description attribute of the DynamicTable.
end
properties (Access = protected)
properties (Constant, Access = private)
GroupPropertyNames = {'dynamictable'}
end

Expand Down
7 changes: 6 additions & 1 deletion +types/+hdmf_common/DynamicTable.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
classdef DynamicTable < types.hdmf_common.Container & types.untyped.GroupClass
classdef DynamicTable < types.hdmf_common.Container & types.untyped.GroupClass & matnwb.mixin.HasUnnamedGroups
% DYNAMICTABLE - A group containing multiple datasets that are aligned on the first dimension (Currently, this requirement if left up to APIs to check and enforce). These datasets represent different columns in the table. Apart from a column that contains unique identifiers for each row, there are no other required datasets. Users are free to add any number of custom VectorData objects (columns) here. DynamicTable also supports ragged array columns, where each element can be of a different size. To add a ragged array column, use a VectorIndex type to index the corresponding VectorData type. See documentation for VectorData and VectorIndex for more details. Unlike a compound data type, which is analogous to storing an array-of-structs, a DynamicTable can be thought of as a struct-of-arrays. This provides an alternative structure to choose from when optimizing storage for anticipated access patterns. Additionally, this type provides a way of creating a table without having to define a compound type up front. Although this convenience may be attractive, users should think carefully about how data will be accessed. DynamicTable is more appropriate for column-centric access, whereas a dataset with a compound type would be more appropriate for row-centric access. Finally, data size should also be taken into account. For small tables, performance loss may be an acceptable trade-off for the flexibility of a DynamicTable.
%
% Required Properties:
Expand All @@ -15,6 +15,9 @@
properties
vectordata; % (VectorData) Vector columns, including index columns, of this dynamic table.
end
properties (Constant, Access = private)
GroupPropertyNames = {'vectordata'}
end

methods
function obj = DynamicTable(varargin)
Expand All @@ -41,6 +44,8 @@
[obj.vectordata, ivarargin] = types.util.parseConstrained(obj,'vectordata', 'types.hdmf_common.VectorData', varargin{:});
varargin(ivarargin) = [];

obj.setupHasUnnamedGroupsMixin()

p = inputParser;
p.KeepUnmatched = true;
p.PartialMatching = false;
Expand Down
4 changes: 2 additions & 2 deletions +types/+hdmf_common/SimpleMultiContainer.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
container; % (Container) Container objects held within this SimpleMultiContainer.
data; % (Data) Data objects held within this SimpleMultiContainer.
end
properties (Access = protected)
GroupPropertyNames = {'container'}
properties (Constant, Access = private)
GroupPropertyNames = {'container', 'data'}
end

methods
Expand Down
19 changes: 18 additions & 1 deletion +types/+untyped/Anon.m
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,21 @@
tf = strcmp(obj.name, name);
end
end
end

% Methods mirroring Set methods.
methods
function name = getPropertyName(obj, name)
% getPropertyName - Get property name given the actual name of an entry
assert(strcmp(obj.name, name), ...
'NWB:Anon:InvalidName', ...
'name `%s` is not part of Anon', name);
end

function value = get(obj, name)
assert(strcmp(obj.name, name), ...
'NWB:Anon:InvalidName', ...
'name `%s` is not part of Anon', name);
value = obj.value;
end
end
end
Loading