Skip to content

Commit 4d43731

Browse files
authored
Merge branch 'main' into add-mixin-for-type-with-group-properties
2 parents b117d12 + f73c347 commit 4d43731

File tree

15 files changed

+270
-147
lines changed

15 files changed

+270
-147
lines changed

+file/fillValidators.m

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@
168168
{['types.util.checkSet(''' name ''', struct(), constrained, val);']} ...
169169
];
170170
else
171-
if ~isempty(prop.type)
171+
if ~isempty(prop.type) % Dataset class like e.g. VectorData
172172
fullname = getFullClassName(namespaceReg, prop.type, name);
173173
if isempty(fullname)
174174
return
@@ -250,13 +250,10 @@
250250
if isstruct(type)
251251
fnames = fieldnames(type);
252252
fdvstr = strjoin({...
253-
'if isempty(val) || isa(val, ''types.untyped.DataStub'')'...
254-
' return;'...
255-
'end'...
256-
'if ~istable(val) && ~isstruct(val) && ~isa(val, ''containers.Map'')'...
257-
[' error(''NWB:Type:InvalidPropertyType'', ''Property `' name '` must be a table, struct, or containers.Map.'');']...
258-
'end'...
259-
'vprops = struct();'...
253+
'if isempty(val)'...
254+
' % skip validation for empty values'...
255+
'else'...
256+
' vprops = struct();'...
260257
}, newline);
261258
vprops = cell(length(fnames),1);
262259
for i=1:length(fnames)
@@ -266,10 +263,11 @@
266263
else
267264
typeval = type.(nm);
268265
end
269-
vprops{i} = ['vprops.' nm ' = ''' typeval ''';'];
266+
vprops{i} = [' vprops.' nm ' = ''' typeval ''';'];
270267
end
271268
fdvstr = [fdvstr, newline, strjoin(vprops, newline), newline, ...
272-
'val = types.util.checkDtype(''' name ''', vprops, val);'];
269+
' val = types.util.checkDtype(''' name ''', vprops, val);' ...
270+
newline, 'end'];
273271
elseif isReferenceType(type)
274272
fdvstr = fillReferenceTypeValidation(name, type, namespaceReg);
275273
else

+io/getNeurodataTypeInfo.m

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
function typeInfo = getNeurodataTypeInfo(attributeInfo)
2+
% getNeurodataTypeInfo - Get neurodata type info from attribute info structure
3+
%
4+
% Syntax:
5+
% typeInfo = io.getNeurodataTypeInfo(attributeInfo)
6+
%
7+
% Input Arguments:
8+
% attributeInfo - A struct containing attributes information from which to
9+
% extract neurodata type information.
10+
%
11+
% Output Arguments:
12+
% typeInfo - A struct containing 'namespace', 'name', and 'typename'
13+
% fields describing the neurodata type. Fields are empty
14+
% character vectors if neurodata type info is not present.
15+
16+
% Todo: return empty structure instead. Requires updating functions that use output
17+
typeInfo = struct('namespace', '', 'name', '', 'typename', '');
18+
19+
if isempty(attributeInfo)
20+
return
21+
end
22+
23+
names = {attributeInfo.Name};
24+
25+
% Get neurodata_type if present
26+
typeDefMask = strcmp(names, 'neurodata_type');
27+
hasTypeDef = any(typeDefMask);
28+
if hasTypeDef
29+
typeDef = attributeInfo(typeDefMask).Value;
30+
if iscellstr(typeDef) %#ok<ISCLSTR>
31+
typeDef = typeDef{1};
32+
end
33+
typeInfo.name = typeDef;
34+
end
35+
36+
% Get namespace if present
37+
namespaceMask = strcmp(names, 'namespace');
38+
hasNamespace = any(namespaceMask);
39+
if hasNamespace
40+
namespace = attributeInfo(namespaceMask).Value;
41+
if iscellstr(namespace) %#ok<ISCLSTR>
42+
namespace = namespace{1};
43+
end
44+
typeInfo.namespace = namespace;
45+
end
46+
47+
% Get full classname given a namespace and a neurodata type
48+
if hasTypeDef && hasNamespace
49+
typeInfo.typename = matnwb.common.composeFullClassName(...
50+
typeInfo.namespace, typeInfo.name);
51+
end
52+
end

+io/parseAttributes.m

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,39 @@
11
function [args, type] = parseAttributes(filename, attributes, context, Blacklist)
2-
%typename is the type of name if it exists. Empty string otherwise
3-
%args is a containers.Map of all valid attributes
2+
% parseAttributes - Parse an attribute info structure
3+
%
4+
% Syntax:
5+
% [args, type] = io.parseAttributes(filename, attributes, context, Blacklist)
6+
% This function parses a given attribute info structure and returns a
7+
% containers.Map of valid attributes along with neurodata type info if it
8+
% exists.
9+
%
10+
% Input Arguments:
11+
% filename - The name of the file containing attributes.
12+
% attributes - The attributes to be parsed.
13+
% context - The context (h5 location) in which the attributes are located.
14+
% Blacklist - A list of attributes to be excluded from the parsing.
15+
%
16+
% Output Arguments:
17+
% args - A containers.Map of all valid attributes.
18+
% type - A structure with type information (see io.getNeurodataTypeInfo)
19+
%
20+
% See also: io.getNeurodataTypeInfo
21+
422
args = containers.Map;
5-
type = struct('namespace', '', 'name', '', 'typename', '');
23+
type = io.getNeurodataTypeInfo(attributes);
24+
625
if isempty(attributes)
726
return;
827
end
9-
names = {attributes.Name};
10-
11-
typeDefMask = strcmp(names, 'neurodata_type');
12-
hasTypeDef = any(typeDefMask);
13-
if hasTypeDef
14-
typeDef = attributes(typeDefMask).Value;
15-
if iscellstr(typeDef)
16-
typeDef = typeDef{1};
17-
end
18-
type.name = typeDef;
19-
end
2028

21-
namespaceMask = strcmp(names, 'namespace');
22-
hasNamespace = any(namespaceMask);
23-
if hasNamespace
24-
namespace = attributes(namespaceMask).Value;
25-
if iscellstr(namespace)
26-
namespace = namespace{1};
27-
end
28-
type.namespace = namespace;
29-
end
29+
names = {attributes.Name};
3030

31-
if hasTypeDef && hasNamespace
32-
validNamespace = misc.str2validName(type.namespace);
33-
validName = misc.str2validName(type.name);
34-
type.typename = ['types.' validNamespace '.' validName];
35-
end
31+
% We already got type information (if present), so we add type-specific
32+
% attributes to the blacklist before parsing the rest of the attribute list
33+
Blacklist.attributes = [Blacklist.attributes, {'neurodata_type', 'namespace'}];
3634

3735
blacklistMask = ismember(names, Blacklist.attributes);
38-
deleteMask = typeDefMask | namespaceMask | blacklistMask;
39-
attributes(deleteMask) = [];
36+
attributes(blacklistMask) = [];
4037
for i=1:length(attributes)
4138
attr = attributes(i);
4239

+io/parseGroup.m

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,9 @@
5353
S = h5info(filename, link.Value{1});
5454
% Suggested improvement: Use info structure if Link is located in the group (or
5555
% subgroup) which is currently being parsed.
56-
if ismember('neurodata_type', {S.Attributes.Name})
57-
namespace = h5readatt(filename, link.Value{1}, 'namespace');
58-
neurodataType = h5readatt(filename, link.Value{1}, 'neurodata_type');
59-
fullTargetTypeName = matnwb.common.composeFullClassName(namespace, neurodataType);
60-
else
61-
fullTargetTypeName = '';
62-
end
56+
57+
typeInfo = io.getNeurodataTypeInfo(S.Attributes);
58+
fullTargetTypeName = typeInfo.typename;
6359

6460
lnk = types.untyped.SoftLink(link.Value{1}, fullTargetTypeName);
6561
case 'external link'

+matnwb/+common/composeFullClassName.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
namespaceName = strrep(namespaceName, '-', '_');
1010
end
1111

12+
for i = 1:numel(namespaceName)
13+
namespaceName{i} = misc.str2validName(namespaceName{i});
14+
neurodataType{i} = misc.str2validName(neurodataType{i});
15+
end
16+
1217
fullClassName = compose("types.%s.%s", namespaceName, neurodataType);
1318
fullClassName = transpose(fullClassName); % Return as row vector
1419
end

+tests/+unit/+schema/DataTypesTest.m

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,8 @@ function testRoundtripValidation(testCase)
321321
testCase.verifyEqual(incContainerIn.included_data_must_be_text.data.load(), '1');
322322
testCase.verifyEqual(incContainerIn.included_data_must_be_integer.data.load(), int32(1));
323323
testCase.verifyEqual(incContainerIn.included_data_must_be_float.data.load(), single(1));
324-
testCase.verifyClass(incContainerIn.included_data_must_be_compound.load(), 'struct');
324+
testCase.verifyClass(incContainerIn.included_data_must_be_compound, 'types.dt.AnyData'); % Sanity check
325+
testCase.verifyClass(incContainerIn.included_data_must_be_compound.data.load(), 'struct');
325326

326327
testCase.verifyEqual(inheritanceContainerIn.any_data.data.load(), uint8(1));
327328
testCase.verifyEqual(inheritanceContainerIn.text_data.data.load(), '1');

+types/+core/ElectrodeGroup.m

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,15 @@
8585
types.util.validateShape('location', {[1]}, val)
8686
end
8787
function val = validate_position(obj, val)
88-
if isempty(val) || isa(val, 'types.untyped.DataStub')
89-
return;
90-
end
91-
if ~istable(val) && ~isstruct(val) && ~isa(val, 'containers.Map')
92-
error('NWB:Type:InvalidPropertyType', 'Property `position` must be a table, struct, or containers.Map.');
88+
if isempty(val)
89+
% skip validation for empty values
90+
else
91+
vprops = struct();
92+
vprops.x = 'single';
93+
vprops.y = 'single';
94+
vprops.z = 'single';
95+
val = types.util.checkDtype('position', vprops, val);
9396
end
94-
vprops = struct();
95-
vprops.x = 'single';
96-
vprops.y = 'single';
97-
vprops.z = 'single';
98-
val = types.util.checkDtype('position', vprops, val);
9997
types.util.validateShape('position', {[1]}, val)
10098
end
10199
%% EXPORT

+types/+core/PlaneSegmentation.m

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,15 @@
123123
types.util.checkType('pixel_mask', 'types.hdmf_common.VectorData', val);
124124
if ~isempty(val)
125125
[val, originalVal] = types.util.unwrapValue(val);
126-
if isempty(val) || isa(val, 'types.untyped.DataStub')
127-
return;
126+
if isempty(val)
127+
% skip validation for empty values
128+
else
129+
vprops = struct();
130+
vprops.x = 'uint32';
131+
vprops.y = 'uint32';
132+
vprops.weight = 'single';
133+
val = types.util.checkDtype('pixel_mask', vprops, val);
128134
end
129-
if ~istable(val) && ~isstruct(val) && ~isa(val, 'containers.Map')
130-
error('NWB:Type:InvalidPropertyType', 'Property `pixel_mask` must be a table, struct, or containers.Map.');
131-
end
132-
vprops = struct();
133-
vprops.x = 'uint32';
134-
vprops.y = 'uint32';
135-
vprops.weight = 'single';
136-
val = types.util.checkDtype('pixel_mask', vprops, val);
137135
val = types.util.rewrapValue(val, originalVal);
138136
end
139137
end
@@ -149,18 +147,16 @@
149147
types.util.checkType('voxel_mask', 'types.hdmf_common.VectorData', val);
150148
if ~isempty(val)
151149
[val, originalVal] = types.util.unwrapValue(val);
152-
if isempty(val) || isa(val, 'types.untyped.DataStub')
153-
return;
154-
end
155-
if ~istable(val) && ~isstruct(val) && ~isa(val, 'containers.Map')
156-
error('NWB:Type:InvalidPropertyType', 'Property `voxel_mask` must be a table, struct, or containers.Map.');
150+
if isempty(val)
151+
% skip validation for empty values
152+
else
153+
vprops = struct();
154+
vprops.x = 'uint32';
155+
vprops.y = 'uint32';
156+
vprops.z = 'uint32';
157+
vprops.weight = 'single';
158+
val = types.util.checkDtype('voxel_mask', vprops, val);
157159
end
158-
vprops = struct();
159-
vprops.x = 'uint32';
160-
vprops.y = 'uint32';
161-
vprops.z = 'uint32';
162-
vprops.weight = 'single';
163-
val = types.util.checkDtype('voxel_mask', vprops, val);
164160
val = types.util.rewrapValue(val, originalVal);
165161
end
166162
end

+types/+core/ScratchData.m

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
%% VALIDATORS
5050

5151
function val = validate_data(obj, val)
52-
5352
end
5453
function val = validate_notes(obj, val)
5554
val = types.util.checkDtype('notes', 'char', val);

+types/+core/TimeSeriesReferenceVectorData.m

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,15 @@
4141
%% VALIDATORS
4242

4343
function val = validate_data(obj, val)
44-
if isempty(val) || isa(val, 'types.untyped.DataStub')
45-
return;
46-
end
47-
if ~istable(val) && ~isstruct(val) && ~isa(val, 'containers.Map')
48-
error('NWB:Type:InvalidPropertyType', 'Property `data` must be a table, struct, or containers.Map.');
44+
if isempty(val)
45+
% skip validation for empty values
46+
else
47+
vprops = struct();
48+
vprops.idx_start = 'int32';
49+
vprops.count = 'int32';
50+
vprops.timeseries = 'types.untyped.ObjectView';
51+
val = types.util.checkDtype('data', vprops, val);
4952
end
50-
vprops = struct();
51-
vprops.idx_start = 'int32';
52-
vprops.count = 'int32';
53-
vprops.timeseries = 'types.untyped.ObjectView';
54-
val = types.util.checkDtype('data', vprops, val);
5553
types.util.validateShape('data', {[Inf,Inf,Inf,Inf], [Inf,Inf,Inf], [Inf,Inf], [Inf]}, val)
5654
end
5755
%% EXPORT

0 commit comments

Comments
 (0)