Skip to content

Commit fa04ab0

Browse files
Refactor: Add arguments block to main MatNWB api functions (#619)
* Refactor: Add arguments block to main MatNWB api functions - Move some functions io.spec namespace - Add validation functions in matnwb.common namespace * Update functionSignatures * Create functionSignatures.json * Improve coverage for main functions * Create TypeConversionTest.m * Add unit tests for io.isBool and io.pathParts * Update functionSignatures * Add test for cloneNwbFileClass * Create local validator function for reftype in file.fillProps - Simplify logic in fillExport * Create local function for duplicated code block * Simplify file cleanup in case of error * Update writeNamespace.m continue is unecessary here * Fix failing tests * Improve coverage * Add tests for misc functions * Simplify spec.loadCache Add arguments block * Remove unused function * Add misc unittests * Add datapipe test and fix bug in DynamicFilter class * Add unittests for functions in +types namespace * Add class setup to +types function tests * Add tests for clearing dynamictable plus fix related bugs * Add input options for nwbClearGenerated * Add cleanup for writeAttribute Removes lines that can not be reached * Update Point.m Simplify * Add SpaceTest * Fix bug with writing and parsing logical data in compound data type * Fix variableName in test * Add more unittests to WriteTest * Remove unused function * Add unit tests for functions in +types namespace * Change exist to isfolder/isfile --------- Co-authored-by: Ben Dichter <[email protected]>
1 parent 21bd143 commit fa04ab0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1298
-522
lines changed

+file/cloneNwbFileClass.m

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,21 @@ function cloneNwbFileClass(typeFileName, fullTypeName)
55

66
nwbFilePath = which('NwbFile');
77
installPath = fileparts(nwbFilePath);
8-
fileId = fopen(nwbFilePath);
9-
text = strrep(char(fread(fileId) .'),...
10-
'NwbFile < types.core.NWBFile',...
8+
nwbFileClassDef = fileread(nwbFilePath);
9+
10+
% Update superclass name
11+
updatedNwbFileClassDef = strrep(nwbFileClassDef, ...
12+
'NwbFile < types.core.NWBFile', ...
1113
sprintf('NwbFile < %s', fullTypeName));
12-
fclose(fileId);
14+
15+
% Update call to superclass constructor
16+
updatedNwbFileClassDef = strrep(updatedNwbFileClassDef, ...
17+
'obj = [email protected]', ...
18+
sprintf('obj = obj@%s', fullTypeName));
1319

1420
fileId = fopen(fullfile(installPath, [typeFileName '.m']), 'W');
15-
fwrite(fileId, text);
21+
fwrite(fileId, updatedNwbFileClassDef);
1622
fclose(fileId);
17-
end
1823

24+
rehash();
25+
end

+file/fillExport.m

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@
2121
for i = 1:length(propertyNames)
2222
propertyName = propertyNames{i};
2323
pathProps = traverseRaw(propertyName, RawClass);
24-
if isempty(pathProps)
25-
keyboard;
26-
end
2724
prop = pathProps{end};
2825
elideProps = pathProps(1:end-1);
2926
elisions = cell(length(elideProps),1);
@@ -84,11 +81,10 @@
8481
path = {};
8582

8683
if isa(RawClass, 'file.Dataset')
87-
if isempty(RawClass.attributes)
88-
return;
84+
if ~isempty(RawClass.attributes)
85+
matchesAttribute = strcmp({RawClass.attributes.name}, propertyName);
86+
path = {RawClass.attributes(matchesAttribute)};
8987
end
90-
matchesAttribute = strcmp({RawClass.attributes.name}, propertyName);
91-
path = {RawClass.attributes(matchesAttribute)};
9288
return;
9389
end
9490

+file/fillProps.m

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,30 +52,14 @@
5252
typeStr = ['Table with columns: (', strjoin(columnDocStr, ', '), ')'];
5353
elseif isa(prop, 'file.Attribute')
5454
if isa(prop.dtype, 'containers.Map')
55-
switch prop.dtype('reftype')
56-
case 'region'
57-
refTypeName = 'Region';
58-
case 'object'
59-
refTypeName = 'Object';
60-
otherwise
61-
error('NWB:ClassGenerator:InvalidRefType', ...
62-
'Invalid reftype found while filling description for class property "%s".', propName);
63-
end
64-
typeStr = sprintf('%s Reference to %s', refTypeName, prop.dtype('target_type'));
55+
assertValidRefType(prop.dtype('reftype'))
56+
typeStr = sprintf('%s reference to %s', capitalize(prop.dtype('reftype')), prop.dtype('target_type'));
6557
else
6658
typeStr = prop.dtype;
6759
end
6860
elseif isa(prop, 'containers.Map')
69-
switch prop('reftype')
70-
case 'region'
71-
refTypeName = 'region';
72-
case 'object'
73-
refTypeName = 'object';
74-
otherwise
75-
error('NWB:ClassGenerator:InvalidRefType', ...
76-
'Invalid reftype found while filling description for class property "%s".', propName);
77-
end
78-
typeStr = sprintf('%s Reference to %s', refTypeName, prop('target_type'));
61+
assertValidRefType(prop('reftype'))
62+
typeStr = sprintf('%s reference to %s', capitalize(prop('reftype')), prop('target_type'));
7963
elseif isa(prop, 'file.interface.HasProps')
8064
typeStrCell = cell(size(prop));
8165
for iProp = 1:length(typeStrCell)
@@ -108,4 +92,20 @@
10892
if nargin >= 2
10993
propStr = [propName ' = ' propStr];
11094
end
95+
end
96+
97+
function assertValidRefType(referenceType)
98+
arguments
99+
referenceType (1,1) string
100+
end
101+
assert( ismember(referenceType, ["region", "object"]), ...
102+
'NWB:ClassGenerator:InvalidRefType', ...
103+
'Invalid reftype found while filling description for class properties.')
104+
end
105+
106+
function word = capitalize(word)
107+
arguments
108+
word (1,:) char
109+
end
110+
word(1) = upper(word(1));
111111
end

+file/fillValidators.m

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -155,34 +155,19 @@
155155
fillDimensionValidation(prop.dtype, prop.shape)...
156156
}, newline);
157157
elseif prop.isConstrainedSet
158-
try
159-
fullname = namespaceReg.getFullClassName(prop.type);
160-
catch ME
161-
if ~endsWith(ME.identifier, 'Namespace:NotFound')
162-
rethrow(ME);
163-
end
164-
165-
warning('NWB:Fill:Validators:NamespaceNotFound',...
166-
['Namespace could not be found for type `%s`.' ...
167-
' Skipping Validation for property `%s`.'], prop.type, name);
168-
return;
158+
fullname = getFullClassName(namespaceReg, prop.type, name);
159+
if isempty(fullname)
160+
return
169161
end
162+
170163
unitValidationStr = strjoin({unitValidationStr...
171164
['constrained = { ''' fullname ''' };']...
172165
['types.util.checkSet(''' name ''', struct(), constrained, val);']...
173166
}, newline);
174167
else
175-
try
176-
fullname = namespaceReg.getFullClassName(prop.type);
177-
catch ME
178-
if ~endsWith(ME.identifier, 'Namespace:NotFound')
179-
rethrow(ME);
180-
end
181-
182-
warning('NWB:Fill:Validators:NamespaceNotFound',...
183-
['Namespace could not be found for type `%s`.' ...
184-
' Skipping Validation for property `%s`.'], prop.type, name);
185-
return;
168+
fullname = getFullClassName(namespaceReg, prop.type, name);
169+
if isempty(fullname)
170+
return
186171
end
187172
unitValidationStr = [unitValidationStr newline fillDtypeValidation(name, fullname)];
188173
end
@@ -318,4 +303,19 @@
318303
condition, ...
319304
sprintf(' %s', errorStr), ...
320305
'end' }, newline );
306+
end
307+
308+
function fullname = getFullClassName(namespaceReg, propType, name)
309+
fullname = '';
310+
try
311+
fullname = namespaceReg.getFullClassName(propType);
312+
catch ME
313+
if ~endsWith(ME.identifier, 'Namespace:NotFound')
314+
rethrow(ME);
315+
end
316+
317+
warning('NWB:Fill:Validators:NamespaceNotFound',...
318+
['Namespace could not be found for type `%s`.' ...
319+
' Skipping Validation for property `%s`.'], propType, name);
320+
end
321321
end

+file/writeNamespace.m

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ function writeNamespace(namespaceName, saveDir)
44

55
classFileDir = fullfile(saveDir, '+types', ['+' misc.str2validName(Namespace.name)]);
66

7-
if 7 ~= exist(classFileDir, 'dir')
7+
if ~isfolder(classFileDir)
88
mkdir(classFileDir);
99
end
1010

@@ -14,18 +14,14 @@ function writeNamespace(namespaceName, saveDir)
1414
className = classes{i};
1515
[processed, classprops, inherited] = file.processClass(className, Namespace, pregenerated);
1616

17-
if isempty(processed)
18-
continue;
19-
end
20-
21-
fid = fopen(fullfile(classFileDir, [className '.m']), 'W');
22-
try
17+
if ~isempty(processed)
18+
fid = fopen(fullfile(classFileDir, [className '.m']), 'W');
19+
% Create cleanup object to close to file in case the write operation fails.
20+
fileCleanupObj = onCleanup(@(id) fclose(fid));
2321
fwrite(fid, file.fillClass(className, Namespace, processed, ...
2422
classprops, inherited), 'char');
25-
catch ME
26-
fclose(fid);
27-
rethrow(ME)
23+
else
24+
% pass
2825
end
29-
fclose(fid);
3026
end
3127
end

+io/+space/+shape/Point.m

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@
2222
end
2323

2424
function varargout = getMatlabIndex(obj)
25-
if 0 == nargout
26-
return;
25+
if nargout > 0
26+
varargout{1} = obj.index;
2727
end
28-
29-
varargout{1} = obj.index;
3028
end
3129
end
3230
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
function specLocation = readEmbeddedSpecLocation(fid, specLocAttributeName)
2+
arguments
3+
fid (1,1) H5ML.id
4+
specLocAttributeName (1,1) string = '.specloc'
5+
end
6+
7+
specLocation = '';
8+
try % Check .specloc
9+
attributeId = H5A.open(fid, specLocAttributeName);
10+
attributeCleanup = onCleanup(@(id) H5A.close(attributeId));
11+
referenceRawData = H5A.read(attributeId);
12+
specLocation = H5R.get_name(attributeId, 'H5R_OBJECT', referenceRawData);
13+
catch ME
14+
if ~strcmp(ME.identifier, 'MATLAB:imagesci:hdf5lib:libraryError')
15+
rethrow(ME);
16+
end % don't error if the attribute doesn't exist.
17+
end
18+
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
function specLocation = getEmbeddedSpecLocation(filename, options)
2+
% getEmbeddedSpecLocation - Get location of embedded specs in NWB file
3+
%
4+
% Note: Returns an empty string if the spec location does not exist
5+
%
6+
% See also io.spec.internal.readEmbeddedSpecLocation
7+
8+
arguments
9+
filename (1,1) string {matnwb.common.mustBeNwbFile}
10+
options.SpecLocAttributeName (1,1) string = '.specloc'
11+
end
12+
13+
fid = H5F.open(filename);
14+
fileCleanup = onCleanup(@(id) H5F.close(fid) );
15+
specLocation = io.spec.internal.readEmbeddedSpecLocation(fid, options.SpecLocAttributeName);
16+
end
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
function specs = readEmbeddedSpecifications(filename, specLocation)
2+
% readEmbeddedSpecifications - Read embedded specs from an NWB file
3+
%
4+
% specs = io.spec.readEmbeddedSpecifications(filename, specLocation) read
5+
% embedded specs from the specLocation in an NWB file
6+
%
7+
% Inputs:
8+
% filename (string) : Absolute path of an nwb file
9+
% specLocation (string) : h5 path for the location of specs inside the NWB file
10+
%
11+
% Outputs
12+
% specs cell: A cell array of structs with one element for each embedded
13+
% specification. Each struct has two fields:
14+
%
15+
% - namespaceName (char) : Name of the namespace for a specification
16+
% - namespaceText (char) : The namespace declaration for a specification
17+
% - schemaMap (containers.Map): A set of schema specifications for the namespace
18+
19+
arguments
20+
filename (1,1) string {matnwb.common.mustBeNwbFile}
21+
specLocation (1,1) string
22+
end
23+
24+
specInfo = h5info(filename, specLocation);
25+
specs = deal( cell(size(specInfo.Groups)) );
26+
27+
fid = H5F.open(filename);
28+
fileCleanup = onCleanup(@(id) H5F.close(fid) );
29+
30+
for iGroup = 1:length(specInfo.Groups)
31+
location = specInfo.Groups(iGroup).Groups(1);
32+
33+
namespaceName = split(specInfo.Groups(iGroup).Name, '/');
34+
namespaceName = namespaceName{end};
35+
36+
filenames = {location.Datasets.Name};
37+
if ~any(strcmp('namespace', filenames))
38+
warning('NWB:Read:GenerateSpec:CacheInvalid',...
39+
'Couldn''t find a `namespace` in namespace `%s`. Skipping cache generation.',...
40+
namespaceName);
41+
return;
42+
end
43+
sourceNames = {location.Datasets.Name};
44+
fileLocation = strcat(location.Name, '/', sourceNames);
45+
schemaMap = containers.Map;
46+
for iFileLocation = 1:length(fileLocation)
47+
did = H5D.open(fid, fileLocation{iFileLocation});
48+
if strcmp('namespace', sourceNames{iFileLocation})
49+
namespaceText = H5D.read(did);
50+
else
51+
schemaMap(sourceNames{iFileLocation}) = H5D.read(did);
52+
end
53+
H5D.close(did);
54+
end
55+
56+
specs{iGroup}.namespaceName = namespaceName;
57+
specs{iGroup}.namespaceText = namespaceText;
58+
specs{iGroup}.schemaMap = schemaMap;
59+
end
60+
end
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
function writeEmbeddedSpecifications(fid, jsonSpecs)
2+
specLocation = io.spec.internal.readEmbeddedSpecLocation(fid);
3+
4+
if isempty(specLocation)
5+
specLocation = '/specifications';
6+
io.writeGroup(fid, specLocation);
7+
specView = types.untyped.ObjectView(specLocation);
8+
io.writeAttribute(fid, '/.specloc', specView);
9+
end
10+
11+
for iJson = 1:length(jsonSpecs)
12+
JsonDatum = jsonSpecs(iJson);
13+
schemaNamespaceLocation = strjoin({specLocation, JsonDatum.name}, '/');
14+
namespaceExists = io.writeGroup(fid, schemaNamespaceLocation);
15+
if namespaceExists
16+
namespaceGroupId = H5G.open(fid, schemaNamespaceLocation);
17+
names = getVersionNames(namespaceGroupId);
18+
H5G.close(namespaceGroupId);
19+
for iNames = 1:length(names)
20+
H5L.delete(fid, [schemaNamespaceLocation '/' names{iNames}],...
21+
'H5P_DEFAULT');
22+
end
23+
end
24+
schemaLocation =...
25+
strjoin({schemaNamespaceLocation, JsonDatum.version}, '/');
26+
io.writeGroup(fid, schemaLocation);
27+
Json = JsonDatum.json;
28+
schemeNames = keys(Json);
29+
for iScheme = 1:length(schemeNames)
30+
name = schemeNames{iScheme};
31+
path = [schemaLocation '/' name];
32+
io.writeDataset(fid, path, Json(name));
33+
end
34+
end
35+
end
36+
37+
function versionNames = getVersionNames(namespaceGroupId)
38+
[~, ~, versionNames] = H5L.iterate(namespaceGroupId,...
39+
'H5_INDEX_NAME', 'H5_ITER_NATIVE',...
40+
0, @removeGroups, {});
41+
function [status, versionNames] = removeGroups(~, name, versionNames)
42+
versionNames{end+1} = name;
43+
status = 0;
44+
end
45+
end

0 commit comments

Comments
 (0)