Skip to content

Commit

Permalink
update html representation of linked objects
Browse files Browse the repository at this point in the history
  • Loading branch information
stephprince committed Jan 29, 2024
1 parent ee3af9e commit bce6858
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 64 deletions.
27 changes: 22 additions & 5 deletions src/pynwb/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,21 +288,38 @@ 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':
value = value.data
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)

Expand Down
62 changes: 3 additions & 59 deletions tests/unit/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <style>\n .container-fields {\n font-family: "Open '
'Sans", Arial, sans-serif;\n }\n .container-fields .field-value {\n '
' color: #00788E;\n }\n .container-fields details > '
'summary {\n cursor: pointer;\n display: list-item;\n '
' }\n .container-fields details > summary:hover {\n color: '
'#0A6EAA;\n }\n </style>\n \n <script>\n '
'function copyToClipboard(text) {\n navigator.clipboard.writeText('
'text).then(function() {\n console.log(\'Copied to clipboard: \' + '
'text);\n }, function(err) {\n console.error(\'Could not '
'copy text: \', err);\n });\n }\n\n '
'document.addEventListener(\'DOMContentLoaded\', function() {\n let '
'fieldKeys = document.querySelectorAll(\'.container-fields .field-key\');\n '
'fieldKeys.forEach(function(fieldKey) {\n fieldKey.addEventListener('
'\'click\', function() {\n let accessCode = fieldKey.getAttribute('
'\'title\').replace(\'Access code: \', \'\');\n copyToClipboard('
'accessCode);\n });\n });\n });\n '
'</script>\n <div class=\'container-wrap\'><div class=\'container-header\'><div '
'class=\'xr-obj-type\'><h3>test_ts2 (TimeSeries)</h3></div></div><div style="margin-left: '
'0px;" class="container-fields"><span class="field-key" title=".resolution">resolution: '
'</span><span class="field-value">-1.0</span></div><div style="margin-left: 0px;" '
'class="container-fields"><span class="field-key" title=".comments">comments: </span><span '
'class="field-value">no comments</span></div><div style="margin-left: 0px;" '
'class="container-fields"><span class="field-key" title=".description">description: '
'</span><span class="field-value">no description</span></div><div style="margin-left: '
'0px;" class="container-fields"><span class="field-key" title=".conversion">conversion: '
'</span><span class="field-value">1.0</span></div><div style="margin-left: 0px;" '
'class="container-fields"><span class="field-key" title=".offset">offset: </span><span '
'class="field-value">0.0</span></div><div style="margin-left: 0px;" '
'class="container-fields"><span class="field-key" title=".unit">unit: </span><span '
'class="field-value">grams</span></div><details><summary style="display: list-item; '
'margin-left: 0px;" class="container-fields field-key" '
'title=".data"><b>data</b></summary><div style="margin-left: 20px;" '
'class="container-fields"><span class="field-key" title=".data[0]">0: </span><span '
'class="field-value">4</span></div><div style="margin-left: 20px;" '
'class="container-fields"><span class="field-key" title=".data[0][1]">1: </span><span '
'class="field-value">5</span></div><div style="margin-left: 20px;" '
'class="container-fields"><span class="field-key" title=".data[0][1][2]">2: </span><span '
'class="field-value">6</span></div><div style="margin-left: 20px;" '
'class="container-fields"><span class="field-key" title=".data[0][1][2][3]">3: '
'</span><span class="field-value">7</span></div></details><details><summary '
'style="display: list-item; margin-left: 0px;" class="container-fields field-key" '
'title=".timestamps"><b>timestamps (link to /test_ts1)</b></summary><div '
'style="margin-left: 20px;" class="container-fields"><span class="field-key" '
'title=".timestamps[0]">0: </span><span class="field-value">0.0</span></div><div '
'style="margin-left: 20px;" class="container-fields"><span class="field-key" '
'title=".timestamps[0][1]">1: </span><span class="field-value">0.1</span></div><div '
'style="margin-left: 20px;" class="container-fields"><span class="field-key" '
'title=".timestamps[0][1][2]">2: </span><span class="field-value">0.2</span></div><div '
'style="margin-left: 20px;" class="container-fields"><span class="field-key" '
'title=".timestamps[0][1][2][3]">3: </span><span '
'class="field-value">0.3</span></div></details><div style="margin-left: 0px;" '
'class="container-fields"><span class="field-key" '
'title=".timestamps_unit">timestamps_unit: </span><span '
'class="field-value">seconds</span></div><div style="margin-left: 0px;" '
'class="container-fields"><span class="field-key" title=".interval">interval: </span><span '
'class="field-value">1</span></div></div>')
assert ts2._repr_html_() == expected_output
self.assertIn('(link to test_ts1/timestamps)', ts2._repr_html_())


class TestImage(TestCase):
Expand Down

0 comments on commit bce6858

Please sign in to comment.