Skip to content

Commit f4260f1

Browse files
authored
Various cleanup and changes inspired from openMINDS-MATLAB-UI (#20)
* unreviewed changes * Update getSchemaShortName.m * fix * Change: do not add controlled instances to collection * Update Collection, ensure compatibility with R2022b * Minor changes * Update functions providing setup and startup routines * Add isInstance and isMixedInstance utility functions * Fix change namespace reference "om" to "openminds" * ... * Minor changes from reviewing PR #20 * Add newline eof * Minor changes for PR #20 * Update loadInstances.m fix typo * Fix isMixedInstance: mixed up namespace order * Update: Allow comparison of empty object in LinkedCategory class
1 parent 07dc77a commit f4260f1

File tree

89 files changed

+766
-184
lines changed

Some content is hidden

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

89 files changed

+766
-184
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Vocab files
2+
code/resources/.vocab
3+
14
# Downloaded files / temporary folders
25
downloads/
36
target/

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ See also: [Crew Member Collection Tutorial](./docs/tutorials/crewMemberCollectio
4444
```matlab
4545
% Schema classes for all the openMINDS model versions are available in this
4646
% toolbox. To ensure the latest version is used, run the following command:
47-
selectOpenMindsVersion("latest")
47+
openminds.startup("latest")
4848
```
4949
### Import schemas from the core model
5050
```matlab

code/+openminds/@Collection/Collection.m

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
Description (1,1) string
5151
end
5252

53-
properties (SetAccess = private)
53+
properties (SetAccess = protected)
5454
Nodes (1,1) dictionary
5555
end
5656

@@ -105,7 +105,7 @@
105105
if ~isempty(instance) && ~isempty(instance{1})
106106
isFilePath = @(x) (ischar(x) || isstring(x)) && isfile(x);
107107
isFolderPath = @(x) (ischar(x) || isstring(x)) && isfolder(x);
108-
isMetadata = @(x) isa(x, 'openminds.abstract.Schema');
108+
isMetadata = @(x) openminds.utility.isInstance(x);
109109

110110
% Initialize from file(s)
111111
if all( cellfun(isFilePath, instance) )
@@ -134,7 +134,7 @@
134134
len = numEntries(obj.Nodes);
135135
end
136136

137-
function add(obj, instance)
137+
function add(obj, instance, options)
138138
%add Add single or multiple instances to a collection.
139139
%
140140
% Example usage:
@@ -149,26 +149,44 @@ function add(obj, instance)
149149
arguments (Repeating)
150150
instance % openminds.abstract.Schema
151151
end
152+
arguments
153+
options.AddSubNodesOnly = false;
154+
end
152155

153156
for i = 1:numel(instance)
154157
thisInstance = instance{i};
155158
for j = 1:numel(thisInstance) % If thisInstance is an array
156-
obj.addNode(thisInstance(j));
159+
obj.addNode(thisInstance(j), "AddSubNodesOnly", options.AddSubNodesOnly);
157160
end
158161
end
159162
end
160163

161164
function tf = contains(obj, instance)
162165
% Todo:work for arrays
163-
if isKey(obj.Nodes, instance.id)
164-
tf = true;
165-
else
166-
tf = false;
166+
tf = false;
167+
168+
if isConfigured(obj.Nodes)
169+
if isKey(obj.Nodes, instance.id)
170+
tf = true;
171+
end
167172
end
168173
end
169174

170175
function remove(obj, instance)
171-
error('not implemented')
176+
177+
if isstring(instance) || ischar(instance)
178+
instanceId = instance;
179+
elseif openminds.utility.isInstance(instance)
180+
instanceId = instance.id;
181+
else
182+
error('Unexpected type "%s" for instance argument', class(instance))
183+
end
184+
185+
if isConfigured(obj.Nodes) && isKey(obj.Nodes, instanceId)
186+
obj.Nodes(instanceId) = [];
187+
else
188+
error('Instance with id %s is not found in collection')
189+
end
172190
end
173191

174192
function instance = get(obj, nodeKey)
@@ -191,13 +209,22 @@ function remove(obj, instance)
191209
return
192210
end
193211

194-
keys = obj.Nodes.keys;
195-
isMatch = startsWith(keys, type);
196-
keys = keys(isMatch);
197-
212+
typeKeys = obj.TypeMap.keys;
213+
isMatch = endsWith(typeKeys, "."+type); %i.e ".Person"
214+
if any(isMatch)
215+
if isMATLABReleaseOlderThan("R2023b")
216+
keys = string( obj.TypeMap(typeKeys(isMatch)) );
217+
else
218+
keys = obj.TypeMap{typeKeys(isMatch)};
219+
end
220+
else
221+
return
222+
end
223+
198224
instances = obj.Nodes(keys);
199225
instances = [instances{:}];
200226

227+
% Filter by property values:
201228
for i = 1:numel(propertyName)
202229
thisName = propertyName{i};
203230
thisValue = propertyValue{i};
@@ -298,7 +325,11 @@ function load(obj, filePath)%, options)
298325

299326
instances = obj.loadInstances(jsonldFilePaths);
300327
for i = 1:numel(instances)
301-
obj.addNode(instances{i})
328+
if openminds.utility.isInstance(instances{i})
329+
obj.addNode(instances{i})
330+
else
331+
warning('todo')
332+
end
302333
end
303334
end
304335

@@ -312,7 +343,7 @@ function load(obj, filePath)%, options)
312343

313344
end
314345

315-
methods (Access = private)
346+
methods (Access = protected)
316347

317348
%Add an instance to the Node container.
318349
function addNode(obj, instance, options)
@@ -328,6 +359,11 @@ function addNode(obj, instance, options)
328359
instance.id = obj.getBlankNodeIdentifier();
329360
end
330361

362+
% Do not add openminds controlled term instances
363+
if startsWith(instance.id, "https://openminds.ebrains.eu/instances/")
364+
return
365+
end
366+
331367
if isConfigured(obj.Nodes)
332368
if isKey(obj.Nodes, instance.id)
333369
%warning('Node with id %s already exists in collection', instance.id)
@@ -362,7 +398,9 @@ function addSubNodes(obj, instance)
362398
% Add links.
363399
linkedTypes = instance.getLinkedTypes();
364400
for i = 1:numel(linkedTypes)
365-
obj.addNode(linkedTypes{i});
401+
if openminds.utility.isInstance(linkedTypes{i})
402+
obj.addNode(linkedTypes{i});
403+
end
366404
end
367405

368406
% Add embeddings.
@@ -377,7 +415,6 @@ function addSubNodes(obj, instance)
377415
identifier = length(obj) + 1;
378416
identifier = sprintf(fmt, identifier);
379417
end
380-
381418
end
382419

383420
end

code/+openminds/@Collection/loadInstances.m

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,50 @@
2020
switch serializationFormat
2121
case ".jsonld"
2222

23-
%str = fileread(filePath);
23+
% Read one or more files
2424
str = arrayfun(@fileread, filePath, 'UniformOutput', false);
2525

26-
%structInstances = jsonld2struct(str);
26+
% Produce a cell array of instances represented as structs
2727
if numel(str) == 1
2828
structInstances = jsonld2struct(str);
29+
if ~iscell(structInstances); structInstances={structInstances};end
2930
else
3031
structInstances = cellfun(@jsonld2struct, str, 'UniformOutput', false);
3132
end
3233

34+
% Create instance objects
3335
instances = cell(size(structInstances));
34-
35-
% Create instances...
3636
for i = 1:numel(structInstances)
3737

3838
thisInstance = structInstances{i};
39-
40-
openMindsType = thisInstance.at_type;
41-
className = openminds.internal.utility.string.type2class(openMindsType);
42-
43-
assert( isequal( eval(sprintf('%s.X_TYPE', className)), openMindsType), ...
44-
"Instance type does not match schema type. This is not supposed to happen, please report!")
4539

46-
instances{i} = feval(className, thisInstance);
40+
if ~isfield(thisInstance, 'at_type')
41+
continue % Todo: Why skip?
42+
%instances{i} = struct('id', thisInstance.at_id);
43+
else
44+
openMindsType = thisInstance.at_type;
45+
className = openminds.internal.utility.string.type2class(openMindsType);
46+
47+
assert( isequal( eval(sprintf('%s.X_TYPE', className)), openMindsType), ...
48+
"Instance type does not match schema type. This is not supposed to happen, please report!")
49+
50+
try
51+
instances{i} = feval(className, thisInstance);
52+
catch ME
53+
warning(ME.message)
54+
end
55+
end
4756
end
57+
58+
isEmpty = cellfun(@(c) isempty(c), instances);
59+
instances(isEmpty) = [];
60+
61+
instanceIds = cellfun(@(instance) instance.id, instances, 'UniformOutput', false);
62+
instanceIds = string(instanceIds);
4863

4964
% Link instances / Resolve linked objects...
5065
for i = 1:numel(instances)
51-
resolveLinks(instances{i}, instances)
66+
resolveLinks(instances{i}, instanceIds, instances)
5267
end
5368

5469
otherwise
@@ -60,14 +75,26 @@
6075
end
6176
end
6277

63-
function resolveLinks(instance, instanceCollection)
78+
function resolveLinks(instance, instanceIds, instanceCollection)
6479
%resolveLinks Resolve linked types, i.e replace an @id with the actual
6580
% instance object.
6681

67-
schemaInspector = openminds.internal.SchemaInspector(instance);
68-
69-
instanceIds = cellfun(@(instance) instance.id, instanceCollection, 'UniformOutput', false);
82+
if isstruct(instance) % Instance is not resolvable (E.g belongs to remote collection)
83+
return
84+
end
85+
86+
persistent schemaInspectorMap
87+
if isempty(schemaInspectorMap)
88+
schemaInspectorMap = dictionary;
89+
end
90+
91+
instanceType = class(instance);
92+
if ~isConfigured(schemaInspectorMap) || ~isKey(schemaInspectorMap, instanceType)
93+
schemaInspectorMap(instanceType) = openminds.internal.SchemaInspector(instance);
94+
end
7095

96+
schemaInspector = schemaInspectorMap(instanceType);
97+
7198
for i = 1:schemaInspector.NumProperties
7299
thisPropertyName = schemaInspector.PropertyNames{i};
73100
if schemaInspector.isPropertyWithLinkedType(thisPropertyName)
@@ -76,7 +103,7 @@ function resolveLinks(instance, instanceCollection)
76103
resolvedInstances = cell(size(linkedInstances));
77104

78105
for j = 1:numel(linkedInstances)
79-
if isa(linkedInstances(j), 'openminds.internal.abstract.LinkedCategory')
106+
if openminds.utility.isMixedInstance(linkedInstances(j))
80107
try
81108
instanceId = linkedInstances(j).Instance.id;
82109
catch
@@ -86,16 +113,23 @@ function resolveLinks(instance, instanceCollection)
86113
instanceId = linkedInstances(j).id;
87114
end
88115

89-
isMatchedInstance = strcmp(instanceIds, instanceId);
116+
isMatchedInstance = instanceIds == string(instanceId);
90117

91118
if any(isMatchedInstance)
92119
resolvedInstances{j} = instanceCollection{isMatchedInstance};
93-
resolveLinks(resolvedInstances{j}, instanceCollection)
120+
resolveLinks(resolvedInstances{j}, instanceIds, instanceCollection)
121+
else
122+
% Check if instance is a controlled instance
123+
if startsWith(instanceId, "https://openminds.ebrains.eu/instances/")
124+
resolvedInstances{j} = om.instance.getControlledInstance(instanceId);
125+
end
94126
end
95127
end
96128

97129
try
98130
resolvedInstances = [resolvedInstances{:}];
131+
catch
132+
% pass. Todo, should there be error handling here?
99133
end
100134

101135
if ~isempty(resolvedInstances)
@@ -106,14 +140,13 @@ function resolveLinks(instance, instanceCollection)
106140
embeddedInstances = instance.(thisPropertyName);
107141

108142
for j = 1:numel(embeddedInstances)
109-
if isa(embeddedInstances(j), 'openminds.internal.abstract.LinkedCategory')
143+
if openminds.utility.isMixedInstance(embeddedInstances(j))
110144
embeddedInstance = embeddedInstances(j).Instance;
111145
else
112146
embeddedInstance = embeddedInstances(j);
113147
end
114-
resolveLinks(embeddedInstance, instanceCollection)
148+
resolveLinks(embeddedInstance, instanceIds, instanceCollection)
115149
end
116150
end
117151
end
118152
end
119-

code/+openminds/getSchemaVersion.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010
end
1111

1212
versionStr = strrep(pathSplit{matchedIdx(1)}, schemaFolder, '');
13-
end
13+
end

code/+openminds/startup.m

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
function startup(version)
2+
% startup - Startup routines for openMINDS_MATLAB
3+
%
4+
% This function ensures that only one version of openMINDS schema classes
5+
% are on MATLAB's search path.
6+
7+
arguments
8+
version (1,1) string = "latest"
9+
end
10+
11+
disp('Initializing openMINDS_MATLAB...')
12+
13+
% NB: Assumes this function is located in code/+openminds:
14+
codePath = fileparts( fileparts( mfilename('fullpath') ) );
15+
addpath( fullfile(codePath, 'internal') )
16+
17+
% Run internal function that correctly configures the search path
18+
openminds.selectOpenMindsVersion(version)
19+
disp('Added schemas from the "latest" version to path')
20+
end

code/internal/+openminds/+abstract/ControlledTerm.m

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@
6969
end
7070
end
7171
end
72-
7372

7473
methods (Access = protected) % Implement method for the CustomInstanceDisplay mixin
7574

@@ -101,16 +100,26 @@ function deserializeFromName(obj, instanceName)
101100
[~, instanceName] = openminds.utility.parseAtID(instanceName);
102101
end
103102

103+
[instanceName, instanceNameOrig] = deal(instanceName);
104+
if ~any(strcmp(obj.CONTROLLED_INSTANCES, instanceName))
105+
% Try to make a valid name
106+
instanceName = strrep(instanceName, ' ', '');
107+
instanceName = matlab.lang.makeValidName(instanceName, 'ReplacementStyle', 'delete');
108+
end
109+
104110
% Todo: Use a proper deserializer
105111
if any(strcmpi(obj.CONTROLLED_INSTANCES, instanceName))
106112
try
107113
data = getControlledInstance(instanceName, schemaName, 'controlledTerms');
108114
catch
109-
warning('Controlled instance "%s" is not available.', instanceName)
115+
s = warning('off', 'backtrace');
116+
warning('Controlled instance "%s" is not available.', instanceNameOrig)
117+
warning(s);
110118
return
111119
end
112120
else
113-
error('Deserialization from user instance is not implemented yet')
121+
error('No matching instances were found for name "%s"', instanceName)
122+
%error('Deserialization from user instance is not implemented yet')
114123
end
115124
propNames = {'at_id', 'name', 'definition', 'description', 'interlexIdentifier', 'knowledgeSpaceLink', 'preferredOntologyIdentifier', 'synonym'};
116125

@@ -150,4 +159,4 @@ function deserializeFromName(obj, instanceName)
150159
end
151160
end
152161

153-
end
162+
end

0 commit comments

Comments
 (0)