[Bugfix] Fix VectorData height calculation when data is BoundPipe #802
[Bugfix] Fix VectorData height calculation when data is BoundPipe #802ehennestad wants to merge 8 commits intomainfrom
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #802 +/- ##
==========================================
- Coverage 95.53% 95.48% -0.06%
==========================================
Files 192 192
Lines 7011 7014 +3
==========================================
- Hits 6698 6697 -1
- Misses 313 317 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
size([], n) = 1 if n>=3, but should be 0
| dataDims = size(data); | ||
| vecHeight = dataDims(end); | ||
| elseif isempty(data.internal.data) | ||
| vecHeight = 0; |
There was a problem hiding this comment.
Add explicit case for empty DataPipe data.
in MATLAB, size([], n) = 1 if n>= 3.
So for nd DataPipe with empty data, the VectorData height/length would be 1 instead of 0.
There was a problem hiding this comment.
Pull request overview
Fixes DynamicTable construction failures when VectorData is backed by a bound DataPipe (i.e., BoundPipe) with multiple extendable dimensions by computing column “height” from the last MATLAB dimension rather than the bound pipe’s inferred append axis.
Changes:
- Update
types.util.dynamictable.checkConfigto derive boundDataPipeheight fromsize(data)(last MATLAB dimension). - Add a unit test reproducing the multi-extendable-dimension bound-pipe scenario.
- Adjust a system test
DataPipeconfiguration for multi-dimensional appending.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
+types/+util/+dynamictable/checkConfig.m |
Changes height detection for bound DataPipe-backed VectorData to use MATLAB dimension ordering. |
+tests/+unit/dynamicTableTest.m |
Adds a unit test to ensure DynamicTable validation succeeds for a multi-dim bound DataPipe. |
+tests/+system/DynamicTableTest.m |
Updates DataPipe axis/maxSize and sample row shape for multi-dimensional appends. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| testCase.verifyEqual(size(boundPipe), [100, 100, 30]); | ||
| testCase.verifyEqual(dynamicTable.id.data, idColumn.data); | ||
| end | ||
|
|
There was a problem hiding this comment.
This new unit test covers the multi-dimensional bound-pipe case, but the updated checkConfig logic can also affect bound pipes that are effectively vectors stored as a 2-D [N 1] dataset (e.g., created from DataPipe('data', rand(N,1))). Adding a companion assertion/test that a bound [N 1] DataPipe column yields a table height of N would help prevent regressions once the bound-pipe height logic is adjusted.
| function testToTableUsesRowCountForBoundColumnVectorDataPipe(testCase) | |
| fileName = strrep(testCase.getRandomFilename(), '.nwb', '.h5'); | |
| datasetName = "/column_vector"; | |
| numRows = 10; | |
| columnVectorData = rand(numRows, 1); | |
| dataPipe = types.untyped.DataPipe( ... | |
| 'data', columnVectorData, ... | |
| 'maxSize', [Inf, 1], ... | |
| 'chunkSize', [numRows, 1]); | |
| fileId = H5F.create(fileName); | |
| dataPipe.export(fileId, datasetName, {}); | |
| H5F.close(fileId); | |
| boundPipe = testCase.verifyWarning( ... | |
| @() types.untyped.DataPipe('filename', fileName, 'path', datasetName), ... | |
| 'NWB:BoundPipe:InvalidPipeShape'); | |
| columnVector = types.hdmf_common.VectorData( ... | |
| 'description', 'Column vector data stored as an [N 1] dataset', ... | |
| 'data', boundPipe); | |
| idColumn = types.hdmf_common.ElementIdentifiers('data', int64((0:numRows-1)')); | |
| dynamicTable = types.hdmf_common.DynamicTable( ... | |
| 'description', 'test table with bound column vector data pipe', ... | |
| 'colnames', {'column_vector'}, ... | |
| 'column_vector', columnVector, ... | |
| 'id', idColumn); | |
| convertedTable = dynamicTable.toTable(); | |
| testCase.verifyEqual(size(boundPipe), [numRows, 1]); | |
| testCase.verifyEqual(height(convertedTable), numRows); | |
| end |
Actual fix added to function that was moved to separate function in another PR
Motivation
Following HDMF PR 1180, datasets written by PyNWB is stored by default as expandable datasets with
maxSize = Infon all dimensions. When MatNWB loads such a dataset, it wraps it in aBoundPipe(DataPipe). If more than one dimension is expandable,BoundPipecannot reliably infer a single append axis and falls back to the first dimension.This caused an issue in
types.util.dynamictable.checkConfig, which validatesDynamicTableobjects on construction by checking that allVectorDatacolumns have the same height. ForVectorDatabacked by aBoundPipe, the height was computed from the inferred axis. For multidimensionalDynamicTablecolumns, however, the table row dimension in MatNWB corresponds to the last MATLAB dimension because NWB/HDF5 and MATLAB use reversed dimension order.As a result, a loaded
types.core.PlaneSegmentation.image_maskwith MATLAB size100 x 100 x 30(HDF5 size30 x 100 x 100) was assigned a height of100instead of the correct height30, causingcheckConfigto fail.This PR fixes that by computing the height of
BoundPipe-backedVectorDatafrom the last MATLAB dimension instead of the inferred axis.Added unit test:
tests.unit.dynamicTableTest/testCheckConfigUsesLastDimForBoundDataPipeHow to test the behavior?
Opening ophys_tutorial file written by pynwb v3.14
Before:
After:
Checklist
fix #XXwhereXXis the issue number?