Skip to content

Commit c166b1a

Browse files
authored
Merge pull request #263 from NeurodataWithoutBorders/update-basic-tutorials
Update Basic Usage and Conversion Tutorials
2 parents e748549 + be650ab commit c166b1a

File tree

11 files changed

+567
-549
lines changed

11 files changed

+567
-549
lines changed

+tests/+unit/dataStubTest.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,10 @@ function testRegionRead(testCase)
6060
testCase.verifyEqual(stub(primeInd, :, 1), data(primeInd, :, 1));
6161
testCase.verifyEqual(stub(primeInd, [1 2 5]), data(primeInd, [1 2 5]));
6262
testCase.verifyEqual(stub([1 25], [1 5], [1 4], [1 2], [1 5]), data([1 25], [1 5], [1 4], [1 2], [1 5]));
63+
64+
% test duplicate indices
65+
testCase.verifyEqual(stub([1 1 1 1]), data([1 1 1 1]));
66+
67+
% test out of order indices
68+
testCase.verifyEqual(stub([5 4 3 2 2]), data([5 4 3 2 2]));
6369
end

+types/+untyped/+datastub/findShapes.m

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
assert(isvector(indices),...
1616
'MatNwb:DataStub:FindShapes:InvalidShape',...
1717
'Indices cannot be matrices.');
18-
indices = sort(indices);
18+
indices = unique(indices);
1919
shapes = {};
2020
while ~isempty(indices)
2121
BlockSelection = findOptimalBlock(indices);
@@ -37,7 +37,7 @@
3737
end
3838
stop = 1;
3939
start = 1;
40-
step = 0;
40+
step = 1;
4141
count = 0;
4242
for i = 1:length(indices)
4343
tempStart = indices(i);
@@ -47,19 +47,20 @@
4747
end
4848
for j = 1:(length(indices)-i)
4949
tempStep = indices(i+j) - indices(i);
50-
for k = fliplr(i:length(indices))
50+
for k = length(indices):-1:i
5151
tempStop = indices(k);
5252
idealRange = tempStart:tempStep:tempStop;
53-
tempRange = intersect(indices, idealRange, 'stable');
54-
if length(tempRange) <= count
53+
rangeMatches = ismembc(idealRange, indices);
54+
numMatches = sum(rangeMatches);
55+
if numMatches <= count
5556
% number of intersected items is shorter than what we have.
5657
break;
5758
end
58-
if isequal(tempRange, idealRange)
59+
if all(rangeMatches)
5960
start = tempStart;
6061
step = tempStep;
6162
stop = tempStop;
62-
count = length(tempRange);
63+
count = numMatches;
6364
break;
6465
end
6566
end

+types/+untyped/DataStub.m

Lines changed: 84 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,11 @@
176176
dims = obj.dims;
177177
rank = length(dims);
178178
for i = 1:length(varargin)
179-
if ischar(varargin{i})
179+
ind = varargin{i};
180+
if ischar(ind) || isempty(ind)
180181
continue;
181182
end
182-
validateattributes(varargin{i}, {'numeric'}, {'vector', '<=', dims(i)});
183+
validateattributes(ind, {'numeric'}, {'vector', '<=', dims(i)});
183184
end
184185
shapes = getShapes(varargin, dims);
185186

@@ -208,8 +209,17 @@
208209
shapeInd(iterateInd) = shapeInd(iterateInd) + 1;
209210
shapeInd(1:(iterateInd-1)) = 1;
210211
end
211-
212-
memSize = getMemSize(varargin, dims);
212+
memSize = zeros(1, rank);
213+
for i = 1:rank
214+
for j = 1:length(shapes{i})
215+
Selection = shapes{i}{j};
216+
if isa(Selection, 'types.untyped.datastub.shape.Point')
217+
memSize(i) = memSize(i) + 1;
218+
else
219+
memSize(i) = memSize(i) + Selection.length;
220+
end
221+
end
222+
end
213223
memSid = H5S.create_simple(length(memSize), fliplr(memSize), []);
214224
% read data.
215225
fid = H5F.open(obj.filename);
@@ -220,6 +230,76 @@
220230
H5S.close(memSid);
221231
H5S.close(sid);
222232

233+
expectedSize = dims;
234+
for i = 1:length(varargin)
235+
if ~ischar(varargin{i})
236+
expectedSize(i) = length(varargin{i});
237+
end
238+
end
239+
240+
if ischar(varargin{end})
241+
% dangling ':' where leftover dimensions are folded into
242+
% the last selection.
243+
selDimInd = length(varargin);
244+
expectedSize = [expectedSize(1:(selDimInd-1)) prod(dims(selDimInd:end))];
245+
else
246+
expectedSize = expectedSize(1:length(varargin));
247+
end
248+
249+
if isscalar(expectedSize)
250+
expectedSize = [1 expectedSize];
251+
end
252+
253+
selections = varargin;
254+
openSelInd = find(cellfun('isclass', selections, 'char'));
255+
for i = 1:length(openSelInd)
256+
selections{i} = 1:dims(i);
257+
end
258+
data = reorderLoadedData(data, selections);
259+
data = reshape(data, expectedSize);
260+
261+
function reordered = reorderLoadedData(data, selections)
262+
% dataset loading does not account for duplicate or unordered
263+
% indices so we have to re-order everything here.
264+
% we presume data is the indexed values of a unique(ind)
265+
if isempty(data)
266+
reordered = data;
267+
return;
268+
end
269+
270+
indKey = cell(size(selections));
271+
isSelectionNormal = false(size(selections)); % that is, without duplicates or out of order.
272+
for i = 1:length(indKey)
273+
indKey{i} = unique(selections{i});
274+
isSelectionNormal = isequal(indKey{i}, selections{i});
275+
end
276+
if all(isSelectionNormal)
277+
reordered = data;
278+
return;
279+
end
280+
indKeyIndMax = cellfun('length', indKey);
281+
if isscalar(indKeyIndMax)
282+
reordered = repmat(data(1), indKeyIndMax, 1);
283+
else
284+
reordered = repmat(data(1), indKeyIndMax);
285+
end
286+
indKeyInd = ones(size(selections));
287+
while true
288+
selInd = cell(size(selections));
289+
for i = 1:length(selections)
290+
selInd{i} = selections{i} == indKey{i}(indKeyInd(i));
291+
end
292+
indKeyIndArgs = num2cell(indKeyInd);
293+
reordered(selInd{:}) = data(indKeyIndArgs{:});
294+
indKeyIndNextInd = find(indKeyIndMax ~= indKeyInd, 1, 'last');
295+
if isempty(indKeyIndNextInd)
296+
break;
297+
end
298+
indKeyInd(indKeyIndNextInd) = indKeyInd(indKeyIndNextInd) + 1;
299+
indKeyInd((indKeyIndNextInd+1):end) = 1;
300+
end
301+
end
302+
223303
function shapes = getShapes(selections, dims)
224304
rank = length(dims);
225305
shapes = cell(1, rank); % cell array of cell arrays of shapes
@@ -240,27 +320,6 @@
240320
end
241321
end
242322
end
243-
244-
function memSize = getMemSize(selections, dims)
245-
% replace dims with number of selections
246-
indexSelections = find(~cellfun('isclass', selections, 'char'));
247-
vararginSizes = cellfun('length', selections);
248-
dims(indexSelections) = vararginSizes(indexSelections);
249-
250-
% case where varargin rank is smaller than actual data
251-
% space rank.
252-
selectionRank = length(selections);
253-
fileSpaceRank = length(dims);
254-
isOpenEnded = ischar(selections{end});
255-
if fileSpaceRank > selectionRank && ~isOpenEnded
256-
% when there are no trailing ':' then the remainder
257-
% dims are scalar. Otherwise, just load the rest of the
258-
% data.
259-
scalarDims = selectionRank + 1;
260-
dims(scalarDims:end) = 1;
261-
end
262-
memSize = dims;
263-
end
264323
end
265324

266325
function refs = export(obj, fid, fullpath, refs)
@@ -351,30 +410,6 @@
351410
'Cannot index into %d dimensions when max rank is %d',...
352411
selectionRank, rank);
353412
data = obj.load_mat_style(CurrentSubRef.subs{:});
354-
expectedSize = dims;
355-
for i = 1:length(CurrentSubRef.subs)
356-
if ~ischar(CurrentSubRef.subs{i})
357-
expectedSize(i) = length(CurrentSubRef.subs{i});
358-
end
359-
end
360-
361-
if ischar(CurrentSubRef.subs{end})
362-
% dangling ':' where leftover dimensions are folded into
363-
% the last selection.
364-
selDimInd = length(CurrentSubRef.subs);
365-
expectedSize = [expectedSize(1:(selDimInd-1)) prod(dims(selDimInd:end))];
366-
else
367-
expectedSize = expectedSize(1:length(CurrentSubRef.subs));
368-
end
369-
370-
if isscalar(expectedSize)
371-
expectedSize = [1 expectedSize];
372-
end
373-
374-
if ~isequal(size(data), expectedSize)
375-
data = reshape(data, expectedSize);
376-
end
377-
378413
if isscalar(S)
379414
B = data;
380415
else

+types/+untyped/MetaClass.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@
6161
end
6262
end
6363

64+
refLen = length(refs);
6465
refs = obj.write_base(fid, fullpath, refs);
66+
if refLen ~= length(refs)
67+
return;
68+
end
6569

6670
uuid = char(java.util.UUID.randomUUID().toString());
6771
if isa(obj, 'NwbFile')

+types/+util/+dynamictable/addRow.m

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@ function addRow(DynamicTable, varargin)
6363
rv = p.Results.(rn);
6464

6565
if isKey(TypeMap, rn)
66-
TypeStruct = TypeMap(rn);
67-
validateattributes(rv, {TypeStruct.type}, {'size', [NaN TypeStruct.dims(2:end)]});
66+
rv = validateType(TypeMap(rn), rv);
6867
else
6968
assert(iscellstr(rv) || ~iscell(rv),...
7069
'MatNWB:DynamicTable:AddRow:InvalidCellArray',...
@@ -132,6 +131,19 @@ function addRow(DynamicTable, varargin)
132131
end
133132
end
134133

134+
function rv = validateType(TypeStruct, rv)
135+
if strcmp(TypeStruct.type, 'cellstr')
136+
assert(iscellstr(rv) || (ischar(rv) && 1 == size(rv, 1)),...
137+
'MatNWB:DynamicTable:AddRow:InvalidType',...
138+
'Type of value must be a cell array of character vectors or a scalar character');
139+
if ischar(rv)
140+
rv = {rv};
141+
end
142+
else
143+
validateattributes(rv, {TypeStruct.type}, {'size', [NaN TypeStruct.dims(2:end)]});
144+
end
145+
end
146+
135147
function TypeMap = constructTypeMap(DynamicTable)
136148
TypeMap = containers.Map;
137149
if isempty(DynamicTable.id.data)...
@@ -152,13 +164,19 @@ function addRow(DynamicTable, varargin)
152164
else
153165
colval = colVecData.data(1);
154166
end
155-
TypeStruct.type = class(colval);
167+
168+
if iscellstr(colval)
169+
TypeStruct.type = 'cellstr';
170+
else
171+
TypeStruct.type = class(colval);
172+
end
156173

157174
if isa(colVecData.data, 'types.untyped.DataPipe')
158175
TypeStruct.dims = colVecData.data.internal.maxSize;
159176
else
160177
TypeStruct.dims = size(colVecData.data);
161178
end
179+
162180
TypeMap(colnm) = TypeStruct;
163181
end
164182
end
@@ -187,27 +205,40 @@ function appendData(DynamicTable, column, data, index)
187205
end
188206

189207
if ~isempty(index)
190-
if isa(VecData.data, 'types.untyped.DataPipe')
191-
raggedIndex = VecData.data.offset;
192-
else
193-
raggedIndex = size(VecData.data, 1);
194-
end
195-
196208
if isprop(DynamicTable, index)
197209
VecInd = DynamicTable.(index);
198210
else
199211
VecInd = DynamicTable.vectorindex.get(index);
200212
end
213+
201214
if isa(VecInd.data, 'types.untyped.DataPipe')
202-
VecInd.data.append(raggedIndex);
215+
if 0 == VecInd.data.dims
216+
raggedOffset = 0;
217+
else
218+
raggedOffset = VecInd.data.load(VecInd.data.dims);
219+
end
203220
else
204-
VecInd.data = [VecInd.data; raggedIndex];
221+
if isempty(VecInd.data)
222+
raggedOffset = 0;
223+
else
224+
raggedOffset = VecInd.data(end);
225+
end
226+
end
227+
228+
raggedValue = raggedOffset + size(data, 1);
229+
if isa(VecInd.data, 'types.untyped.DataPipe')
230+
VecInd.data.append(raggedValue);
231+
else
232+
VecInd.data = [VecInd.data; raggedValue];
205233
end
206234
end
207235

208236
if isa(VecData.data, 'types.untyped.DataPipe')
209237
VecData.data.append(data);
210238
else
239+
if ischar(data)
240+
data = {data};
241+
end
211242
VecData.data = [VecData.data; data];
212243
end
213244
end

+types/+util/+dynamictable/getIndex.m

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,34 @@
66
'MatNWB:GetIndex:InvalidColumn',...
77
'Column name not found `%s`', column);
88
indexName = '';
9+
910
vecIndKeys = keys(DynamicTable.vectorindex);
1011
for i = 1:length(vecIndKeys)
1112
vik = vecIndKeys{i};
12-
VecInd = DynamicTable.vectorindex.get(vik);
13-
if endsWith(VecInd.target.path, ['/' column])
13+
if isVecIndColumn(DynamicTable.vectorindex.get(vik), column)
14+
indexName = vik;
15+
return;
16+
end
17+
end
18+
19+
DynamicTableProps = properties(DynamicTable);
20+
isPropVecInd = false(size(DynamicTableProps));
21+
for i = 1:length(DynamicTableProps)
22+
isPropVecInd(i) = isa(DynamicTable.(DynamicTableProps{i}), 'types.hdmf_common.VectorIndex');
23+
end
24+
25+
DynamicTableProps = DynamicTableProps(isPropVecInd);
26+
for i = 1:length(DynamicTableProps)
27+
vik = DynamicTableProps{i};
28+
VecInd = DynamicTable.(vik);
29+
if isVecIndColumn(VecInd, column)
1430
indexName = vik;
1531
return;
1632
end
1733
end
1834
end
1935

36+
function tf = isVecIndColumn(VectorIndex, column)
37+
tf = endsWith(VectorIndex.target.path, ['/' column]);
38+
end
39+

0 commit comments

Comments
 (0)