From 180fe1a7cf8ad3b852ca2a9dbf08cacb7fa685fd Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 7 Apr 2026 22:00:23 +0200 Subject: [PATCH 1/3] Clean whitespace and indentation --- .../+internal/initDynamicTableId.m | 2 +- +types/+util/+dynamictable/addColumn.m | 42 +- +types/+util/+dynamictable/addRawData.m | 2 +- +types/+util/+dynamictable/addRow.m | 48 +-- +types/+util/+dynamictable/addTableColumn.m | 7 +- +types/+util/+dynamictable/addVecInd.m | 84 ++-- +types/+util/+dynamictable/clear.m | 36 +- +types/+util/+dynamictable/getIndex.m | 117 +++--- +types/+util/+dynamictable/getRow.m | 361 +++++++++--------- +types/+util/+dynamictable/getTypeMap.m | 67 ++-- +types/+util/+dynamictable/nwbToTable.m | 133 +++---- 11 files changed, 450 insertions(+), 449 deletions(-) diff --git a/+types/+util/+dynamictable/+internal/initDynamicTableId.m b/+types/+util/+dynamictable/+internal/initDynamicTableId.m index 798cda8f9..361f7042b 100644 --- a/+types/+util/+dynamictable/+internal/initDynamicTableId.m +++ b/+types/+util/+dynamictable/+internal/initDynamicTableId.m @@ -15,7 +15,7 @@ function initDynamicTableId(dynamicTable, tableHeight) else idData = []; end - + if exist('types.hdmf_common.ElementIdentifiers', 'class') == 8 dynamicTable.id = types.hdmf_common.ElementIdentifiers('data', idData); else % legacy ElementIdentifiers diff --git a/+types/+util/+dynamictable/addColumn.m b/+types/+util/+dynamictable/addColumn.m index 3d2d55ce0..d1bb318de 100644 --- a/+types/+util/+dynamictable/addColumn.m +++ b/+types/+util/+dynamictable/addColumn.m @@ -3,7 +3,7 @@ function addColumn(DynamicTable, varargin) % more columns, add one or more columns to the dynamic table by providing % either keywords or a MATLAB table % -% ADDCOLUMN(DT,TABLE) append the columns of the MATLAB Table TABLE to the +% ADDCOLUMN(DT,TABLE) append the columns of the MATLAB Table TABLE to the % DynamicTable % % ADDCOLUMN(DT,col_name1,col_vector1,...,col_namen,col_vectorn) @@ -15,24 +15,24 @@ function addColumn(DynamicTable, varargin) % 2) The height of the columns to be appended matches the height of the % existing columns -validateattributes(DynamicTable,... - {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'},... - {'scalar'}); - -assert(nargin > 1, 'NWB:DynamicTable:AddColumn:NoData', 'Not enough arguments'); - - -if isempty(DynamicTable.id) - types.util.dynamictable.internal.initDynamicTableId(DynamicTable); -end - -assert(~isa(DynamicTable.id.data, 'types.untyped.DataStub'),... - 'NWB:DynamicTable:AddColumn:Uneditable',... - ['Cannot write to on-file Dynamic Tables without enabling data pipes. '... - 'If this was produced with pynwb, please enable chunking for this table.']); - -if istable(varargin{1}) - types.util.dynamictable.addTableColumn(DynamicTable, varargin{:}); -else - types.util.dynamictable.addVarargColumn(DynamicTable, varargin{:}); + validateattributes(DynamicTable,... + {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'},... + {'scalar'}); + + assert(nargin > 1, 'NWB:DynamicTable:AddColumn:NoData', 'Not enough arguments'); + + if isempty(DynamicTable.id) + types.util.dynamictable.internal.initDynamicTableId(DynamicTable); + end + + assert(~isa(DynamicTable.id.data, 'types.untyped.DataStub'),... + 'NWB:DynamicTable:AddColumn:Uneditable',... + ['Cannot write to on-file Dynamic Tables without enabling data pipes. '... + 'If this was produced with pynwb, please enable chunking for this table.']); + + if istable(varargin{1}) + types.util.dynamictable.addTableColumn(DynamicTable, varargin{:}); + else + types.util.dynamictable.addVarargColumn(DynamicTable, varargin{:}); + end end diff --git a/+types/+util/+dynamictable/addRawData.m b/+types/+util/+dynamictable/addRawData.m index 43de6e4e7..cb4135927 100644 --- a/+types/+util/+dynamictable/addRawData.m +++ b/+types/+util/+dynamictable/addRawData.m @@ -210,4 +210,4 @@ function add2Index(VectorIndex, numElem) else VectorIndex.data = [double(VectorIndex.data); data]; end -end \ No newline at end of file +end diff --git a/+types/+util/+dynamictable/addRow.m b/+types/+util/+dynamictable/addRow.m index 328223c15..869f2030a 100644 --- a/+types/+util/+dynamictable/addRow.m +++ b/+types/+util/+dynamictable/addRow.m @@ -22,28 +22,28 @@ function addRow(DynamicTable, varargin) % 5) The type of the data cannot be a cell array of numeric values if using % keyword arguments. For table appending mode, this is how ragged arrays % are represented. - -validateattributes(DynamicTable,... - {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'},... - {'scalar'}); -assert(~isempty(DynamicTable.colnames),... - 'NWB:DynamicTable:AddRow:NoColumns',... - ['The `colnames` property of the Dynamic Table needs to be populated with a cell array '... - 'of column names before being able to add row data.']); -assert(nargin > 1, 'NWB:DynamicTable:AddRow:NoData', 'Not enough arguments'); - -types.util.dynamictable.checkConfig(DynamicTable); - -assert(~isa(DynamicTable.id.data, 'types.untyped.DataStub'),... - 'NWB:DynamicTable:AddRow:Uneditable',... - ['Cannot write to on-file Dynamic Tables without enabling data pipes. '... - 'If this was produced with pynwb, please enable chunking for this table.']); - -if istable(varargin{1}) - error('NWB:DynamicTable', ... - ['Using MATLAB tables as input to the addRow DynamicTable method has '... - 'been deprecated. Please, use key-value pairs instead']); -else - types.util.dynamictable.addVarargRow(DynamicTable, varargin{:}); + + validateattributes(DynamicTable,... + {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'},... + {'scalar'}); + assert(~isempty(DynamicTable.colnames),... + 'NWB:DynamicTable:AddRow:NoColumns',... + ['The `colnames` property of the Dynamic Table needs to be populated with a cell array '... + 'of column names before being able to add row data.']); + assert(nargin > 1, 'NWB:DynamicTable:AddRow:NoData', 'Not enough arguments'); + + types.util.dynamictable.checkConfig(DynamicTable); + + assert(~isa(DynamicTable.id.data, 'types.untyped.DataStub'),... + 'NWB:DynamicTable:AddRow:Uneditable',... + ['Cannot write to on-file Dynamic Tables without enabling data pipes. '... + 'If this was produced with pynwb, please enable chunking for this table.']); + + if istable(varargin{1}) + error('NWB:DynamicTable', ... + ['Using MATLAB tables as input to the addRow DynamicTable method has '... + 'been deprecated. Please, use key-value pairs instead']); + else + types.util.dynamictable.addVarargRow(DynamicTable, varargin{:}); + end end -end \ No newline at end of file diff --git a/+types/+util/+dynamictable/addTableColumn.m b/+types/+util/+dynamictable/addTableColumn.m index dfdba5349..25d3c34ba 100644 --- a/+types/+util/+dynamictable/addTableColumn.m +++ b/+types/+util/+dynamictable/addTableColumn.m @@ -1,4 +1,5 @@ function addTableColumn(DynamicTable, subTable) -error('NWB:DynamicTable', ... - ['Using MATLAB tables as input to the addColumn DynamicTable method has '... - 'been deprecated. Please, use key-value pairs instead']) \ No newline at end of file + error('NWB:DynamicTable', ... + ['Using MATLAB tables as input to the addColumn DynamicTable method has '... + 'been deprecated. Please, use key-value pairs instead']) +end diff --git a/+types/+util/+dynamictable/addVecInd.m b/+types/+util/+dynamictable/addVecInd.m index 5248eb414..6f00de1b3 100644 --- a/+types/+util/+dynamictable/addVecInd.m +++ b/+types/+util/+dynamictable/addVecInd.m @@ -1,45 +1,45 @@ function vecIndName = addVecInd(DynamicTable, colName) %ADDVECIND Add VectorIndex object to DynamicTable -validateattributes(colName, {'char'}, {'scalartext'}); -vecIndName = [colName '_index']; % arbitrary convention of appending '_index' to data column names - -if isprop(DynamicTable, colName) - VecData = DynamicTable.(colName); -elseif isprop(DynamicTable, 'vectorindex') && isKey(DynamicTable.vectorindex, colName) - VecData = DynamicTable.vectorindex.get(colName); -else - VecData = DynamicTable.vectordata.get(colName); + validateattributes(colName, {'char'}, {'scalartext'}); + vecIndName = [colName '_index']; % arbitrary convention of appending '_index' to data column names + + if isprop(DynamicTable, colName) + VecData = DynamicTable.(colName); + elseif isprop(DynamicTable, 'vectorindex') && isKey(DynamicTable.vectorindex, colName) + VecData = DynamicTable.vectorindex.get(colName); + else + VecData = DynamicTable.vectordata.get(colName); + end + + if isa(VecData.data, 'types.untyped.DataPipe') + oldDataHeight = VecData.data.offset; + elseif isa(VecData.data, 'types.untyped.DataStub') + oldDataHeight = VecData.data.dims(end); + elseif isvector(VecData.data) + oldDataHeight = length(VecData.data); + else + oldDataHeight = size(VecData.data, ndims(VecData.data)); + end + + % we presume that if data already existed in the vectordata, then + % it was never a ragged array and thus its elements corresponded + % directly to each row index. + vecView = types.untyped.ObjectView(VecData); + if 8 == exist('types.hdmf_common.VectorIndex', 'class') + VecIndex = types.hdmf_common.VectorIndex('target', vecView, 'data', (1:oldDataHeight) .'); + else + VecIndex = types.core.VectorIndex('target', vecView, 'data', (1:oldDataHeight) .'); + end + + if isprop(VecIndex, 'description') + VecIndex.description = sprintf('Index into column %s', colName); + end + + if isprop(DynamicTable, vecIndName) + DynamicTable.(vecIndName) = VecIndex; + elseif isprop(DynamicTable, 'vectorindex') + DynamicTable.vectorindex.set(vecIndName, VecIndex); + else + DynamicTable.vectordata.set(vecIndName, VecIndex); + end end - -if isa(VecData.data, 'types.untyped.DataPipe') - oldDataHeight = VecData.data.offset; -elseif isa(VecData.data, 'types.untyped.DataStub') - oldDataHeight = VecData.data.dims(end); -elseif isvector(VecData.data) - oldDataHeight = length(VecData.data); -else - oldDataHeight = size(VecData.data, ndims(VecData.data)); -end - -% we presume that if data already existed in the vectordata, then -% it was never a ragged array and thus its elements corresponded -% directly to each row index. -vecView = types.untyped.ObjectView(VecData); -if 8 == exist('types.hdmf_common.VectorIndex', 'class') - VecIndex = types.hdmf_common.VectorIndex('target', vecView, 'data', (1:oldDataHeight) .'); -else - VecIndex = types.core.VectorIndex('target', vecView, 'data', (1:oldDataHeight) .'); -end - -if isprop(VecIndex, 'description') - VecIndex.description = sprintf('Index into column %s', colName); -end - -if isprop(DynamicTable, vecIndName) - DynamicTable.(vecIndName) = VecIndex; -elseif isprop(DynamicTable, 'vectorindex') - DynamicTable.vectorindex.set(vecIndName, VecIndex); -else - DynamicTable.vectordata.set(vecIndName, VecIndex); -end -end \ No newline at end of file diff --git a/+types/+util/+dynamictable/clear.m b/+types/+util/+dynamictable/clear.m index cff609812..d64979bb6 100644 --- a/+types/+util/+dynamictable/clear.m +++ b/+types/+util/+dynamictable/clear.m @@ -1,23 +1,23 @@ function clear(DynamicTable) %CLEAR Given a valid DynamicTable object, clears all rows and type % information in the table. -validateattributes(DynamicTable, {'types.hdmf_common.DynamicTable', 'types.core.DynamicTable'}, {'scalar'}); - -if isa(DynamicTable, 'types.core.DynamicTable') % Schema version <2.2.0 - elementIdentifierClass = @types.core.ElementIdentifiers; - vectorDataClassName = 'types.core.VectorData'; - vectorIndexClassName = 'types.core.VectorIndex'; -else - elementIdentifierClass = @types.hdmf_common.ElementIdentifiers; - vectorDataClassName = 'types.hdmf_common.VectorData'; - vectorIndexClassName = 'types.hdmf_common.VectorIndex'; -end + validateattributes(DynamicTable, {'types.hdmf_common.DynamicTable', 'types.core.DynamicTable'}, {'scalar'}); -DynamicTable.id = elementIdentifierClass(); -DynamicTable.vectordata = types.untyped.Set(@(nm, val)types.util.checkConstraint(... - 'vectordata', nm, struct(), {vectorDataClassName}, val)); -if isprop(DynamicTable, 'vectorindex') % Schema version <2.3.0 - DynamicTable.vectorindex = types.untyped.Set(@(nm, val)types.util.checkConstraint(... - 'vectorindex', nm, struct(), {vectorIndexClassName}, val)); -end + if isa(DynamicTable, 'types.core.DynamicTable') % Schema version <2.2.0 + elementIdentifierClass = @types.core.ElementIdentifiers; + vectorDataClassName = 'types.core.VectorData'; + vectorIndexClassName = 'types.core.VectorIndex'; + else + elementIdentifierClass = @types.hdmf_common.ElementIdentifiers; + vectorDataClassName = 'types.hdmf_common.VectorData'; + vectorIndexClassName = 'types.hdmf_common.VectorIndex'; + end + + DynamicTable.id = elementIdentifierClass(); + DynamicTable.vectordata = types.untyped.Set(@(nm, val)types.util.checkConstraint(... + 'vectordata', nm, struct(), {vectorDataClassName}, val)); + if isprop(DynamicTable, 'vectorindex') % Schema version <2.3.0 + DynamicTable.vectorindex = types.untyped.Set(@(nm, val)types.util.checkConstraint(... + 'vectorindex', nm, struct(), {vectorIndexClassName}, val)); + end end diff --git a/+types/+util/+dynamictable/getIndex.m b/+types/+util/+dynamictable/getIndex.m index 7e88b17f4..15a227c21 100644 --- a/+types/+util/+dynamictable/getIndex.m +++ b/+types/+util/+dynamictable/getIndex.m @@ -1,73 +1,72 @@ function indexName = getIndex(DynamicTable, column) %GETINDEX Given a dynamic table and its column name, get its VectorIndex column name -validateattributes(DynamicTable,... - {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'},... - {'scalar'}); -validateattributes(column, {'char'}, {'scalartext'}); -indexName = ''; -if strcmp(column, 'id') - return; -end - -% after Schema version 2.3.0, VectorIndex objects subclass VectorData which -% meant that vectorindex and vectordata sets could be combined. -isLegacyDynamicTable = isprop(DynamicTable, 'vectorindex'); -if isLegacyDynamicTable - vecKeys = keys(DynamicTable.vectorindex); -else - vecKeys = keys(DynamicTable.vectordata); -end -for i = 1:length(vecKeys) - vk = vecKeys{i}; + validateattributes(DynamicTable,... + {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'},... + {'scalar'}); + validateattributes(column, {'char'}, {'scalartext'}); + indexName = ''; + if strcmp(column, 'id') + return; + end + + % after Schema version 2.3.0, VectorIndex objects subclass VectorData which + % meant that vectorindex and vectordata sets could be combined. + isLegacyDynamicTable = isprop(DynamicTable, 'vectorindex'); if isLegacyDynamicTable - vecData = DynamicTable.vectorindex.get(vk); + vecKeys = keys(DynamicTable.vectorindex); else - vecData = DynamicTable.vectordata.get(vk); + vecKeys = keys(DynamicTable.vectordata); end - if ~isa(vecData, 'types.hdmf_common.VectorIndex')... - && ~isa(vecData, 'types.core.VectorIndex') - continue; + for i = 1:length(vecKeys) + vk = vecKeys{i}; + if isLegacyDynamicTable + vecData = DynamicTable.vectorindex.get(vk); + else + vecData = DynamicTable.vectordata.get(vk); + end + if ~isa(vecData, 'types.hdmf_common.VectorIndex')... + && ~isa(vecData, 'types.core.VectorIndex') + continue; + end + if isVecIndColumn(DynamicTable, vecData, column) + indexName = vk; + return; + end end - if isVecIndColumn(DynamicTable, vecData, column) - indexName = vk; - return; + + % check if dynamic table object has extended properties which point to + % vector indices. These are specifically defined by the schema to be + % properties. + DynamicTableProps = properties(DynamicTable); + isPropVecInd = false(size(DynamicTableProps)); + for i = 1:length(DynamicTableProps) + PropVec = DynamicTable.(DynamicTableProps{i}); + isPropVecInd(i) = isa(PropVec, 'types.hdmf_common.VectorIndex')... + || isa(PropVec, 'types.core.VectorIndex'); end -end - -% check if dynamic table object has extended properties which point to -% vector indices. These are specifically defined by the schema to be -% properties. -DynamicTableProps = properties(DynamicTable); -isPropVecInd = false(size(DynamicTableProps)); -for i = 1:length(DynamicTableProps) - PropVec = DynamicTable.(DynamicTableProps{i}); - isPropVecInd(i) = isa(PropVec, 'types.hdmf_common.VectorIndex')... - || isa(PropVec, 'types.core.VectorIndex'); -end - -DynamicTableProps = DynamicTableProps(isPropVecInd); -for i = 1:length(DynamicTableProps) - vk = DynamicTableProps{i}; - VecInd = DynamicTable.(vk); - if isVecIndColumn(DynamicTable, VecInd, column) - indexName = vk; - return; + + DynamicTableProps = DynamicTableProps(isPropVecInd); + for i = 1:length(DynamicTableProps) + vk = DynamicTableProps{i}; + VecInd = DynamicTable.(vk); + if isVecIndColumn(DynamicTable, VecInd, column) + indexName = vk; + return; + end end end -end function tf = isVecIndColumn(DynamicTable, VectorIndex, column) -if VectorIndex.target.has_path() - tf = endsWith(VectorIndex.target.path, ['/' column]); -elseif isprop(DynamicTable, column) - tf = VectorIndex.target.target == DynamicTable.(column); -else - if isprop(DynamicTable, 'vectorindex') && DynamicTable.vectorindex.isKey(column) - Vec = DynamicTable.vectorindex.get(column); + if VectorIndex.target.has_path() + tf = endsWith(VectorIndex.target.path, ['/' column]); + elseif isprop(DynamicTable, column) + tf = VectorIndex.target.target == DynamicTable.(column); else - Vec = DynamicTable.vectordata.get(column); + if isprop(DynamicTable, 'vectorindex') && DynamicTable.vectorindex.isKey(column) + Vec = DynamicTable.vectorindex.get(column); + else + Vec = DynamicTable.vectordata.get(column); + end + tf = VectorIndex.target.target == Vec; end - tf = VectorIndex.target.target == Vec; -end end - diff --git a/+types/+util/+dynamictable/getRow.m b/+types/+util/+dynamictable/getRow.m index 6d48cb4bf..0550f94e3 100644 --- a/+types/+util/+dynamictable/getRow.m +++ b/+types/+util/+dynamictable/getRow.m @@ -8,218 +8,219 @@ % The returned value is a set of output arguments in the order of % `colnames` or "columns" keyword argument if one exists. -validateattributes(DynamicTable,... - {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'}, {'scalar'}); -validateattributes(ind, {'numeric'}, {'integer', 'vector'}); - -p = inputParser; -addParameter(p, 'columns', DynamicTable.colnames, @(x)iscellstr(x)); -addParameter(p, 'useId', false, @(x)islogical(x)); -parse(p, varargin{:}); - -columns = p.Results.columns; -row = cell(1, length(columns)); - -if p.Results.useId - assert(~isempty(DynamicTable.id), ... - 'NWB:DynamicTable:GetRow:MissingId', ... - 'Cannot retrieve rows by `id` because the DynamicTable has no `id` column.'); - ind = getIndById(DynamicTable, ind); -else - validateattributes(ind, {'numeric'}, {'positive', 'vector'}); - validateRowIndices(DynamicTable, ind); -end - -for i = 1:length(columns) - cn = columns{i}; - - indexNames = {cn}; - while true - name = types.util.dynamictable.getIndex(DynamicTable, indexNames{end}); - if isempty(name) - break; - end - indexNames{end+1} = name; + validateattributes(DynamicTable,... + {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'}, {'scalar'}); + validateattributes(ind, {'numeric'}, {'integer', 'vector'}); + + p = inputParser; + addParameter(p, 'columns', DynamicTable.colnames, @(x)iscellstr(x)); + addParameter(p, 'useId', false, @(x)islogical(x)); + parse(p, varargin{:}); + + columns = p.Results.columns; + row = cell(1, length(columns)); + + if p.Results.useId + assert(~isempty(DynamicTable.id), ... + 'NWB:DynamicTable:GetRow:MissingId', ... + 'Cannot retrieve rows by `id` because the DynamicTable has no `id` column.'); + ind = getIndById(DynamicTable, ind); + else + validateattributes(ind, {'numeric'}, {'positive', 'vector'}); + validateRowIndices(DynamicTable, ind); end - row{i} = select(DynamicTable, indexNames, ind); + for i = 1:length(columns) + cn = columns{i}; - if ~istable(row{i}) - if iscolumn(row{i}) - % keep column vectors as is - elseif isrow(row{i}) - row{i} = row{i} .'; % transpose row vectors - elseif ndims(row{i}) >= 2 % i.e nd array where ndims >= 2 - % permute arrays to place last dimension first - array_size = size(row{i}); - num_rows = numel(ind); - - is_row_dim = array_size == num_rows; - if sum(is_row_dim) == 1 - if ~(is_row_dim(1) || is_row_dim(end)) - throw( InvalidVectorDataShapeError(cn) ) + indexNames = {cn}; + while true + name = types.util.dynamictable.getIndex(DynamicTable, indexNames{end}); + if isempty(name) + break; + end + indexNames{end+1} = name; + end + + row{i} = select(DynamicTable, indexNames, ind); + + if ~istable(row{i}) + if iscolumn(row{i}) + % keep column vectors as is + elseif isrow(row{i}) + row{i} = row{i} .'; % transpose row vectors + elseif ndims(row{i}) >= 2 % i.e nd array where ndims >= 2 + % permute arrays to place last dimension first + array_size = size(row{i}); + num_rows = numel(ind); + + is_row_dim = array_size == num_rows; + if sum(is_row_dim) == 1 + if ~(is_row_dim(1) || is_row_dim(end)) + throw( InvalidVectorDataShapeError(cn) ) + end + elseif sum(is_row_dim) > 1 + if is_row_dim(1) && is_row_dim(end) + % Last dimension takes precedence + is_row_dim(1:end-1) = false; + warning('NWB:DynamicTable:VectorDataAmbiguousSize', ... + ['The length of the first and last dimensions of ', ... + 'VectorData for column "%s" match the number of ', ... + 'rows in the dynamic table. Data is rearranged based on ', ... + 'the last dimension, assuming it corresponds with the table rows.'], cn) + elseif is_row_dim(1) + is_row_dim(2:end) = false; + elseif is_row_dim(end) + is_row_dim(1:end-1) = false; + else + throw( InvalidVectorDataShapeError(cn) ) + end end - elseif sum(is_row_dim) > 1 - if is_row_dim(1) && is_row_dim(end) - % Last dimension takes precedence - is_row_dim(1:end-1) = false; - warning('NWB:DynamicTable:VectorDataAmbiguousSize', ... - ['The length of the first and last dimensions of ', ... - 'VectorData for column "%s" match the number of ', ... - 'rows in the dynamic table. Data is rearranged based on ', ... - 'the last dimension, assuming it corresponds with the table rows.'], cn) - elseif is_row_dim(1) - is_row_dim(2:end) = false; - elseif is_row_dim(end) - is_row_dim(1:end-1) = false; - else - throw( InvalidVectorDataShapeError(cn) ) + row{i} = permute( row{i}, [find(is_row_dim), find(~is_row_dim)]); + end + end + + % cell-wrap single multidimensional matrices to prevent invalid + % MATLAB tables + if isscalar(ind) && ~iscell(row{i}) && ~istable(row{i}) && ~isscalar(row{i}) + row{i} = row(i); + end + + % convert compound data type scalar struct into an array of + % structs. + if isscalar(row{i}) && isstruct(row{i}) + structNames = fieldnames(row{i}); + scalarStruct = row{i}; + rowStruct = row{i}; % same as scalarStruct to maintain the field names. + for iRow = 1:length(ind) + for iField = 1:length(structNames) + fieldName = structNames{iField}; + fieldData = scalarStruct.(fieldName); + rowStruct(iRow).(fieldName) = fieldData(iRow); end end - row{i} = permute( row{i}, [find(is_row_dim), find(~is_row_dim)]); + row{i} = rowStruct .'; end end - % cell-wrap single multidimensional matrices to prevent invalid - % MATLAB tables - if isscalar(ind) && ~iscell(row{i}) && ~istable(row{i}) && ~isscalar(row{i}) - row{i} = row(i); - end - - % convert compound data type scalar struct into an array of - % structs. - if isscalar(row{i}) && isstruct(row{i}) - structNames = fieldnames(row{i}); - scalarStruct = row{i}; - rowStruct = row{i}; % same as scalarStruct to maintain the field names. - for iRow = 1:length(ind) - for iField = 1:length(structNames) - fieldName = structNames{iField}; - fieldData = scalarStruct.(fieldName); - rowStruct(iRow).(fieldName) = fieldData(iRow); - end - end - row{i} = rowStruct .'; + if isempty(columns) + subTable = table('Size', [numel(ind), 0], 'VariableTypes', {}, 'VariableNames', {}); + else + subTable = table(row{:}, 'VariableNames', columns); end end -if isempty(columns) - subTable = table('Size', [numel(ind), 0], 'VariableTypes', {}, 'VariableNames', {}); -else - subTable = table(row{:}, 'VariableNames', columns); -end -end function selected = select(DynamicTable, colIndStack, matInd) % recursive function which consumes the colIndStack and produces a nested % cell array. -column = colIndStack{end}; -if isprop(DynamicTable, column) - Vector = DynamicTable.(column); -elseif isprop(DynamicTable, 'vectorindex') && DynamicTable.vectorindex.isKey(column) % Schema version < 2.3.0 - Vector = DynamicTable.vectorindex.get(column); -else - Vector = DynamicTable.vectordata.get(column); -end - -if isscalar(colIndStack) - if isa(Vector.data, 'types.untyped.DataStub') || ... - isa(Vector.data,'types.untyped.DataPipe') - if isa(Vector.data, 'types.untyped.DataStub') - refProp = Vector.data.dims; - else - refProp = Vector.data.internal.maxSize; - end - if length(refProp) == 2 && refProp(2) == 1 - % catch row vector - rank = 1; + column = colIndStack{end}; + if isprop(DynamicTable, column) + Vector = DynamicTable.(column); + elseif isprop(DynamicTable, 'vectorindex') && DynamicTable.vectorindex.isKey(column) % Schema version < 2.3.0 + Vector = DynamicTable.vectorindex.get(column); + else + Vector = DynamicTable.vectordata.get(column); + end + + if isscalar(colIndStack) + if isa(Vector.data, 'types.untyped.DataStub') || ... + isa(Vector.data,'types.untyped.DataPipe') + if isa(Vector.data, 'types.untyped.DataStub') + refProp = Vector.data.dims; + else + refProp = Vector.data.internal.maxSize; + end + if length(refProp) == 2 && refProp(2) == 1 + % catch row vector + rank = 1; + else + rank = length(refProp); + end else - rank = length(refProp); + if iscolumn(Vector.data) + % catch row vector + rank = 1; + elseif istable(Vector.data) + rank = 1; + else + rank = ndims(Vector.data); + end end - else - if iscolumn(Vector.data) - %catch row vector - rank = 1; - elseif istable(Vector.data) - rank = 1; + + selectInd = repmat({':'}, 1, rank); + if isa(Vector.data, 'types.untyped.DataPipe') + selectInd{Vector.data.axis} = matInd; else - rank = ndims(Vector.data); + selectInd{end} = matInd; end - end - selectInd = repmat({':'}, 1, rank); - if isa(Vector.data, 'types.untyped.DataPipe') - selectInd{Vector.data.axis} = matInd; - else - selectInd{end} = matInd; - end + if (isstruct(Vector.data) && isscalar(Vector.data)) || istable(Vector.data) + if istable(Vector.data) + selected = table(); + fields = Vector.data.Properties.VariableNames; + else + selected = struct(); + fields = fieldnames(Vector.data); + end - if (isstruct(Vector.data) && isscalar(Vector.data)) || istable(Vector.data) - if istable(Vector.data) - selected = table(); - fields = Vector.data.Properties.VariableNames; + for i = 1:length(fields) + fieldName = fields{i}; + columnData = Vector.data.(fieldName); + selected.(fieldName) = columnData(selectInd{:}); + end else - selected = struct(); - fields = fieldnames(Vector.data); + selected = Vector.data(selectInd{:}); end - - for i = 1:length(fields) - fieldName = fields{i}; - columnData = Vector.data.(fieldName); - selected.(fieldName) = columnData(selectInd{:}); + + % shift dimensions of non-row vectors. otherwise will result in + % invalid MATLAB table with uneven column height + if isa(Vector.data, 'types.untyped.DataPipe') + selected = permute(selected, ... + circshift(1:ndims(selected), -(Vector.data.axis-1))); end else - selected = Vector.data(selectInd{:}); - end - - % shift dimensions of non-row vectors. otherwise will result in - % invalid MATLAB table with uneven column height - if isa(Vector.data, 'types.untyped.DataPipe') - selected = permute(selected, ... - circshift(1:ndims(selected), -(Vector.data.axis-1))); - end -else - assert(isa(Vector, 'types.hdmf_common.VectorIndex') || isa(Vector, 'types.core.VectorIndex'),... - 'NWB:DynamicTable:GetRow:InternalError',... - 'Internal VectorIndex Stack is not using VectorIndex objects!'); - if isa(Vector.data, 'types.untyped.DataStub') || isa(Vector.data, 'types.untyped.DataPipe') - stopInds = uint64(Vector.data.load(matInd)); - else - stopInds = uint64(Vector.data(matInd)); - end - - startIndInd = matInd - 1; - zeroMask = startIndInd == 0; - startInds = zeros(size(startIndInd)); - if ~isempty(startIndInd(~zeroMask)) + assert(isa(Vector, 'types.hdmf_common.VectorIndex') || isa(Vector, 'types.core.VectorIndex'),... + 'NWB:DynamicTable:GetRow:InternalError',... + 'Internal VectorIndex Stack is not using VectorIndex objects!'); if isa(Vector.data, 'types.untyped.DataStub') || isa(Vector.data, 'types.untyped.DataPipe') - startInds(~zeroMask) = Vector.data.load(startIndInd(~zeroMask)); + stopInds = uint64(Vector.data.load(matInd)); else - startInds(~zeroMask) = Vector.data(startIndInd(~zeroMask)); + stopInds = uint64(Vector.data(matInd)); + end + + startIndInd = matInd - 1; + zeroMask = startIndInd == 0; + startInds = zeros(size(startIndInd)); + if ~isempty(startIndInd(~zeroMask)) + if isa(Vector.data, 'types.untyped.DataStub') || isa(Vector.data, 'types.untyped.DataPipe') + startInds(~zeroMask) = Vector.data.load(startIndInd(~zeroMask)); + else + startInds(~zeroMask) = Vector.data(startIndInd(~zeroMask)); + end + end + startInds = startInds + 1; + + selected = cell(length(matInd), 1); + for iRange = 1:length(matInd) + startInd = startInds(iRange); + stopInd = stopInds(iRange); + selected{iRange} = select(DynamicTable,... + colIndStack(1:(end-1)),... + startInd:stopInd); end end - startInds = startInds + 1; - - selected = cell(length(matInd), 1); - for iRange = 1:length(matInd) - startInd = startInds(iRange); - stopInd = stopInds(iRange); - selected{iRange} = select(DynamicTable,... - colIndStack(1:(end-1)),... - startInd:stopInd); - end -end end function ind = getIndById(DynamicTable, id) -if isa(DynamicTable.id.data, 'types.untyped.DataStub')... - || isa(DynamicTable.id.data, 'types.untyped.DataPipe') - ids = DynamicTable.id.data.load(); -else - ids = DynamicTable.id.data; -end -[idMatch, ind] = ismember(id, ids); -assert(all(idMatch), 'NWB:DynamicTable:GetRow:InvalidId',... - 'Invalid ids found. If you wish to use row indices directly, remove the `useId` flag.'); + if isa(DynamicTable.id.data, 'types.untyped.DataStub')... + || isa(DynamicTable.id.data, 'types.untyped.DataPipe') + ids = DynamicTable.id.data.load(); + else + ids = DynamicTable.id.data; + end + [idMatch, ind] = ismember(id, ids); + assert(all(idMatch), 'NWB:DynamicTable:GetRow:InvalidId',... + 'Invalid ids found. If you wish to use row indices directly, remove the `useId` flag.'); end function validateRowIndices(dynamicTable, rowIndices) diff --git a/+types/+util/+dynamictable/getTypeMap.m b/+types/+util/+dynamictable/getTypeMap.m index 67b409c11..6c33197f7 100644 --- a/+types/+util/+dynamictable/getTypeMap.m +++ b/+types/+util/+dynamictable/getTypeMap.m @@ -1,42 +1,41 @@ function TypeMap = getTypeMap(DynamicTable) % GETTYPEMAP returns containers.Map mapping column name to struct % containing type name and size. -TypeMap = containers.Map; -if isempty(DynamicTable.id.data)... - || (isa(DynamicTable.id.data, 'types.untyped.DataPipe')... - && 0 == DynamicTable.id.data.offset) - return; -end -TypeStruct = struct('type', '', 'dims', [0, 0]); -for i = 1:length(DynamicTable.colnames) - colnm = DynamicTable.colnames{i}; - if isprop(DynamicTable, colnm) - colVecData = DynamicTable.(colnm); - else - colVecData = DynamicTable.vectordata.get(colnm); - end - - if isa(colVecData.data, 'types.untyped.DataPipe') - colval = colVecData.data.load(1); - elseif istable(colVecData.data) - colval = colVecData.data; - else - colval = colVecData.data(1); + TypeMap = containers.Map; + if isempty(DynamicTable.id.data)... + || (isa(DynamicTable.id.data, 'types.untyped.DataPipe')... + && 0 == DynamicTable.id.data.offset) + return; end + TypeStruct = struct('type', '', 'dims', [0, 0]); + for i = 1:length(DynamicTable.colnames) + colnm = DynamicTable.colnames{i}; + if isprop(DynamicTable, colnm) + colVecData = DynamicTable.(colnm); + else + colVecData = DynamicTable.vectordata.get(colnm); + end - if iscellstr(colval) - TypeStruct.type = 'cellstr'; - else - TypeStruct.type = class(colval); - end + if isa(colVecData.data, 'types.untyped.DataPipe') + colval = colVecData.data.load(1); + elseif istable(colVecData.data) + colval = colVecData.data; + else + colval = colVecData.data(1); + end - if isa(colVecData.data, 'types.untyped.DataPipe') - TypeStruct.dims = colVecData.data.internal.maxSize; - else - TypeStruct.dims = size(colVecData.data); - end + if iscellstr(colval) + TypeStruct.type = 'cellstr'; + else + TypeStruct.type = class(colval); + end - TypeMap(colnm) = TypeStruct; -end + if isa(colVecData.data, 'types.untyped.DataPipe') + TypeStruct.dims = colVecData.data.internal.maxSize; + else + TypeStruct.dims = size(colVecData.data); + end + + TypeMap(colnm) = TypeStruct; + end end - diff --git a/+types/+util/+dynamictable/nwbToTable.m b/+types/+util/+dynamictable/nwbToTable.m index 2d9ca3b46..50657d24c 100644 --- a/+types/+util/+dynamictable/nwbToTable.m +++ b/+types/+util/+dynamictable/nwbToTable.m @@ -1,13 +1,13 @@ function matlabTable = nwbToTable(DynamicTable, index) -%NWBTOTABLE converts from a NWB DynamicTable to a MATLAB table +%NWBTOTABLE converts from a NWB DynamicTable to a MATLAB table % % MATLABTABLE = NWBTOTABLE(T) converts object T of class types.core.DynamicTable % into a MATLAB Table -% +% % MATLABTABLE = NWBTOTABLE(T, INDEX) If INDEX is FALSE, includes rows referenced by a % DynamicTableRegion as nested subtables % -% EXAMPLE +% EXAMPLE % MYTABLE = types.hdmf_common.DynamicTable( ... % 'description','an example table', ... % 'colnames', {'col1','col2'}, ... @@ -23,71 +23,72 @@ % ); % MATLABTABLE = nwb2table(MYTABLE); -%make sure input is dynamic table -validateattributes(DynamicTable,... - {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'},... - {'scalar'}); - -if nargin < 2 - index = true; -end - -if isempty(DynamicTable.id) - matlabTable = table({}, 'VariableNames', [{'id'} DynamicTable.colnames]); - return; -end - -% initialize table with id column -if isa(DynamicTable.id.data, 'types.untyped.DataStub')... - || isa(DynamicTable.id.data, 'types.untyped.DataPipe') - ids = DynamicTable.id.data.load(); -else - ids = DynamicTable.id.data; -end -matlabTable = table( ... - ids, ... - 'VariableNames', {'id'} ... -); - -% deal with DynamicTableRegion columns when index is false -[columns, remainingColumns] = deal(DynamicTable.colnames); -columnDescriptions = repmat({''}, 1, length(columns)); - -for i = 1:length(columns) - cn = columns{i}; - if isprop(DynamicTable, cn) - cv = DynamicTable.(cn); - elseif isprop(DynamicTable, 'vectorindex') && DynamicTable.vectorindex.isKey(cn) % Schema version < 2.3.0 - cv = DynamicTable.vectorindex.get(cn); + % make sure input is dynamic table + validateattributes(DynamicTable,... + {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'},... + {'scalar'}); + + if nargin < 2 + index = true; + end + + if isempty(DynamicTable.id) + matlabTable = table({}, 'VariableNames', [{'id'} DynamicTable.colnames]); + return; + end + + % initialize table with id column + if isa(DynamicTable.id.data, 'types.untyped.DataStub')... + || isa(DynamicTable.id.data, 'types.untyped.DataPipe') + ids = DynamicTable.id.data.load(); else - cv = DynamicTable.vectordata.get(cn); + ids = DynamicTable.id.data; end - columnDescriptions{i} = cv.description; - if ~index && ... - (isa(cv,'types.hdmf_common.DynamicTableRegion') ||... - isa(cv,'types.core.DynamicTableRegion')) - row_idxs = cv.data; - ref_table = cv.table.target; - cv = cell(length(row_idxs),1); - for r = 1:length(row_idxs) - cv{r,1} = ref_table.getRow(row_idxs(r)+1); + matlabTable = table( ... + ids, ... + 'VariableNames', {'id'} ... + ); + + % deal with DynamicTableRegion columns when index is false + [columns, remainingColumns] = deal(DynamicTable.colnames); + columnDescriptions = repmat({''}, 1, length(columns)); + + for i = 1:length(columns) + cn = columns{i}; + if isprop(DynamicTable, cn) + cv = DynamicTable.(cn); + elseif isprop(DynamicTable, 'vectorindex') && DynamicTable.vectorindex.isKey(cn) % Schema version < 2.3.0 + cv = DynamicTable.vectorindex.get(cn); + else + cv = DynamicTable.vectordata.get(cn); + end + columnDescriptions{i} = cv.description; + if ~index && ... + (isa(cv,'types.hdmf_common.DynamicTableRegion') ||... + isa(cv,'types.core.DynamicTableRegion')) + row_idxs = cv.data; + ref_table = cv.table.target; + cv = cell(length(row_idxs),1); + for r = 1:length(row_idxs) + cv{r,1} = ref_table.getRow(row_idxs(r)+1); + end + matlabTable.(cn) = cv; + remainingColumns = setdiff(remainingColumns, cn, 'stable'); + else + % pass end - matlabTable.(cn) = cv; - remainingColumns = setdiff(remainingColumns, cn, 'stable'); - else - % pass end + % append remaining columns to table + % making the assumption that length of ids reflects table height + matlabTable = [matlabTable DynamicTable.getRow( ... + 1:length(ids), ... + 'columns', remainingColumns ... + )]; + + % Update the columns order to be the same as the original + if iscolumn(columns); columns = transpose(columns); end + matlabTable = matlabTable(:, [{'id'}, columns]); + + % Add variable descriptions + matlabTable.Properties.VariableDescriptions = [{''}, columnDescriptions]; end -% append remaining columns to table -% making the assumption that length of ids reflects table height -matlabTable = [matlabTable DynamicTable.getRow( ... - 1:length(ids), ... - 'columns', remainingColumns ... -)]; - -% Update the columns order to be the same as the original -if iscolumn(columns); columns = transpose(columns); end -matlabTable = matlabTable(:, [{'id'}, columns]); - -% Add variable descriptions -matlabTable.Properties.VariableDescriptions = [{''}, columnDescriptions]; From 813eb859a9b601c814cd59eae3202820b5223dc9 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 14 Apr 2026 21:37:10 +0200 Subject: [PATCH 2/3] Add dynamic table validation in argument blocks of internal functions --- .../+util/+dynamictable/+internal/getOutermostIndexColumnName.m | 2 +- +types/+util/+dynamictable/+internal/getTableHeight.m | 2 +- +types/+util/+dynamictable/+internal/initDynamicTableId.m | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/+types/+util/+dynamictable/+internal/getOutermostIndexColumnName.m b/+types/+util/+dynamictable/+internal/getOutermostIndexColumnName.m index 552aaa751..cfa3f5249 100644 --- a/+types/+util/+dynamictable/+internal/getOutermostIndexColumnName.m +++ b/+types/+util/+dynamictable/+internal/getOutermostIndexColumnName.m @@ -4,7 +4,7 @@ % Note: Relevant for ragged array columns arguments - dynamicTable + dynamicTable {matnwb.common.validation.mustBeDynamicTable} columnName {mustBeTextScalar} end diff --git a/+types/+util/+dynamictable/+internal/getTableHeight.m b/+types/+util/+dynamictable/+internal/getTableHeight.m index caf0e1a04..61116327e 100644 --- a/+types/+util/+dynamictable/+internal/getTableHeight.m +++ b/+types/+util/+dynamictable/+internal/getTableHeight.m @@ -6,7 +6,7 @@ % column object. arguments - dynamicTable + dynamicTable {matnwb.common.validation.mustBeDynamicTable} end if ~isempty(dynamicTable.id) diff --git a/+types/+util/+dynamictable/+internal/initDynamicTableId.m b/+types/+util/+dynamictable/+internal/initDynamicTableId.m index 361f7042b..1b97ad9ea 100644 --- a/+types/+util/+dynamictable/+internal/initDynamicTableId.m +++ b/+types/+util/+dynamictable/+internal/initDynamicTableId.m @@ -6,7 +6,7 @@ function initDynamicTableId(dynamicTable, tableHeight) % core namespace. arguments - dynamicTable + dynamicTable {matnwb.common.validation.mustBeDynamicTable} tableHeight = [] end From e650d547a38d7279727c57412340b5520a2340fe Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 14 Apr 2026 22:24:26 +0200 Subject: [PATCH 3/3] Add argument blocks, improve and unify variable names --- .../+dynamictable/+internal/getColumnHeight.m | 7 + .../+internal/getColumnRowHeight.m | 2 +- +types/+util/+dynamictable/addColumn.m | 24 +- +types/+util/+dynamictable/addRawData.m | 171 ++++++++----- +types/+util/+dynamictable/addRow.m | 28 ++- +types/+util/+dynamictable/addTableColumn.m | 7 +- +types/+util/+dynamictable/addVarargColumn.m | 162 +++++++------ +types/+util/+dynamictable/addVarargRow.m | 81 ++++--- +types/+util/+dynamictable/addVecInd.m | 55 +++-- +types/+util/+dynamictable/checkConfig.m | 2 +- +types/+util/+dynamictable/clear.m | 16 +- +types/+util/+dynamictable/getIndex.m | 84 ++++--- +types/+util/+dynamictable/getRow.m | 229 ++++++++++-------- +types/+util/+dynamictable/getTypeMap.m | 50 ++-- +types/+util/+dynamictable/nwbToTable.m | 62 +++-- 15 files changed, 550 insertions(+), 430 deletions(-) diff --git a/+types/+util/+dynamictable/+internal/getColumnHeight.m b/+types/+util/+dynamictable/+internal/getColumnHeight.m index 68bf0b7ae..da0b44e42 100644 --- a/+types/+util/+dynamictable/+internal/getColumnHeight.m +++ b/+types/+util/+dynamictable/+internal/getColumnHeight.m @@ -3,6 +3,9 @@ % % This helper inspects the underlying stored data shape only. It does not % resolve VectorIndex chains to determine DynamicTable row height. + arguments + column + end if isempty(column) columnHeight = 0; @@ -12,6 +15,10 @@ end function columnHeight = getDataHeight(data) + arguments + data + end + if isempty(data) columnHeight = 0; elseif isa(data, 'types.untyped.DataPipe') diff --git a/+types/+util/+dynamictable/+internal/getColumnRowHeight.m b/+types/+util/+dynamictable/+internal/getColumnRowHeight.m index 2f6adda52..91628d1f2 100644 --- a/+types/+util/+dynamictable/+internal/getColumnRowHeight.m +++ b/+types/+util/+dynamictable/+internal/getColumnRowHeight.m @@ -5,7 +5,7 @@ % column, whose height corresponds to the number of table rows. arguments - dynamicTable + dynamicTable {matnwb.common.validation.mustBeDynamicTable} columnName {mustBeTextScalar} end diff --git a/+types/+util/+dynamictable/addColumn.m b/+types/+util/+dynamictable/addColumn.m index d1bb318de..47228d274 100644 --- a/+types/+util/+dynamictable/addColumn.m +++ b/+types/+util/+dynamictable/addColumn.m @@ -1,4 +1,4 @@ -function addColumn(DynamicTable, varargin) +function addColumn(dynamicTable, varargin) % ADDCOLUMN Given a dynamic table and a set of keyword arguments for one or % more columns, add one or more columns to the dynamic table by providing % either keywords or a MATLAB table @@ -15,24 +15,28 @@ function addColumn(DynamicTable, varargin) % 2) The height of the columns to be appended matches the height of the % existing columns - validateattributes(DynamicTable,... - {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'},... - {'scalar'}); + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + end + arguments (Repeating) + varargin + end - assert(nargin > 1, 'NWB:DynamicTable:AddColumn:NoData', 'Not enough arguments'); + assert(nargin > 1, 'NWB:DynamicTable:AddColumn:NoData', ... + 'Not enough arguments'); - if isempty(DynamicTable.id) - types.util.dynamictable.internal.initDynamicTableId(DynamicTable); + if isempty(dynamicTable.id) + types.util.dynamictable.internal.initDynamicTableId(dynamicTable); end - assert(~isa(DynamicTable.id.data, 'types.untyped.DataStub'),... + assert(~isa(dynamicTable.id.data, 'types.untyped.DataStub'),... 'NWB:DynamicTable:AddColumn:Uneditable',... ['Cannot write to on-file Dynamic Tables without enabling data pipes. '... 'If this was produced with pynwb, please enable chunking for this table.']); if istable(varargin{1}) - types.util.dynamictable.addTableColumn(DynamicTable, varargin{:}); + types.util.dynamictable.addTableColumn(dynamicTable, varargin{:}); else - types.util.dynamictable.addVarargColumn(DynamicTable, varargin{:}); + types.util.dynamictable.addVarargColumn(dynamicTable, varargin{:}); end end diff --git a/+types/+util/+dynamictable/addRawData.m b/+types/+util/+dynamictable/addRawData.m index cb4135927..431fc67e7 100644 --- a/+types/+util/+dynamictable/addRawData.m +++ b/+types/+util/+dynamictable/addRawData.m @@ -1,48 +1,54 @@ -function addRawData(DynamicTable, column, data) +function addRawData(dynamicTable, columnName, data) %ADDRAWDATA Internal method for adding data to DynamicTable given column % name and data. Indices are determined based on data format and available % indices. - validateattributes(column, {'char'}, {'scalartext'}); + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + columnName {mustBeTextScalar} + data + end + + columnName = char(columnName); - if (isprop(DynamicTable, column) && isempty(DynamicTable.(column))) ... - || (~isprop(DynamicTable, column) && ~isKey(DynamicTable.vectordata, column)) + if (isprop(dynamicTable, columnName) && isempty(dynamicTable.(columnName))) ... + || (~isprop(dynamicTable, columnName) && ~isKey(dynamicTable.vectordata, columnName)) % No vecdata found anywhere. Initialize. - initVecData(DynamicTable, column, class(data)); + initVecData(dynamicTable, columnName, class(data)); end - if isprop(DynamicTable, column) - Vector = DynamicTable.(column); - elseif isprop(DynamicTable, 'vectorindex') && DynamicTable.vectorindex.isKey(column) - Vector = DynamicTable.vectorindex.get(column); + if isprop(dynamicTable, columnName) + vectorData = dynamicTable.(columnName); + elseif isprop(dynamicTable, 'vectorindex') && dynamicTable.vectorindex.isKey(columnName) + vectorData = dynamicTable.vectorindex.get(columnName); else - Vector = DynamicTable.vectordata.get(column); + vectorData = dynamicTable.vectordata.get(columnName); end % grab all available indices for column. - indexChain = {column}; + indexChain = {columnName}; while true - index = types.util.dynamictable.getIndex(DynamicTable, indexChain{end}); - if isempty(index) + indexName = types.util.dynamictable.getIndex(dynamicTable, indexChain{end}); + if isempty(indexName) break; end - indexChain{end+1} = index; + indexChain{end+1} = indexName; end - if ~isa(Vector.data, 'types.untyped.DataPipe') + if ~isa(vectorData.data, 'types.untyped.DataPipe') % validate shape for appending in memory. checkNestedShape(data); end % find true nesting depth of column data. - if isa(Vector.data, 'types.untyped.DataPipe') - depth = getNestedDataDepth(data, 'dataPipeDimension', Vector.data.axis); + if isa(vectorData.data, 'types.untyped.DataPipe') + depth = getNestedDataDepth(data, 'dataPipeDimension', vectorData.data.axis); else depth = getNestedDataDepth(data); end % add indices until it matches depth. for iVec = (length(indexChain)+1):depth - indexChain{iVec} = types.util.dynamictable.addVecInd(DynamicTable, indexChain{end}); + indexChain{iVec} = types.util.dynamictable.addVecInd(dynamicTable, indexChain{end}); end % wrap until available vector indices match depth. @@ -55,7 +61,7 @@ function addRawData(DynamicTable, column, data) end % Now in index->data order. - nestedAdd(DynamicTable, flip(indexChain), data); + nestedAdd(dynamicTable, flip(indexChain), data); end function checkNestedShape(data) @@ -71,15 +77,21 @@ function checkNestedShape(data) end end -function initVecData(DynamicTable, column, dataType) +function initVecData(dynamicTable, columnName, dataType) + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + columnName (1,:) char + dataType (1,:) char + end + % Don't set the data until after indices are updated. if 8 == exist('types.hdmf_common.VectorData', 'class') - VecData = types.hdmf_common.VectorData(); + vectorData = types.hdmf_common.VectorData(); else - VecData = types.core.VectorData(); + vectorData = types.core.VectorData(); end - VecData.description = sprintf('AUTOGENERATED description for column `%s`', column); + vectorData.description = sprintf('AUTOGENERATED description for column `%s`', columnName); if strcmp(dataType, 'logical') % Logical is the lowest precedent type when concatenating primitive @@ -87,22 +99,25 @@ function initVecData(DynamicTable, column, dataType) % https://www.mathworks.com/help/releases/R2022a/matlab/matlab_prog/valid-combinations-of-unlike-classes.html % That said, we still use doubles by default because character arrays % will error if concatenated with logical arrays. - VecData.data = logical([]); + vectorData.data = logical([]); else - VecData.data = []; + vectorData.data = []; end - if isprop(DynamicTable, column) - DynamicTable.(column) = VecData; + if isprop(dynamicTable, columnName) + dynamicTable.(columnName) = vectorData; else - DynamicTable.vectordata.set(column, VecData); + dynamicTable.vectordata.set(columnName, vectorData); end end -function depth = getNestedDataDepth(data, varargin) - p = inputParser; - p.addParameter('dataPipeDimension', [], @(x)isnumeric(x) && (isempty(x) || isscalar(x))); - p.parse(varargin{:}); +function depth = getNestedDataDepth(data, options) + arguments + data + options.dataPipeDimension = [] + end + + validateDataPipeDimension(options.dataPipeDimension); depth = 1; subData = data; @@ -113,7 +128,7 @@ function initVecData(DynamicTable, column, dataType) % special case where the final data is in fact multiple rows to begin % with. - if isempty(p.Results.dataPipeDimension) + if isempty(options.dataPipeDimension) if ischar(subData) isMultiRow = 1 < size(subData, 1); else @@ -121,56 +136,66 @@ function initVecData(DynamicTable, column, dataType) || (isvector(subData) && 1 < length(subData)); end else - isMultiRow = 1 < size(subData, p.Results.dataPipeDimension); + isMultiRow = 1 < size(subData, options.dataPipeDimension); end if isMultiRow depth = depth + 1; end end -function numRows = nestedAdd(DynamicTable, indChain, data) - name = indChain{1}; +function numRows = nestedAdd(dynamicTable, indexChain, data) + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + indexChain (1,:) cell + data + end + + vectorName = indexChain{1}; - if isprop(DynamicTable, name) - Vector = DynamicTable.(name); - elseif isprop(DynamicTable, 'vectorindex') && DynamicTable.vectorindex.isKey(name) - Vector = DynamicTable.vectorindex.get(name); + if isprop(dynamicTable, vectorName) + vectorData = dynamicTable.(vectorName); + elseif isprop(dynamicTable, 'vectorindex') && dynamicTable.vectorindex.isKey(vectorName) + vectorData = dynamicTable.vectorindex.get(vectorName); else - Vector = DynamicTable.vectordata.get(name); + vectorData = dynamicTable.vectordata.get(vectorName); end - - if isa(Vector, 'types.hdmf_common.VectorIndex') || isa(Vector, 'types.core.VectorIndex') + + if isa(vectorData, 'types.hdmf_common.VectorIndex') || isa(vectorData, 'types.core.VectorIndex') if iscell(data) && ~iscellstr(data) numRows = length(data); for iEntry = 1:numRows - nestedAdd(DynamicTable, indChain(2:end), data{iEntry}); + nestedAdd(dynamicTable, indexChain(2:end), data{iEntry}); end else - numRows = nestedAdd(DynamicTable, indChain(2:end), data); + numRows = nestedAdd(dynamicTable, indexChain(2:end), data); end - add2Index(Vector, numRows); + add2Index(vectorData, numRows); else if ischar(data) data = mat2cell(data, ones(size(data, 1), 1)); end % char matrices converted to cell arrays containing character vectors. - if isa(Vector.data, 'types.untyped.DataPipe') - Vector.data.append(data); - numRows = size(Vector.data, Vector.data.axis); + if isa(vectorData.data, 'types.untyped.DataPipe') + vectorData.data.append(data); + numRows = size(vectorData.data, vectorData.data.axis); else - numRows = add2MemData(Vector, data); + numRows = add2MemData(vectorData, data); end end end -function numRows = add2MemData(VectorData, data) +function numRows = add2MemData(vectorData, data) %ADD2MEMDATA add to in-memory data. + arguments + vectorData + data + end - if isempty(VectorData.data) || isscalar(VectorData.data) + if isempty(vectorData.data) || isscalar(vectorData.data) appendBasis = data; else - appendBasis = VectorData.data; + appendBasis = vectorData.data; end % determine the basis for finding the concatenation dimension. if istable(appendBasis) @@ -187,27 +212,39 @@ function initVecData(DynamicTable, column, dataType) numRows = length(data); end - VectorData.data = cat(catDim, VectorData.data, data); + vectorData.data = cat(catDim, vectorData.data, data); end -function add2Index(VectorIndex, numElem) +function add2Index(vectorIndex, numElem) + arguments + vectorIndex + numElem (1,1) double + end + raggedOffset = 0; - if isa(VectorIndex.data, 'types.untyped.DataPipe') - if isa(VectorIndex.data.internal, 'types.untyped.datapipe.BlueprintPipe')... - && ~isempty(VectorIndex.data.internal.data) - raggedOffset = VectorIndex.data.internal.data(end); - elseif isa(VectorIndex.data.internal, 'types.untyped.datapipe.BoundPipe')... - && ~any(VectorIndex.data.internal.stub.dims == 0) - raggedOffset = VectorIndex.data.internal.stub(end); + if isa(vectorIndex.data, 'types.untyped.DataPipe') + if isa(vectorIndex.data.internal, 'types.untyped.datapipe.BlueprintPipe')... + && ~isempty(vectorIndex.data.internal.data) + raggedOffset = vectorIndex.data.internal.data(end); + elseif isa(vectorIndex.data.internal, 'types.untyped.datapipe.BoundPipe')... + && ~any(vectorIndex.data.internal.stub.dims == 0) + raggedOffset = vectorIndex.data.internal.stub(end); end - elseif ~isempty(VectorIndex.data) - raggedOffset = VectorIndex.data(end); + elseif ~isempty(vectorIndex.data) + raggedOffset = vectorIndex.data(end); end data = double(raggedOffset) + numElem; - if isa(VectorIndex.data, 'types.untyped.DataPipe') - VectorIndex.data.append(data); + if isa(vectorIndex.data, 'types.untyped.DataPipe') + vectorIndex.data.append(data); else - VectorIndex.data = [double(VectorIndex.data); data]; + vectorIndex.data = [double(vectorIndex.data); data]; + end +end + +function validateDataPipeDimension(dataPipeDimension) + if ~(isempty(dataPipeDimension) || (isnumeric(dataPipeDimension) && isscalar(dataPipeDimension))) + error('MATLAB:validation:UnableToConvert', ... + '`dataPipeDimension` must be empty or a numeric scalar.'); end end diff --git a/+types/+util/+dynamictable/addRow.m b/+types/+util/+dynamictable/addRow.m index 869f2030a..bd31cee6b 100644 --- a/+types/+util/+dynamictable/addRow.m +++ b/+types/+util/+dynamictable/addRow.m @@ -1,4 +1,4 @@ -function addRow(DynamicTable, varargin) +function addRow(dynamicTable, varargin) % ADDROW Given a dynamic table and a set of keyword arguments for the row, % add a single row to the dynamic table if using keywords, or multiple rows % if using a table. @@ -23,18 +23,24 @@ function addRow(DynamicTable, varargin) % keyword arguments. For table appending mode, this is how ragged arrays % are represented. - validateattributes(DynamicTable,... - {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'},... - {'scalar'}); - assert(~isempty(DynamicTable.colnames),... + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + end + arguments (Repeating) + varargin + end + + assert(nargin > 1, 'NWB:DynamicTable:AddRow:NoData', ... + 'Not enough arguments'); + + assert(~isempty(dynamicTable.colnames),... 'NWB:DynamicTable:AddRow:NoColumns',... - ['The `colnames` property of the Dynamic Table needs to be populated with a cell array '... - 'of column names before being able to add row data.']); - assert(nargin > 1, 'NWB:DynamicTable:AddRow:NoData', 'Not enough arguments'); + ['The `colnames` property of the Dynamic Table needs to be populated '... + 'with a cell array of column names before being able to add row data.']); - types.util.dynamictable.checkConfig(DynamicTable); + types.util.dynamictable.checkConfig(dynamicTable); - assert(~isa(DynamicTable.id.data, 'types.untyped.DataStub'),... + assert(~isa(dynamicTable.id.data, 'types.untyped.DataStub'),... 'NWB:DynamicTable:AddRow:Uneditable',... ['Cannot write to on-file Dynamic Tables without enabling data pipes. '... 'If this was produced with pynwb, please enable chunking for this table.']); @@ -44,6 +50,6 @@ function addRow(DynamicTable, varargin) ['Using MATLAB tables as input to the addRow DynamicTable method has '... 'been deprecated. Please, use key-value pairs instead']); else - types.util.dynamictable.addVarargRow(DynamicTable, varargin{:}); + types.util.dynamictable.addVarargRow(dynamicTable, varargin{:}); end end diff --git a/+types/+util/+dynamictable/addTableColumn.m b/+types/+util/+dynamictable/addTableColumn.m index 25d3c34ba..be1792bce 100644 --- a/+types/+util/+dynamictable/addTableColumn.m +++ b/+types/+util/+dynamictable/addTableColumn.m @@ -1,4 +1,9 @@ -function addTableColumn(DynamicTable, subTable) +function addTableColumn(dynamicTable, subTable) + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + subTable table + end + error('NWB:DynamicTable', ... ['Using MATLAB tables as input to the addColumn DynamicTable method has '... 'been deprecated. Please, use key-value pairs instead']) diff --git a/+types/+util/+dynamictable/addVarargColumn.m b/+types/+util/+dynamictable/addVarargColumn.m index 7d3baccd4..68554a3c1 100644 --- a/+types/+util/+dynamictable/addVarargColumn.m +++ b/+types/+util/+dynamictable/addVarargColumn.m @@ -1,64 +1,69 @@ -function addVarargColumn(DynamicTable, varargin) - -% parse inputs -p = inputParser(); -p.KeepUnmatched = true; -p.StructExpand = false; -parse(p, varargin{:}); -newColNames = DynamicTable.validate_colnames(fieldnames(p.Unmatched)); -newVectorData = p.Unmatched; -storageTargets = resolveStorageTargets(DynamicTable, newColNames, struct2cell(newVectorData)); - -% Check if any of the new columns already exist in the table -existingCols = getExistingColumns(DynamicTable, newColNames, storageTargets); -assert(isempty(existingCols), ... - 'NWB:DynamicTable:AddColumn:ColumnExists', ... - 'Column(s) { %s } already exist in the table', strjoin(existingCols, ', ')); - -% Check if this is the first column being added (no existing columns and no id data) -isFirstColumn = isempty(DynamicTable.colnames) && ... - (isempty(DynamicTable.id) || isempty(DynamicTable.id.data)); - -% get current table height - assume id length reflects table height -if ~isempty(DynamicTable.colnames) - tableHeight = types.util.dynamictable.internal.getColumnHeight(DynamicTable.id); -end +function addVarargColumn(dynamicTable, columnName, vectorData) -% If adding the first column, initialize the id with 0-indexed values -if isFirstColumn && ~isempty(newColNames) - % Determine the height of the first column - firstColName = newColNames{1}; - indexName = getIndexInSet(newVectorData, firstColName); - if isempty(indexName) - firstColData = newVectorData.(firstColName); - else - firstColData = newVectorData.(indexName); + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} end - newTableHeight = types.util.dynamictable.internal.getColumnHeight(firstColData); - types.util.dynamictable.internal.initDynamicTableId(DynamicTable, newTableHeight); - tableHeight = newTableHeight; -end + arguments (Repeating) + columnName (1,1) string + vectorData (1,1) {matnwb.common.validation.mustBeVectorData} + end -for i = 1:length(newColNames) - new_cn = newColNames{i}; - new_cv = newVectorData.(new_cn); - % check height match before adding column - if ~isempty(DynamicTable.colnames) - indexName = getIndexInSet(newVectorData,new_cn); + % Use cellstr so brace-indexing of column names below is valid. + newColumnNames = cellstr([columnName{:}]); + newVectorData = cell2struct(vectorData, newColumnNames, 2); % 2nd dim because vectorData is a row vector + storageTargets = resolveStorageTargets(dynamicTable, newColumnNames, vectorData); + % Check if any of the new columns already exist in the table + existingCols = getExistingColumns(dynamicTable, newColumnNames, storageTargets); + assert(isempty(existingCols), ... + 'NWB:DynamicTable:AddColumn:ColumnExists', ... + 'Column(s) { %s } already exist in the table', strjoin(existingCols, ', ')); + + % Check if this is the first column being added (no existing columns and no id data) + isFirstColumn = isempty(dynamicTable.colnames) && ... + (isempty(dynamicTable.id) || isempty(dynamicTable.id.data)); + + % get current table height - assume id length reflects table height + if ~isempty(dynamicTable.colnames) + tableHeight = types.util.dynamictable.internal.getColumnHeight(dynamicTable.id); + end + + % If adding the first column, initialize the id with 0-indexed values + if isFirstColumn && ~isempty(newColumnNames) + % Determine the height of the first column + firstColName = newColumnNames{1}; + indexName = getIndexInSet(newVectorData, firstColName); if isempty(indexName) - heightColumn = new_cv; + firstColData = newVectorData.(firstColName); else - heightColumn = newVectorData.(indexName); + firstColData = newVectorData.(indexName); end - currentColumnHeight = types.util.dynamictable.internal.getColumnHeight(heightColumn); - validateColumnHeight(new_cn, currentColumnHeight, tableHeight) + newTableHeight = types.util.dynamictable.internal.getColumnHeight(firstColData); + types.util.dynamictable.internal.initDynamicTableId(dynamicTable, newTableHeight); + tableHeight = newTableHeight; + end + + for iColumn = 1:length(newColumnNames) + newColumnName = newColumnNames{iColumn}; + newColumnVector = newVectorData.(newColumnName); + % check height match before adding column + if ~isempty(dynamicTable.colnames) + indexName = getIndexInSet(newVectorData, newColumnName); + + if isempty(indexName) + heightColumn = newColumnVector; + else + heightColumn = newVectorData.(indexName); + end + currentColumnHeight = types.util.dynamictable.internal.getColumnHeight(heightColumn); + + validateColumnHeight(newColumnName, currentColumnHeight, tableHeight) + end + assignColumn(dynamicTable, newColumnName, newColumnVector, storageTargets{iColumn}); + updateColnames(dynamicTable, newColumnName, newColumnVector) end - assignColumn(DynamicTable, new_cn, new_cv, storageTargets{i}); - updateColnames(DynamicTable, new_cn, new_cv) -end end function storageTargets = resolveStorageTargets(dynamicTable, columnNames, columnData) @@ -69,19 +74,19 @@ function addVarargColumn(DynamicTable, varargin) end end -function existingCols = getExistingColumns(dynamicTable, newColNames, storageTargets) +function existingCols = getExistingColumns(dynamicTable, newColumnNames, storageTargets) existingCols = {}; - + if ~isempty(dynamicTable.colnames) - existingCols = intersect(newColNames, dynamicTable.colnames); + existingCols = intersect(newColumnNames, dynamicTable.colnames); end - - for i = 1:length(newColNames) - newColumnName = newColNames{i}; + + for i = 1:length(newColumnNames) + newColumnName = newColumnNames{i}; if any(strcmp(existingCols, newColumnName)) continue; end - + switch storageTargets{i} case 'property' if ~isempty(dynamicTable.(newColumnName)) @@ -96,53 +101,62 @@ function addVarargColumn(DynamicTable, varargin) end end -function assignColumn(DynamicTable, columnName, columnValue, storageTarget) +function assignColumn(dynamicTable, columnName, columnValue, storageTarget) assert(any(strcmp(storageTarget, {'property', 'vectordata'})), ... 'NWB:DynamicTable:AddColumn:InternalError', ... 'Unrecognized storage target `%s` for column `%s`.', ... storageTarget, columnName); - + switch storageTarget case 'property' - DynamicTable.(columnName) = columnValue; + dynamicTable.(columnName) = columnValue; case 'vectordata' - DynamicTable.vectordata.set(columnName, columnValue); + dynamicTable.vectordata.set(columnName, columnValue); end end -function updateColnames(DynamicTable, new_cn, new_cv) +function updateColnames(dynamicTable, newColumnName, newColumnVector) % Skip update if new column name is already present in colnames. Column % names for schema-defined columns are added to the colnames property % automatically via property post-set hooks. - if any(strcmp(DynamicTable.colnames, new_cn)) + if any(strcmp(dynamicTable.colnames, newColumnName)) return end % Update colnames property if the column vector being added is not a vector index. - if ~isa(new_cv, 'types.hdmf_common.VectorIndex') || isa(new_cv, 'types.core.VectorIndex') - % assignColumn will update colnames for schema-defined columns via - % post set hook. Only add column name to colnames if if is not - % wlaready added. - DynamicTable.colnames{end+1} = new_cn; + if ~isa(newColumnVector, 'types.hdmf_common.VectorIndex') || isa(newColumnVector, 'types.core.VectorIndex') + dynamicTable.colnames{end+1} = newColumnName; end end function indexName = getIndexInSet(inputStruct, inputName) + arguments + inputStruct (1,1) struct + inputName {mustBeTextScalar} + end + % wrap input set with an empty dynamic table - T = types.hdmf_common.DynamicTable(); + dynamicTable = types.hdmf_common.DynamicTable(); % convert input structure to a set columnNames = fieldnames(inputStruct); - for i = 1:length(columnNames) - T.vectordata.set(columnNames{i},inputStruct.(columnNames{i})); + for iColumn = 1:length(columnNames) + columnName = columnNames{iColumn}; + dynamicTable.vectordata.set(columnName, inputStruct.(columnName)); end % use dynamic table function to get index name - indexName = types.util.dynamictable.getIndex(T, inputName); + indexName = types.util.dynamictable.getIndex(dynamicTable, inputName); end function validateColumnHeight(columnName, currentColumnHeight, tableHeight) + arguments + columnName {mustBeTextScalar} + currentColumnHeight (1,1) double + tableHeight (1,1) double + end + if currentColumnHeight ~= tableHeight error('NWB:DynamicTable:AddColumn:MissingRows', ... 'Column `%s` has detected height %d, but the table height is %d.', ... - columnName, currentColumnHeight, tableHeight) + columnName, currentColumnHeight, tableHeight) end end diff --git a/+types/+util/+dynamictable/addVarargRow.m b/+types/+util/+dynamictable/addVarargRow.m index 3a37c322b..9fbc10272 100644 --- a/+types/+util/+dynamictable/addVarargRow.m +++ b/+types/+util/+dynamictable/addVarargRow.m @@ -1,71 +1,84 @@ -function addVarargRow(DynamicTable, varargin) - p = inputParser(); - p.KeepUnmatched = true; - p.StructExpand = false; - addParameter(p, 'id', []); % `id` override but doesn't actually show up in `colnames` +function addVarargRow(dynamicTable, varargin) + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + end + arguments (Repeating) + varargin + end + + parser = inputParser(); + parser.KeepUnmatched = true; + parser.StructExpand = false; + addParameter(parser, 'id', []); % `id` override but doesn't actually show up in `colnames` - for iColumn = 1:length(DynamicTable.colnames) - addParameter(p, DynamicTable.colnames{iColumn}, []); + for iColumn = 1:length(dynamicTable.colnames) + addParameter(parser, dynamicTable.colnames{iColumn}, []); end - parse(p, varargin{:}); + parse(parser, varargin{:}); - assert(isempty(fieldnames(p.Unmatched)),... + assert(isempty(fieldnames(parser.Unmatched)),... 'NWB:DynamicTable:AddRow:InvalidColumns',... - 'Invalid column name(s) { %s }', strjoin(fieldnames(p.Unmatched), ', ')); + 'Invalid column name(s) { %s }', strjoin(fieldnames(parser.Unmatched), ', ')); - rowNames = fieldnames(p.Results); + rowNames = fieldnames(parser.Results); % not using setDiff because we want to retain set order. rowNames(strcmp(rowNames, 'id')) = []; - missingColumns = setdiff(p.UsingDefaults, {'id'}); + missingColumns = setdiff(parser.UsingDefaults, {'id'}); assert(isempty(missingColumns),... 'NWB:DynamicTable:AddRow:MissingColumns',... 'Missing columns { %s }', strjoin(missingColumns, ', ')); - specifiesId = ~any(strcmp(p.UsingDefaults, 'id')); + specifiesId = ~any(strcmp(parser.UsingDefaults, 'id')); if specifiesId - validateattributes(p.Results.id, {'numeric'}, {'scalar'}); + validateattributes(parser.Results.id, {'numeric'}, {'scalar'}); end - TypeMap = types.util.dynamictable.getTypeMap(DynamicTable); + typeMap = types.util.dynamictable.getTypeMap(dynamicTable); for iRow = 1:length(rowNames) - rn = rowNames{iRow}; - rv = p.Results.(rn); + rowName = rowNames{iRow}; + rowValue = parser.Results.(rowName); - if isKey(TypeMap, rn) - rv = validateType(TypeMap(rn), rv, rn); + if isKey(typeMap, rowName) + rowValue = validateType(typeMap(rowName), rowValue, rowName); end - types.util.dynamictable.addRawData(DynamicTable, rn, rv); + types.util.dynamictable.addRawData(dynamicTable, rowName, rowValue); end if specifiesId - newId = p.Results.id; - elseif isa(DynamicTable.id.data, 'types.untyped.DataPipe') - newId = DynamicTable.id.data.offset; + newId = parser.Results.id; + elseif isa(dynamicTable.id.data, 'types.untyped.DataPipe') + newId = dynamicTable.id.data.offset; else - newId = length(DynamicTable.id.data); + newId = length(dynamicTable.id.data); end - if isa(DynamicTable.id.data, 'types.untyped.DataPipe') - DynamicTable.id.data.append(newId); + if isa(dynamicTable.id.data, 'types.untyped.DataPipe') + dynamicTable.id.data.append(newId); else - DynamicTable.id.data = [double(DynamicTable.id.data); newId]; + dynamicTable.id.data = [double(dynamicTable.id.data); newId]; end end -function rv = validateType(TypeStruct, rv, rowName) - if strcmp(TypeStruct.type, 'cellstr') - assert(iscellstr(rv) || (ischar(rv) && (isempty(rv) || 1 == size(rv, 1))),... +function rowValue = validateType(typeStruct, rowValue, rowName) + arguments + typeStruct (1,1) struct + rowValue + rowName {mustBeTextScalar} + end + + if strcmp(typeStruct.type, 'cellstr') + assert(iscellstr(rowValue) || (ischar(rowValue) && (isempty(rowValue) || 1 == size(rowValue, 1))),... 'NWB:DynamicTable:AddRow:InvalidType',... 'Type of value must be a cell array of character vectors or a scalar character'); - elseif iscell(rv) - for iVal = 1:length(rv) - validateType(TypeStruct, rv{iVal}, rowName); + elseif iscell(rowValue) + for iVal = 1:length(rowValue) + validateType(typeStruct, rowValue{iVal}, rowName); end else - rv = types.util.checkDtype(rowName, TypeStruct.type, rv); + rowValue = types.util.checkDtype(rowName, typeStruct.type, rowValue); end end diff --git a/+types/+util/+dynamictable/addVecInd.m b/+types/+util/+dynamictable/addVecInd.m index 6f00de1b3..0159e4c50 100644 --- a/+types/+util/+dynamictable/addVecInd.m +++ b/+types/+util/+dynamictable/addVecInd.m @@ -1,45 +1,50 @@ -function vecIndName = addVecInd(DynamicTable, colName) +function vectorIndexName = addVecInd(dynamicTable, columnName) %ADDVECIND Add VectorIndex object to DynamicTable - validateattributes(colName, {'char'}, {'scalartext'}); - vecIndName = [colName '_index']; % arbitrary convention of appending '_index' to data column names + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + columnName {mustBeTextScalar} + end + + columnName = char(columnName); + vectorIndexName = [columnName '_index']; % arbitrary convention of appending '_index' to data column names - if isprop(DynamicTable, colName) - VecData = DynamicTable.(colName); - elseif isprop(DynamicTable, 'vectorindex') && isKey(DynamicTable.vectorindex, colName) - VecData = DynamicTable.vectorindex.get(colName); + if isprop(dynamicTable, columnName) + vectorData = dynamicTable.(columnName); + elseif isprop(dynamicTable, 'vectorindex') && isKey(dynamicTable.vectorindex, columnName) + vectorData = dynamicTable.vectorindex.get(columnName); else - VecData = DynamicTable.vectordata.get(colName); + vectorData = dynamicTable.vectordata.get(columnName); end - if isa(VecData.data, 'types.untyped.DataPipe') - oldDataHeight = VecData.data.offset; - elseif isa(VecData.data, 'types.untyped.DataStub') - oldDataHeight = VecData.data.dims(end); - elseif isvector(VecData.data) - oldDataHeight = length(VecData.data); + if isa(vectorData.data, 'types.untyped.DataPipe') + oldDataHeight = vectorData.data.offset; + elseif isa(vectorData.data, 'types.untyped.DataStub') + oldDataHeight = vectorData.data.dims(end); + elseif isvector(vectorData.data) + oldDataHeight = length(vectorData.data); else - oldDataHeight = size(VecData.data, ndims(VecData.data)); + oldDataHeight = size(vectorData.data, ndims(vectorData.data)); end % we presume that if data already existed in the vectordata, then % it was never a ragged array and thus its elements corresponded % directly to each row index. - vecView = types.untyped.ObjectView(VecData); + vectorView = types.untyped.ObjectView(vectorData); if 8 == exist('types.hdmf_common.VectorIndex', 'class') - VecIndex = types.hdmf_common.VectorIndex('target', vecView, 'data', (1:oldDataHeight) .'); + vectorIndex = types.hdmf_common.VectorIndex('target', vectorView, 'data', (1:oldDataHeight) .'); else - VecIndex = types.core.VectorIndex('target', vecView, 'data', (1:oldDataHeight) .'); + vectorIndex = types.core.VectorIndex('target', vectorView, 'data', (1:oldDataHeight) .'); end - if isprop(VecIndex, 'description') - VecIndex.description = sprintf('Index into column %s', colName); + if isprop(vectorIndex, 'description') + vectorIndex.description = sprintf('Index into column %s', columnName); end - if isprop(DynamicTable, vecIndName) - DynamicTable.(vecIndName) = VecIndex; - elseif isprop(DynamicTable, 'vectorindex') - DynamicTable.vectorindex.set(vecIndName, VecIndex); + if isprop(dynamicTable, vectorIndexName) + dynamicTable.(vectorIndexName) = vectorIndex; + elseif isprop(dynamicTable, 'vectorindex') + dynamicTable.vectorindex.set(vectorIndexName, vectorIndex); else - DynamicTable.vectordata.set(vecIndName, VecIndex); + dynamicTable.vectordata.set(vectorIndexName, vectorIndex); end end diff --git a/+types/+util/+dynamictable/checkConfig.m b/+types/+util/+dynamictable/checkConfig.m index 947bc38f6..c5818379f 100644 --- a/+types/+util/+dynamictable/checkConfig.m +++ b/+types/+util/+dynamictable/checkConfig.m @@ -19,7 +19,7 @@ function checkConfig(DynamicTable, ignoreList) % 5) No infinite VectorIndex reference loops exist. arguments - DynamicTable + DynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} ignoreList (1,:) cell = {}; end diff --git a/+types/+util/+dynamictable/clear.m b/+types/+util/+dynamictable/clear.m index d64979bb6..029d883c2 100644 --- a/+types/+util/+dynamictable/clear.m +++ b/+types/+util/+dynamictable/clear.m @@ -1,9 +1,11 @@ -function clear(DynamicTable) +function clear(dynamicTable) %CLEAR Given a valid DynamicTable object, clears all rows and type % information in the table. - validateattributes(DynamicTable, {'types.hdmf_common.DynamicTable', 'types.core.DynamicTable'}, {'scalar'}); + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + end - if isa(DynamicTable, 'types.core.DynamicTable') % Schema version <2.2.0 + if isa(dynamicTable, 'types.core.DynamicTable') % Schema version <2.2.0 elementIdentifierClass = @types.core.ElementIdentifiers; vectorDataClassName = 'types.core.VectorData'; vectorIndexClassName = 'types.core.VectorIndex'; @@ -13,11 +15,11 @@ function clear(DynamicTable) vectorIndexClassName = 'types.hdmf_common.VectorIndex'; end - DynamicTable.id = elementIdentifierClass(); - DynamicTable.vectordata = types.untyped.Set(@(nm, val)types.util.checkConstraint(... + dynamicTable.id = elementIdentifierClass(); + dynamicTable.vectordata = types.untyped.Set(@(nm, val)types.util.checkConstraint(... 'vectordata', nm, struct(), {vectorDataClassName}, val)); - if isprop(DynamicTable, 'vectorindex') % Schema version <2.3.0 - DynamicTable.vectorindex = types.untyped.Set(@(nm, val)types.util.checkConstraint(... + if isprop(dynamicTable, 'vectorindex') % Schema version <2.3.0 + dynamicTable.vectorindex = types.untyped.Set(@(nm, val)types.util.checkConstraint(... 'vectorindex', nm, struct(), {vectorIndexClassName}, val)); end end diff --git a/+types/+util/+dynamictable/getIndex.m b/+types/+util/+dynamictable/getIndex.m index 15a227c21..16516fae9 100644 --- a/+types/+util/+dynamictable/getIndex.m +++ b/+types/+util/+dynamictable/getIndex.m @@ -1,35 +1,37 @@ -function indexName = getIndex(DynamicTable, column) +function indexName = getIndex(dynamicTable, columnName) %GETINDEX Given a dynamic table and its column name, get its VectorIndex column name - validateattributes(DynamicTable,... - {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'},... - {'scalar'}); - validateattributes(column, {'char'}, {'scalartext'}); + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + columnName {mustBeTextScalar} + end + + columnName = char(columnName); indexName = ''; - if strcmp(column, 'id') + if strcmp(columnName, 'id') return; end % after Schema version 2.3.0, VectorIndex objects subclass VectorData which % meant that vectorindex and vectordata sets could be combined. - isLegacyDynamicTable = isprop(DynamicTable, 'vectorindex'); + isLegacyDynamicTable = isprop(dynamicTable, 'vectorindex'); if isLegacyDynamicTable - vecKeys = keys(DynamicTable.vectorindex); + vectorKeys = keys(dynamicTable.vectorindex); else - vecKeys = keys(DynamicTable.vectordata); + vectorKeys = keys(dynamicTable.vectordata); end - for i = 1:length(vecKeys) - vk = vecKeys{i}; + for iKey = 1:length(vectorKeys) + vectorKey = vectorKeys{iKey}; if isLegacyDynamicTable - vecData = DynamicTable.vectorindex.get(vk); + vectorData = dynamicTable.vectorindex.get(vectorKey); else - vecData = DynamicTable.vectordata.get(vk); + vectorData = dynamicTable.vectordata.get(vectorKey); end - if ~isa(vecData, 'types.hdmf_common.VectorIndex')... - && ~isa(vecData, 'types.core.VectorIndex') + if ~isa(vectorData, 'types.hdmf_common.VectorIndex')... + && ~isa(vectorData, 'types.core.VectorIndex') continue; end - if isVecIndColumn(DynamicTable, vecData, column) - indexName = vk; + if isVecIndColumn(dynamicTable, vectorData, columnName) + indexName = vectorKey; return; end end @@ -37,36 +39,42 @@ % check if dynamic table object has extended properties which point to % vector indices. These are specifically defined by the schema to be % properties. - DynamicTableProps = properties(DynamicTable); - isPropVecInd = false(size(DynamicTableProps)); - for i = 1:length(DynamicTableProps) - PropVec = DynamicTable.(DynamicTableProps{i}); - isPropVecInd(i) = isa(PropVec, 'types.hdmf_common.VectorIndex')... - || isa(PropVec, 'types.core.VectorIndex'); + dynamicTableProps = properties(dynamicTable); + isPropertyVectorIndex = false(size(dynamicTableProps)); + for iProp = 1:length(dynamicTableProps) + propertyValue = dynamicTable.(dynamicTableProps{iProp}); + isPropertyVectorIndex(iProp) = isa(propertyValue, 'types.hdmf_common.VectorIndex')... + || isa(propertyValue, 'types.core.VectorIndex'); end - DynamicTableProps = DynamicTableProps(isPropVecInd); - for i = 1:length(DynamicTableProps) - vk = DynamicTableProps{i}; - VecInd = DynamicTable.(vk); - if isVecIndColumn(DynamicTable, VecInd, column) - indexName = vk; + dynamicTableProps = dynamicTableProps(isPropertyVectorIndex); + for iProp = 1:length(dynamicTableProps) + vectorKey = dynamicTableProps{iProp}; + vectorIndex = dynamicTable.(vectorKey); + if isVecIndColumn(dynamicTable, vectorIndex, columnName) + indexName = vectorKey; return; end end end -function tf = isVecIndColumn(DynamicTable, VectorIndex, column) - if VectorIndex.target.has_path() - tf = endsWith(VectorIndex.target.path, ['/' column]); - elseif isprop(DynamicTable, column) - tf = VectorIndex.target.target == DynamicTable.(column); +function tf = isVecIndColumn(dynamicTable, vectorIndex, columnName) + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + vectorIndex + columnName (1,:) char + end + + if vectorIndex.target.has_path() + tf = endsWith(vectorIndex.target.path, ['/' columnName]); + elseif isprop(dynamicTable, columnName) + tf = vectorIndex.target.target == dynamicTable.(columnName); else - if isprop(DynamicTable, 'vectorindex') && DynamicTable.vectorindex.isKey(column) - Vec = DynamicTable.vectorindex.get(column); + if isprop(dynamicTable, 'vectorindex') && dynamicTable.vectorindex.isKey(columnName) + vectorData = dynamicTable.vectorindex.get(columnName); else - Vec = DynamicTable.vectordata.get(column); + vectorData = dynamicTable.vectordata.get(columnName); end - tf = VectorIndex.target.target == Vec; + tf = vectorIndex.target.target == vectorData; end end diff --git a/+types/+util/+dynamictable/getRow.m b/+types/+util/+dynamictable/getRow.m index 0550f94e3..7bf861d5a 100644 --- a/+types/+util/+dynamictable/getRow.m +++ b/+types/+util/+dynamictable/getRow.m @@ -1,4 +1,4 @@ -function subTable = getRow(DynamicTable, ind, varargin) +function subTable = getRow(dynamicTable, rowIndices, varargin) %GETROW get row for dynamictable % Index is a scalar 0-based index of the expected row. % optional keyword argument "columns" allows for only grabbing certain @@ -8,127 +8,137 @@ % The returned value is a set of output arguments in the order of % `colnames` or "columns" keyword argument if one exists. - validateattributes(DynamicTable,... - {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'}, {'scalar'}); - validateattributes(ind, {'numeric'}, {'integer', 'vector'}); + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + rowIndices {mustBeNumeric, mustBeInteger, mustBeVector} + end + arguments (Repeating) + varargin + end - p = inputParser; - addParameter(p, 'columns', DynamicTable.colnames, @(x)iscellstr(x)); - addParameter(p, 'useId', false, @(x)islogical(x)); - parse(p, varargin{:}); + parser = inputParser; + addParameter(parser, 'columns', dynamicTable.colnames, @(x)iscellstr(x)); + addParameter(parser, 'useId', false, @(x)islogical(x) && isscalar(x)); + parse(parser, varargin{:}); - columns = p.Results.columns; + columns = parser.Results.columns; row = cell(1, length(columns)); - if p.Results.useId - assert(~isempty(DynamicTable.id), ... + if parser.Results.useId + assert(~isempty(dynamicTable.id), ... 'NWB:DynamicTable:GetRow:MissingId', ... 'Cannot retrieve rows by `id` because the DynamicTable has no `id` column.'); - ind = getIndById(DynamicTable, ind); + rowIndices = getIndById(dynamicTable, rowIndices); else - validateattributes(ind, {'numeric'}, {'positive', 'vector'}); - validateRowIndices(DynamicTable, ind); + validateattributes(rowIndices, {'numeric'}, {'positive', 'vector'}); + validateRowIndices(dynamicTable, rowIndices); end - for i = 1:length(columns) - cn = columns{i}; + for iColumn = 1:length(columns) + columnName = columns{iColumn}; - indexNames = {cn}; + indexNames = {columnName}; while true - name = types.util.dynamictable.getIndex(DynamicTable, indexNames{end}); - if isempty(name) + indexName = types.util.dynamictable.getIndex(dynamicTable, indexNames{end}); + if isempty(indexName) break; end - indexNames{end+1} = name; + indexNames{end+1} = indexName; end - row{i} = select(DynamicTable, indexNames, ind); + row{iColumn} = select(dynamicTable, indexNames, rowIndices); - if ~istable(row{i}) - if iscolumn(row{i}) + if ~istable(row{iColumn}) + if iscolumn(row{iColumn}) % keep column vectors as is - elseif isrow(row{i}) - row{i} = row{i} .'; % transpose row vectors - elseif ndims(row{i}) >= 2 % i.e nd array where ndims >= 2 + elseif isrow(row{iColumn}) + row{iColumn} = row{iColumn} .'; % transpose row vectors + elseif ndims(row{iColumn}) >= 2 % i.e nd array where ndims >= 2 % permute arrays to place last dimension first - array_size = size(row{i}); - num_rows = numel(ind); + arraySize = size(row{iColumn}); + numRows = numel(rowIndices); - is_row_dim = array_size == num_rows; - if sum(is_row_dim) == 1 - if ~(is_row_dim(1) || is_row_dim(end)) - throw( InvalidVectorDataShapeError(cn) ) + isRowDim = arraySize == numRows; + if sum(isRowDim) == 1 + if ~(isRowDim(1) || isRowDim(end)) + throw( invalidVectorDataShapeError(columnName) ) end - elseif sum(is_row_dim) > 1 - if is_row_dim(1) && is_row_dim(end) + elseif sum(isRowDim) > 1 + if isRowDim(1) && isRowDim(end) % Last dimension takes precedence - is_row_dim(1:end-1) = false; + isRowDim(1:end-1) = false; warning('NWB:DynamicTable:VectorDataAmbiguousSize', ... ['The length of the first and last dimensions of ', ... 'VectorData for column "%s" match the number of ', ... 'rows in the dynamic table. Data is rearranged based on ', ... - 'the last dimension, assuming it corresponds with the table rows.'], cn) - elseif is_row_dim(1) - is_row_dim(2:end) = false; - elseif is_row_dim(end) - is_row_dim(1:end-1) = false; + 'the last dimension, assuming it corresponds with the table rows.'], columnName) + elseif isRowDim(1) + isRowDim(2:end) = false; + elseif isRowDim(end) + isRowDim(1:end-1) = false; else - throw( InvalidVectorDataShapeError(cn) ) + throw( invalidVectorDataShapeError(columnName) ) end end - row{i} = permute( row{i}, [find(is_row_dim), find(~is_row_dim)]); + row{iColumn} = permute(row{iColumn}, [find(isRowDim), find(~isRowDim)]); end end % cell-wrap single multidimensional matrices to prevent invalid % MATLAB tables - if isscalar(ind) && ~iscell(row{i}) && ~istable(row{i}) && ~isscalar(row{i}) - row{i} = row(i); + if isscalar(rowIndices) && ~iscell(row{iColumn}) && ~istable(row{iColumn}) && ~isscalar(row{iColumn}) + row{iColumn} = row(iColumn); end % convert compound data type scalar struct into an array of % structs. - if isscalar(row{i}) && isstruct(row{i}) - structNames = fieldnames(row{i}); - scalarStruct = row{i}; - rowStruct = row{i}; % same as scalarStruct to maintain the field names. - for iRow = 1:length(ind) + if isscalar(row{iColumn}) && isstruct(row{iColumn}) + structNames = fieldnames(row{iColumn}); + scalarStruct = row{iColumn}; + rowStruct = row{iColumn}; % same as scalarStruct to maintain the field names. + for iRow = 1:length(rowIndices) for iField = 1:length(structNames) fieldName = structNames{iField}; fieldData = scalarStruct.(fieldName); rowStruct(iRow).(fieldName) = fieldData(iRow); end end - row{i} = rowStruct .'; + row{iColumn} = rowStruct .'; end end if isempty(columns) - subTable = table('Size', [numel(ind), 0], 'VariableTypes', {}, 'VariableNames', {}); + subTable = table('Size', [numel(rowIndices), 0], 'VariableTypes', {}, 'VariableNames', {}); else subTable = table(row{:}, 'VariableNames', columns); end end -function selected = select(DynamicTable, colIndStack, matInd) +function selected = select(dynamicTable, columnIndexStack, matrixIndices) % recursive function which consumes the colIndStack and produces a nested % cell array. - column = colIndStack{end}; - if isprop(DynamicTable, column) - Vector = DynamicTable.(column); - elseif isprop(DynamicTable, 'vectorindex') && DynamicTable.vectorindex.isKey(column) % Schema version < 2.3.0 - Vector = DynamicTable.vectorindex.get(column); + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + columnIndexStack (1,:) cell + matrixIndices {mustBeNumeric, mustBeInteger, mustBeVector} + end + + columnName = columnIndexStack{end}; + if isprop(dynamicTable, columnName) + vectorData = dynamicTable.(columnName); + elseif isprop(dynamicTable, 'vectorindex') && dynamicTable.vectorindex.isKey(columnName) % Schema version < 2.3.0 + vectorData = dynamicTable.vectorindex.get(columnName); else - Vector = DynamicTable.vectordata.get(column); + vectorData = dynamicTable.vectordata.get(columnName); end - if isscalar(colIndStack) - if isa(Vector.data, 'types.untyped.DataStub') || ... - isa(Vector.data,'types.untyped.DataPipe') - if isa(Vector.data, 'types.untyped.DataStub') - refProp = Vector.data.dims; + if isscalar(columnIndexStack) + if isa(vectorData.data, 'types.untyped.DataStub') || ... + isa(vectorData.data,'types.untyped.DataPipe') + if isa(vectorData.data, 'types.untyped.DataStub') + refProp = vectorData.data.dims; else - refProp = Vector.data.internal.maxSize; + refProp = vectorData.data.internal.maxSize; end if length(refProp) == 2 && refProp(2) == 1 % catch row vector @@ -137,88 +147,93 @@ rank = length(refProp); end else - if iscolumn(Vector.data) + if iscolumn(vectorData.data) % catch row vector rank = 1; - elseif istable(Vector.data) + elseif istable(vectorData.data) rank = 1; else - rank = ndims(Vector.data); + rank = ndims(vectorData.data); end end selectInd = repmat({':'}, 1, rank); - if isa(Vector.data, 'types.untyped.DataPipe') - selectInd{Vector.data.axis} = matInd; + if isa(vectorData.data, 'types.untyped.DataPipe') + selectInd{vectorData.data.axis} = matrixIndices; else - selectInd{end} = matInd; + selectInd{end} = matrixIndices; end - if (isstruct(Vector.data) && isscalar(Vector.data)) || istable(Vector.data) - if istable(Vector.data) + if (isstruct(vectorData.data) && isscalar(vectorData.data)) || istable(vectorData.data) + if istable(vectorData.data) selected = table(); - fields = Vector.data.Properties.VariableNames; + fields = vectorData.data.Properties.VariableNames; else selected = struct(); - fields = fieldnames(Vector.data); + fields = fieldnames(vectorData.data); end - for i = 1:length(fields) - fieldName = fields{i}; - columnData = Vector.data.(fieldName); + for iField = 1:length(fields) + fieldName = fields{iField}; + columnData = vectorData.data.(fieldName); selected.(fieldName) = columnData(selectInd{:}); end else - selected = Vector.data(selectInd{:}); + selected = vectorData.data(selectInd{:}); end % shift dimensions of non-row vectors. otherwise will result in % invalid MATLAB table with uneven column height - if isa(Vector.data, 'types.untyped.DataPipe') + if isa(vectorData.data, 'types.untyped.DataPipe') selected = permute(selected, ... - circshift(1:ndims(selected), -(Vector.data.axis-1))); + circshift(1:ndims(selected), -(vectorData.data.axis-1))); end else - assert(isa(Vector, 'types.hdmf_common.VectorIndex') || isa(Vector, 'types.core.VectorIndex'),... + assert(isa(vectorData, 'types.hdmf_common.VectorIndex') || isa(vectorData, 'types.core.VectorIndex'),... 'NWB:DynamicTable:GetRow:InternalError',... 'Internal VectorIndex Stack is not using VectorIndex objects!'); - if isa(Vector.data, 'types.untyped.DataStub') || isa(Vector.data, 'types.untyped.DataPipe') - stopInds = uint64(Vector.data.load(matInd)); + if isa(vectorData.data, 'types.untyped.DataStub') || isa(vectorData.data, 'types.untyped.DataPipe') + stopInds = uint64(vectorData.data.load(matrixIndices)); else - stopInds = uint64(Vector.data(matInd)); + stopInds = uint64(vectorData.data(matrixIndices)); end - startIndInd = matInd - 1; - zeroMask = startIndInd == 0; - startInds = zeros(size(startIndInd)); - if ~isempty(startIndInd(~zeroMask)) - if isa(Vector.data, 'types.untyped.DataStub') || isa(Vector.data, 'types.untyped.DataPipe') - startInds(~zeroMask) = Vector.data.load(startIndInd(~zeroMask)); + startIndexIndices = matrixIndices - 1; + zeroMask = startIndexIndices == 0; + startInds = zeros(size(startIndexIndices)); + if ~isempty(startIndexIndices(~zeroMask)) + if isa(vectorData.data, 'types.untyped.DataStub') || isa(vectorData.data, 'types.untyped.DataPipe') + startInds(~zeroMask) = vectorData.data.load(startIndexIndices(~zeroMask)); else - startInds(~zeroMask) = Vector.data(startIndInd(~zeroMask)); + startInds(~zeroMask) = vectorData.data(startIndexIndices(~zeroMask)); end end startInds = startInds + 1; - selected = cell(length(matInd), 1); - for iRange = 1:length(matInd) + selected = cell(length(matrixIndices), 1); + for iRange = 1:length(matrixIndices) startInd = startInds(iRange); stopInd = stopInds(iRange); - selected{iRange} = select(DynamicTable,... - colIndStack(1:(end-1)),... + selected{iRange} = select(dynamicTable,... + columnIndexStack(1:(end-1)),... startInd:stopInd); end end end -function ind = getIndById(DynamicTable, id) - if isa(DynamicTable.id.data, 'types.untyped.DataStub')... - || isa(DynamicTable.id.data, 'types.untyped.DataPipe') - ids = DynamicTable.id.data.load(); +function rowIndices = getIndById(dynamicTable, idValues) + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + idValues {mustBeNumeric, mustBeInteger, mustBeVector} + end + + if isa(dynamicTable.id.data, 'types.untyped.DataStub')... + || isa(dynamicTable.id.data, 'types.untyped.DataPipe') + ids = dynamicTable.id.data.load(); else - ids = DynamicTable.id.data; + ids = dynamicTable.id.data; end - [idMatch, ind] = ismember(id, ids); + [idMatch, rowIndices] = ismember(idValues, ids); assert(all(idMatch), 'NWB:DynamicTable:GetRow:InvalidId',... 'Invalid ids found. If you wish to use row indices directly, remove the `useId` flag.'); end @@ -232,8 +247,12 @@ function validateRowIndices(dynamicTable, rowIndices) strjoin(compose('%d', rowIndices(rowIndices > tableHeight) ), ', '), tableHeight); end -function ME = InvalidVectorDataShapeError(column_name) - ME = MException('NWB:DynamicTable:InvalidVectorDataShape', ... - sprintf( ['Array data for column "%s" has a shape which do ', ... - 'not match the number of rows in the dynamic table.'], column_name )); +function exception = invalidVectorDataShapeError(columnName) + arguments + columnName {mustBeTextScalar} + end + + exception = MException('NWB:DynamicTable:InvalidVectorDataShape', ... + sprintf(['Array data for column "%s" has a shape which do ', ... + 'not match the number of rows in the dynamic table.'], columnName)); end diff --git a/+types/+util/+dynamictable/getTypeMap.m b/+types/+util/+dynamictable/getTypeMap.m index 6c33197f7..e5b9009be 100644 --- a/+types/+util/+dynamictable/getTypeMap.m +++ b/+types/+util/+dynamictable/getTypeMap.m @@ -1,41 +1,45 @@ -function TypeMap = getTypeMap(DynamicTable) +function typeMap = getTypeMap(dynamicTable) % GETTYPEMAP returns containers.Map mapping column name to struct % containing type name and size. - TypeMap = containers.Map; - if isempty(DynamicTable.id.data)... - || (isa(DynamicTable.id.data, 'types.untyped.DataPipe')... - && 0 == DynamicTable.id.data.offset) + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + end + + typeMap = containers.Map; + if isempty(dynamicTable.id.data)... + || (isa(dynamicTable.id.data, 'types.untyped.DataPipe')... + && 0 == dynamicTable.id.data.offset) return; end - TypeStruct = struct('type', '', 'dims', [0, 0]); - for i = 1:length(DynamicTable.colnames) - colnm = DynamicTable.colnames{i}; - if isprop(DynamicTable, colnm) - colVecData = DynamicTable.(colnm); + typeInfo = struct('type', '', 'dims', [0, 0]); + for iColumn = 1:length(dynamicTable.colnames) + columnName = dynamicTable.colnames{iColumn}; + if isprop(dynamicTable, columnName) + columnVectorData = dynamicTable.(columnName); else - colVecData = DynamicTable.vectordata.get(colnm); + columnVectorData = dynamicTable.vectordata.get(columnName); end - if isa(colVecData.data, 'types.untyped.DataPipe') - colval = colVecData.data.load(1); - elseif istable(colVecData.data) - colval = colVecData.data; + if isa(columnVectorData.data, 'types.untyped.DataPipe') + columnValue = columnVectorData.data.load(1); + elseif istable(columnVectorData.data) + columnValue = columnVectorData.data; else - colval = colVecData.data(1); + columnValue = columnVectorData.data(1); end - if iscellstr(colval) - TypeStruct.type = 'cellstr'; + if iscellstr(columnValue) + typeInfo.type = 'cellstr'; else - TypeStruct.type = class(colval); + typeInfo.type = class(columnValue); end - if isa(colVecData.data, 'types.untyped.DataPipe') - TypeStruct.dims = colVecData.data.internal.maxSize; + if isa(columnVectorData.data, 'types.untyped.DataPipe') + typeInfo.dims = columnVectorData.data.internal.maxSize; else - TypeStruct.dims = size(colVecData.data); + typeInfo.dims = size(columnVectorData.data); end - TypeMap(colnm) = TypeStruct; + typeMap(columnName) = typeInfo; end end diff --git a/+types/+util/+dynamictable/nwbToTable.m b/+types/+util/+dynamictable/nwbToTable.m index 50657d24c..a8cca77c3 100644 --- a/+types/+util/+dynamictable/nwbToTable.m +++ b/+types/+util/+dynamictable/nwbToTable.m @@ -1,4 +1,4 @@ -function matlabTable = nwbToTable(DynamicTable, index) +function matlabTable = nwbToTable(dynamicTable, index) %NWBTOTABLE converts from a NWB DynamicTable to a MATLAB table % % MATLABTABLE = NWBTOTABLE(T) converts object T of class types.core.DynamicTable @@ -23,26 +23,22 @@ % ); % MATLABTABLE = nwb2table(MYTABLE); - % make sure input is dynamic table - validateattributes(DynamicTable,... - {'types.core.DynamicTable', 'types.hdmf_common.DynamicTable'},... - {'scalar'}); - - if nargin < 2 - index = true; + arguments + dynamicTable (1,1) {matnwb.common.validation.mustBeDynamicTable} + index (1,1) logical = true end - if isempty(DynamicTable.id) - matlabTable = table({}, 'VariableNames', [{'id'} DynamicTable.colnames]); + if isempty(dynamicTable.id) + matlabTable = table({}, 'VariableNames', [{'id'} dynamicTable.colnames]); return; end % initialize table with id column - if isa(DynamicTable.id.data, 'types.untyped.DataStub')... - || isa(DynamicTable.id.data, 'types.untyped.DataPipe') - ids = DynamicTable.id.data.load(); + if isa(dynamicTable.id.data, 'types.untyped.DataStub')... + || isa(dynamicTable.id.data, 'types.untyped.DataPipe') + ids = dynamicTable.id.data.load(); else - ids = DynamicTable.id.data; + ids = dynamicTable.id.data; end matlabTable = table( ... ids, ... @@ -50,37 +46,37 @@ ); % deal with DynamicTableRegion columns when index is false - [columns, remainingColumns] = deal(DynamicTable.colnames); + [columns, remainingColumns] = deal(dynamicTable.colnames); columnDescriptions = repmat({''}, 1, length(columns)); - for i = 1:length(columns) - cn = columns{i}; - if isprop(DynamicTable, cn) - cv = DynamicTable.(cn); - elseif isprop(DynamicTable, 'vectorindex') && DynamicTable.vectorindex.isKey(cn) % Schema version < 2.3.0 - cv = DynamicTable.vectorindex.get(cn); + for iColumn = 1:length(columns) + columnName = columns{iColumn}; + if isprop(dynamicTable, columnName) + columnVector = dynamicTable.(columnName); + elseif isprop(dynamicTable, 'vectorindex') && dynamicTable.vectorindex.isKey(columnName) % Schema version < 2.3.0 + columnVector = dynamicTable.vectorindex.get(columnName); else - cv = DynamicTable.vectordata.get(cn); + columnVector = dynamicTable.vectordata.get(columnName); end - columnDescriptions{i} = cv.description; + columnDescriptions{iColumn} = columnVector.description; if ~index && ... - (isa(cv,'types.hdmf_common.DynamicTableRegion') ||... - isa(cv,'types.core.DynamicTableRegion')) - row_idxs = cv.data; - ref_table = cv.table.target; - cv = cell(length(row_idxs),1); - for r = 1:length(row_idxs) - cv{r,1} = ref_table.getRow(row_idxs(r)+1); + (isa(columnVector,'types.hdmf_common.DynamicTableRegion') ||... + isa(columnVector,'types.core.DynamicTableRegion')) + rowIndices = columnVector.data; + referencedTable = columnVector.table.target; + columnValue = cell(length(rowIndices),1); + for iRow = 1:length(rowIndices) + columnValue{iRow,1} = referencedTable.getRow(rowIndices(iRow)+1); end - matlabTable.(cn) = cv; - remainingColumns = setdiff(remainingColumns, cn, 'stable'); + matlabTable.(columnName) = columnValue; + remainingColumns = setdiff(remainingColumns, columnName, 'stable'); else % pass end end % append remaining columns to table % making the assumption that length of ids reflects table height - matlabTable = [matlabTable DynamicTable.getRow( ... + matlabTable = [matlabTable dynamicTable.getRow( ... 1:length(ids), ... 'columns', remainingColumns ... )];