From f0eed0eba920a49ef7e9114f04eb0c11f54988cd Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Mon, 9 Mar 2020 17:08:08 -0700 Subject: [PATCH 1/3] create Electrodes class to replace electrodes table --- src/pynwb/file.py | 133 ++++++++++++++++++++++++++-------------- src/pynwb/io/file.py | 43 ++++++++++++- tests/unit/test_file.py | 20 ++++-- 3 files changed, 143 insertions(+), 53 deletions(-) diff --git a/src/pynwb/file.py b/src/pynwb/file.py index 90ceaee24..8d10924f1 100644 --- a/src/pynwb/file.py +++ b/src/pynwb/file.py @@ -18,8 +18,7 @@ from .ophys import ImagingPlane from .ogen import OptogeneticStimulusSite from .misc import Units -from .core import NWBContainer, NWBDataInterface, MultiContainerInterface, \ - ScratchData, LabelledDict +from .core import NWBContainer, NWBDataInterface, MultiContainerInterface, ScratchData, LabelledDict from hdmf.common import DynamicTableRegion, DynamicTable @@ -74,6 +73,65 @@ def __init__(self, **kwargs): self.date_of_birth = date_of_birth +# NOTE: the electrodes table is not its own neurodata_type +class Electrodes(DynamicTable): + + __defaultname__ = 'electrodes' + + __columns__ = ( + {'name': 'x', 'description': 'the x coordinate of the channel location (+x is posterior)', 'required': True}, + {'name': 'y', 'description': 'the y coordinate of the channel location (+y is inferior)', 'required': True}, + {'name': 'z', 'description': 'the z coordinate of the channel location (+z is right)', 'required': True}, + {'name': 'imp', 'description': 'the impedance of the channel', 'required': True}, + {'name': 'location', 'description': 'the location of channel within the subject e.g. brain region', + 'required': True}, + {'name': 'filtering', 'description': 'description of hardware filtering', 'required': True}, + {'name': 'group', 'description': 'a reference to the ElectrodeGroup this electrode is a part of', + 'required': True}, + {'name': 'group_name', 'description': 'the name of the ElectrodeGroup this electrode is a part of', + 'required': True}, + {'name': 'rel_x', 'description': 'the x coordinate within the electrode group'}, + {'name': 'rel_y', 'description': 'the y coordinate within the electrode group'}, + {'name': 'rel_z', 'description': 'the z coordinate within the electrode group'}, + {'name': 'reference', 'description': 'Description of the reference used for this electrode.'} + ) + + @docval({'name': 'name', 'type': str, 'doc': 'name of this Electrodes table', 'default': 'electrodes'}, + {'name': 'description', 'type': str, 'doc': 'description of this Electrodes table', + 'default': "metadata about extracellular electrodes"}, + *get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames')) + def __init__(self, **kwargs): + call_docval_func(super().__init__, kwargs) + + @docval({'name': 'x', 'type': 'float', 'doc': 'the x coordinate of the position (+x is posterior)', + 'default': None}, + {'name': 'y', 'type': 'float', 'doc': 'the y coordinate of the position (+y is inferior)', + 'default': None}, + {'name': 'z', 'type': 'float', 'doc': 'the z coordinate of the position (+z is right)', + 'default': None}, + {'name': 'imp', 'type': 'float', 'doc': 'the impedance of the electrode', + 'default': None}, + {'name': 'location', 'type': str, 'doc': 'the location of electrode within the subject e.g. brain region', + 'default': None}, + {'name': 'filtering', 'type': str, 'doc': 'description of hardware filtering', + 'default': None}, + {'name': 'group', 'type': ElectrodeGroup, 'doc': 'the ElectrodeGroup object to add to this NWBFile', + 'default': None}, + {'name': 'id', 'type': int, 'doc': 'a unique identifier for the electrode', 'default': None}, + {'name': 'rel_x', 'type': 'float', 'doc': 'the x coordinate within the electrode group', 'default': None}, + {'name': 'rel_y', 'type': 'float', 'doc': 'the y coordinate within the electrode group', 'default': None}, + {'name': 'rel_z', 'type': 'float', 'doc': 'the z coordinate within the electrode group', 'default': None}, + {'name': 'reference', 'type': str, 'doc': 'Description of the reference used for this electrode.', + 'default': None}, + allow_extra=True) + def add_electrode(self, **kwargs): + d = _copy.copy(kwargs['data']) if kwargs.get('data') is not None else kwargs + if d.get('group_name', None) is None: + d['group_name'] = d['group'].name + + super().add_row(**d) + + @register_class('NWBFile', CORE_NAMESPACE) class NWBFile(MultiContainerInterface): """ @@ -482,9 +540,9 @@ def add_epoch(self, **kwargs): def __check_electrodes(self): if self.electrodes is None: - self.electrodes = ElectrodeTable() + self.electrodes = Electrodes() - @docval(*get_docval(DynamicTable.add_column)) + @docval(*get_docval(Electrodes.add_column)) def add_electrode_column(self, **kwargs): """ Add a column to the electrode table. @@ -493,23 +551,10 @@ def add_electrode_column(self, **kwargs): self.__check_electrodes() call_docval_func(self.electrodes.add_column, kwargs) - @docval({'name': 'x', 'type': 'float', 'doc': 'the x coordinate of the position (+x is posterior)'}, - {'name': 'y', 'type': 'float', 'doc': 'the y coordinate of the position (+y is inferior)'}, - {'name': 'z', 'type': 'float', 'doc': 'the z coordinate of the position (+z is right)'}, - {'name': 'imp', 'type': 'float', 'doc': 'the impedance of the electrode'}, - {'name': 'location', 'type': str, 'doc': 'the location of electrode within the subject e.g. brain region'}, - {'name': 'filtering', 'type': str, 'doc': 'description of hardware filtering'}, - {'name': 'group', 'type': ElectrodeGroup, 'doc': 'the ElectrodeGroup object to add to this NWBFile'}, - {'name': 'id', 'type': int, 'doc': 'a unique identifier for the electrode', 'default': None}, - {'name': 'rel_x', 'type': 'float', 'doc': 'the x coordinate within the electrode group', 'default': None}, - {'name': 'rel_y', 'type': 'float', 'doc': 'the y coordinate within the electrode group', 'default': None}, - {'name': 'rel_z', 'type': 'float', 'doc': 'the z coordinate within the electrode group', 'default': None}, - {'name': 'reference', 'type': str, 'doc': 'Description of the reference used for this electrode.', - 'default': None}, - allow_extra=True) + @docval(*get_docval(Electrodes.add_electrode), allow_extra=True) def add_electrode(self, **kwargs): """ - Add a unit to the unit table. + Add an electrode to the electrodes table. See :py:meth:`~hdmf.common.DynamicTable.add_row` for more details. Required fields are *x*, *y*, *z*, *imp*, *location*, *filtering*, @@ -517,21 +562,7 @@ def add_electrode(self, **kwargs): (through calls to `add_electrode_columns`). """ self.__check_electrodes() - d = _copy.copy(kwargs['data']) if kwargs.get('data') is not None else kwargs - if d.get('group_name', None) is None: - d['group_name'] = d['group'].name - - new_cols = [('rel_x', 'the x coordinate within the electrode group'), - ('rel_y', 'the y coordinate within the electrode group'), - ('rel_z', 'the z coordinate within the electrode group'), - ('reference', 'Description of the reference used for this electrode.')] - for col_name, col_doc in new_cols: - if kwargs[col_name] is not None and col_name not in self.electrodes: - self.electrodes.add_column(col_name, col_doc) - else: - d.pop(col_name) # remove args from d if not set - - call_docval_func(self.electrodes.add_row, d) + call_docval_func(self.electrodes.add_electrode, kwargs) @docval({'name': 'region', 'type': (slice, list, tuple), 'doc': 'the indices of the table'}, {'name': 'description', 'type': str, 'doc': 'a brief description of what this electrode is'}, @@ -763,24 +794,32 @@ def _tablefunc(table_name, description, columns): return t -def ElectrodeTable(name='electrodes', - description='metadata about extracellular electrodes'): - return _tablefunc(name, description, - [('x', 'the x coordinate of the channel location'), - ('y', 'the y coordinate of the channel location'), - ('z', 'the z coordinate of the channel location'), - ('imp', 'the impedance of the channel'), - ('location', 'the location of channel within the subject e.g. brain region'), - ('filtering', 'description of hardware filtering'), - ('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 ElectrodeTable(name='electrodes', description='metadata about extracellular electrodes'): + warn("Use of the ElectrodeTable method is deprecated. " + "Use Electrodes to initialize the electrodes table instead. The optional columns 'rel_x', 'rel_y', 'rel_z', " + "and 'reference' will not be initialized in the tabke returned from this function", DeprecationWarning) + columns = [('x', 'the x coordinate of the channel location'), + ('y', 'the y coordinate of the channel location'), + ('z', 'the z coordinate of the channel location'), + ('imp', 'the impedance of the channel'), + ('location', 'the location of channel within the subject e.g. brain region'), + ('filtering', 'description of hardware filtering'), + ('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')] + t = DynamicTable(name, description) + for c in columns: + t.add_column(c[0], c[1]) + return t def TrialTable(name='trials', description='metadata about experimental trials'): + warn("Use of the TrialTable method is deprecated. " + "Use add_trial or add_trial_column to initialize the trials table instead.", DeprecationWarning) return _tablefunc(name, description, ['start_time', 'stop_time']) def InvalidTimesTable(name='invalid_times', description='time intervals to be removed from analysis'): + warn("Use of the InvalidTimesTable method is deprecated. " + "Use add_invalid_time_interval or add_invalid_times_column to initialize the " + "invalid times table instead.", DeprecationWarning) return _tablefunc(name, description, ['start_time', 'stop_time']) diff --git a/src/pynwb/io/file.py b/src/pynwb/io/file.py index 74a9b2062..1f3106d03 100644 --- a/src/pynwb/io/file.py +++ b/src/pynwb/io/file.py @@ -1,8 +1,8 @@ from dateutil.parser import parse as dateutil_parse from hdmf.build import ObjectMapper from .. import register_map -from ..file import NWBFile, Subject -from ..core import ScratchData +from ..file import NWBFile, Subject, Electrodes +from ..core import ScratchData, DynamicTable @register_map(NWBFile) @@ -196,6 +196,45 @@ def publication_obj_attr(self, container, manager): ret = (container.related_publications,) return ret + @ObjectMapper.constructor_arg('electrodes') + def electrodes_carg(self, builder, manager): + # change typing of constructed electrodes table from DynamicTable to Electrodes + electrodes_builder = builder['general']['extracellular_ephys']['electrodes'] + # construct the electrodes table from the spec (as a DynamicTable) + # this has happened earlier in the construct process but this function does not have access to the previously + # constructed object, so we do it again here + constructed = manager.construct(electrodes_builder) + ret = Electrodes.__new__( + Electrodes, + container_source=constructed.container_source, + parent=constructed.parent, + object_id=constructed.object_id + ) + + ret.__init__( + name=constructed.name, + description=constructed.description, + id=constructed.id, + columns=constructed.columns, + colnames=constructed.colnames + ) + return ret + + @ObjectMapper.object_attr('electrodes') + def electrodes_obj_attr(self, container, manager): + ret = None + if isinstance(container.electrodes, Electrodes): + ret = container.electrodes + elif isinstance(container.electrodes, DynamicTable): + ret = Electrodes( + name=container.electrodes.name, + description=container.electrodes.description, + id=container.electrodes.id, + columns=container.electrodes.columns, + colnames=container.electrodes.colnames + ) + return ret + @register_map(Subject) class SubjectMap(ObjectMapper): diff --git a/tests/unit/test_file.py b/tests/unit/test_file.py index 638e5e3b8..16d76afd0 100644 --- a/tests/unit/test_file.py +++ b/tests/unit/test_file.py @@ -5,7 +5,7 @@ from dateutil.tz import tzlocal, tzutc from pynwb import NWBFile, TimeSeries, NWBHDF5IO -from pynwb.file import Subject, ElectrodeTable +from pynwb.file import Subject, ElectrodeTable, Electrodes from pynwb.epoch import TimeIntervals from pynwb.ecephys import ElectricalSeries from pynwb.testing import TestCase, remove_test_file @@ -186,7 +186,11 @@ def test_add_acquisition_invalid_name(self): self.nwbfile.get_acquisition("TEST_TS") def test_set_electrode_table(self): - table = ElectrodeTable() + msg = ("Use of the ElectrodeTable method is deprecated. " + "Use Electrodes to initialize the electrodes table instead. The optional columns 'rel_x', 'rel_y', " + "'rel_z', and 'reference' will not be initialized in the tabke returned from this function") + with self.assertWarnsWith(DeprecationWarning, msg): + table = ElectrodeTable() dev1 = self.nwbfile.create_device('dev1') group = self.nwbfile.create_electrode_group('tetrode1', 'tetrode description', 'tetrode location', dev1) table.add_row(x=1.0, y=2.0, z=3.0, imp=-1.0, location='CA1', filtering='none', group=group, @@ -229,7 +233,6 @@ def test_add_invalid_times_column(self): self.assertEqual(self.nwbfile.invalid_times.colnames, ('start_time', 'stop_time', 'comments')) def test_add_invalid_time_interval(self): - self.nwbfile.add_invalid_time_interval(start_time=0.0, stop_time=12.0) self.assertEqual(len(self.nwbfile.invalid_times), 1) self.nwbfile.add_invalid_time_interval(start_time=15.0, stop_time=16.0) @@ -244,15 +247,24 @@ def test_add_invalid_time_w_ts(self): def test_add_electrode(self): dev1 = self.nwbfile.create_device('dev1') group = self.nwbfile.create_electrode_group('tetrode1', 'tetrode description', 'tetrode location', dev1) - self.nwbfile.add_electrode(1.0, 2.0, 3.0, -1.0, 'CA1', 'none', group=group, id=1) + self.nwbfile.add_electrode(x=1.0, y=2.0, z=3.0, imp=-1.0, location='CA1', filtering='none', group=group, id=1) + self.assertIsIstance(self.electrodes, Electrodes) elec = self.nwbfile.electrodes[0] self.assertEqual(elec.index[0], 1) self.assertEqual(elec.iloc[0]['x'], 1.0) self.assertEqual(elec.iloc[0]['y'], 2.0) self.assertEqual(elec.iloc[0]['z'], 3.0) + self.assertEqual(elec.iloc[0]['imp'], -1.0) self.assertEqual(elec.iloc[0]['location'], 'CA1') self.assertEqual(elec.iloc[0]['filtering'], 'none') self.assertEqual(elec.iloc[0]['group'], group) + self.assertEqual(set(self.electrodes.colnames), {'id', 'x', 'y', 'z', 'imp', 'location', 'filtering', 'group', + 'group_name'}) + + def test_add_electrode_column(self): + self.nwbfile.add_electrode_column(name='new_col', description='a new column') + self.assertEqual(set(self.electrodes.colnames), {'id', 'x', 'y', 'z', 'imp', 'location', 'filtering', 'group', + 'group_name', 'a new column'}) def test_all_children(self): ts1 = TimeSeries('test_ts1', [0, 1, 2, 3, 4, 5], 'grams', timestamps=[0.0, 0.1, 0.2, 0.3, 0.4, 0.5]) From 9ba9381765e5e44a2caf7c2676b3503edbf65bda Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Wed, 11 Mar 2020 18:51:43 -0700 Subject: [PATCH 2/3] Cover case when electrodes table is missing --- 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 1f3106d03..63a5bda62 100644 --- a/src/pynwb/io/file.py +++ b/src/pynwb/io/file.py @@ -199,6 +199,9 @@ def publication_obj_attr(self, container, manager): @ObjectMapper.constructor_arg('electrodes') def electrodes_carg(self, builder, manager): # change typing of constructed electrodes table from DynamicTable to Electrodes + if ('extracellular_ephys' not in builder['general'] or + 'electrodes' not in builder['general']['extracellular_ephys']): + return None electrodes_builder = builder['general']['extracellular_ephys']['electrodes'] # construct the electrodes table from the spec (as a DynamicTable) # this has happened earlier in the construct process but this function does not have access to the previously From 25960723a3e7fdaf14abe0c51d5e89cf279871ad Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Wed, 25 Mar 2020 17:09:56 -0700 Subject: [PATCH 3/3] Update API and tests with new Electrodes class and cast method --- src/pynwb/file.py | 17 ++++++++++++++- src/pynwb/io/file.py | 27 +++--------------------- tests/integration/hdf5/test_ecephys.py | 4 ++-- tests/integration/hdf5/test_nwbfile.py | 8 +++---- tests/unit/test_ecephys.py | 4 ++-- tests/unit/test_file.py | 29 +++++++++++++++++++++----- 6 files changed, 51 insertions(+), 38 deletions(-) diff --git a/src/pynwb/file.py b/src/pynwb/file.py index 8d10924f1..7c6348721 100644 --- a/src/pynwb/file.py +++ b/src/pynwb/file.py @@ -131,6 +131,16 @@ def add_electrode(self, **kwargs): super().add_row(**d) + @classmethod + @docval({'name': 'table', 'type': DynamicTable, 'doc': 'the DynamicTable object to cast into an Electrodes object'}) + def cast(cls, table): + """Cast a DynamicTable object into an Electrodes object. The input table is cast (no copy) and returned. + Class columns defined in Electrodes.__columns__ are added to the table. + """ + table.__class__ = cls + table._init_class_columns() # create columns and attrs for columns defined in Electrodes.__columns__ + return table + @register_class('NWBFile', CORE_NAMESPACE) class NWBFile(MultiContainerInterface): @@ -380,7 +390,6 @@ def __init__(self, **kwargs): 'keywords', 'processing', 'epoch_tags', - 'electrodes', 'electrode_groups', 'devices', 'imaging_planes', @@ -412,6 +421,11 @@ def __init__(self, **kwargs): for attr in fieldnames: setattr(self, attr, kwargs.get(attr, None)) + electrodes = kwargs.get('electrodes', None) + if electrodes is not None and not isinstance(electrodes, Electrodes): + electrodes = Electrodes.cast(electrodes) + setattr(self, 'electrodes', electrodes) + # backwards-compatibility code for ic_electrodes / icephys_electrodes ic_elec_val = kwargs.get('icephys_electrodes', None) if ic_elec_val is None and kwargs.get('ic_electrodes', None) is not None: @@ -795,6 +809,7 @@ def _tablefunc(table_name, description, columns): def ElectrodeTable(name='electrodes', description='metadata about extracellular electrodes'): + """DEPRECATED. Initialize the electrodes table.""" warn("Use of the ElectrodeTable method is deprecated. " "Use Electrodes to initialize the electrodes table instead. The optional columns 'rel_x', 'rel_y', 'rel_z', " "and 'reference' will not be initialized in the tabke returned from this function", DeprecationWarning) diff --git a/src/pynwb/io/file.py b/src/pynwb/io/file.py index 63a5bda62..3d64421c3 100644 --- a/src/pynwb/io/file.py +++ b/src/pynwb/io/file.py @@ -207,35 +207,14 @@ def electrodes_carg(self, builder, manager): # this has happened earlier in the construct process but this function does not have access to the previously # constructed object, so we do it again here constructed = manager.construct(electrodes_builder) - ret = Electrodes.__new__( - Electrodes, - container_source=constructed.container_source, - parent=constructed.parent, - object_id=constructed.object_id - ) - - ret.__init__( - name=constructed.name, - description=constructed.description, - id=constructed.id, - columns=constructed.columns, - colnames=constructed.colnames - ) + ret = Electrodes.cast(constructed) return ret @ObjectMapper.object_attr('electrodes') def electrodes_obj_attr(self, container, manager): ret = None - if isinstance(container.electrodes, Electrodes): - ret = container.electrodes - elif isinstance(container.electrodes, DynamicTable): - ret = Electrodes( - name=container.electrodes.name, - description=container.electrodes.description, - id=container.electrodes.id, - columns=container.electrodes.columns, - colnames=container.electrodes.colnames - ) + if not isinstance(container.electrodes, Electrodes) and isinstance(container.electrodes, DynamicTable): + ret = Electrodes.cast(container.electrodes) return ret diff --git a/tests/integration/hdf5/test_ecephys.py b/tests/integration/hdf5/test_ecephys.py index d31e52490..3e91a2c7b 100644 --- a/tests/integration/hdf5/test_ecephys.py +++ b/tests/integration/hdf5/test_ecephys.py @@ -3,7 +3,7 @@ from pynwb.ecephys import ElectrodeGroup, ElectricalSeries, FilteredEphys, LFP, Clustering, ClusterWaveforms,\ SpikeEventSeries, EventWaveform, EventDetection, FeatureExtraction from pynwb.device import Device -from pynwb.file import ElectrodeTable as get_electrode_table +from pynwb.file import Electrodes from pynwb.testing import NWBH5IOMixin, AcquisitionH5IOMixin, TestCase @@ -30,7 +30,7 @@ class TestElectricalSeriesIO(AcquisitionH5IOMixin, TestCase): @staticmethod def make_electrode_table(self): """ Make an electrode table, electrode group, and device """ - self.table = get_electrode_table() + self.table = Electrodes() self.dev1 = Device('dev1') self.group = ElectrodeGroup('tetrode1', 'tetrode description', 'tetrode location', self.dev1) diff --git a/tests/integration/hdf5/test_nwbfile.py b/tests/integration/hdf5/test_nwbfile.py index 7cb9699e2..9baa4d92e 100644 --- a/tests/integration/hdf5/test_nwbfile.py +++ b/tests/integration/hdf5/test_nwbfile.py @@ -438,13 +438,13 @@ def addContainer(self, nwbfile): self.group = nwbfile.create_electrode_group('tetrode1', 'tetrode description', 'tetrode location', self.dev1) nwbfile.add_electrode(id=1, x=1.0, y=2.0, z=3.0, imp=-1.0, location='CA1', filtering='none', group=self.group, - group_name='tetrode1') + group_name='tetrode1', rel_x=1.0) nwbfile.add_electrode(id=2, x=1.0, y=2.0, z=3.0, imp=-2.0, location='CA1', filtering='none', group=self.group, - group_name='tetrode1') + group_name='tetrode1', rel_x=1.0) nwbfile.add_electrode(id=3, x=1.0, y=2.0, z=3.0, imp=-3.0, location='CA1', filtering='none', group=self.group, - group_name='tetrode1') + group_name='tetrode1', rel_x=1.0) nwbfile.add_electrode(id=4, x=1.0, y=2.0, z=3.0, imp=-4.0, location='CA1', filtering='none', group=self.group, - group_name='tetrode1') + group_name='tetrode1', rel_x=1.0) self.container = nwbfile.electrodes # override self.container which has the placeholder diff --git a/tests/unit/test_ecephys.py b/tests/unit/test_ecephys.py index d8b2682b3..96c12bbb7 100644 --- a/tests/unit/test_ecephys.py +++ b/tests/unit/test_ecephys.py @@ -3,14 +3,14 @@ from pynwb.ecephys import ElectricalSeries, SpikeEventSeries, EventDetection, Clustering, EventWaveform,\ ClusterWaveforms, LFP, FilteredEphys, FeatureExtraction, ElectrodeGroup from pynwb.device import Device -from pynwb.file import ElectrodeTable +from pynwb.file import Electrodes from pynwb.testing import TestCase from hdmf.common import DynamicTableRegion def make_electrode_table(): - table = ElectrodeTable() + table = Electrodes() dev1 = Device('dev1') group = ElectrodeGroup('tetrode1', 'tetrode description', 'tetrode location', dev1) table.add_row(id=1, x=1.0, y=2.0, z=3.0, imp=-1.0, location='CA1', filtering='none', diff --git a/tests/unit/test_file.py b/tests/unit/test_file.py index 16d76afd0..70a30df19 100644 --- a/tests/unit/test_file.py +++ b/tests/unit/test_file.py @@ -248,7 +248,7 @@ def test_add_electrode(self): dev1 = self.nwbfile.create_device('dev1') group = self.nwbfile.create_electrode_group('tetrode1', 'tetrode description', 'tetrode location', dev1) self.nwbfile.add_electrode(x=1.0, y=2.0, z=3.0, imp=-1.0, location='CA1', filtering='none', group=group, id=1) - self.assertIsIstance(self.electrodes, Electrodes) + self.assertIsInstance(self.nwbfile.electrodes, Electrodes) elec = self.nwbfile.electrodes[0] self.assertEqual(elec.index[0], 1) self.assertEqual(elec.iloc[0]['x'], 1.0) @@ -258,13 +258,32 @@ def test_add_electrode(self): self.assertEqual(elec.iloc[0]['location'], 'CA1') self.assertEqual(elec.iloc[0]['filtering'], 'none') self.assertEqual(elec.iloc[0]['group'], group) - self.assertEqual(set(self.electrodes.colnames), {'id', 'x', 'y', 'z', 'imp', 'location', 'filtering', 'group', - 'group_name'}) + self.assertEqual(set(self.nwbfile.electrodes.colnames), + {'x', 'y', 'z', 'imp', 'location', 'filtering', 'group', 'group_name'}) + + def test_add_electrode_opt_col(self): + dev1 = self.nwbfile.create_device('dev1') + group = self.nwbfile.create_electrode_group('tetrode1', 'tetrode description', 'tetrode location', dev1) + self.nwbfile.add_electrode(x=1.0, y=2.0, z=3.0, imp=-1.0, location='CA1', filtering='none', group=group, id=1, + rel_x=5.0) + self.assertIsInstance(self.nwbfile.electrodes, Electrodes) + elec = self.nwbfile.electrodes[0] + self.assertEqual(elec.index[0], 1) + self.assertEqual(elec.iloc[0]['x'], 1.0) + self.assertEqual(elec.iloc[0]['y'], 2.0) + self.assertEqual(elec.iloc[0]['z'], 3.0) + self.assertEqual(elec.iloc[0]['imp'], -1.0) + self.assertEqual(elec.iloc[0]['location'], 'CA1') + self.assertEqual(elec.iloc[0]['filtering'], 'none') + self.assertEqual(elec.iloc[0]['group'], group) + self.assertEqual(elec.iloc[0]['rel_x'], 5.0) + self.assertEqual(set(self.nwbfile.electrodes.colnames), + {'x', 'y', 'z', 'imp', 'location', 'filtering', 'group', 'group_name', 'rel_x'}) def test_add_electrode_column(self): self.nwbfile.add_electrode_column(name='new_col', description='a new column') - self.assertEqual(set(self.electrodes.colnames), {'id', 'x', 'y', 'z', 'imp', 'location', 'filtering', 'group', - 'group_name', 'a new column'}) + self.assertEqual(set(self.nwbfile.electrodes.colnames), + {'x', 'y', 'z', 'imp', 'location', 'filtering', 'group', 'group_name', 'new_col'}) def test_all_children(self): ts1 = TimeSeries('test_ts1', [0, 1, 2, 3, 4, 5], 'grams', timestamps=[0.0, 0.1, 0.2, 0.3, 0.4, 0.5])