Skip to content

Commit 6ff7e44

Browse files
rlystephprince
andauthored
Add 'target_tables' kwarg to DynamicTable subclasses (#2097)
Co-authored-by: Steph Prince <[email protected]>
1 parent b0973cc commit 6ff7e44

File tree

7 files changed

+54
-13
lines changed

7 files changed

+54
-13
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## PyNWB 3.1.3 (Unreleased)
44

5+
### Added
6+
- Added 'target_tables' kwarg to DynamicTable subclasses to allow classes that extend DynamicTable subclasses to specify the mapping of DynamicTableRegion columns to the target tables. @rly, @stephprince [#2096](https://github.com/NeurodataWithoutBorders/pynwb/issues/2096)
7+
58
### Fixed
69
- Fixed incorrect warning for path not ending in `.nwb` when no path argument was provided. @t-b [#2130](https://github.com/NeurodataWithoutBorders/pynwb/pull/2130)
710
- Fixed issue with setting `neurodata_type_inc` when reading NWB files with cached schema versions less than 2.2.0. @rly [#2135](https://github.com/NeurodataWithoutBorders/pynwb/pull/2135)

src/pynwb/ecephys.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ class ElectrodesTable(DynamicTable):
8989
'for this electrode.'), 'required': False}
9090
)
9191

92-
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'))
92+
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
93+
allow_positional=AllowPositional.WARNING,)
9394
def __init__(self, **kwargs):
9495
kwargs['name'] = 'electrodes'
9596
kwargs['description'] = 'metadata about extracellular electrodes'

src/pynwb/epoch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class TimeIntervals(DynamicTable):
2828
@docval({'name': 'name', 'type': str, 'doc': 'name of this TimeIntervals'}, # required
2929
{'name': 'description', 'type': str, 'doc': 'Description of this TimeIntervals',
3030
'default': "experimental intervals"},
31-
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
31+
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
3232
allow_positional=AllowPositional.WARNING,)
3333
def __init__(self, **kwargs):
3434
super().__init__(**kwargs)

src/pynwb/icephys.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ class IntracellularElectrodesTable(DynamicTable):
422422
'table': False},
423423
)
424424

425-
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
425+
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
426426
allow_positional=AllowPositional.WARNING,)
427427
def __init__(self, **kwargs):
428428
# Define defaultb name and description settings
@@ -452,7 +452,7 @@ class IntracellularStimuliTable(DynamicTable):
452452
'class': TimeSeriesReferenceVectorData},
453453
)
454454

455-
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
455+
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
456456
allow_positional=AllowPositional.WARNING,)
457457
def __init__(self, **kwargs):
458458
# Define defaultb name and description settings
@@ -476,7 +476,7 @@ class IntracellularResponsesTable(DynamicTable):
476476
'class': TimeSeriesReferenceVectorData},
477477
)
478478

479-
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
479+
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
480480
allow_positional=AllowPositional.WARNING,)
481481
def __init__(self, **kwargs):
482482
# Define defaultb name and description settings
@@ -493,7 +493,8 @@ class IntracellularRecordingsTable(AlignedDynamicTable):
493493
a single simultaneous_recording. Each row in the table represents a single recording consisting
494494
typically of a stimulus and a corresponding response.
495495
"""
496-
@docval(*get_docval(AlignedDynamicTable.__init__, 'id', 'columns', 'colnames', 'category_tables', 'categories'),
496+
@docval(*get_docval(AlignedDynamicTable.__init__, 'id', 'columns', 'colnames',
497+
'category_tables', 'categories', 'target_tables'),
497498
allow_positional=AllowPositional.WARNING,)
498499
def __init__(self, **kwargs):
499500
kwargs['name'] = 'intracellular_recordings'
@@ -782,7 +783,7 @@ class SimultaneousRecordingsTable(DynamicTable):
782783
'reading the Container from file as the table attribute is already populated in this case '
783784
'but otherwise this is required.',
784785
'default': None},
785-
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
786+
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
786787
allow_positional=AllowPositional.WARNING,)
787788
def __init__(self, **kwargs):
788789
intracellular_recordings_table = popargs('intracellular_recordings_table', kwargs)
@@ -842,7 +843,7 @@ class SequentialRecordingsTable(DynamicTable):
842843
'column indexes. May be None when reading the Container from file as the '
843844
'table attribute is already populated in this case but otherwise this is required.',
844845
'default': None},
845-
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
846+
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
846847
allow_positional=AllowPositional.WARNING,)
847848
def __init__(self, **kwargs):
848849
simultaneous_recordings_table = popargs('simultaneous_recordings_table', kwargs)
@@ -900,7 +901,7 @@ class RepetitionsTable(DynamicTable):
900901
'be None when reading the Container from file as the table attribute is already populated '
901902
'in this case but otherwise this is required.',
902903
'default': None},
903-
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
904+
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
904905
allow_positional=AllowPositional.WARNING,)
905906
def __init__(self, **kwargs):
906907
sequential_recordings_table = popargs('sequential_recordings_table', kwargs)
@@ -953,7 +954,7 @@ class ExperimentalConditionsTable(DynamicTable):
953954
'type': RepetitionsTable,
954955
'doc': 'the RepetitionsTable table that the repetitions column indexes',
955956
'default': None},
956-
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
957+
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
957958
allow_positional=AllowPositional.WARNING,)
958959
def __init__(self, **kwargs):
959960
repetitions_table = popargs('repetitions_table', kwargs)

src/pynwb/misc.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ class Units(DynamicTable):
165165
)
166166

167167
@docval({'name': 'name', 'type': str, 'doc': 'Name of this Units interface', 'default': 'Units'},
168-
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
168+
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
169169
{'name': 'description', 'type': str, 'doc': 'a description of what is in this table', 'default': None},
170170
{'name': 'electrode_table', 'type': DynamicTable,
171171
'doc': 'the table that the *electrodes* column indexes', 'default': None},
@@ -265,7 +265,8 @@ class FrequencyBandsTable(DynamicTable):
265265
{'name': 'band_stdev', 'description': 'The standard deviation Gaussian filters, in Hz.', 'required': False}
266266
)
267267

268-
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'))
268+
@docval(*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
269+
allow_positional=AllowPositional.WARNING,)
269270
def __init__(self, **kwargs):
270271
kwargs['name'] = 'bands'
271272
kwargs['description'] = 'Table for describing the bands that DecompositionSeries was generated from.'

src/pynwb/ophys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ class PlaneSegmentation(DynamicTable):
348348
{'name': 'name', 'type': str, 'doc': 'name of PlaneSegmentation.', 'default': None},
349349
{'name': 'reference_images', 'type': (ImageSeries, list, dict, tuple), 'default': None,
350350
'doc': 'One or more image stacks that the masks apply to (can be oneelement stack).'},
351-
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames'),
351+
*get_docval(DynamicTable.__init__, 'id', 'columns', 'colnames', 'target_tables'),
352352
allow_positional=AllowPositional.WARNING,)
353353
def __init__(self, **kwargs):
354354
imaging_plane, reference_images = popargs('imaging_plane', 'reference_images', kwargs)

tests/unit/test_extension.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,42 @@ def test_lab_meta_auto(self):
136136
nwbfile = NWBFile("a file with header data", "NB123A", datetime(2017, 5, 1, 12, 0, 0, tzinfo=tzlocal()))
137137

138138
nwbfile.add_lab_meta_data(MyTestMetaData(name='test_name', test_attr=5.))
139+
140+
def test_custom_target_table(self):
141+
ns_builder = NWBNamespaceBuilder('Extension for custom target table', self.prefix, version='0.1.0')
142+
test_epochs_table_ext = NWBGroupSpec(
143+
neurodata_type_def="MyEpochsTable",
144+
neurodata_type_inc="TimeIntervals",
145+
doc=("Custom table for storing my epochs. Inherits from TimeIntervals."),
146+
datasets=[
147+
NWBDatasetSpec(
148+
name="my_locations",
149+
doc="References row(s) of MyLocationsTable.",
150+
neurodata_type_inc="DynamicTableRegion",
151+
),
152+
]
153+
)
154+
test_locations_table_ext = NWBGroupSpec(
155+
neurodata_type_def="MyLocationsTable",
156+
neurodata_type_inc="DynamicTable",
157+
doc=("Table to reference."),
158+
default_name="my_locations_table",
159+
)
160+
161+
ns_builder.add_spec(self.ext_source, test_epochs_table_ext)
162+
ns_builder.add_spec(self.ext_source, test_locations_table_ext)
163+
ns_builder.export(self.ns_path, outdir=self.tempdir)
164+
ns_abs_path = os.path.join(self.tempdir, self.ns_path)
165+
166+
load_namespaces(ns_abs_path)
139167

168+
MyLocationsTable = get_class('MyLocationsTable', self.prefix)
169+
MyEpochsTable = get_class('MyEpochsTable', self.prefix)
170+
my_locations_table = MyLocationsTable(name='test_name', description='test desc')
171+
my_epochs_table = MyEpochsTable(name='test_name',
172+
description='test desc',
173+
target_tables={'my_locations': my_locations_table})
174+
self.assertIs(my_epochs_table['my_locations'].table, my_locations_table)
140175

141176
class TestCatchDupNS(TestCase):
142177

0 commit comments

Comments
 (0)