Skip to content

Commit 2c1b738

Browse files
Added getTypeObjects method (#745)
* Added getTypeObjects method * Fix behavior of getTypeObjects when search for subtypes - Added properly formatted examples in docstring - Refactor code and fix bugs for getting type including subtypes * Update searchTest.m Added tests for new getTypeObject methods and a test comparing its behavior to searchFor * Properly format method docstring for readthedocs * Update NwbFile.m Remove unused function Changed order of local functions * Update NwbFile.m Co-authored-by: Ben Dichter <[email protected]> * Fixed method to also resolve types from specific namespaces * Fix failing test --------- Co-authored-by: Ben Dichter <[email protected]>
1 parent ba9f0c5 commit 2c1b738

File tree

4 files changed

+221
-19
lines changed

4 files changed

+221
-19
lines changed

+tests/+unit/searchTest.m

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,88 @@ function testSearch(testCase)
2424
% use includeSubClasses keyword
2525
testCase.assertLength(nwb.searchFor('types.core.TimeSeries', 'includeSubClasses'), 2);
2626
end
27+
28+
function testSearchWithoutTypeNamespace(testCase)
29+
nwb = NwbFile();
30+
nwb.acquisition.set('ts1', types.core.TimeSeries());
31+
nwb.acquisition.set('ts2', types.core.BehavioralTimeSeries());
32+
33+
testCase.assertLength(nwb.searchFor('TimeSeries'), 2);
34+
end
35+
36+
function testGetType(testCase)
37+
% Test that getTypeObjects method does exact type match,
38+
% contrary to searchFor
39+
nwb = NwbFile();
40+
nwb.acquisition.set('ts1', types.core.TimeSeries());
41+
nwb.acquisition.set('ts2', types.core.BehavioralTimeSeries());
42+
testCase.assertLength(nwb.getTypeObjects('TimeSeries'), 1);
43+
end
44+
45+
function testGetTypeWithSubclasses(testCase)
46+
% Test that getTypeObjects method does exact type match and
47+
% includes subclasses
48+
nwb = NwbFile();
49+
nwb.acquisition.set('ts1', types.core.TimeSeries());
50+
nwb.acquisition.set('ts2', types.core.PatchClampSeries());
51+
nwb.acquisition.set('ts3', types.core.BehavioralTimeSeries());
52+
53+
testCase.verifyLength(...
54+
nwb.getTypeObjects('TimeSeries', 'IncludeSubTypes', true), 2);
55+
end
56+
57+
function testGetTypeVsSearchFor(testCase)
58+
% Test that getTypeObjects method does exact type match,
59+
% contrary to searchFor
60+
nwb = NwbFile();
61+
nwb.acquisition.set('im1', types.core.Images());
62+
nwb.acquisition.set('im2', types.core.ImageSeries());
63+
nwb.acquisition.set('im3', types.core.OpticalSeries());
64+
65+
% GetTypeObjects should only match objects of "Images" type
66+
testCase.verifyLength(...
67+
nwb.getTypeObjects('Images'), 1);
68+
69+
% searchFor should find both objects of "Images" and "ImageSeries" type
70+
testCase.verifyLength(...
71+
nwb.searchFor('Images'), 2);
72+
73+
% GetTypeObjects with subclasses should only match objects of "Images" type
74+
testCase.verifyLength(...
75+
nwb.getTypeObjects('Images', 'IncludeSubTypes', true), 1);
76+
77+
% searchFor with subclasses will also match OpticalSeries as it
78+
% is a subclass of ImageSeries.
79+
testCase.verifyLength(...
80+
nwb.searchFor('Images', 'includeSubClasses'), 3);
81+
end
82+
83+
function testSameNameDifferentNamespace(testCase)
84+
85+
schemaRootDirectory = fullfile(misc.getMatnwbDir(), '+tests', 'test-schema');
86+
87+
import tests.fixtures.ExtensionGenerationFixture
88+
89+
F = testCase.getSharedTestFixtures();
90+
isMatch = arrayfun(@(x) isa(x, 'tests.fixtures.GenerateCoreFixture'), F);
91+
F = F(isMatch);
92+
93+
typesOutputFolder = F.TypesOutputFolder;
94+
95+
namespaceFilePath = fullfile( ...
96+
schemaRootDirectory, 'dupliNameSchema', 'dn.namespace.yaml');
97+
98+
testCase.applyFixture( ...
99+
ExtensionGenerationFixture(namespaceFilePath, typesOutputFolder) )
100+
101+
nwb = NwbFile();
102+
nwb.acquisition.set('im1', types.core.Images());
103+
nwb.acquisition.set('im2', types.dn.Images());
104+
105+
testCase.verifyLength( nwb.getTypeObjects('Images'), 2 );
106+
107+
testCase.verifyLength( nwb.getTypeObjects('types.core.Images'), 1 );
108+
testCase.verifyLength( nwb.getTypeObjects('types.dn.Images'), 1 );
109+
end
27110
end
28111
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
groups:
2+
- neurodata_type_def: Images
3+
neurodata_type_inc: NWBDataInterface
4+
default_name: Images
5+
doc: A type with same name as Images in core namespace
6+
attributes:
7+
- name: description
8+
dtype: text
9+
doc: A description
10+
datasets:
11+
- neurodata_type_inc: BaseImage
12+
doc: Images stored in this collection.
13+
quantity: '+'
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespaces:
2+
- full_name: Duplicate Type Names Schema Test
3+
name: dn
4+
schema:
5+
- namespace: core
6+
- source: dn.dupliname.yaml
7+
version: 1.0.0
8+

NwbFile.m

Lines changed: 117 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,69 @@ function export(obj, filename, mode)
119119
typename,...
120120
varargin{:});
121121
end
122+
123+
function nwbObjects = getTypeObjects(obj, typeName, options)
124+
% GETTYPEOBJECTS - Retrieve NWB objects of a specified type.
125+
%
126+
% Syntax:
127+
% nwbObjects = GETTYPEOBJECTS(obj, typeName) Retrieves NWB
128+
% objects of the specified type from the NwbFile object.
129+
%
130+
% nwbObjects = GETTYPEOBJECTS(obj, typeName, Name, Value) Retrieves NWB
131+
% objects of the specified type from the NwbFile object using provided
132+
% name-value pairs controlling options.
133+
%
134+
% Input Arguments:
135+
% - obj (NwbFile) -
136+
% The NwbFile object from which to retrieve NWB objects.
137+
%
138+
% - typeName (1,1) string -
139+
% The name of the type to search for. Can include namespace, but
140+
% does not have to, i.e types.core.TimeSeries and TimeSeries are
141+
% supported.
142+
%
143+
% - options (name-value pairs) -
144+
% Optional name-value pairs. Available options:
145+
%
146+
% - IncludeSubTypes logical -
147+
% Optional: set to true to include subclasses in the search.
148+
% Default is false.
149+
%
150+
% Output Arguments:
151+
% - nwbObjects (cell) -
152+
% A cell array of NWB objects of the specified type.
153+
%
154+
% Usage:
155+
% Example 1 - Get all ElectricalSeries objects from NwbFile::
156+
%
157+
% evalc('run("ecephys.mlx")');
158+
% nwb.getTypeObjects('ElectricalSeries')
159+
%
160+
% Example 2 - Get all ElectricalSeries and subtype objects from NwbFile::
161+
%
162+
% evalc('run("ecephys.mlx")')
163+
% nwb.getTypeObjects('ElectricalSeries', 'IncludeSubTypes', true)
164+
165+
arguments
166+
obj
167+
typeName (1,1) string
168+
options.IncludeSubTypes (1,1) logical = false
169+
end
170+
flags = {};
171+
if options.IncludeSubTypes
172+
flags{end+1} = 'includeSubClasses';
173+
end
174+
175+
objectMap = searchProperties(...
176+
containers.Map,...
177+
obj,...
178+
'',...
179+
char(typeName),...
180+
flags{:}, 'exactTypeMatch');
181+
182+
% Filter to return exact types.
183+
nwbObjects = objectMap.values;
184+
end
122185

123186
function nwbTypeNames = listNwbTypes(obj, options)
124187
% listNwbTypes - List all unique NWB (neurodata) types in file
@@ -255,23 +318,7 @@ function resolveReferences(obj, fid, references)
255318
end
256319
end
257320

258-
function tf = metaHasType(mc, typeSuffix)
259-
assert(isa(mc, 'meta.class'));
260-
tf = false;
261-
if contains(mc.Name, typeSuffix, 'IgnoreCase', true)
262-
tf = true;
263-
return;
264-
end
265-
266-
for i = 1:length(mc.SuperclassList)
267-
sc = mc.SuperclassList(i);
268-
if metaHasType(sc, typeSuffix)
269-
tf = true;
270-
return;
271-
end
272-
end
273-
end
274-
321+
% Local functions
275322
function pathToObjectMap = searchProperties(...
276323
pathToObjectMap,...
277324
obj,...
@@ -282,6 +329,7 @@ function resolveReferences(obj, fid, references)
282329
'NWB:NwbFile:SearchProperties:InvalidVariableArguments',...
283330
'Optional keywords for searchFor must be char arrays.');
284331
shouldSearchSuperClasses = any(strcmpi(varargin, 'includeSubClasses'));
332+
exactTypeMatch = any(strcmpi(varargin, 'exactTypeMatch'));
285333

286334
if isa(obj, 'types.untyped.MetaClass')
287335
propertyNames = properties(obj);
@@ -297,9 +345,9 @@ function resolveReferences(obj, fid, references)
297345
'Invalid object type passed %s', class(obj));
298346
end
299347

300-
searchTypename = @(obj, typename) contains(class(obj), typename, 'IgnoreCase', true);
348+
searchTypename = @(obj, typename) isMatchedType(class(obj), typename, 'ExactMatch', exactTypeMatch);
301349
if shouldSearchSuperClasses
302-
searchTypename = @(obj, typename) metaHasType(metaclass(obj), typename);
350+
searchTypename = @(obj, typename) metaHasType(metaclass(obj), typename, 'ExactMatch', exactTypeMatch);
303351
end
304352

305353
for i = 1:length(propertyNames)
@@ -319,6 +367,56 @@ function resolveReferences(obj, fid, references)
319367
end
320368
end
321369

370+
function tf = metaHasType(mc, typeSuffix, options)
371+
arguments
372+
mc meta.class
373+
typeSuffix (1,1) string
374+
options.ExactMatch (1,1) logical = false
375+
end
376+
377+
tf = false;
378+
if isMatchedType(mc.Name, typeSuffix, 'ExactMatch', options.ExactMatch)
379+
tf = true;
380+
return;
381+
end
382+
383+
for i = 1:length(mc.SuperclassList)
384+
sc = mc.SuperclassList(i);
385+
if metaHasType(sc, typeSuffix, 'ExactMatch', options.ExactMatch)
386+
tf = true;
387+
return;
388+
end
389+
end
390+
end
391+
392+
function tf = isMatchedType(typeNameA, typeNameB, options)
393+
arguments
394+
typeNameA (1,1) string
395+
typeNameB (1,1) string
396+
options.ExactMatch (1,1) logical = false
397+
end
398+
399+
if options.ExactMatch
400+
if contains(typeNameB, '.')
401+
% If namespace is provided, need to match on namespace and type.
402+
tf = strcmpi(typeNameA, typeNameB);
403+
else
404+
tf = strcmpi(...
405+
extractTypeNameWithoutNamespace(typeNameA), ...
406+
extractTypeNameWithoutNamespace(typeNameB));
407+
end
408+
else
409+
tf = contains(typeNameA, typeNameB, 'IgnoreCase', true);
410+
end
411+
end
412+
413+
function typeName = extractTypeNameWithoutNamespace(typeName)
414+
if contains(typeName, '.')
415+
splitName = split(typeName, '.');
416+
typeName = splitName{end};
417+
end
418+
end
419+
322420
function namespaceNames = getNamespacesForDataTypes(nwbTypeNames)
323421
% getNamespacesOfTypes - Get namespace names for a list of nwb types
324422
arguments

0 commit comments

Comments
 (0)