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
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 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',