From a623d386921e87ee6114bbf71819e2060699bc06 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Sep 2024 21:33:59 +0200 Subject: [PATCH 1/5] Fix: Ensure autogenerated id column is column vector in util.table2nwb --- +util/table2nwb.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+util/table2nwb.m b/+util/table2nwb.m index b87a2d95..c7416a31 100644 --- a/+util/table2nwb.m +++ b/+util/table2nwb.m @@ -18,7 +18,7 @@ if ismember('id', T.Properties.VariableNames) id = T.id; else - id = 0:height(T)-1; + id = transpose( 0:height(T)-1 ); % Must be column vector end nwbtable = types.hdmf_common.DynamicTable( ... From 9de705dc1bc93f5cc22e896056a13e286b3864c0 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Sep 2024 22:37:18 +0200 Subject: [PATCH 2/5] Add table's variable description to vectordata description property --- +util/table2nwb.m | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/+util/table2nwb.m b/+util/table2nwb.m index c7416a31..b2513b94 100644 --- a/+util/table2nwb.m +++ b/+util/table2nwb.m @@ -28,8 +28,15 @@ for col = T if ~strcmp(col.Properties.VariableNames{1},'id') + + if ~isempty(col.Properties.VariableDescriptions{1}) + description = col.Properties.VariableDescriptions{1}; + else + description = 'no description provided'; + end + nwbtable.vectordata.set(col.Properties.VariableNames{1}, ... types.hdmf_common.VectorData('data', col.Variables',... - 'description', 'my description')); + 'description', description)); end end \ No newline at end of file From dfcbfe5a3eddc8b071ad158c2e130c04b4599a29 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Sep 2024 23:10:21 +0200 Subject: [PATCH 3/5] Add nwb type descriptions to table's VariableDescription property --- +types/+util/+dynamictable/nwbToTable.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/+types/+util/+dynamictable/nwbToTable.m b/+types/+util/+dynamictable/nwbToTable.m index 734f5372..de4f3d8b 100644 --- a/+types/+util/+dynamictable/nwbToTable.m +++ b/+types/+util/+dynamictable/nwbToTable.m @@ -51,6 +51,7 @@ % 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}; @@ -61,6 +62,7 @@ else cv = DynamicTable.vectordata.get(cn); end + columnDescriptions{i} = cv.description; if ~index && ... (isa(cv,'types.hdmf_common.DynamicTableRegion') ||... isa(cv,'types.core.DynamicTableRegion')) @@ -85,3 +87,6 @@ % Update the columns order to be the same as the original matlabTable = matlabTable(:, [{'id'}, columns]); + +% Add variable descriptions +matlabTable.Properties.VariableDescriptions = [{''}, columnDescriptions]; From 475866b7bfdaba63d4778ff91662f29c0a240073 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Sep 2024 23:15:27 +0200 Subject: [PATCH 4/5] Add conversion from table to nwb table in tutorial (Issue #571 ) --- tutorials/dynamic_tables.mlx | Bin 10947 -> 11621 bytes tutorials/html/dynamic_tables.html | 81 +++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/tutorials/dynamic_tables.mlx b/tutorials/dynamic_tables.mlx index 48c94846809563e06d9162ab239ae80696dfe605..35411bdfcfcc520f0ac0983dc6cebf9cf9e0df88 100644 GIT binary patch delta 9697 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 H`H%WPM21kC diff --git a/tutorials/html/dynamic_tables.html b/tutorials/html/dynamic_tables.html index 5d46a4fd..1549783e 100644 --- a/tutorials/html/dynamic_tables.html +++ b/tutorials/html/dynamic_tables.html @@ -38,12 +38,12 @@ .rightPaneElement .textElement { padding-top: 2px; padding-left: 9px;} .S12 { border-left: 1px solid rgb(217, 217, 217); border-right: 1px solid rgb(217, 217, 217); border-top: 1px solid rgb(217, 217, 217); border-bottom: 1px solid rgb(217, 217, 217); border-radius: 4px; padding: 6px 45px 4px 13px; line-height: 18.004px; min-height: 0px; white-space: nowrap; color: rgb(33, 33, 33); font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 14px; } .S13 { margin: 15px 10px 5px 4px; padding: 0px; line-height: 18px; min-height: 0px; white-space: pre-wrap; color: rgb(33, 33, 33); font-family: Helvetica, Arial, sans-serif; font-style: normal; font-size: 17px; font-weight: 700; text-align: left; } -.S14 { border-left: 1px solid rgb(217, 217, 217); border-right: 1px solid rgb(217, 217, 217); border-top: 1px solid rgb(217, 217, 217); border-bottom: 1px solid rgb(217, 217, 217); border-radius: 4px 4px 0px 0px; padding: 6px 45px 4px 13px; line-height: 18.004px; min-height: 0px; white-space: nowrap; color: rgb(33, 33, 33); font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 14px; } .embeddedOutputsVariableTableElement .ClientViewDiv table tr { height: 22px; white-space: nowrap;} .embeddedOutputsVariableTableElement .ClientViewDiv table tr td,.embeddedOutputsVariableTableElement .ClientViewDiv table tr th { background-color:white; text-overflow: ellipsis; font-family: 'Arial', sans-serif; font-size: 12px; overflow : hidden;} .embeddedOutputsVariableTableElement .ClientViewDiv table tr span { text-overflow: ellipsis; padding: 3px;} .embeddedOutputsVariableTableElement .ClientViewDiv table tr th { color: rgba(0,0,0,0.5); padding: 3px; font-size: 9px;} /* ClientDocument has a summary bar child that takes up 17px, this clashes with overflow on the view which allots space for scrollbars. On print preview, this causes headers from to overlap on subsequent pages. Displaying Document as flex renders summarybar and view in column format and fixes the issue g2788485 */.embeddedOutputsVariableTableElement .ClientDocument { display: flex; flex-direction: column;} +.S14 { border-left: 1px solid rgb(217, 217, 217); border-right: 1px solid rgb(217, 217, 217); border-top: 1px solid rgb(217, 217, 217); border-bottom: 1px solid rgb(217, 217, 217); border-radius: 4px 4px 0px 0px; padding: 6px 45px 4px 13px; line-height: 18.004px; min-height: 0px; white-space: nowrap; color: rgb(33, 33, 33); font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 14px; } .S15 { margin: 10px 10px 9px 4px; padding: 0px; line-height: 21px; min-height: 0px; white-space: pre-wrap; color: rgb(33, 33, 33); font-family: Helvetica, Arial, sans-serif; font-style: normal; font-size: 14px; font-weight: 400; text-align: left; } .embeddedOutputsMatrixElement,.eoOutputWrapper .matrixElement { min-height: 18px; box-sizing: border-box;} .embeddedOutputsMatrixElement .matrixElement,.eoOutputWrapper .matrixElement,.rtcDataTipElement .matrixElement { position: relative;} @@ -69,31 +69,41 @@ .outputsOnRight .embeddedOutputsVariableElement.rightPaneElement .eoOutputContent { /* Remove extra space allocated for navigation border */ margin-top: 0; margin-bottom: 0;} .variableNameElement { margin-bottom: 3px; display: inline-block;} /* * Ellipses as base64 for HTML export. */.matrixElement .horizontalEllipsis,.rtcDataTipElement .matrixElement .horizontalEllipsis { display: inline-block; margin-top: 3px; /* base64 encoded version of images-liveeditor/HEllipsis.png */ width: 30px; height: 12px; background-repeat: no-repeat; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAJCAYAAADO1CeCAAAAJUlEQVR42mP4//8/A70xw0i29BUDFPxnAEtTW37wWDqakIa4pQDvOOG89lHX2gAAAABJRU5ErkJggg==");} -.matrixElement .verticalEllipsis,.textElement .verticalEllipsis,.rtcDataTipElement .matrixElement .verticalEllipsis,.rtcDataTipElement .textElement .verticalEllipsis { margin-left: 35px; /* base64 encoded version of images-liveeditor/VEllipsis.png */ width: 12px; height: 30px; background-repeat: no-repeat; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAZCAYAAAAIcL+IAAAALklEQVR42mP4//8/AzGYgWyFMECMwv8QddRS+P//KyimlmcGUOFoOI6GI/UVAgDnd8Dd4+NCwgAAAABJRU5ErkJggg==");}

DynamicTables Tutorial

This is a user guide to interacting with DynamicTable objects in MatNWB.

MatNWB Setup

Start by setting up your MATLAB workspace. The code below adds the directory containing the MatNWB package to the MATLAB search path. MatNWB works by automatically creating API classes based on a defined schema.
%{
path_to_matnwb = '~/Repositories/matnwb'; % change to your own path location
addpath(genpath(pwd));
%}

Constructing a table with initialized columns

The DynamicTable class represents a column-based table to which you can add custom columns. It consists of a a description, a list of columns , and a list of row IDs. You can create a DynamicTable by first defining the VectorData objects that will make up the columns of the table. Each VectorData object must contain the same number of rows. A list of rows IDs may be passed to the DynamicTable using the id argument. Row IDs are a useful way to access row information independent of row location index. The list of row IDs must be cast as an ElementIdentifiers object before being passed to the DynamicTable object. If no value is passed to id, an ElementIdentifiers object with 0-indexed row IDs will be created for you automatically.
MATLAB Syntax Note: Using column vectors is crucial to properly build vectors and tables. When defining individual values, make sure to use semi-colon (;) instead of instead of comma (,) when defining the data fields of these.
col1 = types.hdmf_common.VectorData( ...
'description', 'column #1', ...
'data', [1;2] ...
);
 
col2 = types.hdmf_common.VectorData( ...
'description', 'column #2', ...
'data', {'a';'b'} ...
);
 
my_table = types.hdmf_common.DynamicTable( ...
'description', 'an example table', ...
'colnames', {'col1', 'col2'}, ...
'col1', col1, ...
'col2', col2, ...
'id', types.hdmf_common.ElementIdentifiers('data', [0;1]) ... % 0-indexed, for compatibility with Python
);
my_table
my_table =
DynamicTable with properties: +.matrixElement .verticalEllipsis,.textElement .verticalEllipsis,.rtcDataTipElement .matrixElement .verticalEllipsis,.rtcDataTipElement .textElement .verticalEllipsis { margin-left: 35px; /* base64 encoded version of images-liveeditor/VEllipsis.png */ width: 12px; height: 30px; background-repeat: no-repeat; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAZCAYAAAAIcL+IAAAALklEQVR42mP4//8/AzGYgWyFMECMwv8QddRS+P//KyimlmcGUOFoOI6GI/UVAgDnd8Dd4+NCwgAAAABJRU5ErkJggg==");}

DynamicTables Tutorial

This is a user guide to interacting with DynamicTable objects in MatNWB.

MatNWB Setup

Start by setting up your MATLAB workspace. The code below adds the directory containing the MatNWB package to the MATLAB search path. MatNWB works by automatically creating API classes based on a defined schema.
%{
path_to_matnwb = '~/Repositories/matnwb'; % change to your own path location
addpath(genpath(pwd));
%}

Constructing a table with initialized columns

The DynamicTable class represents a column-based table to which you can add custom columns. It consists of a description, a list of columns , and a list of row IDs. You can create a DynamicTable by first defining the VectorData objects that will make up the columns of the table. Each VectorData object must contain the same number of rows. A list of rows IDs may be passed to the DynamicTable using the id argument. Row IDs are a useful way to access row information independent of row location index. The list of row IDs must be cast as an ElementIdentifiers object before being passed to the DynamicTable object. If no value is passed to id, an ElementIdentifiers object with 0-indexed row IDs will be created for you automatically.
MATLAB Syntax Note: Using column vectors is crucial to properly build vectors and tables. When defining individual values, make sure to use semi-colon (;) instead of instead of comma (,) when defining the data fields of these.
col1 = types.hdmf_common.VectorData( ...
'description', 'column #1', ...
'data', [1;2] ...
);
 
col2 = types.hdmf_common.VectorData( ...
'description', 'column #2', ...
'data', {'a';'b'} ...
);
 
my_table = types.hdmf_common.DynamicTable( ...
'description', 'an example table', ...
'colnames', {'col1', 'col2'}, ...
'col1', col1, ...
'col2', col2, ...
'id', types.hdmf_common.ElementIdentifiers('data', [0;1]) ... % 0-indexed, for compatibility with Python
);
my_table
my_table =
DynamicTable with properties: id: [1×1 types.hdmf_common.ElementIdentifiers] colnames: {'col1' 'col2'} description: 'an example table' vectordata: [2×1 types.untyped.Set] -

Adding rows

You can add rows to an existing DynamicTable using the object's addRow method. One way of using this method is to pass in the names of columns as parameter names followed by the elements to append. The class of the elements of the column must match the elements to append.
my_table.addRow('col1', 3, 'col2', {'c'}, 'id', 2);

Adding columns

You can add new columns to an existing DynamicTable object using the addColumn method. One way of using this method is to pass in the names of each new column followed by the corresponding values for each new column. The height of the new columns must match the height of the table.
col3 = types.hdmf_common.VectorData('description', 'column #3', ...
'data', [100; 200; 300]);
col4 = types.hdmf_common.VectorData('description', 'column #4', ...
'data', {'a1'; 'b2'; 'c3'});
 
my_table.addColumn('col3', col3,'col4', col4);

Enumerated (categorical) data

EnumData is a special type of column for storing an enumerated data type. This way each unique value is stored once, and the data references those values by index. Using this method is more efficient than storing a single value many times, and has the advantage of communicating to downstream tools that the data is categorical in nature.

Warning Regarding EnumData

EnumData is currently an experimental feature and as such should not be used in a production environment.
CellTypeElements = types.hdmf_common.VectorData(...
'description', 'fixed set of elements referenced by cell_type' ...
, 'data', {'aa', 'bb', 'cc'} ... % the enumerated elements
);
CellType = types.hdmf_experimental.EnumData( ...
'description', 'this column holds categorical variables' ... % properties derived from VectorData
, 'data', [0, 1, 2, 1, 0] ... % zero-indexed offset to elements.
, 'elements', types.untyped.ObjectView(CellTypeElements) ...
);
 
MyTable = types.hdmf_common.DynamicTable('description', 'an example table');
MyTable.vectordata.set('cell_type_elements', CellTypeElements); % the *_elements format is required for compatibility with pynwb
MyTable.addColumn('cell_type', CellType);

Ragged array columns

A table column with a different number of elements for each row is called a "ragged array column." To define a table with a ragged array column, pass both the VectorData and the corresponding VectorIndex as columns of the DynamicTable object. The VectorData columns will contain the data values. The VectorIndex column serves to indicate how to arrange the data across rows. By convention the VectorIndex object corresponding to a particular column must have have the same name with the addition of the '_index' suffix.
Below, the VectorIndex values indicate to place the 1st to 3rd (inclusive) elements of the VectorData into the first row and 4th element into the second row. The resulting table will have the cell {'1a'; '1b'; '1c'} in the first row and the cell {'2a'} in the second row.
 
col1 = types.hdmf_common.VectorData( ...
'description', 'column #1', ...
'data', {'1a'; '1b'; '1c'; '2a'} ...
);
 
col1_index = types.hdmf_common.VectorIndex( ...
'description', 'column #1 index', ...
'target',types.untyped.ObjectView(col1), ... % object view of target column
'data', [3; 4] ...
);
 
table_ragged_col = types.hdmf_common.DynamicTable( ...
'description', 'an example table', ...
'colnames', {'col1'}, ...
'col1', col1, ...
'col1_index', col1_index, ...
'id', types.hdmf_common.ElementIdentifiers('data', [0; 1]) ... % 0-indexed, for compatibility with Python
);

Adding ragged array rows

You can add a new row to the ragged array column. Under the hood, the addRow method will add the appropriate value to the VectorIndex column to maintain proper formatting.
table_ragged_col.addRow('col1', {'3a'; '3b'; '3c'}, 'id', 2);

Accessing row elements

You can access data from entire rows of a DynamicTable object by calling the getRow method for the corresponding object. You can supply either an individual row number or a list of row numbers.
my_table.getRow(1)
ans = 1×4 table
 col1col2col3col4
11'a'100'a1'
If you want to access values for just a subset of columns you can pass in the 'columns' arguement along with a cell array with the desired column names
my_table.getRow(1:3, 'columns', {'col1'})
ans = 3×1 table
 col1
11
22
33
You can also access specific rows by their corresponding row ID's, if they have been defined, by supplying a 'true' Boolean to the 'useId' parameter
my_table.getRow(1, 'useId', true)
ans = 1×4 table
 col1col2col3col4
12'b'200'b2'
For a ragged array columns, the getRow method will return a cell with different number of elements for each row
table_ragged_col.getRow(1:2)
ans = 2×1 table
 col1
1[{'1a'};{'1b'};{'1c'}]
21×1 cell

Accessing column elements

To acess all rows from a particular column use the .get method on the vectordata field of the DynamicTable object
 
my_table.vectordata.get('col2').data
ans = 3×1 cell
'a'
'b'
'c'

Referencing rows of other tables

You can create a column that references rows of other tables by adding a DynamicTableRegion object as a column of a DynamicTable. This is analogous to a foreign key in a relational database. The DynamicTableRegion class takes in an ObjectView object as arguement. ObjectView objects create links from one object type referencing another.
dtr_col = types.hdmf_common.DynamicTableRegion( ...
'description', 'references multiple rows of earlier table', ...
'data', [0; 1; 1; 0], ... # 0-indexed
'table',types.untyped.ObjectView(my_table) ... % object view of target table
);
 
data_col = types.hdmf_common.VectorData( ...
'description', 'data column', ...
'data', {'a'; 'b'; 'c'; 'd'} ...
);
 
dtr_table = types.hdmf_common.DynamicTable( ...
'description', 'test table with DynamicTableRegion', ...
'colnames', {'dtr_col','data_col'}, ...
'dtr_col', dtr_col, ...
'data_col',data_col, ...
'id',types.hdmf_common.ElementIdentifiers('data', [0; 1; 2; 3]) ...
);

Converting a DynamicTable to a MATLAB table

You can convert a DynamicTable object to a MATLAB table by making use of the object's toTable method. This is a useful way to view the whole table in a human-readable format.
my_table.toTable()
ans = 3×5 table
 idcol1col2col3col4
101'a'100'a1'
212'b'200'b2'
323'c'300'c3'
When the DynamicTable object contains a column that references other tables, you can pass in a Boolean to indicate whether to include just the row indices of the referenced table. Passing in false will result in inclusion of the referenced rows as nested tables.
dtr_table.toTable(false)
ans = 4×3 table
 iddtr_coldata_col
101×4 table'a'
211×4 table'b'
321×4 table'c'
431×4 table'd'

Creating an expandable table

When using the default HDF5 backend, each column of these tables is an HDF5 Dataset, which by default are set to an unchangeable size. This means that once a file is written, it is not possible to add a new row. If you want to be able to save this file, load it, and add more rows to the table, you will need to set this up when you create the VectorData and ElementIdentifiers columns of a DynamicTable. Specifically, you must wrap the column data with a DataPipe object. The DataPipe class takes in maxSize and axis as arguments to indicate the maximum desired size for each axis and the axis to whcih to append to, respectively. For example, creating a DataPipe object with a maxSize value equal to [Inf, 1] indicates that the number of rows may increase indifinetely. In contrast, setting maxSize equal to [8, 1] would allow the column to grow to a maximum height of 8.
% create NwbFile object with required fields
file= NwbFile( ...
'session_start_time', datetime('2021-01-01 00:00:00', 'TimeZone', 'local'), ...
'identifier', 'ident1', ...
'session_description', 'ExpandableTableTutorial' ...
);
 
% create VectorData objects with DataPipe objects
start_time_exp = types.hdmf_common.VectorData( ...
'description', 'start times column', ...
'data', types.untyped.DataPipe( ...
'data', [1, 2], ... # data must be numerical
'maxSize', Inf ...
) ...
);
 
stop_time_exp = types.hdmf_common.VectorData( ...
'description', 'stop times column', ...
'data', types.untyped.DataPipe( ...
'data', [2, 3], ... #data must be numerical
'maxSize', Inf ...
) ...
);
 
random_exp = types.hdmf_common.VectorData( ...
'description', 'random data column', ...
'data', types.untyped.DataPipe( ...
'data', rand(5, 2), ... #data must be numerical
'maxSize', [5, Inf], ...
'axis', 2 ...
) ...
);
 
ids_exp = types.hdmf_common.ElementIdentifiers( ...
'data', types.untyped.DataPipe( ...
'data', int32([0; 1]), ... # data must be numerical
'maxSize', Inf ...
) ...
);
% create expandable table
colnames = {'start_time', 'stop_time', 'randomvalues'};
file.intervals_trials = types.core.TimeIntervals( ...
'description', 'test expdandable dynamic table', ...
'colnames', colnames, ...
'start_time', start_time_exp, ...
'stop_time', stop_time_exp, ...
'randomvalues', random_exp, ...
'id', ids_exp ...
);
% export file
nwbExport(file, 'expandableTableTestFile.nwb');
Now, you can read in the file, add more rows, and save again to file
readFile = nwbRead('expandableTableTestFile.nwb', 'ignorecache');
readFile.intervals_trials.addRow( ...
'start_time', 3, ...
'stop_time', 4, ...
'randomvalues', rand(5,1), ...
'id', 2 ...
)
nwbExport(readFile, 'expandableTableTestFile.nwb');
Note: DataPipe objects change how the dimension of the datasets for each column map onto the shape of HDF5 datasets. See README for more details.

Multidimensional Columns

The order of dimensions of multidimensional columns in MatNWB is reversed relative to the Python HDMF package (see README for detailed explanation). Therefore, the height of a multidimensional column belonging to a DynamicTable object is defined by the shape of its last dimension. A valid DynamicTable must have matched height across columns.

Constructing multidimensional columns

% Define 1D column
simple_col = types.hdmf_common.VectorData( ...
'description', '1D column',...
'data', rand(10,1) ...
);
% Define ND column
multi_col = types.hdmf_common.VectorData( ...
'description', 'multidimensional column',...
'data', rand(3,2,10) ...
);
% construct table
multi_dim_table = types.hdmf_common.DynamicTable( ...
'description','test table', ...
'colnames', {'simple','multi'}, ...
'simple', simple_col, ...
'multi', multi_col, ...
'id', types.hdmf_common.ElementIdentifiers('data', (0:9)') ... % 0-indexed, for compatibility with Python
);
 

Multidimensional ragged array columns

DynamicTable objects with multidimensional ragged array columns can be constructed by passing in the corresponding VectorIndex column
% Define column with data
multi_ragged_col = types.hdmf_common.VectorData( ...
'description', 'multidimensional ragged array column',...
'data', rand(2,3,5) ...
);
% Define column with VectorIndex
multi_ragged_index = types.hdmf_common.VectorIndex( ...
'description', 'index to multi_ragged_col', ...
'target', types.untyped.ObjectView(multi_ragged_col),'data', [2; 3; 5] ...
);
 
multi_ragged_table = types.hdmf_common.DynamicTable( ...
'description','test table', ...
'colnames', {'multi_ragged'}, ...
'multi_ragged', multi_ragged_col, ...
'multi_ragged_index', multi_ragged_index, ...
'id', types.hdmf_common.ElementIdentifiers('data', [0; 1; 2]) ... % 0-indexed, for compatibility with Python
);

Adding rows to multidimensional array columns

DynamicTable objects with multidimensional array columns can also be constructed by adding a single row at a time. This method makes use of DataPipe objects due to the fact that MATLAB doesn't support singleton dimensions for arrays with more than 2 dimensions. The code block below demonstates how to build a DynamicTable object with a mutidimensional raaged array column in this manner.
% Create file
file = NwbFile( ...
'session_start_time', datetime('2021-01-01 00:00:00', 'TimeZone', 'local'), ...
'identifier', 'ident1', ...
'session_description', 'test_file' ...
);
 
% Define Vector Data Objects with first row of table
start_time_exp = types.hdmf_common.VectorData( ...
'description', 'start times column', ...
'data', types.untyped.DataPipe( ...
'data', 1, ...
'maxSize', Inf ...
) ...
);
stop_time_exp = types.hdmf_common.VectorData( ...
'description', 'stop times column', ...
'data', types.untyped.DataPipe( ...
'data', 10, ...
'maxSize', Inf ...
) ...
);
random_exp = types.hdmf_common.VectorData( ...
'description', 'random data column', ...
'data', types.untyped.DataPipe( ...
'data', rand(3,2,5), ... #random data
'maxSize', [3, 2, Inf], ...
'axis', 3 ...
) ...
);
random_exp_index = types.hdmf_common.VectorIndex( ...
'description', 'index to random data column', ...
'target',types.untyped.ObjectView(random_exp), ...
'data', types.untyped.DataPipe( ...
'data', uint64(5), ...
'maxSize', Inf ...
) ...
);
ids_exp = types.hdmf_common.ElementIdentifiers( ...
'data', types.untyped.DataPipe( ...
'data', int64(0), ... # data must be numerical
'maxSize', Inf ...
) ...
);
% Create expandable table
colnames = {'start_time', 'stop_time', 'randomvalues'};
file.intervals_trials = types.core.TimeIntervals( ...
'description', 'test expdandable dynamic table', ...
'colnames', colnames, ...
'start_time', start_time_exp, ...
'stop_time', stop_time_exp, ...
'randomvalues', random_exp, ...
'randomvalues_index', random_exp_index, ...
'id', ids_exp ...
);
% Export file
nwbExport(file, 'multiRaggedExpandableTableTest.nwb');
% Read in file
read_file = nwbRead('multiRaggedExpandableTableTest.nwb', 'ignorecache');
% add individual rows
read_file.intervals_trials.addRow( ...
'start_time', 2, ...
'stop_time', 20, ...
'randomvalues', rand(3,2,6), ...
'id', 1 ...
);
read_file.intervals_trials.addRow( ...
'start_time', 3, ...
'stop_time', 30, ...
'randomvalues', rand(3,2,3), ...
'id', 2 ...
);
read_file.intervals_trials.addRow( ...
'start_time', 4, ...
'stop_time', 40, ...
'randomvalues', rand(3,2,8), ...
'id', 3 ...
);
 

Learn More!

Python Tutorial

+

Adding rows

You can add rows to an existing DynamicTable using the object's addRow method. One way of using this method is to pass in the names of columns as parameter names followed by the elements to append. The class of the elements of the column must match the elements to append.
my_table.addRow('col1', 3, 'col2', {'c'}, 'id', 2);

Adding columns

You can add new columns to an existing DynamicTable object using the addColumn method. One way of using this method is to pass in the names of each new column followed by the corresponding values for each new column. The height of the new columns must match the height of the table.
col3 = types.hdmf_common.VectorData('description', 'column #3', ...
'data', [100; 200; 300]);
col4 = types.hdmf_common.VectorData('description', 'column #4', ...
'data', {'a1'; 'b2'; 'c3'});
 
my_table.addColumn('col3', col3,'col4', col4);

Create MATLAB table and convert to dynamic table

As an alternative to building a dynamic table using the DynamicTable and VectorData data types, it is also possible to create a MATLAB table and convert it to a dynamic table. Lets create the same table as before, but using MATLAB's table class:
% Create a table with two variables (columns):
T = table([1;2], {'a';'b'}, 'VariableNames', {'col1', 'col2'});
T.Properties.VariableDescriptions = {'column #1', 'column #2'};

Adding rows

T(end+1, :) = {3, 'c'};

Adding variables (columns)

T = addvars(T, [100;200;300], 'NewVariableNames',{'col3'});
T.Properties.VariableDescriptions{3} = 'column #3';
 
% Alternatively, a new variable can be added directly using dot syntax.
T.col4 = {'a1'; 'b2'; 'c3'};
T.Properties.VariableDescriptions{4} = 'column #4';
T
T = 3×4 table
 col1col2col3col4
11'a'100'a1'
22'b'200'b2'
33'c'300'c3'

Convert to dynamic table

dynamic_table = util.table2nwb(T, 'A MATLAB table that was converted to a dynamic table')
dynamic_table =
DynamicTable with properties: + + id: [1×1 types.hdmf_common.ElementIdentifiers] + colnames: {'col1' 'col2' 'col3' 'col4'} + description: 'A MATLAB table that was converted to a dynamic table' + vectordata: [4×1 types.untyped.Set] +

Enumerated (categorical) data

EnumData is a special type of column for storing an enumerated data type. This way each unique value is stored once, and the data references those values by index. Using this method is more efficient than storing a single value many times, and has the advantage of communicating to downstream tools that the data is categorical in nature.

Warning Regarding EnumData

EnumData is currently an experimental feature and as such should not be used in a production environment.
CellTypeElements = types.hdmf_common.VectorData(...
'description', 'fixed set of elements referenced by cell_type' ...
, 'data', {'aa', 'bb', 'cc'} ... % the enumerated elements
);
CellType = types.hdmf_experimental.EnumData( ...
'description', 'this column holds categorical variables' ... % properties derived from VectorData
, 'data', [0, 1, 2, 1, 0] ... % zero-indexed offset to elements.
, 'elements', types.untyped.ObjectView(CellTypeElements) ...
);
 
MyTable = types.hdmf_common.DynamicTable('description', 'an example table');
MyTable.vectordata.set('cell_type_elements', CellTypeElements); % the *_elements format is required for compatibility with pynwb
MyTable.addColumn('cell_type', CellType);

Ragged array columns

A table column with a different number of elements for each row is called a "ragged array column." To define a table with a ragged array column, pass both the VectorData and the corresponding VectorIndex as columns of the DynamicTable object. The VectorData columns will contain the data values. The VectorIndex column serves to indicate how to arrange the data across rows. By convention the VectorIndex object corresponding to a particular column must have have the same name with the addition of the '_index' suffix.
Below, the VectorIndex values indicate to place the 1st to 3rd (inclusive) elements of the VectorData into the first row and 4th element into the second row. The resulting table will have the cell {'1a'; '1b'; '1c'} in the first row and the cell {'2a'} in the second row.
 
col1 = types.hdmf_common.VectorData( ...
'description', 'column #1', ...
'data', {'1a'; '1b'; '1c'; '2a'} ...
);
 
col1_index = types.hdmf_common.VectorIndex( ...
'description', 'column #1 index', ...
'target',types.untyped.ObjectView(col1), ... % object view of target column
'data', [3; 4] ...
);
 
table_ragged_col = types.hdmf_common.DynamicTable( ...
'description', 'an example table', ...
'colnames', {'col1'}, ...
'col1', col1, ...
'col1_index', col1_index, ...
'id', types.hdmf_common.ElementIdentifiers('data', [0; 1]) ... % 0-indexed, for compatibility with Python
);

Adding ragged array rows

You can add a new row to the ragged array column. Under the hood, the addRow method will add the appropriate value to the VectorIndex column to maintain proper formatting.
table_ragged_col.addRow('col1', {'3a'; '3b'; '3c'}, 'id', 2);

Accessing row elements

You can access data from entire rows of a DynamicTable object by calling the getRow method for the corresponding object. You can supply either an individual row number or a list of row numbers.
my_table.getRow(1)
ans = 1×4 table
 col1col2col3col4
11'a'100'a1'
If you want to access values for just a subset of columns you can pass in the 'columns' arguement along with a cell array with the desired column names
my_table.getRow(1:3, 'columns', {'col1'})
ans = 3×1 table
 col1
11
22
33
You can also access specific rows by their corresponding row ID's, if they have been defined, by supplying a 'true' Boolean to the 'useId' parameter
my_table.getRow(1, 'useId', true)
ans = 1×4 table
 col1col2col3col4
12'b'200'b2'
For a ragged array columns, the getRow method will return a cell with different number of elements for each row
table_ragged_col.getRow(1:2)
ans = 2×1 table
 col1
1[{'1a'};{'1b'};{'1c'}]
21×1 cell

Accessing column elements

To acess all rows from a particular column use the .get method on the vectordata field of the DynamicTable object
 
my_table.vectordata.get('col2').data
ans = 3×1 cell
'a'
'b'
'c'

Referencing rows of other tables

You can create a column that references rows of other tables by adding a DynamicTableRegion object as a column of a DynamicTable. This is analogous to a foreign key in a relational database. The DynamicTableRegion class takes in an ObjectView object as arguement. ObjectView objects create links from one object type referencing another.
dtr_col = types.hdmf_common.DynamicTableRegion( ...
'description', 'references multiple rows of earlier table', ...
'data', [0; 1; 1; 0], ... # 0-indexed
'table',types.untyped.ObjectView(my_table) ... % object view of target table
);
 
data_col = types.hdmf_common.VectorData( ...
'description', 'data column', ...
'data', {'a'; 'b'; 'c'; 'd'} ...
);
 
dtr_table = types.hdmf_common.DynamicTable( ...
'description', 'test table with DynamicTableRegion', ...
'colnames', {'data_col', 'dtr_col'}, ...
'dtr_col', dtr_col, ...
'data_col',data_col, ...
'id',types.hdmf_common.ElementIdentifiers('data', [0; 1; 2; 3]) ...
);

Converting a DynamicTable to a MATLAB table

You can convert a DynamicTable object to a MATLAB table by making use of the object's toTable method. This is a useful way to view the whole table in a human-readable format.
my_table.toTable()
ans = 3×5 table
 idcol1col2col3col4
101'a'100'a1'
212'b'200'b2'
323'c'300'c3'
When the DynamicTable object contains a column that references other tables, you can pass in a Boolean to indicate whether to include just the row indices of the referenced table. Passing in false will result in inclusion of the referenced rows as nested tables.
dtr_table.toTable(false)
ans = 4×3 table
 iddata_coldtr_col
10'a'1×4 table
21'b'1×4 table
32'c'1×4 table
43'd'1×4 table

Creating an expandable table

When using the default HDF5 backend, each column of these tables is an HDF5 Dataset, which by default are set to an unchangeable size. This means that once a file is written, it is not possible to add a new row. If you want to be able to save this file, load it, and add more rows to the table, you will need to set this up when you create the VectorData and ElementIdentifiers columns of a DynamicTable. Specifically, you must wrap the column data with a DataPipe object. The DataPipe class takes in maxSize and axis as arguments to indicate the maximum desired size for each axis and the axis to whcih to append to, respectively. For example, creating a DataPipe object with a maxSize value equal to [Inf, 1] indicates that the number of rows may increase indifinetely. In contrast, setting maxSize equal to [8, 1] would allow the column to grow to a maximum height of 8.
% create NwbFile object with required fields
file= NwbFile( ...
'session_start_time', datetime('2021-01-01 00:00:00', 'TimeZone', 'local'), ...
'identifier', 'ident1', ...
'session_description', 'ExpandableTableTutorial' ...
);
 
% create VectorData objects with DataPipe objects
start_time_exp = types.hdmf_common.VectorData( ...
'description', 'start times column', ...
'data', types.untyped.DataPipe( ...
'data', [1, 2], ... # data must be numerical
'maxSize', Inf ...
) ...
);
 
stop_time_exp = types.hdmf_common.VectorData( ...
'description', 'stop times column', ...
'data', types.untyped.DataPipe( ...
'data', [2, 3], ... #data must be numerical
'maxSize', Inf ...
) ...
);
 
random_exp = types.hdmf_common.VectorData( ...
'description', 'random data column', ...
'data', types.untyped.DataPipe( ...
'data', rand(5, 2), ... #data must be numerical
'maxSize', [5, Inf], ...
'axis', 2 ...
) ...
);
 
ids_exp = types.hdmf_common.ElementIdentifiers( ...
'data', types.untyped.DataPipe( ...
'data', int32([0; 1]), ... # data must be numerical
'maxSize', Inf ...
) ...
);
% create expandable table
colnames = {'start_time', 'stop_time', 'randomvalues'};
file.intervals_trials = types.core.TimeIntervals( ...
'description', 'test expdandable dynamic table', ...
'colnames', colnames, ...
'start_time', start_time_exp, ...
'stop_time', stop_time_exp, ...
'randomvalues', random_exp, ...
'id', ids_exp ...
);
% export file
nwbExport(file, 'expandableTableTestFile.nwb');
Now, you can read in the file, add more rows, and save again to file
readFile = nwbRead('expandableTableTestFile.nwb', 'ignorecache');
readFile.intervals_trials.addRow( ...
'start_time', 3, ...
'stop_time', 4, ...
'randomvalues', rand(5,1), ...
'id', 2 ...
)
nwbExport(readFile, 'expandableTableTestFile.nwb');
Note: DataPipe objects change how the dimension of the datasets for each column map onto the shape of HDF5 datasets. See README for more details.

Multidimensional Columns

The order of dimensions of multidimensional columns in MatNWB is reversed relative to the Python HDMF package (see README for detailed explanation). Therefore, the height of a multidimensional column belonging to a DynamicTable object is defined by the shape of its last dimension. A valid DynamicTable must have matched height across columns.

Constructing multidimensional columns

% Define 1D column
simple_col = types.hdmf_common.VectorData( ...
'description', '1D column',...
'data', rand(10,1) ...
);
% Define ND column
multi_col = types.hdmf_common.VectorData( ...
'description', 'multidimensional column',...
'data', rand(3,2,10) ...
);
% construct table
multi_dim_table = types.hdmf_common.DynamicTable( ...
'description','test table', ...
'colnames', {'simple','multi'}, ...
'simple', simple_col, ...
'multi', multi_col, ...
'id', types.hdmf_common.ElementIdentifiers('data', (0:9)') ... % 0-indexed, for compatibility with Python
);
 

Multidimensional ragged array columns

DynamicTable objects with multidimensional ragged array columns can be constructed by passing in the corresponding VectorIndex column
% Define column with data
multi_ragged_col = types.hdmf_common.VectorData( ...
'description', 'multidimensional ragged array column',...
'data', rand(2,3,5) ...
);
% Define column with VectorIndex
multi_ragged_index = types.hdmf_common.VectorIndex( ...
'description', 'index to multi_ragged_col', ...
'target', types.untyped.ObjectView(multi_ragged_col),'data', [2; 3; 5] ...
);
 
multi_ragged_table = types.hdmf_common.DynamicTable( ...
'description','test table', ...
'colnames', {'multi_ragged'}, ...
'multi_ragged', multi_ragged_col, ...
'multi_ragged_index', multi_ragged_index, ...
'id', types.hdmf_common.ElementIdentifiers('data', [0; 1; 2]) ... % 0-indexed, for compatibility with Python
);

Adding rows to multidimensional array columns

DynamicTable objects with multidimensional array columns can also be constructed by adding a single row at a time. This method makes use of DataPipe objects due to the fact that MATLAB doesn't support singleton dimensions for arrays with more than 2 dimensions. The code block below demonstates how to build a DynamicTable object with a mutidimensional raaged array column in this manner.
% Create file
file = NwbFile( ...
'session_start_time', datetime('2021-01-01 00:00:00', 'TimeZone', 'local'), ...
'identifier', 'ident1', ...
'session_description', 'test_file' ...
);
 
% Define Vector Data Objects with first row of table
start_time_exp = types.hdmf_common.VectorData( ...
'description', 'start times column', ...
'data', types.untyped.DataPipe( ...
'data', 1, ...
'maxSize', Inf ...
) ...
);
stop_time_exp = types.hdmf_common.VectorData( ...
'description', 'stop times column', ...
'data', types.untyped.DataPipe( ...
'data', 10, ...
'maxSize', Inf ...
) ...
);
random_exp = types.hdmf_common.VectorData( ...
'description', 'random data column', ...
'data', types.untyped.DataPipe( ...
'data', rand(3,2,5), ... #random data
'maxSize', [3, 2, Inf], ...
'axis', 3 ...
) ...
);
random_exp_index = types.hdmf_common.VectorIndex( ...
'description', 'index to random data column', ...
'target',types.untyped.ObjectView(random_exp), ...
'data', types.untyped.DataPipe( ...
'data', uint64(5), ...
'maxSize', Inf ...
) ...
);
ids_exp = types.hdmf_common.ElementIdentifiers( ...
'data', types.untyped.DataPipe( ...
'data', int64(0), ... # data must be numerical
'maxSize', Inf ...
) ...
);
% Create expandable table
colnames = {'start_time', 'stop_time', 'randomvalues'};
file.intervals_trials = types.core.TimeIntervals( ...
'description', 'test expdandable dynamic table', ...
'colnames', colnames, ...
'start_time', start_time_exp, ...
'stop_time', stop_time_exp, ...
'randomvalues', random_exp, ...
'randomvalues_index', random_exp_index, ...
'id', ids_exp ...
);
% Export file
nwbExport(file, 'multiRaggedExpandableTableTest.nwb');
% Read in file
read_file = nwbRead('multiRaggedExpandableTableTest.nwb', 'ignorecache');
% add individual rows
read_file.intervals_trials.addRow( ...
'start_time', 2, ...
'stop_time', 20, ...
'randomvalues', rand(3,2,6), ...
'id', 1 ...
);
read_file.intervals_trials.addRow( ...
'start_time', 3, ...
'stop_time', 30, ...
'randomvalues', rand(3,2,3), ...
'id', 2 ...
);
read_file.intervals_trials.addRow( ...
'start_time', 4, ...
'stop_time', 40, ...
'randomvalues', rand(3,2,8), ...
'id', 3 ...
);
 

Learn More!

Python Tutorial