From f07e00fb72b09dba5ab42ef3ef19f07aea43646c Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 20:19:42 +0100 Subject: [PATCH 01/12] Add getSize function for dataspace dimension retrieval Introduces io.space.getSize to obtain current and maximum dataset dimensions from an HDF5 dataspace identifier. Handles dimension order conversion and unlimited dimension representation for MATLAB compatibility. --- +io/+space/getSize.m | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 +io/+space/getSize.m diff --git a/+io/+space/getSize.m b/+io/+space/getSize.m new file mode 100644 index 000000000..f92a27b86 --- /dev/null +++ b/+io/+space/getSize.m @@ -0,0 +1,30 @@ +function [datasetSize, datasetMaxSize] = getSize(spaceId) +% getSize - Retrieves the current and maximum sizes of a dataset. +% +% Syntax: +% [datasetSize, datasetMaxSize] = io.space.getSize(spaceId) +% +% Input Arguments: +% spaceId {H5ML.id} - Identifier for the dataspace from which +% the dimensions are retrieved. +% +% Output Arguments: +% datasetSize - Current size of the dataset dimensions. +% datasetMaxSize - Maximum size of the dataset dimensions. +% +% Note: +% - Flips dimensions as the h5 function returns dimensions in C-style order +% whereas MATLAB represents data in F-style order +% - Replaces H5 constants with Inf for unlimited dimensions + + arguments + spaceId {matnwb.common.compatibility.mustBeA(spaceId, "H5ML.id")} + end + + [~, h5Dims, h5MaxDims] = H5S.get_simple_extent_dims(spaceId); + datasetSize = fliplr(h5Dims); + datasetMaxSize = fliplr(h5MaxDims); + + h5Unlimited = H5ML.get_constant_value('H5S_UNLIMITED'); + datasetMaxSize(datasetMaxSize == h5Unlimited) = Inf; +end From c8f95fb64cb49ba17910f86ae0a1312ce679fc5f Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 21:45:30 +0100 Subject: [PATCH 02/12] Update DataStub.m - Changed get_space to private method. - Add maxDims property - Added updateSize method, which can be accessed by BoundPipe, as stubs of BoundPipes are expandable and their size can change - Improved subsref override - Support method calls with no output - Support compound indexing --- +types/+untyped/@DataStub/DataStub.m | 73 +++++++++++++++++++++------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/+types/+untyped/@DataStub/DataStub.m b/+types/+untyped/@DataStub/DataStub.m index 3f0b6681c..977da1609 100644 --- a/+types/+untyped/@DataStub/DataStub.m +++ b/+types/+untyped/@DataStub/DataStub.m @@ -13,9 +13,15 @@ ndims; dataType; end + + properties (Dependent, SetAccess = private, GetAccess = ?types.untyped.datapipe.BoundPipe) + maxDims + end + properties (Access = private) dims_ double - dataType_ {mustBeA(dataType_, ["char", "string", "struct"])} = string.empty % Can be char (simple type) or struct (compound type descriptor) + dataType_ {matnwb.common.compatibility.mustBeA(dataType_, ["char", "string", "struct"])} = string.empty % Can be char (simple type) or struct (compound type descriptor) + maxDims_ double end methods @@ -37,25 +43,25 @@ obj.dataType_ = dataType; % Keep as struct for compound types end end - - function sid = get_space(obj) % Todo: private method - fid = H5F.open(obj.filename); - did = H5D.open(fid, obj.path); - sid = H5D.get_space(did); - H5D.close(did); - H5F.close(fid); - end - + function dims = get.dims(obj) if isempty(obj.dims_) - sid = obj.get_space(); - [~, h5_dims, ~] = H5S.get_simple_extent_dims(sid); - obj.dims_ = fliplr(h5_dims); + sid = get_space(obj); + [obj.dims_, obj.maxDims_] = io.space.getSize(sid); H5S.close(sid); end dims = obj.dims_; end - + + function maxDims = get.maxDims(obj) + if isempty(obj.maxDims_) + sid = get_space(obj); + [obj.dims_, obj.maxDims_] = io.space.getSize(sid); + H5S.close(sid); + end + maxDims = obj.maxDims_; + end + function nd = get.ndims(obj) nd = length(obj.dims); end @@ -185,10 +191,10 @@ refs = export(obj, fid, fullpath, refs); - function B = subsref(obj, S) + function varargout = subsref(obj, S) CurrentSubRef = S(1); if ~isscalar(obj) || strcmp(CurrentSubRef.type, '.') - B = builtin('subsref', obj, S); + [varargout{1:nargout}] = builtin('subsref', obj, S); return; end @@ -200,9 +206,9 @@ selectionRank, rank); data = obj.load_mat_style(CurrentSubRef.subs{:}); if isscalar(S) - B = data; + varargout = {data}; else - B = subsref(data, S(2:end)); + [varargout{1:nargout}] = subsref(data, S(2:end)); end end @@ -225,4 +231,35 @@ tf = isstruct(dt); end end + + methods % Custom indexing + function n = numArgumentsFromSubscript(obj, subs, indexingContext) + if ~isscalar(subs) && strcmp(subs(1).type, '()') + % Typical indexing pattern into compound data type, i.e + % data(1:3).fieldName. Assume/expect one output. + n = 1; + else + n = builtin('numArgumentsFromSubscript', obj, subs, indexingContext); + end + end + end + + methods % (Access = ?types.untyped.datapipe.BoundPipe) + function updateSize(obj) + % updateSize - Should be called when dataset space is expanded + sid = get_space(obj); + [obj.dims_, obj.maxDims_] = io.space.getSize(sid); + H5S.close(sid); + end + end + + methods (Access = private) + function sid = get_space(obj) + fid = H5F.open(obj.filename); + did = H5D.open(fid, obj.path); + sid = H5D.get_space(did); + H5D.close(did); + H5F.close(fid); + end + end end From 7b14f7dacf60fd7bd9703fbc7fe4b01dc84563e7 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 21:59:34 +0100 Subject: [PATCH 03/12] Update dataStubTest.m Add test for nested data indexing --- +tests/+unit/dataStubTest.m | 49 +++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/+tests/+unit/dataStubTest.m b/+tests/+unit/dataStubTest.m index 51214fd7b..41ff5b991 100644 --- a/+tests/+unit/dataStubTest.m +++ b/+tests/+unit/dataStubTest.m @@ -1,5 +1,14 @@ classdef dataStubTest < tests.abstract.NwbTestCase + methods (TestClassSetup) + function generateTestSchemas(testCase) + % Generate the rrs and cs test extensions for use in all tests + % of this test suite, using fixture for proper cleanup + testCase.applyTestSchemaFixture('rrs'); + testCase.applyTestSchemaFixture('cs'); + end + end + methods (TestMethodSetup) function setupMethod(testCase) % Use a fixture to create a temporary working directory @@ -70,9 +79,6 @@ function testRegionRead(testCase) function testObjectCopy(testCase) - testCase.applyTestSchemaFixture('rrs'); - testCase.applyTestSchemaFixture('cs'); - nwb = NwbFile(... 'identifier', 'DATASTUB',... 'session_description', 'test datastub object copy',... @@ -91,6 +97,7 @@ function testObjectCopy(testCase) nwb.analysis.set('rcRef', rcRef); nwbExport(nwb, 'original.nwb'); nwbNew = nwbRead('original.nwb', 'ignorecache'); + tests.util.verifyContainerEqual(testCase, nwbNew, nwb); nwbExport(nwbNew, 'new.nwb'); end @@ -134,11 +141,6 @@ function testLoadWithEmptyIndices(testCase) end function testResolveCompoundDataType(testCase) - - % Generate the compound test schema using fixture - testCase.applyTestSchemaFixture('rrs'); - testCase.applyTestSchemaFixture('cs'); - % Set up file with compound dataset nwb = tests.factory.NWBFile(); @@ -173,5 +175,36 @@ function testResolveCompoundDataType(testCase) compoundRefInDirectRead.dims, ... compoundRefIn.data.dims ) end + + function testNestedDataIndexing(testCase) + % Set up file with compound dataset + + testCase.applyTestSchemaFixture('rrs'); + testCase.applyTestSchemaFixture('cs'); + nwb = tests.factory.NWBFile(); + + ts = tests.factory.TimeSeriesWithTimestamps(); + nwb.acquisition.set('timeseries', ts); + + tsPath = '/acquisition/timeseries'; + tsDataPath = [tsPath '/data']; + + compoundRef = types.cs.CompoundRefData('data', table(... + rand(2, 1),... + rand(2, 1),... + [types.untyped.ObjectView(tsPath); types.untyped.ObjectView(tsPath)],... + [types.untyped.RegionView(tsDataPath, 1:2); types.untyped.RegionView(tsDataPath, 2:3)],... + 'VariableNames', {'a', 'b', 'objref', 'regref'})); + + nwb.analysis.set('compoundRef', compoundRef); + nwbExport(nwb, 'test.nwb'); + + % Read in data + nwbIn = nwbRead('test.nwb', 'ignorecache'); + compoundRefIn = nwbIn.analysis.get('compoundRef'); + + testCase.verifyClass(compoundRefIn.data(1:2).a, 'double'); + testCase.verifyLength(compoundRefIn.data(1:2).a, 2); + end end end From ca8e055cbc169fa87d3ec6ccf5a0bca719ded9ef Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 22:02:10 +0100 Subject: [PATCH 04/12] Update BoundPipe.m - Update DataStub size when dataset is appended/expanded - Delegate detection of current size and max size to datastub --- +types/+untyped/+datapipe/BoundPipe.m | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/+types/+untyped/+datapipe/BoundPipe.m b/+types/+untyped/+datapipe/BoundPipe.m index e563529dd..5e1001cb0 100644 --- a/+types/+untyped/+datapipe/BoundPipe.m +++ b/+types/+untyped/+datapipe/BoundPipe.m @@ -4,7 +4,7 @@ properties (SetAccess = private) config = types.untyped.datapipe.Configuration.empty; pipeProperties = {}; - stub = types.untyped.DataStub.empty; + stub types.untyped.Datastub = types.untyped.DataStub.empty; end properties (SetAccess = private, Dependent) @@ -24,14 +24,8 @@ obj.stub = types.untyped.DataStub(filename, path); - sid = obj.stub.get_space(); - [~, h5_dims, h5_maxdims] = H5S.get_simple_extent_dims(sid); - H5S.close(sid); - - current_size = fliplr(h5_dims); - max_size = fliplr(h5_maxdims); - h5_unlimited = H5ML.get_constant_value('H5S_UNLIMITED'); - max_size(max_size == h5_unlimited) = Inf; + current_size = obj.stub.dims; + max_size = obj.stub.maxDims; did = obj.getDataset(); @@ -224,6 +218,7 @@ function append(obj, data) H5F.close(fid); obj.config.offset = obj.config.offset + data_size(obj.config.axis); + obj.stub.updateSize() end function property = getPipeProperty(obj, type) From b6f6a41591f1ac77cd65897c4b59c26901bf055f Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 22:25:55 +0100 Subject: [PATCH 05/12] Simplify DataStub/export --- +types/+untyped/@DataStub/export.m | 82 +++++++++++++----------------- 1 file changed, 35 insertions(+), 47 deletions(-) diff --git a/+types/+untyped/@DataStub/export.m b/+types/+untyped/@DataStub/export.m index 0ad369a47..8d548caa5 100644 --- a/+types/+untyped/@DataStub/export.m +++ b/+types/+untyped/@DataStub/export.m @@ -1,7 +1,7 @@ function refs = export(obj, fid, fullpath, refs) - %Check for compound data type refs + + % If exporting to the same file this DataStub originates from, skip export. src_fid = H5F.open(obj.filename); - % if filenames are the same, then do nothing src_filename = H5F.get_name(src_fid); dest_filename = H5F.get_name(fid); if strcmp(src_filename, dest_filename) @@ -10,53 +10,27 @@ src_did = H5D.open(src_fid, obj.path); src_tid = H5D.get_type(src_did); - src_sid = H5D.get_space(src_did); - ref_i = false; - char_i = false; - member_name = {}; - ref_tid = {}; + + % Check for compound data type refs if H5T.get_class(src_tid) == H5ML.get_constant_value('H5T_COMPOUND') - ncol = H5T.get_nmembers(src_tid); - ref_i = false(ncol, 1); - member_name = cell(ncol, 1); - char_i = false(ncol, 1); - ref_tid = cell(ncol, 1); - refTypeConst = H5ML.get_constant_value('H5T_REFERENCE'); - strTypeConst = H5ML.get_constant_value('H5T_STRING'); - for i = 1:ncol - member_name{i} = H5T.get_member_name(src_tid, i-1); - subclass = H5T.get_member_class(src_tid, i-1); - subtid = H5T.get_member_type(src_tid, i-1); - char_i(i) = subclass == strTypeConst && ... - ~H5T.is_variable_str(subtid); - if subclass == refTypeConst - ref_i(i) = true; - ref_tid{i} = subtid; - end - end + isCompoundDatasetWithReference = isCompoundWithReference(src_tid); + else + isCompoundDatasetWithReference = false; end - - %manually load the data struct - if any(ref_i) - %This requires loading the entire table. - %Due to this HDF5 library's inability to delete/update - %dataset data, this is unfortunately required. - ref_tid = ref_tid(~cellfun('isempty', ref_tid)); + + % If dataset is compound and contains reference types, data needs to be + % manually read and written to the new file. This is due to a bug in + % the hdf5 library (see e.g. https://github.com/HDFGroup/hdf5/issues/3429) + if isCompoundDatasetWithReference + % This requires loading the entire table. + % Due to this HDF5 library's inability to delete/update + % dataset data, this is unfortunately required. data = H5D.read(src_did); - - refNames = member_name(ref_i); - for i=1:length(refNames) - data.(refNames{i}) = io.parseReference(src_did, ref_tid{i}, ... - data.(refNames{i})); - end - - strNames = member_name(char_i); - for i=1:length(strNames) - s = data.(strNames{i}) .'; - data.(strNames{i}) = mat2cell(s, ones(size(s,1),1)); - end - + + % Reuse io.parseCompound to ensure data types are properly postprocessed + data = io.parseCompound(src_did, data); io.writeCompound(fid, fullpath, data); + elseif ~H5L.exists(fid, fullpath, 'H5P_DEFAULT') % copy data over and return destination. ocpl = H5P.create('H5P_OBJECT_COPY'); @@ -66,7 +40,21 @@ H5P.close(lcpl); end H5T.close(src_tid); - H5S.close(src_sid); H5D.close(src_did); H5F.close(src_fid); -end \ No newline at end of file +end + +function hasReference = isCompoundWithReference(src_tid) + hasReference = false; + + ncol = H5T.get_nmembers(src_tid); + refTypeConst = H5ML.get_constant_value('H5T_REFERENCE'); + + for i = 1:ncol + subclass = H5T.get_member_class(src_tid, i-1); + if subclass == refTypeConst + hasReference = true; + return + end + end +end From 039cb5a56d2613b9a9c9c9bedc758853a37d5ffe Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 22:27:20 +0100 Subject: [PATCH 06/12] Fix typo: Datastub -> DataStub --- +types/+untyped/+datapipe/BoundPipe.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+types/+untyped/+datapipe/BoundPipe.m b/+types/+untyped/+datapipe/BoundPipe.m index 5e1001cb0..598f51300 100644 --- a/+types/+untyped/+datapipe/BoundPipe.m +++ b/+types/+untyped/+datapipe/BoundPipe.m @@ -4,7 +4,7 @@ properties (SetAccess = private) config = types.untyped.datapipe.Configuration.empty; pipeProperties = {}; - stub types.untyped.Datastub = types.untyped.DataStub.empty; + stub types.untyped.DataStub = types.untyped.DataStub.empty; end properties (SetAccess = private, Dependent) From 51dfcd2ff9426af6707fc2db546d6205248a503b Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 22:46:17 +0100 Subject: [PATCH 07/12] Add boolean type to compound dataset for improved testing --- +tests/+unit/dataStubTest.m | 15 ++++++++++----- .../compoundSchema/cs.compoundtypes.yaml | 3 +++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/+tests/+unit/dataStubTest.m b/+tests/+unit/dataStubTest.m index 51214fd7b..484fed077 100644 --- a/+tests/+unit/dataStubTest.m +++ b/+tests/+unit/dataStubTest.m @@ -83,16 +83,20 @@ function testObjectCopy(testCase) rcRef = types.cs.CompoundRefData('data', table(... rand(2, 1),... rand(2, 1),... + [true; false],... [types.untyped.ObjectView(rcPath); types.untyped.ObjectView(rcPath)],... [types.untyped.RegionView(rcDataPath, 1:2, 99:100); types.untyped.RegionView(rcDataPath, 5:6, 88:89)],... - 'VariableNames', {'a', 'b', 'objref', 'regref'})); + 'VariableNames', {'a', 'b', 'c', 'objref', 'regref'})); nwb.acquisition.set('rc', rc); nwb.analysis.set('rcRef', rcRef); nwbExport(nwb, 'original.nwb'); - nwbNew = nwbRead('original.nwb', 'ignorecache'); - tests.util.verifyContainerEqual(testCase, nwbNew, nwb); - nwbExport(nwbNew, 'new.nwb'); + nwbOriginalIn = nwbRead('original.nwb', 'ignorecache'); + tests.util.verifyContainerEqual(testCase, nwbOriginalIn, nwb); + + nwbExport(nwbOriginalIn, 'copy.nwb'); + nwbCopyIn = nwbRead('copy.nwb', 'ignorecache'); + tests.util.verifyContainerEqual(testCase, nwbCopyIn, nwb); end function testLoadWithEmptyIndices(testCase) @@ -151,9 +155,10 @@ function testResolveCompoundDataType(testCase) compoundRef = types.cs.CompoundRefData('data', table(... rand(2, 1),... rand(2, 1),... + [true; false],... [types.untyped.ObjectView(tsPath); types.untyped.ObjectView(tsPath)],... [types.untyped.RegionView(tsDataPath, 1:2); types.untyped.RegionView(tsDataPath, 2:3)],... - 'VariableNames', {'a', 'b', 'objref', 'regref'})); + 'VariableNames', {'a', 'b', 'c', 'objref', 'regref'})); nwb.analysis.set('compoundRef', compoundRef); diff --git a/+tests/test-schema/compoundSchema/cs.compoundtypes.yaml b/+tests/test-schema/compoundSchema/cs.compoundtypes.yaml index 487f13f5e..cdc7ae42c 100644 --- a/+tests/test-schema/compoundSchema/cs.compoundtypes.yaml +++ b/+tests/test-schema/compoundSchema/cs.compoundtypes.yaml @@ -10,6 +10,9 @@ groups: - name: b dtype: float64 doc: 'B' + - name: c + dtype: bool + doc: 'C' - name: objref doc: 'ObjectView in Compound datatype' dtype: From 4cdb98b22ee1b4b677dad74bb0311c582518bbfc Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 22:56:24 +0100 Subject: [PATCH 08/12] Update ExtensionGenerationFixture.m Improve fixture teardown --- +tests/+fixtures/ExtensionGenerationFixture.m | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/+tests/+fixtures/ExtensionGenerationFixture.m b/+tests/+fixtures/ExtensionGenerationFixture.m index a538de190..ca476aeeb 100644 --- a/+tests/+fixtures/ExtensionGenerationFixture.m +++ b/+tests/+fixtures/ExtensionGenerationFixture.m @@ -46,10 +46,13 @@ function clearGenerated(fixture) namespaceName = extractBefore(namespaceFilename, '.'); generatedTypesDirectory = fullfile(fixture.TypesOutputFolder, "+types", "+"+namespaceName); - rmdir(generatedTypesDirectory, 's'); - + if isfolder(generatedTypesDirectory) + rmdir(generatedTypesDirectory, 's'); + end cacheFile = fullfile(fixture.TypesOutputFolder, "namespaces", namespaceName+".mat"); - delete(cacheFile) + if isfile(cacheFile) + delete(cacheFile) + end end end end From cd39b9b9fe1f2fd46adb4b49ca7d2d623debab36 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 23:15:31 +0100 Subject: [PATCH 09/12] Update dataStubTest.m --- +tests/+unit/dataStubTest.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/+tests/+unit/dataStubTest.m b/+tests/+unit/dataStubTest.m index 41ff5b991..a9ca954d0 100644 --- a/+tests/+unit/dataStubTest.m +++ b/+tests/+unit/dataStubTest.m @@ -179,8 +179,6 @@ function testResolveCompoundDataType(testCase) function testNestedDataIndexing(testCase) % Set up file with compound dataset - testCase.applyTestSchemaFixture('rrs'); - testCase.applyTestSchemaFixture('cs'); nwb = tests.factory.NWBFile(); ts = tests.factory.TimeSeriesWithTimestamps(); From cfafc2678c40c7674753a4c7de745e4d09ebcbc7 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 23:19:17 +0100 Subject: [PATCH 10/12] Refactor dims and maxDims getters to use updateSize Simplifies the logic in the dims and maxDims property getters by calling updateSize instead of duplicating code. Also updates the access level for updateSize to allow access from DataStub and BoundPipe classes. --- +types/+untyped/@DataStub/DataStub.m | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/+types/+untyped/@DataStub/DataStub.m b/+types/+untyped/@DataStub/DataStub.m index 977da1609..28c167eff 100644 --- a/+types/+untyped/@DataStub/DataStub.m +++ b/+types/+untyped/@DataStub/DataStub.m @@ -46,18 +46,14 @@ function dims = get.dims(obj) if isempty(obj.dims_) - sid = get_space(obj); - [obj.dims_, obj.maxDims_] = io.space.getSize(sid); - H5S.close(sid); + obj.updateSize() end dims = obj.dims_; end function maxDims = get.maxDims(obj) if isempty(obj.maxDims_) - sid = get_space(obj); - [obj.dims_, obj.maxDims_] = io.space.getSize(sid); - H5S.close(sid); + obj.updateSize() end maxDims = obj.maxDims_; end @@ -244,9 +240,10 @@ end end - methods % (Access = ?types.untyped.datapipe.BoundPipe) + methods (Access = {?types.untyped.DataStub, ?types.untyped.datapipe.BoundPipe}) function updateSize(obj) - % updateSize - Should be called when dataset space is expanded + % updateSize - Should be called to initialize values or when dataset + % space is expanded sid = get_space(obj); [obj.dims_, obj.maxDims_] = io.space.getSize(sid); H5S.close(sid); From 9673c1e0dd89eac764083be5221b6f710ba936c6 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 23:20:49 +0100 Subject: [PATCH 11/12] Update +types/+untyped/@DataStub/export.m Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- +types/+untyped/@DataStub/export.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/+types/+untyped/@DataStub/export.m b/+types/+untyped/@DataStub/export.m index 8d548caa5..d4ad73dc0 100644 --- a/+types/+untyped/@DataStub/export.m +++ b/+types/+untyped/@DataStub/export.m @@ -27,7 +27,8 @@ % dataset data, this is unfortunately required. data = H5D.read(src_did); - % Reuse io.parseCompound to ensure data types are properly postprocessed + % Use io.parseCompound to consistently handle references, character arrays, and logical types, + % ensuring all data types are properly postprocessed in line with the rest of the codebase. data = io.parseCompound(src_did, data); io.writeCompound(fid, fullpath, data); From 932e79f522fb534a6fa21d298ace392296188ab5 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Thu, 30 Oct 2025 14:54:30 +0100 Subject: [PATCH 12/12] Update dataStubTest.m Fix failing test --- +tests/+unit/dataStubTest.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/+tests/+unit/dataStubTest.m b/+tests/+unit/dataStubTest.m index eb89fd90a..4ed67bc07 100644 --- a/+tests/+unit/dataStubTest.m +++ b/+tests/+unit/dataStubTest.m @@ -196,9 +196,10 @@ function testNestedDataIndexing(testCase) compoundRef = types.cs.CompoundRefData('data', table(... rand(2, 1),... rand(2, 1),... + [true; false],... [types.untyped.ObjectView(tsPath); types.untyped.ObjectView(tsPath)],... [types.untyped.RegionView(tsDataPath, 1:2); types.untyped.RegionView(tsDataPath, 2:3)],... - 'VariableNames', {'a', 'b', 'objref', 'regref'})); + 'VariableNames', {'a', 'b', 'c', 'objref', 'regref'})); nwb.analysis.set('compoundRef', compoundRef); nwbExport(nwb, 'test.nwb');