From e952b560c1675381699a3e9c01b943c13e00c15e Mon Sep 17 00:00:00 2001 From: rly Date: Thu, 21 Mar 2024 23:22:44 -0700 Subject: [PATCH 1/3] Fix reading file with linked TimeSeriesReferenceVectorData --- src/pynwb/io/epoch.py | 8 ++++- tests/integration/hdf5/test_file_copy.py | 43 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 tests/integration/hdf5/test_file_copy.py 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..a6c37d56a --- /dev/null +++ b/tests/integration/hdf5/test_file_copy.py @@ -0,0 +1,43 @@ +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"] \ No newline at end of file From ce8e413f0ac47cfb0203ad747018dce75a93fb11 Mon Sep 17 00:00:00 2001 From: rly Date: Thu, 21 Mar 2024 23:26:41 -0700 Subject: [PATCH 2/3] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3f762419..8be5b2e98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # PyNWB Changelog +## PyNWB 2.6.1 (March 22, 2024) + +### Bug fixes +- Fix bug with reading file with linked `TimeSeriesReferenceVectorData` @rly [#1865](https://github.com/NeurodataWithoutBorders/pynwb/pull/1865) + ## PyNWB 2.6.0 (February 21, 2024) ### Enhancements and minor changes From 1aeffd8ea19216b1b3ba6c0f2408cc6affbdb912 Mon Sep 17 00:00:00 2001 From: rly Date: Thu, 21 Mar 2024 23:28:21 -0700 Subject: [PATCH 3/3] Fix flake8 --- tests/integration/hdf5/test_file_copy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/hdf5/test_file_copy.py b/tests/integration/hdf5/test_file_copy.py index a6c37d56a..b491586fb 100644 --- a/tests/integration/hdf5/test_file_copy.py +++ b/tests/integration/hdf5/test_file_copy.py @@ -5,6 +5,7 @@ from pynwb.testing.mock.file import mock_NWBFile from pynwb.testing.mock.base import mock_TimeSeries + class TestFileCopy(TestCase): def setUp(self): @@ -40,4 +41,4 @@ def test_copy_file_link_timeintervals_timeseries(self): nwb = io.read() ts_val = nwb.trials["timeseries"][0][0] assert isinstance(ts_val, TimeSeriesReference) - assert ts_val.timeseries is nwb.acquisition["test_ts"] \ No newline at end of file + assert ts_val.timeseries is nwb.acquisition["test_ts"]