Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add custom html field generation method for TimeSeries #1831

Merged
merged 9 commits into from
Feb 9, 2024
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- 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)
- Fixed bug to allow linking of `TimeSeries.data` by setting the `data` constructor argument to another `TimeSeries`. @oruebel [#1766](https://github.com/NeurodataWithoutBorders/pynwb/pull/1766)
- 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)
Expand Down
2 changes: 1 addition & 1 deletion environment-ros3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion requirements-min.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
36 changes: 36 additions & 0 deletions src/pynwb/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,42 @@
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 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

Check warning on line 306 in src/pynwb/base.py

View check run for this annotation

Codecov / codecov/patch

src/pynwb/base.py#L305-L306

Added lines #L305 - L306 were not covered by tests
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 = find_location_in_memory_nwbfile(key, value)
if key == 'timestamps':
value = value.timestamps
elif key == 'data':
value = value.data

Check warning on line 317 in src/pynwb/base.py

View check run for this annotation

Codecov / codecov/patch

src/pynwb/base.py#L317

Added line #L317 was not covered by tests
key = f'{key} (link to {path_to_linked_object})'

if key in ['timestamp_link', 'data_link']:
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)

@property
def time_unit(self):
return self.__time_unit
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,20 @@ 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
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)
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):
def test_init(self):
Expand Down
Loading