Skip to content

Commit

Permalink
add custom html field generation method for TimeSeries (#1831)
Browse files Browse the repository at this point in the history
* add custom html field generation method for timeseries

* update CHANGELOG.md

* modify html representation of linked timestamps and data

* add test for html representation of linked data

* update html representation of linked objects

* update html representation test

* Use HDMF 3.12.2

---------

Co-authored-by: Ryan Ly <[email protected]>
  • Loading branch information
stephprince and rly authored Feb 9, 2024
1 parent f77f33c commit 0a70990
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 4 deletions.
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 __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):
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 = find_location_in_memory_nwbfile(key, 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']:
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

0 comments on commit 0a70990

Please sign in to comment.