From 4468878d3ddd82ce5e1e0373f31f6b389fe1a30b Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Wed, 24 Jan 2024 12:44:43 -0800 Subject: [PATCH 1/7] add custom html field generation method for timeseries --- src/pynwb/base.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pynwb/base.py b/src/pynwb/base.py index 42f7b7ff3..543b60d50 100644 --- a/src/pynwb/base.py +++ b/src/pynwb/base.py @@ -287,6 +287,13 @@ def __get_links(self, links): def __add_link(self, links_key, link): self.fields.setdefault(links_key, list()).append(link) + def _generate_field_html(self, key, value, level, access_code): + # reassign value if linked timestamp or linked data to avoid recursion error + if key in ['timestamp_link', 'data_link']: + value = {v.name: v.neurodata_type for v in value} + + return super()._generate_field_html(key, value, level, access_code) + @property def time_unit(self): return self.__time_unit From d5559199d3f2244a61646e5df7f80a65aad24042 Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:23:00 -0800 Subject: [PATCH 2/7] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ddcdda7b..1ec263a12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Bug fixes - Fix bug where namespaces were loaded in "w-" mode. @h-mayorquin [#1795](https://github.com/NeurodataWithoutBorders/pynwb/pull/1795) - Fix bug where pynwb version was reported as "unknown" to readthedocs @stephprince [#1810](https://github.com/NeurodataWithoutBorders/pynwb/pull/1810) +- Fix recursion error in html representation generation in jupyter notebooks. @stephprince [#1831](https://github.com/NeurodataWithoutBorders/pynwb/pull/1831) ### Documentation and tutorial enhancements - Add RemFile to streaming tutorial. @bendichter [#1761](https://github.com/NeurodataWithoutBorders/pynwb/pull/1761) From fb4def648faa8f9c2dd5bebf7df3ef762b3746f5 Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Wed, 24 Jan 2024 18:07:14 -0800 Subject: [PATCH 3/7] modify html representation of linked timestamps and data --- src/pynwb/base.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/pynwb/base.py b/src/pynwb/base.py index 543b60d50..a00414745 100644 --- a/src/pynwb/base.py +++ b/src/pynwb/base.py @@ -288,9 +288,21 @@ def __add_link(self, links_key, link): self.fields.setdefault(links_key, list()).append(link) def _generate_field_html(self, key, value, level, access_code): + def get_object_path(obj): + path = '/'.join([a.name for a in obj.get_ancestors()[::-1]]) + return f'{path}/{obj.name}' + # reassign value if linked timestamp or linked data to avoid recursion error + if key in ['timestamps', 'data'] and isinstance(value, TimeSeries): + path_to_linked_object = get_object_path(value) + if key == 'timestamps': + value = value.timestamps + elif key == 'data': + value = value.data + key = f'{key} (link to {path_to_linked_object})' + if key in ['timestamp_link', 'data_link']: - value = {v.name: v.neurodata_type for v in value} + value = [get_object_path(v) for v in value] return super()._generate_field_html(key, value, level, access_code) From ee3af9e90dc7c2b32715dffdfa1fc3f57beefb26 Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Wed, 24 Jan 2024 18:08:02 -0800 Subject: [PATCH 4/7] add test for html representation of linked data --- tests/unit/test_base.py | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/unit/test_base.py b/tests/unit/test_base.py index ad4ce6739..d8e152fa4 100644 --- a/tests/unit/test_base.py +++ b/tests/unit/test_base.py @@ -464,6 +464,73 @@ def test_file_with_starting_time_and_timestamps_in_construct_mode(self): timestamps=[1, 2, 3, 4, 5] ) + def test_repr_html(self): + """ Test that html representation of linked timestamp data will occur as expected and will not cause a Recursion + Error + """ + data1 = [0, 1, 2, 3] + data2 = [4, 5, 6, 7] + timestamps = [0.0, 0.1, 0.2, 0.3] + ts1 = TimeSeries(name="test_ts1", data=data1, unit="grams", timestamps=timestamps) + ts2 = TimeSeries(name="test_ts2", data=data2, unit="grams", timestamps=ts1) + expected_output = ('\n \n \n \n

test_ts2 (TimeSeries)

resolution: ' + '-1.0
comments: no comments
description: ' + 'no description
conversion: ' + '1.0
offset: 0.0
unit: grams
data
0: 4
1: 5
2: 6
3: ' + '7
timestamps (link to /test_ts1)
0: 0.0
1: 0.1
2: 0.2
3: 0.3
timestamps_unit: seconds
interval: 1
') + assert ts2._repr_html_() == expected_output + class TestImage(TestCase): def test_init(self): From bce68582b2e0c2487906402dbbad5cde2d828157 Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Mon, 29 Jan 2024 11:20:09 -0800 Subject: [PATCH 5/7] update html representation of linked objects --- src/pynwb/base.py | 27 ++++++++++++++---- tests/unit/test_base.py | 62 ++--------------------------------------- 2 files changed, 25 insertions(+), 64 deletions(-) diff --git a/src/pynwb/base.py b/src/pynwb/base.py index a00414745..36c71cbb1 100644 --- a/src/pynwb/base.py +++ b/src/pynwb/base.py @@ -288,13 +288,29 @@ def __add_link(self, links_key, link): self.fields.setdefault(links_key, list()).append(link) def _generate_field_html(self, key, value, level, access_code): - def get_object_path(obj): - path = '/'.join([a.name for a in obj.get_ancestors()[::-1]]) - return f'{path}/{obj.name}' + def find_location_in_memory_nwbfile(current_location: str, neurodata_object) -> str: + """ + Method for determining the location of a neurodata object within an in-memory NWBFile object. Adapted from + neuroconv package. + """ + parent = neurodata_object.parent + if parent is None: + return neurodata_object.name + "/" + current_location + elif parent.name == 'root': + # Items in defined top-level places like acquisition, intervals, etc. do not act as 'containers' + # in that they do not set the `.parent` attribute; ask if object is in their in-memory dictionaries + # instead + for parent_field_name, parent_field_value in parent.fields.items(): + if isinstance(parent_field_value, dict) and neurodata_object.name in parent_field_value: + return parent_field_name + "/" + neurodata_object.name + "/" + current_location + return neurodata_object.name + "/" + current_location + return find_location_in_memory_nwbfile( + current_location=neurodata_object.name + "/" + current_location, neurodata_object=parent + ) # reassign value if linked timestamp or linked data to avoid recursion error if key in ['timestamps', 'data'] and isinstance(value, TimeSeries): - path_to_linked_object = get_object_path(value) + path_to_linked_object = find_location_in_memory_nwbfile(key, value) if key == 'timestamps': value = value.timestamps elif key == 'data': @@ -302,7 +318,8 @@ def get_object_path(obj): key = f'{key} (link to {path_to_linked_object})' if key in ['timestamp_link', 'data_link']: - value = [get_object_path(v) for v in value] + linked_key = 'timestamps' if key == 'timestamp_link' else 'data' + value = [find_location_in_memory_nwbfile(linked_key, v) for v in value] return super()._generate_field_html(key, value, level, access_code) diff --git a/tests/unit/test_base.py b/tests/unit/test_base.py index d8e152fa4..3bc0bc6e3 100644 --- a/tests/unit/test_base.py +++ b/tests/unit/test_base.py @@ -465,71 +465,15 @@ def test_file_with_starting_time_and_timestamps_in_construct_mode(self): ) def test_repr_html(self): - """ Test that html representation of linked timestamp data will occur as expected and will not cause a Recursion - Error + """ Test that html representation of linked timestamp data will occur as expected and will not cause a + RecursionError """ data1 = [0, 1, 2, 3] data2 = [4, 5, 6, 7] timestamps = [0.0, 0.1, 0.2, 0.3] ts1 = TimeSeries(name="test_ts1", data=data1, unit="grams", timestamps=timestamps) ts2 = TimeSeries(name="test_ts2", data=data2, unit="grams", timestamps=ts1) - expected_output = ('\n \n \n \n

test_ts2 (TimeSeries)

resolution: ' - '-1.0
comments: no comments
description: ' - 'no description
conversion: ' - '1.0
offset: 0.0
unit: grams
data
0: 4
1: 5
2: 6
3: ' - '7
timestamps (link to /test_ts1)
0: 0.0
1: 0.1
2: 0.2
3: 0.3
timestamps_unit: seconds
interval: 1
') - assert ts2._repr_html_() == expected_output + self.assertIn('(link to test_ts1/timestamps)', ts2._repr_html_()) class TestImage(TestCase): From d24bde8be1b94b5af96a0d4ce425e9d8ca4b2d71 Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:35:24 -0800 Subject: [PATCH 6/7] update html representation test --- tests/unit/test_base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_base.py b/tests/unit/test_base.py index 3bc0bc6e3..f8c08f68f 100644 --- a/tests/unit/test_base.py +++ b/tests/unit/test_base.py @@ -473,7 +473,10 @@ def test_repr_html(self): timestamps = [0.0, 0.1, 0.2, 0.3] ts1 = TimeSeries(name="test_ts1", data=data1, unit="grams", timestamps=timestamps) ts2 = TimeSeries(name="test_ts2", data=data2, unit="grams", timestamps=ts1) - self.assertIn('(link to test_ts1/timestamps)', ts2._repr_html_()) + pm = ProcessingModule(name="processing", description="a test processing module") + pm.add(ts1) + pm.add(ts2) + self.assertIn('(link to processing/test_ts1/timestamps)', pm._repr_html_()) class TestImage(TestCase): From 88c0d447a117122f3ca44cd3eba13cec52587f44 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Fri, 9 Feb 2024 11:22:44 -0800 Subject: [PATCH 7/7] Use HDMF 3.12.2 --- environment-ros3.yml | 2 +- requirements-min.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/environment-ros3.yml b/environment-ros3.yml index f5edea6ad..07838258e 100644 --- a/environment-ros3.yml +++ b/environment-ros3.yml @@ -6,7 +6,7 @@ channels: dependencies: - python==3.11 - h5py==3.8.0 - - hdmf==3.12.1 + - hdmf==3.12.2 - matplotlib==3.7.1 - numpy==1.24.2 - pandas==2.0.0 diff --git a/requirements-min.txt b/requirements-min.txt index 098aea15d..0e8bde429 100644 --- a/requirements-min.txt +++ b/requirements-min.txt @@ -1,6 +1,6 @@ # minimum versions of package dependencies for installing PyNWB h5py==2.10 # support for selection of datasets with list of indices added in 2.10 -hdmf==3.12.1 +hdmf==3.12.2 numpy==1.18 pandas==1.1.5 python-dateutil==2.7.3 diff --git a/requirements.txt b/requirements.txt index 0add9c54d..ad5d748bd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # pinned dependencies to reproduce an entire development environment to use PyNWB h5py==3.10.0 -hdmf==3.12.1 +hdmf==3.12.2 numpy==1.26.1 pandas==2.1.2 python-dateutil==2.8.2 diff --git a/setup.py b/setup.py index d03688905..2a9ecb19e 100755 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ reqs = [ 'h5py>=2.10', - 'hdmf>=3.12.1', + 'hdmf>=3.12.2', 'numpy>=1.16', 'pandas>=1.1.5', 'python-dateutil>=2.7.3',