From 2be97c9b4708d98a400086ccb6373822a58e990f Mon Sep 17 00:00:00 2001 From: mavaylon1 Date: Thu, 18 Apr 2024 12:20:38 -0500 Subject: [PATCH 01/13] ElectrodesTable --- src/pynwb/ecephys.py | 45 +++++++++++++++++++++++++++++-- src/pynwb/file.py | 34 +++++++++++------------ src/pynwb/io/file.py | 2 ++ src/pynwb/nwb-schema | 2 +- tests/unit/foo.py | 64 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 20 deletions(-) create mode 100644 tests/unit/foo.py diff --git a/src/pynwb/ecephys.py b/src/pynwb/ecephys.py index 3187cca4a..1ce143f94 100644 --- a/src/pynwb/ecephys.py +++ b/src/pynwb/ecephys.py @@ -1,9 +1,9 @@ import warnings from collections.abc import Iterable -from hdmf.common import DynamicTableRegion +from hdmf.common import DynamicTableRegion, DynamicTable, VectorData from hdmf.data_utils import DataChunkIterator, assertEqualShape -from hdmf.utils import docval, popargs, get_docval, popargs_to_dict, get_data_shape +from hdmf.utils import docval, popargs, getargs, get_docval, popargs_to_dict, get_data_shape from . import register_class, CORE_NAMESPACE from .base import TimeSeries @@ -37,6 +37,47 @@ def __init__(self, **kwargs): setattr(self, key, val) +@register_class('ElectrodesTable', CORE_NAMESPACE) +class ElectrodesTable(DynamicTable): + """TODO""" + + __columns__ = ( + {'name': 'location', 'description': 'TODO', 'required': True}, + {'name': 'group', 'description': 'TODO', 'required': True}) + + @docval({'name': 'group_name', 'type': VectorData, 'doc':'TODO', 'default': None}, + {'name': 'x', 'type': float, 'doc':'TODO', 'default': None}, + {'name': 'y', 'type': float, 'doc':'TODO', 'default': None}, + {'name': 'z', 'type': float, 'doc':'TODO', 'default': None}, + {'name': 'imp', 'type': float, 'doc':'TODO', 'default': None}, + {'name': 'filtering', 'type': str, 'doc':'TODO', 'default': None}, + {'name': 'rel_x', 'type': float, 'doc':'TODO', 'default': None}, + {'name': 'rel_y', 'type': float, 'doc':'TODO', 'default': None}, + {'name': 'rel_z', 'type': float, 'doc':'TODO', 'default': None}, + {'name': 'reference', 'type': VectorData, 'doc':'TODO', 'default': None},) + def __init__(self, **kwargs): + kwargs['name'] = 'electrodes' + kwargs['description'] = 'metadata about extracellular electrodes' + + # optional fields + keys_to_set = ( + 'group_name', + 'x', + 'y', + 'z', + 'imp', + 'filtering', + 'rel_x', + 'rel_y', + 'rel_z', + 'reference') + args_to_set = popargs_to_dict(keys_to_set, kwargs) + for key, val in args_to_set.items(): + setattr(self, key, val) + + super().__init__(**kwargs) + + @register_class('ElectricalSeries', CORE_NAMESPACE) class ElectricalSeries(TimeSeries): """ diff --git a/src/pynwb/file.py b/src/pynwb/file.py index 0b294e873..886d2ec20 100644 --- a/src/pynwb/file.py +++ b/src/pynwb/file.py @@ -15,7 +15,7 @@ from .base import TimeSeries, ProcessingModule from .device import Device from .epoch import TimeIntervals -from .ecephys import ElectrodeGroup +from .ecephys import ElectrodeGroup, ElectrodesTable from .icephys import (IntracellularElectrode, SweepTable, PatchClampSeries, IntracellularRecordingsTable, SimultaneousRecordingsTable, SequentialRecordingsTable, RepetitionsTable, ExperimentalConditionsTable) @@ -377,7 +377,7 @@ class NWBFile(MultiContainerInterface, HERDManager): {'name': 'lab_meta_data', 'type': (list, tuple), 'default': None, 'doc': 'an extension that contains lab-specific meta-data'}, {'name': 'electrodes', 'type': DynamicTable, - 'doc': 'the ElectrodeTable that belongs to this NWBFile', 'default': None}, + 'doc': 'the ElectrodesTable that belongs to this NWBFile', 'default': None}, {'name': 'electrode_groups', 'type': Iterable, 'doc': 'the ElectrodeGroups that belong to this NWBFile', 'default': None}, {'name': 'ic_electrodes', 'type': (list, tuple), @@ -644,7 +644,7 @@ def add_epoch(self, **kwargs): def __check_electrodes(self): if self.electrodes is None: - self.electrodes = ElectrodeTable() + self.electrodes = ElectrodesTable() @docval(*get_docval(DynamicTable.add_column), allow_extra=True) def add_electrode_column(self, **kwargs): @@ -701,8 +701,8 @@ def add_electrode(self, **kwargs): raise ValueError("The 'location' argument is required when creating an electrode.") if not kwargs['group']: raise ValueError("The 'group' argument is required when creating an electrode.") - if d.get('group_name', None) is None: - d['group_name'] = d['group'].name + # if d.get('group_name', None) is None: + # d['group_name'] = d['group'].name new_cols = [('x', 'the x coordinate of the position (+x is posterior)'), ('y', 'the y coordinate of the position (+y is inferior)'), @@ -738,7 +738,7 @@ def create_electrode_table_region(self, **kwargs): for idx in region: if idx < 0 or idx >= len(self.electrodes): raise IndexError('The index ' + str(idx) + - ' is out of range for the ElectrodeTable of length ' + ' is out of range for the ElectrodesTable of length ' + str(len(self.electrodes))) desc = getargs('description', kwargs) name = getargs('name', kwargs) @@ -820,13 +820,13 @@ def add_invalid_time_interval(self, **kwargs): self.__check_invalid_times() self.invalid_times.add_interval(**kwargs) - @docval({'name': 'electrode_table', 'type': DynamicTable, 'doc': 'the ElectrodeTable for this file'}) + @docval({'name': 'electrode_table', 'type': DynamicTable, 'doc': 'the ElectrodesTable for this file'}) def set_electrode_table(self, **kwargs): """ - Set the electrode table of this NWBFile to an existing ElectrodeTable + Set the electrode table of this NWBFile to an existing ElectrodesTable """ if self.electrodes is not None: - msg = 'ElectrodeTable already exists, cannot overwrite' + msg = 'ElectrodesTable already exists, cannot overwrite' raise ValueError(msg) electrode_table = getargs('electrode_table', kwargs) self.electrodes = electrode_table @@ -1179,14 +1179,14 @@ def _tablefunc(table_name, description, columns): return t -def ElectrodeTable(name='electrodes', - description='metadata about extracellular electrodes'): - return _tablefunc(name, description, - [('location', 'the location of channel within the subject e.g. brain region'), - ('group', 'a reference to the ElectrodeGroup this electrode is a part of'), - ('group_name', 'the name of the ElectrodeGroup this electrode is a part of') - ] - ) +# def ElectrodesTable(name='electrodes', +# description='metadata about extracellular electrodes'): +# return _tablefunc(name, description, +# [('location', 'the location of channel within the subject e.g. brain region'), +# ('group', 'a reference to the ElectrodeGroup this electrode is a part of'), +# ('group_name', 'the name of the ElectrodeGroup this electrode is a part of') +# ] +# ) def TrialTable(name='trials', description='metadata about experimental trials'): diff --git a/src/pynwb/io/file.py b/src/pynwb/io/file.py index 1908c6b31..0e9eb5d90 100644 --- a/src/pynwb/io/file.py +++ b/src/pynwb/io/file.py @@ -34,6 +34,7 @@ def __init__(self, spec): # map "stimulus" to NWBDataInterface and DynamicTable and unmap the spec for TimeSeries because it is # included in the mapping to NWBDataInterface self.unmap(stimulus_spec.get_group('presentation').get_neurodata_type('TimeSeries')) + # breakpoint() self.map_spec('stimulus', stimulus_spec.get_group('presentation').get_neurodata_type('NWBDataInterface')) self.map_spec('stimulus', stimulus_spec.get_group('presentation').get_neurodata_type('DynamicTable')) self.map_spec('stimulus_template', stimulus_spec.get_group('templates').get_neurodata_type('TimeSeries')) @@ -73,6 +74,7 @@ def __init__(self, spec): ecephys_spec = general_spec.get_group('extracellular_ephys') self.unmap(ecephys_spec) + # breakpoint() self.map_spec('electrodes', ecephys_spec.get_group('electrodes')) self.map_spec('electrode_groups', ecephys_spec.get_neurodata_type('ElectrodeGroup')) diff --git a/src/pynwb/nwb-schema b/src/pynwb/nwb-schema index d65d42257..7393211bb 160000 --- a/src/pynwb/nwb-schema +++ b/src/pynwb/nwb-schema @@ -1 +1 @@ -Subproject commit d65d42257003543c569ea7ac0cd6d7aee01c88d6 +Subproject commit 7393211bb04a61c153239dc49691598b4a41fa39 diff --git a/tests/unit/foo.py b/tests/unit/foo.py new file mode 100644 index 000000000..1c167f6df --- /dev/null +++ b/tests/unit/foo.py @@ -0,0 +1,64 @@ +from datetime import datetime +from uuid import uuid4 + +import numpy as np +from dateutil.tz import tzlocal +from hdmf.common import VectorData + +from pynwb import NWBHDF5IO, NWBFile +from pynwb.ecephys import LFP, ElectricalSeries, ElectrodeGroup, ElectrodesTable + +nwbfile = NWBFile( + session_description="my first synthetic recording", + identifier=str(uuid4()), + session_start_time=datetime.now(tzlocal()), + experimenter=[ + "Baggins, Bilbo", + ], + lab="Bag End Laboratory", + institution="University of Middle Earth at the Shire", + experiment_description="I went on an adventure to reclaim vast treasures.", + session_id="LONELYMTN001", +) + +device = nwbfile.create_device( + name="array", description="the best array", manufacturer="Probe Company 9000" +) + +group = ElectrodeGroup( name='foo', + description="electrode group", + device=device, + location="brain area",) +# location_col = VectorData(name='location', description='foo', data=['brain area']) +# group_col = VectorData(name='groups', description='foo', data=[group]) + +# table = ElectrodesTable() +# nwbfile.electrodes = table +# nwbfile.add_electrode(group=group, location='brain') +# breakpoint() +# nwbfile.add_electrode_column(name="label", description="label of electrode") + +nshanks = 4 +nchannels_per_shank = 3 +electrode_counter = 0 +# +for ishank in range(nshanks): + # create an electrode group for this shank + electrode_group = nwbfile.create_electrode_group( + name="shank{}".format(ishank), + description="electrode group for shank {}".format(ishank), + device=device, + location="brain area", + ) + # add electrodes to the electrode table + for ielec in range(nchannels_per_shank): + nwbfile.add_electrode( + group=electrode_group, + label="shank{}elec{}".format(ishank, ielec), + location="brain area", + ) + electrode_counter += 1 +# breakpoint() +with NWBHDF5IO("ecephys_tutorial.nwb", "w") as io: + io.write(nwbfile) +breakpoint() From ea7d60fc56e8d37ba134003b9734821db0624b82 Mon Sep 17 00:00:00 2001 From: mavaylon1 Date: Thu, 18 Apr 2024 12:36:12 -0500 Subject: [PATCH 02/13] foo --- tests/unit/foo.py | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/unit/foo.py b/tests/unit/foo.py index 1c167f6df..349970fe8 100644 --- a/tests/unit/foo.py +++ b/tests/unit/foo.py @@ -32,32 +32,32 @@ # location_col = VectorData(name='location', description='foo', data=['brain area']) # group_col = VectorData(name='groups', description='foo', data=[group]) -# table = ElectrodesTable() -# nwbfile.electrodes = table -# nwbfile.add_electrode(group=group, location='brain') +table = ElectrodesTable() +nwbfile.electrodes = table +nwbfile.add_electrode(group=group, location='brain') # breakpoint() # nwbfile.add_electrode_column(name="label", description="label of electrode") -nshanks = 4 -nchannels_per_shank = 3 -electrode_counter = 0 -# -for ishank in range(nshanks): - # create an electrode group for this shank - electrode_group = nwbfile.create_electrode_group( - name="shank{}".format(ishank), - description="electrode group for shank {}".format(ishank), - device=device, - location="brain area", - ) - # add electrodes to the electrode table - for ielec in range(nchannels_per_shank): - nwbfile.add_electrode( - group=electrode_group, - label="shank{}elec{}".format(ishank, ielec), - location="brain area", - ) - electrode_counter += 1 +# nshanks = 4 +# nchannels_per_shank = 3 +# electrode_counter = 0 +# # +# for ishank in range(nshanks): +# # create an electrode group for this shank +# electrode_group = nwbfile.create_electrode_group( +# name="shank{}".format(ishank), +# description="electrode group for shank {}".format(ishank), +# device=device, +# location="brain area", +# ) +# # add electrodes to the electrode table +# for ielec in range(nchannels_per_shank): +# nwbfile.add_electrode( +# group=electrode_group, +# label="shank{}elec{}".format(ishank, ielec), +# location="brain area", +# ) +# electrode_counter += 1 # breakpoint() with NWBHDF5IO("ecephys_tutorial.nwb", "w") as io: io.write(nwbfile) From fe28745202ca8b29195cafad64ecaca55af5da8a Mon Sep 17 00:00:00 2001 From: mavaylon1 Date: Thu, 18 Apr 2024 15:22:39 -0500 Subject: [PATCH 03/13] check --- src/pynwb/io/file.py | 2 -- tests/unit/foo.py | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pynwb/io/file.py b/src/pynwb/io/file.py index 0e9eb5d90..1908c6b31 100644 --- a/src/pynwb/io/file.py +++ b/src/pynwb/io/file.py @@ -34,7 +34,6 @@ def __init__(self, spec): # map "stimulus" to NWBDataInterface and DynamicTable and unmap the spec for TimeSeries because it is # included in the mapping to NWBDataInterface self.unmap(stimulus_spec.get_group('presentation').get_neurodata_type('TimeSeries')) - # breakpoint() self.map_spec('stimulus', stimulus_spec.get_group('presentation').get_neurodata_type('NWBDataInterface')) self.map_spec('stimulus', stimulus_spec.get_group('presentation').get_neurodata_type('DynamicTable')) self.map_spec('stimulus_template', stimulus_spec.get_group('templates').get_neurodata_type('TimeSeries')) @@ -74,7 +73,6 @@ def __init__(self, spec): ecephys_spec = general_spec.get_group('extracellular_ephys') self.unmap(ecephys_spec) - # breakpoint() self.map_spec('electrodes', ecephys_spec.get_group('electrodes')) self.map_spec('electrode_groups', ecephys_spec.get_neurodata_type('ElectrodeGroup')) diff --git a/tests/unit/foo.py b/tests/unit/foo.py index 349970fe8..3c9cace6f 100644 --- a/tests/unit/foo.py +++ b/tests/unit/foo.py @@ -34,6 +34,7 @@ table = ElectrodesTable() nwbfile.electrodes = table +nwbfile.add_electrode_group(group) nwbfile.add_electrode(group=group, location='brain') # breakpoint() # nwbfile.add_electrode_column(name="label", description="label of electrode") @@ -61,4 +62,6 @@ # breakpoint() with NWBHDF5IO("ecephys_tutorial.nwb", "w") as io: io.write(nwbfile) +with NWBHDF5IO("ecephys_tutorial.nwb", "r") as io: + read_nwbfile = io.read() breakpoint() From 5ac732173f6bf3a3bfeb4ea5da10a9e4e3741933 Mon Sep 17 00:00:00 2001 From: mavaylon1 Date: Thu, 18 Apr 2024 15:38:47 -0500 Subject: [PATCH 04/13] file --- .../2.6.0_DynamicTableElectrodes.nwb | Bin 0 -> 201304 bytes tests/unit/foo.py | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 tests/back_compat/2.6.0_DynamicTableElectrodes.nwb diff --git a/tests/back_compat/2.6.0_DynamicTableElectrodes.nwb b/tests/back_compat/2.6.0_DynamicTableElectrodes.nwb new file mode 100644 index 0000000000000000000000000000000000000000..e0f2594031164ea3c5c59d52fbc5a597dc02abfc GIT binary patch literal 201304 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 Date: Thu, 25 Apr 2024 13:13:41 -0700 Subject: [PATCH 05/13] checkpoint --- src/pynwb/__init__.py | 29 +++++++++++++++++++++++--- src/pynwb/file.py | 2 +- src/pynwb/testing/mock/ecephys.py | 4 ++-- tests/integration/hdf5/test_ecephys.py | 2 +- tests/integration/hdf5/test_misc.py | 2 +- tests/unit/foo.py | 3 +++ tests/unit/test_ecephys.py | 2 +- tests/unit/test_file.py | 4 ++-- tests/unit/test_misc.py | 4 ++-- 9 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/pynwb/__init__.py b/src/pynwb/__init__.py index 9ea18efb8..789f362a0 100644 --- a/src/pynwb/__init__.py +++ b/src/pynwb/__init__.py @@ -3,7 +3,7 @@ ''' import os.path from pathlib import Path -from copy import deepcopy +from copy import deepcopy, copy import h5py from hdmf.spec import NamespaceCatalog @@ -46,11 +46,34 @@ def _get_resources(): __NS_CATALOG = NamespaceCatalog(NWBGroupSpec, NWBDatasetSpec, NWBNamespace) +class NWBTypeMap(TypeMap): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def __copy__(self): + ret = NWBTypeMap(namespaces=copy(self._TypeMap__ns_catalog), + mapper_cls=self._TypeMap__default_mapper_cls, + type_config=self.type_config) + ret.merge(self) + return ret + + def modify_builder(self, builder): + try: + ndtype = builder['neurodata_type'] + except KeyError: + ndtype = 'Not Group' + # remap ElectrodesTable from a DynamicTable named electrodes + if ndtype == 'DynamicTable' and builder.name == 'electrodes': + builder.set_attribute(name='namespace', value='core') + builder.set_attribute(name='neurodata_type', value='ElectrodesTable') + return builder + else: + return None + hdmf_typemap = hdmf.common.get_type_map() -__TYPE_MAP = TypeMap(__NS_CATALOG) +__TYPE_MAP = NWBTypeMap(namespaces=__NS_CATALOG) __TYPE_MAP.merge(hdmf_typemap, ns_catalog=True) - @docval({'name': 'extensions', 'type': (str, TypeMap, list), 'doc': 'a path to a namespace, a TypeMap, or a list consisting of paths to namespaces and TypeMaps', 'default': None}, diff --git a/src/pynwb/file.py b/src/pynwb/file.py index 886d2ec20..aec23353f 100644 --- a/src/pynwb/file.py +++ b/src/pynwb/file.py @@ -820,7 +820,7 @@ def add_invalid_time_interval(self, **kwargs): self.__check_invalid_times() self.invalid_times.add_interval(**kwargs) - @docval({'name': 'electrode_table', 'type': DynamicTable, 'doc': 'the ElectrodesTable for this file'}) + @docval({'name': 'electrode_table', 'type': ElectrodesTable, 'doc': 'the ElectrodesTable for this file'}) def set_electrode_table(self, **kwargs): """ Set the electrode table of this NWBFile to an existing ElectrodesTable diff --git a/src/pynwb/testing/mock/ecephys.py b/src/pynwb/testing/mock/ecephys.py index 97831c3de..380369f37 100644 --- a/src/pynwb/testing/mock/ecephys.py +++ b/src/pynwb/testing/mock/ecephys.py @@ -5,8 +5,8 @@ from hdmf.common.table import DynamicTableRegion, DynamicTable from ...device import Device -from ...file import ElectrodeTable, NWBFile -from ...ecephys import ElectricalSeries, ElectrodeGroup, SpikeEventSeries +from ...file import NWBFile +from ...ecephys import ElectricalSeries, ElectrodeGroup, SpikeEventSeries, ElectrodesTable from .device import mock_Device from .utils import name_generator from ...misc import Units diff --git a/tests/integration/hdf5/test_ecephys.py b/tests/integration/hdf5/test_ecephys.py index ff67d27c9..2cdd15db0 100644 --- a/tests/integration/hdf5/test_ecephys.py +++ b/tests/integration/hdf5/test_ecephys.py @@ -14,7 +14,7 @@ FeatureExtraction, ) from pynwb.device import Device -from pynwb.file import ElectrodeTable as get_electrode_table +from pynwb.ecephys import ElectrodesTable as get_electrode_table from pynwb.testing import NWBH5IOMixin, AcquisitionH5IOMixin, NWBH5IOFlexMixin, TestCase diff --git a/tests/integration/hdf5/test_misc.py b/tests/integration/hdf5/test_misc.py index cd9ab1706..7ca29aa52 100644 --- a/tests/integration/hdf5/test_misc.py +++ b/tests/integration/hdf5/test_misc.py @@ -6,7 +6,7 @@ from pynwb.testing import NWBH5IOMixin, AcquisitionH5IOMixin, TestCase from pynwb.ecephys import ElectrodeGroup from pynwb.device import Device -from pynwb.file import ElectrodeTable as get_electrode_table +from pynwb.ecephys import ElectrodesTable as get_electrode_table class TestUnitsIO(AcquisitionH5IOMixin, TestCase): diff --git a/tests/unit/foo.py b/tests/unit/foo.py index 083049e4d..09cdcb490 100644 --- a/tests/unit/foo.py +++ b/tests/unit/foo.py @@ -62,6 +62,9 @@ # breakpoint() # with NWBHDF5IO("ecephys_tutorial.nwb", "w") as io: # io.write(nwbfile) +# new='/Users/mavaylon/Research/NWB/pynwb/ecephys_tutorial.nwb' io= NWBHDF5IO("/Users/mavaylon/Research/NWB/pynwb/tests/back_compat/2.6.0_DynamicTableElectrodes.nwb", "r") +# io= NWBHDF5IO(new, 'r') read_nwbfile = io.read() + breakpoint() diff --git a/tests/unit/test_ecephys.py b/tests/unit/test_ecephys.py index f81b61f84..120d86d58 100644 --- a/tests/unit/test_ecephys.py +++ b/tests/unit/test_ecephys.py @@ -14,9 +14,9 @@ FilteredEphys, FeatureExtraction, ElectrodeGroup, + ElectrodesTable ) from pynwb.device import Device -from pynwb.file import ElectrodeTable from pynwb.testing import TestCase from pynwb.testing.mock.ecephys import mock_ElectricalSeries diff --git a/tests/unit/test_file.py b/tests/unit/test_file.py index 98446fa46..87daf7594 100644 --- a/tests/unit/test_file.py +++ b/tests/unit/test_file.py @@ -9,9 +9,9 @@ from hdmf.utils import docval, get_docval, popargs from pynwb import NWBFile, TimeSeries, NWBHDF5IO from pynwb.base import Image, Images -from pynwb.file import Subject, ElectrodeTable, _add_missing_timezone +from pynwb.file import Subject, _add_missing_timezone from pynwb.epoch import TimeIntervals -from pynwb.ecephys import ElectricalSeries +from pynwb.ecephys import ElectricalSeries, ElectrodesTable from pynwb.testing import TestCase, remove_test_file diff --git a/tests/unit/test_misc.py b/tests/unit/test_misc.py index 9350d1d2e..6ea8d24ba 100644 --- a/tests/unit/test_misc.py +++ b/tests/unit/test_misc.py @@ -3,9 +3,9 @@ from hdmf.common import DynamicTable, VectorData, DynamicTableRegion from pynwb.misc import AnnotationSeries, AbstractFeatureSeries, IntervalSeries, Units, DecompositionSeries -from pynwb.file import TimeSeries, ElectrodeTable as get_electrode_table +from pynwb.file import TimeSeries from pynwb.device import Device -from pynwb.ecephys import ElectrodeGroup +from pynwb.ecephys import ElectrodeGroup, ElectrodesTable as get_electrode_table from pynwb.testing import TestCase From f00b134c2cf798e36ec9be959e994f89cb176531 Mon Sep 17 00:00:00 2001 From: mavaylon1 Date: Thu, 1 Aug 2024 08:34:13 -0700 Subject: [PATCH 06/13] not sure clean up --- src/pynwb/__init__.py | 27 +-------------------------- src/pynwb/nwb-schema | 2 +- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/src/pynwb/__init__.py b/src/pynwb/__init__.py index 31556509e..d3564b5bb 100644 --- a/src/pynwb/__init__.py +++ b/src/pynwb/__init__.py @@ -77,33 +77,8 @@ def _get_resources(): global __TYPE_MAP __NS_CATALOG = NamespaceCatalog(NWBGroupSpec, NWBDatasetSpec, NWBNamespace) - -class NWBTypeMap(TypeMap): - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def __copy__(self): - ret = NWBTypeMap(namespaces=copy(self._TypeMap__ns_catalog), - mapper_cls=self._TypeMap__default_mapper_cls, - type_config=self.type_config) - ret.merge(self) - return ret - - def modify_builder(self, builder): - try: - ndtype = builder['neurodata_type'] - except KeyError: - ndtype = 'Not Group' - # remap ElectrodesTable from a DynamicTable named electrodes - if ndtype == 'DynamicTable' and builder.name == 'electrodes': - builder.set_attribute(name='namespace', value='core') - builder.set_attribute(name='neurodata_type', value='ElectrodesTable') - return builder - else: - return None - hdmf_typemap = hdmf.common.get_type_map() -__TYPE_MAP = NWBTypeMap(namespaces=__NS_CATALOG) +__TYPE_MAP = TypeMap(namespaces=__NS_CATALOG) __TYPE_MAP.merge(hdmf_typemap, ns_catalog=True) @docval({'name': 'extensions', 'type': (str, TypeMap, list), diff --git a/src/pynwb/nwb-schema b/src/pynwb/nwb-schema index 7393211bb..65230a20d 160000 --- a/src/pynwb/nwb-schema +++ b/src/pynwb/nwb-schema @@ -1 +1 @@ -Subproject commit 7393211bb04a61c153239dc49691598b4a41fa39 +Subproject commit 65230a20d9f6f895968c61f1cc52b2c15fcec10b From be0614c4912f3a28fb5ab9a73cd57b29595e7012 Mon Sep 17 00:00:00 2001 From: mavaylon1 Date: Sun, 29 Sep 2024 15:39:22 -0700 Subject: [PATCH 07/13] checkpoint repr works as well as able to write --- src/pynwb/ecephys.py | 3 ++- tests/unit/foo.py | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pynwb/ecephys.py b/src/pynwb/ecephys.py index 1ce143f94..146117d53 100644 --- a/src/pynwb/ecephys.py +++ b/src/pynwb/ecephys.py @@ -54,7 +54,8 @@ class ElectrodesTable(DynamicTable): {'name': 'rel_x', 'type': float, 'doc':'TODO', 'default': None}, {'name': 'rel_y', 'type': float, 'doc':'TODO', 'default': None}, {'name': 'rel_z', 'type': float, 'doc':'TODO', 'default': None}, - {'name': 'reference', 'type': VectorData, 'doc':'TODO', 'default': None},) + {'name': 'reference', 'type': VectorData, 'doc':'TODO', 'default': None}, + *get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames')) def __init__(self, **kwargs): kwargs['name'] = 'electrodes' kwargs['description'] = 'metadata about extracellular electrodes' diff --git a/tests/unit/foo.py b/tests/unit/foo.py index 09cdcb490..4c3f75691 100644 --- a/tests/unit/foo.py +++ b/tests/unit/foo.py @@ -36,7 +36,7 @@ nwbfile.electrodes = table nwbfile.add_electrode_group(group) nwbfile.add_electrode(group=group, location='brain') -# breakpoint() +breakpoint() # nwbfile.add_electrode_column(name="label", description="label of electrode") # nshanks = 4 @@ -66,5 +66,4 @@ io= NWBHDF5IO("/Users/mavaylon/Research/NWB/pynwb/tests/back_compat/2.6.0_DynamicTableElectrodes.nwb", "r") # io= NWBHDF5IO(new, 'r') read_nwbfile = io.read() - breakpoint() From c97a0df40a9c8a1f7e89083df96f4c029a214277 Mon Sep 17 00:00:00 2001 From: mavaylon1 Date: Mon, 30 Sep 2024 06:53:15 -0700 Subject: [PATCH 08/13] backwards compat seems to work --- src/pynwb/io/file.py | 16 ++++++++++++++++ tests/unit/foo.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/pynwb/io/file.py b/src/pynwb/io/file.py index 90e8f36b7..81f9bc73b 100644 --- a/src/pynwb/io/file.py +++ b/src/pynwb/io/file.py @@ -180,6 +180,22 @@ def scratch(self, builder, manager): ret.append(manager.construct(d)) return tuple(ret) if len(ret) > 0 else None + @ObjectMapper.constructor_arg('electrodes') + def electrodes(self, builder, manager): + electrodes_builder = builder['general']['extracellular_ephys']['electrodes'] + + if (electrodes_builder is not None and electrodes_builder.attributes['neurodata_type'] != 'ElectrodesTable'): + electrodes_builder.attributes['neurodata_type'] = 'ElectrodesTable' + electrodes_builder.attributes['namespace'] = 'core' + + new_container = manager.construct(electrodes_builder, True) + # mapper = manager.get_map(electrodes_builder) + breakpoint() + return new_container + else: + return None + + @ObjectMapper.constructor_arg('session_start_time') def dateconversion(self, builder, manager): """Set the constructor arg for 'session_start_time' to a datetime object. diff --git a/tests/unit/foo.py b/tests/unit/foo.py index 4c3f75691..16f25db94 100644 --- a/tests/unit/foo.py +++ b/tests/unit/foo.py @@ -36,7 +36,7 @@ nwbfile.electrodes = table nwbfile.add_electrode_group(group) nwbfile.add_electrode(group=group, location='brain') -breakpoint() +# breakpoint() # nwbfile.add_electrode_column(name="label", description="label of electrode") # nshanks = 4 From 64ad906e7a78d7b7b8fc4a47488c918edae07647 Mon Sep 17 00:00:00 2001 From: mavaylon1 Date: Tue, 1 Oct 2024 16:48:19 -0700 Subject: [PATCH 09/13] draft complete --- src/pynwb/ecephys.py | 31 ++++++++++++++++---------- src/pynwb/io/file.py | 8 +++---- src/pynwb/testing/mock/ecephys.py | 8 +++---- tests/integration/hdf5/test_nwbfile.py | 1 + tests/unit/test_ecephys.py | 2 +- tests/unit/test_file.py | 2 +- tests/unit/test_mock.py | 5 ++--- 7 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/pynwb/ecephys.py b/src/pynwb/ecephys.py index 146117d53..dbe1c17e3 100644 --- a/src/pynwb/ecephys.py +++ b/src/pynwb/ecephys.py @@ -43,17 +43,17 @@ class ElectrodesTable(DynamicTable): __columns__ = ( {'name': 'location', 'description': 'TODO', 'required': True}, - {'name': 'group', 'description': 'TODO', 'required': True}) - - @docval({'name': 'group_name', 'type': VectorData, 'doc':'TODO', 'default': None}, - {'name': 'x', 'type': float, 'doc':'TODO', 'default': None}, - {'name': 'y', 'type': float, 'doc':'TODO', 'default': None}, - {'name': 'z', 'type': float, 'doc':'TODO', 'default': None}, - {'name': 'imp', 'type': float, 'doc':'TODO', 'default': None}, - {'name': 'filtering', 'type': str, 'doc':'TODO', 'default': None}, - {'name': 'rel_x', 'type': float, 'doc':'TODO', 'default': None}, - {'name': 'rel_y', 'type': float, 'doc':'TODO', 'default': None}, - {'name': 'rel_z', 'type': float, 'doc':'TODO', 'default': None}, + {'name': 'group', 'description': 'TODO', 'required': True}, + {'name': 'group_name', 'description': 'TODO', 'required': False }) + + @docval({'name': 'x', 'type': VectorData, 'doc':'TODO', 'default': None}, + {'name': 'y', 'type': VectorData, 'doc':'TODO', 'default': None}, + {'name': 'z', 'type': VectorData, 'doc':'TODO', 'default': None}, + {'name': 'imp', 'type': VectorData, 'doc':'TODO', 'default': None}, + {'name': 'filtering', 'type': VectorData, 'doc':'TODO', 'default': None}, + {'name': 'rel_x', 'type': VectorData, 'doc':'TODO', 'default': None}, + {'name': 'rel_y', 'type': VectorData, 'doc':'TODO', 'default': None}, + {'name': 'rel_z', 'type': VectorData, 'doc':'TODO', 'default': None}, {'name': 'reference', 'type': VectorData, 'doc':'TODO', 'default': None}, *get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames')) def __init__(self, **kwargs): @@ -62,7 +62,6 @@ def __init__(self, **kwargs): # optional fields keys_to_set = ( - 'group_name', 'x', 'y', 'z', @@ -78,6 +77,14 @@ def __init__(self, **kwargs): super().__init__(**kwargs) + def copy(self): + """ + Return a copy of this DynamicTable. + This is useful for linking. + """ + kwargs = dict(id=self.id, columns=self.columns, colnames=self.colnames) + return self.__class__(**kwargs) + @register_class('ElectricalSeries', CORE_NAMESPACE) class ElectricalSeries(TimeSeries): diff --git a/src/pynwb/io/file.py b/src/pynwb/io/file.py index 81f9bc73b..3189e697f 100644 --- a/src/pynwb/io/file.py +++ b/src/pynwb/io/file.py @@ -182,15 +182,15 @@ def scratch(self, builder, manager): @ObjectMapper.constructor_arg('electrodes') def electrodes(self, builder, manager): - electrodes_builder = builder['general']['extracellular_ephys']['electrodes'] - + try: + electrodes_builder = builder['general']['extracellular_ephys']['electrodes'] + except KeyError: + electrodes_builder = None if (electrodes_builder is not None and electrodes_builder.attributes['neurodata_type'] != 'ElectrodesTable'): electrodes_builder.attributes['neurodata_type'] = 'ElectrodesTable' electrodes_builder.attributes['namespace'] = 'core' new_container = manager.construct(electrodes_builder, True) - # mapper = manager.get_map(electrodes_builder) - breakpoint() return new_container else: return None diff --git a/src/pynwb/testing/mock/ecephys.py b/src/pynwb/testing/mock/ecephys.py index 260c4a663..fd634fb38 100644 --- a/src/pynwb/testing/mock/ecephys.py +++ b/src/pynwb/testing/mock/ecephys.py @@ -35,10 +35,10 @@ def mock_ElectrodeGroup( return electrode_group -def mock_ElectrodeTable( +def mock_ElectrodesTable( n_rows: int = 5, group: Optional[ElectrodeGroup] = None, nwbfile: Optional[NWBFile] = None ) -> DynamicTable: - electrodes_table = ElectrodeTable() + electrodes_table = ElectrodesTable() group = group if group is not None else mock_ElectrodeGroup(nwbfile=nwbfile) for i in range(n_rows): electrodes_table.add_row( @@ -57,7 +57,7 @@ def mock_electrodes( n_electrodes: int = 5, table: Optional[DynamicTable] = None, nwbfile: Optional[NWBFile] = None ) -> DynamicTableRegion: - table = table or mock_ElectrodeTable(n_rows=5, nwbfile=nwbfile) + table = table or mock_ElectrodesTable(n_rows=5, nwbfile=nwbfile) return DynamicTableRegion( name="electrodes", data=list(range(n_electrodes)), @@ -80,7 +80,7 @@ def mock_ElectricalSeries( conversion: float = 1.0, offset: float = 0., ) -> ElectricalSeries: - + # Set a default rate if timestamps are not provided rate = 30_000.0 if (timestamps is None and rate is None) else rate diff --git a/tests/integration/hdf5/test_nwbfile.py b/tests/integration/hdf5/test_nwbfile.py index e164ec649..081cd7ea7 100644 --- a/tests/integration/hdf5/test_nwbfile.py +++ b/tests/integration/hdf5/test_nwbfile.py @@ -508,6 +508,7 @@ def getContainer(self, nwbfile): def test_roundtrip(self): super().test_roundtrip() + # When comparing the pandas dataframes for the row we drop the 'group' column since the # ElectrodeGroup object after reading will naturally have a different address pd.testing.assert_frame_equal(self.read_container[0].drop('group', axis=1), diff --git a/tests/unit/test_ecephys.py b/tests/unit/test_ecephys.py index c91593ed7..ddd5706e1 100644 --- a/tests/unit/test_ecephys.py +++ b/tests/unit/test_ecephys.py @@ -24,7 +24,7 @@ def make_electrode_table(): - table = ElectrodeTable() + table = ElectrodesTable() dev1 = Device('dev1') group = ElectrodeGroup('tetrode1', 'tetrode description', 'tetrode location', dev1) table.add_row(location='CA1', group=group, group_name='tetrode1') diff --git a/tests/unit/test_file.py b/tests/unit/test_file.py index ef7732322..43eb6f692 100644 --- a/tests/unit/test_file.py +++ b/tests/unit/test_file.py @@ -245,7 +245,7 @@ def test_add_acquisition_invalid_name(self): self.nwbfile.get_acquisition("TEST_TS") def test_set_electrode_table(self): - table = ElectrodeTable() + table = ElectrodesTable() dev1 = self.nwbfile.create_device('dev1') group = self.nwbfile.create_electrode_group('tetrode1', 'tetrode description', 'tetrode location', dev1) diff --git a/tests/unit/test_mock.py b/tests/unit/test_mock.py index 2ce777b65..2fd034a25 100644 --- a/tests/unit/test_mock.py +++ b/tests/unit/test_mock.py @@ -32,7 +32,7 @@ from pynwb.testing.mock.ecephys import ( mock_ElectrodeGroup, - mock_ElectrodeTable, + mock_ElectrodesTable, mock_ElectricalSeries, mock_SpikeEventSeries, mock_Units, @@ -70,7 +70,7 @@ mock_CompassDirection, mock_SpatialSeries, mock_ElectrodeGroup, - mock_ElectrodeTable, + mock_ElectrodesTable, mock_ElectricalSeries, mock_SpikeEventSeries, mock_Subject, @@ -121,4 +121,3 @@ def test_name_generator(): assert name_generator("TimeSeries") == "TimeSeries" assert name_generator("TimeSeries") == "TimeSeries2" - From dc1f7b8567cb227ecfa84e7b954e64ee574065ef Mon Sep 17 00:00:00 2001 From: mavaylon1 Date: Tue, 1 Oct 2024 16:57:40 -0700 Subject: [PATCH 10/13] cleanup 1 --- src/pynwb/file.py | 14 ++------------ src/pynwb/io/file.py | 1 - 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/pynwb/file.py b/src/pynwb/file.py index b48ba7cb0..39a55b128 100644 --- a/src/pynwb/file.py +++ b/src/pynwb/file.py @@ -698,8 +698,8 @@ def add_electrode(self, **kwargs): raise ValueError("The 'location' argument is required when creating an electrode.") if not kwargs['group']: raise ValueError("The 'group' argument is required when creating an electrode.") - # if d.get('group_name', None) is None: - # d['group_name'] = d['group'].name + if d.get('group_name', None) is None: + d['group_name'] = d['group'].name new_cols = [('x', 'the x coordinate of the position (+x is posterior)'), ('y', 'the y coordinate of the position (+y is inferior)'), @@ -1176,16 +1176,6 @@ def _tablefunc(table_name, description, columns): return t -# def ElectrodesTable(name='electrodes', -# description='metadata about extracellular electrodes'): -# return _tablefunc(name, description, -# [('location', 'the location of channel within the subject e.g. brain region'), -# ('group', 'a reference to the ElectrodeGroup this electrode is a part of'), -# ('group_name', 'the name of the ElectrodeGroup this electrode is a part of') -# ] -# ) - - def TrialTable(name='trials', description='metadata about experimental trials'): return _tablefunc(name, description, ['start_time', 'stop_time']) diff --git a/src/pynwb/io/file.py b/src/pynwb/io/file.py index 3189e697f..b4ae412fb 100644 --- a/src/pynwb/io/file.py +++ b/src/pynwb/io/file.py @@ -195,7 +195,6 @@ def electrodes(self, builder, manager): else: return None - @ObjectMapper.constructor_arg('session_start_time') def dateconversion(self, builder, manager): """Set the constructor arg for 'session_start_time' to a datetime object. From c02886ba9edbd6a23e4ca2396ee49b415d89d46b Mon Sep 17 00:00:00 2001 From: mavaylon1 Date: Tue, 1 Oct 2024 16:59:31 -0700 Subject: [PATCH 11/13] cleanup 1 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5909f577..9962dfb32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## PyNWB 2.8.3 (Upcoming) +### Enhancements +- Formally defined and renamed `ElectrodeTable` as the `ElectrodesTable` neurodata_type. @mavaylon1 [#1890](https://github.com/NeurodataWithoutBorders/pynwb/pull/1890) + ### Performance - Cache global type map to speed import 3X. @sneakers-the-rat [#1931](https://github.com/NeurodataWithoutBorders/pynwb/pull/1931) From 0cd8edca5d6e8867970e9bdafd8a3e5416942417 Mon Sep 17 00:00:00 2001 From: mavaylon1 Date: Tue, 1 Oct 2024 17:09:35 -0700 Subject: [PATCH 12/13] clean up 2 --- src/pynwb/ecephys.py | 37 ++++++++++++++---------- src/pynwb/nwb-schema | 2 +- tests/unit/foo.py | 69 -------------------------------------------- 3 files changed, 22 insertions(+), 86 deletions(-) delete mode 100644 tests/unit/foo.py diff --git a/src/pynwb/ecephys.py b/src/pynwb/ecephys.py index dbe1c17e3..aa13d9d54 100644 --- a/src/pynwb/ecephys.py +++ b/src/pynwb/ecephys.py @@ -3,7 +3,7 @@ from hdmf.common import DynamicTableRegion, DynamicTable, VectorData from hdmf.data_utils import DataChunkIterator, assertEqualShape -from hdmf.utils import docval, popargs, getargs, get_docval, popargs_to_dict, get_data_shape +from hdmf.utils import docval, popargs, get_docval, popargs_to_dict, get_data_shape from . import register_class, CORE_NAMESPACE from .base import TimeSeries @@ -39,22 +39,27 @@ def __init__(self, **kwargs): @register_class('ElectrodesTable', CORE_NAMESPACE) class ElectrodesTable(DynamicTable): - """TODO""" + """A table of all electrodes (i.e. channels) used for recording. Introduced in NWB 3.0.0. Replaces the "electrodes" + table (neurodata_type_inc DynamicTable, no neurodata_type_def) that is part of NWBFile.""" __columns__ = ( - {'name': 'location', 'description': 'TODO', 'required': True}, - {'name': 'group', 'description': 'TODO', 'required': True}, - {'name': 'group_name', 'description': 'TODO', 'required': False }) - - @docval({'name': 'x', 'type': VectorData, 'doc':'TODO', 'default': None}, - {'name': 'y', 'type': VectorData, 'doc':'TODO', 'default': None}, - {'name': 'z', 'type': VectorData, 'doc':'TODO', 'default': None}, - {'name': 'imp', 'type': VectorData, 'doc':'TODO', 'default': None}, - {'name': 'filtering', 'type': VectorData, 'doc':'TODO', 'default': None}, - {'name': 'rel_x', 'type': VectorData, 'doc':'TODO', 'default': None}, - {'name': 'rel_y', 'type': VectorData, 'doc':'TODO', 'default': None}, - {'name': 'rel_z', 'type': VectorData, 'doc':'TODO', 'default': None}, - {'name': 'reference', 'type': VectorData, 'doc':'TODO', 'default': None}, + {'name': 'location', 'description': 'Location of the electrode (channel).', 'required': True}, + {'name': 'group', 'description': 'Reference to the ElectrodeGroup.', 'required': True}, + {'name': 'group_name', 'description': 'Name of the ElectrodeGroup.', 'required': False }) + + @docval({'name': 'x', 'type': VectorData, 'doc':'x coordinate of the channel location in the brain', + 'default': None}, + {'name': 'y', 'type': VectorData, 'doc':'y coordinate of the channel location in the brain', + 'default': None}, + {'name': 'z', 'type': VectorData, 'doc':'z coordinate of the channel location in the brain', + 'default': None}, + {'name': 'imp', 'type': VectorData, 'doc':'Impedance of the channel, in ohms.', 'default': None}, + {'name': 'filtering', 'type': VectorData, 'doc':'Description of hardware filtering.', 'default': None}, + {'name': 'rel_x', 'type': VectorData, 'doc':'x coordinate in electrode group', 'default': None}, + {'name': 'rel_y', 'type': VectorData, 'doc':'xy coordinate in electrode group', 'default': None}, + {'name': 'rel_z', 'type': VectorData, 'doc':'z coordinate in electrode group', 'default': None}, + {'name': 'reference', 'type': VectorData, 'default': None, + 'doc':'Description of the reference electrode and/or reference scheme used for this electrode'}, *get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames')) def __init__(self, **kwargs): kwargs['name'] = 'electrodes' @@ -79,7 +84,7 @@ def __init__(self, **kwargs): def copy(self): """ - Return a copy of this DynamicTable. + Return a copy of this ElectrodesTable. This is useful for linking. """ kwargs = dict(id=self.id, columns=self.columns, colnames=self.colnames) diff --git a/src/pynwb/nwb-schema b/src/pynwb/nwb-schema index 65230a20d..78df82c4b 160000 --- a/src/pynwb/nwb-schema +++ b/src/pynwb/nwb-schema @@ -1 +1 @@ -Subproject commit 65230a20d9f6f895968c61f1cc52b2c15fcec10b +Subproject commit 78df82c4bdc6f094a6dfb97fb85d9ae396087d91 diff --git a/tests/unit/foo.py b/tests/unit/foo.py deleted file mode 100644 index 16f25db94..000000000 --- a/tests/unit/foo.py +++ /dev/null @@ -1,69 +0,0 @@ -from datetime import datetime -from uuid import uuid4 - -import numpy as np -from dateutil.tz import tzlocal -from hdmf.common import VectorData - -from pynwb import NWBHDF5IO, NWBFile -from pynwb.ecephys import LFP, ElectricalSeries, ElectrodeGroup, ElectrodesTable - -nwbfile = NWBFile( - session_description="my first synthetic recording", - identifier=str(uuid4()), - session_start_time=datetime.now(tzlocal()), - experimenter=[ - "Baggins, Bilbo", - ], - lab="Bag End Laboratory", - institution="University of Middle Earth at the Shire", - experiment_description="I went on an adventure to reclaim vast treasures.", - session_id="LONELYMTN001", -) - -device = nwbfile.create_device( - name="array", description="the best array", manufacturer="Probe Company 9000" -) - -group = ElectrodeGroup( name='foo', - description="electrode group", - device=device, - location="brain area",) -# location_col = VectorData(name='location', description='foo', data=['brain area']) -# group_col = VectorData(name='groups', description='foo', data=[group]) - -table = ElectrodesTable() -nwbfile.electrodes = table -nwbfile.add_electrode_group(group) -nwbfile.add_electrode(group=group, location='brain') -# breakpoint() -# nwbfile.add_electrode_column(name="label", description="label of electrode") - -# nshanks = 4 -# nchannels_per_shank = 3 -# electrode_counter = 0 -# # -# for ishank in range(nshanks): -# # create an electrode group for this shank -# electrode_group = nwbfile.create_electrode_group( -# name="shank{}".format(ishank), -# description="electrode group for shank {}".format(ishank), -# device=device, -# location="brain area", -# ) -# # add electrodes to the electrode table -# for ielec in range(nchannels_per_shank): -# nwbfile.add_electrode( -# group=electrode_group, -# label="shank{}elec{}".format(ishank, ielec), -# location="brain area", -# ) -# electrode_counter += 1 -# breakpoint() -# with NWBHDF5IO("ecephys_tutorial.nwb", "w") as io: -# io.write(nwbfile) -# new='/Users/mavaylon/Research/NWB/pynwb/ecephys_tutorial.nwb' -io= NWBHDF5IO("/Users/mavaylon/Research/NWB/pynwb/tests/back_compat/2.6.0_DynamicTableElectrodes.nwb", "r") -# io= NWBHDF5IO(new, 'r') -read_nwbfile = io.read() -breakpoint() From 06cf465cb710c15f9b1ccb3cda3c2ca24fedb0e9 Mon Sep 17 00:00:00 2001 From: mavaylon1 Date: Tue, 1 Oct 2024 17:14:25 -0700 Subject: [PATCH 13/13] Add note --- src/pynwb/io/file.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pynwb/io/file.py b/src/pynwb/io/file.py index b4ae412fb..821fcf0d0 100644 --- a/src/pynwb/io/file.py +++ b/src/pynwb/io/file.py @@ -185,6 +185,9 @@ def electrodes(self, builder, manager): try: electrodes_builder = builder['general']['extracellular_ephys']['electrodes'] except KeyError: + # Note: This is here because the ObjectMapper pulls argname from docval and checks to see + # if there is an override even if the file doesn't have what is looking for. In this case, + # electrodes for NWBFile. electrodes_builder = None if (electrodes_builder is not None and electrodes_builder.attributes['neurodata_type'] != 'ElectrodesTable'): electrodes_builder.attributes['neurodata_type'] = 'ElectrodesTable'