From 8b42af34b63c6ce165c5c4c840f6628e6e4f8408 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Fri, 22 Mar 2024 12:47:01 -0700 Subject: [PATCH] Fix reading file with linked TimeSeriesReferenceVectorData (#1865) --- CHANGELOG.md | 3 +- src/pynwb/io/epoch.py | 8 ++++- tests/integration/hdf5/test_file_copy.py | 44 ++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 tests/integration/hdf5/test_file_copy.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 76a32b02c..06f8a31b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # PyNWB Changelog -## PyNWB 2.6.1 (Upcoming) +## PyNWB 2.6.1 (March 25, 2024) ### Bug fixes +- Fix bug with reading file with linked `TimeSeriesReferenceVectorData` @rly [#1865](https://github.com/NeurodataWithoutBorders/pynwb/pull/1865) - Fix bug where extra keyword arguments could not be passed to `NWBFile.add_{x}_column`` for use in custom `VectorData`` classes. @rly [#1861](https://github.com/NeurodataWithoutBorders/pynwb/pull/1861) ## PyNWB 2.6.0 (February 21, 2024) diff --git a/src/pynwb/io/epoch.py b/src/pynwb/io/epoch.py index 2e553c334..e5c35f17f 100644 --- a/src/pynwb/io/epoch.py +++ b/src/pynwb/io/epoch.py @@ -1,3 +1,4 @@ +from hdmf.build import LinkBuilder from hdmf.common.table import VectorData from hdmf.common.io.table import DynamicTableMap @@ -8,13 +9,18 @@ @register_map(TimeIntervals) class TimeIntervalsMap(DynamicTableMap): - pass @DynamicTableMap.constructor_arg('columns') def columns_carg(self, builder, manager): # handle the case when a TimeIntervals is read with a non-TimeSeriesReferenceVectorData "timeseries" column # this is the case for NWB schema v2.4 and earlier, where the timeseries column was a regular VectorData. timeseries_builder = builder.get('timeseries') + + # handle the case when the TimeIntervals has a "timeseries" column that is a link (e.g., it exists in + # a different file that was shallow copied to this file). + if isinstance(timeseries_builder, LinkBuilder): + timeseries_builder = timeseries_builder.builder + # if we have a timeseries column and the type is VectorData instead of TimeSeriesReferenceVectorData if (timeseries_builder is not None and timeseries_builder.attributes['neurodata_type'] != 'TimeSeriesReferenceVectorData'): diff --git a/tests/integration/hdf5/test_file_copy.py b/tests/integration/hdf5/test_file_copy.py new file mode 100644 index 000000000..b491586fb --- /dev/null +++ b/tests/integration/hdf5/test_file_copy.py @@ -0,0 +1,44 @@ +import os +from pynwb.base import TimeSeriesReference +from pynwb import NWBHDF5IO +from pynwb.testing import TestCase +from pynwb.testing.mock.file import mock_NWBFile +from pynwb.testing.mock.base import mock_TimeSeries + + +class TestFileCopy(TestCase): + + def setUp(self): + self.path1 = "test_a.h5" + self.path2 = "test_b.h5" + + def tearDown(self): + if os.path.exists(self.path1): + os.remove(self.path1) + if os.path.exists(self.path2): + os.remove(self.path2) + + def test_copy_file_link_timeintervals_timeseries(self): + """Test copying a file with a TimeSeriesReference in a TimeIntervals object and reading that copy. + + Based on https://github.com/NeurodataWithoutBorders/pynwb/issues/1863 + """ + new_nwb = mock_NWBFile() + test_ts = mock_TimeSeries(name="test_ts", timestamps=[1.0, 2.0, 3.0], data=[1.0, 2.0, 3.0]) + new_nwb.add_acquisition(test_ts) + new_nwb.add_trial(start_time=1.0, stop_time=2.0, timeseries=[test_ts]) + + with NWBHDF5IO(self.path1, 'w') as io: + io.write(new_nwb) + + with NWBHDF5IO(self.path1, 'r') as base_io: + # the TimeIntervals object is copied but the TimeSeriesReferenceVectorData is linked + nwb_add = base_io.read().copy() + with NWBHDF5IO(self.path2, 'w', manager=base_io.manager) as out_io: + out_io.write(nwb_add) + + with NWBHDF5IO(self.path2, 'r') as io: + nwb = io.read() + ts_val = nwb.trials["timeseries"][0][0] + assert isinstance(ts_val, TimeSeriesReference) + assert ts_val.timeseries is nwb.acquisition["test_ts"]