Skip to content

Commit 21f9192

Browse files
committed
Add a SweepTable for icephys data
For icephys a common data acquisition method is to acquire data from multiple electrodes in one run. This "run" is commonly called a sweep. Now at the moment there exists no way of grouping PatchClampSeries together in a NWB file. The SweepTable solves that problem. It is a table with two columns "sweep_number" and a vector of links to PatchClampSeries. Now whenever someone adds some TimeSeries via NWBFile.add_acquisition/add_stimulus/add_stimulus_template we check if the added element is a PatchClampSeries and has a valid sweep number. In that case we add, or append, to the SweepTable. The sweep table is located at `/general/intracellular_ephys/sweep_table` and is a DynamicTable.
1 parent 218d870 commit 21f9192

File tree

6 files changed

+150
-6
lines changed

6 files changed

+150
-6
lines changed

src/pynwb/data/nwb.file.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,10 @@ groups:
391391
- neurodata_type_inc: IntracellularElectrode
392392
doc: 'One of possibly many. COMMENT: Name should be informative.'
393393
quantity: '*'
394+
- neurodata_type_inc: SweepTable
395+
name: sweep_table
396+
doc: The table which groups different PatchClampSeries together.
397+
quantity: '?'
394398
quantity: '?'
395399
- name: optogenetics
396400
doc: Metadata describing optogenetic stimuluation

src/pynwb/data/nwb.icephys.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,35 @@ groups:
230230
- name: device
231231
doc: the device that was used to record from this electrode
232232
target_type: Device
233+
- neurodata_type_def: SweepTable
234+
neurodata_type_inc: DynamicTable
235+
doc: The table which groups different PatchClampSeries together.
236+
attributes:
237+
- name: help
238+
dtype: text
239+
doc: Value is 'The table which groups different PatchClampSeries together'
240+
value: The table which groups different PatchClampSeries together
241+
datasets:
242+
- neurodata_type_inc: TableColumn
243+
name: sweep_number
244+
dtype: uint64
245+
doc: The sweep number of the PatchClampSeries in that row.
246+
attributes:
247+
- name: description
248+
dtype: text
249+
doc: value is 'The sweep number of the PatchClampSeries in that row'
250+
value: The sweep number of the PatchClampSeries in that row
251+
- neurodata_type_inc: VectorData
252+
name: series
253+
dtype:
254+
target_type: PatchClampSeries
255+
reftype: object
256+
doc: The PatchClampSeries with the sweep number in that row
257+
attributes:
258+
- name: help
259+
dtype: text
260+
doc: Value is 'The PatchClampSeries with the sweep number in that row'
261+
value: The PatchClampSeries with the sweep number in that row
262+
- neurodata_type_inc: VectorIndex
263+
name: series_index
264+
doc: Index for series

src/pynwb/file.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .base import TimeSeries, ProcessingModule
1212
from .epoch import TimeIntervals
1313
from .ecephys import ElectrodeGroup, Device
14-
from .icephys import IntracellularElectrode
14+
from .icephys import IntracellularElectrode, SweepTable, PatchClampSeries
1515
from .ophys import ImagingPlane
1616
from .ogen import OptogeneticStimulusSite
1717
from .misc import Units
@@ -70,7 +70,7 @@ class NWBFile(MultiContainerInterface):
7070
__clsconf__ = [
7171
{
7272
'attr': 'acquisition',
73-
'add': 'add_acquisition',
73+
'add': '_add_acquisition_internal',
7474
'type': NWBDataInterface,
7575
'get': 'get_acquisition'
7676
},
@@ -82,13 +82,13 @@ class NWBFile(MultiContainerInterface):
8282
},
8383
{
8484
'attr': 'stimulus',
85-
'add': 'add_stimulus',
85+
'add': '_add_stimulus_internal',
8686
'type': TimeSeries,
8787
'get': 'get_stimulus'
8888
},
8989
{
9090
'attr': 'stimulus_template',
91-
'add': 'add_stimulus_template',
91+
'add': '_add_stimulus_template_internal',
9292
'type': TimeSeries,
9393
'get': 'get_stimulus_template'
9494
},
@@ -166,6 +166,7 @@ class NWBFile(MultiContainerInterface):
166166
{'name': 'trials', 'child': True, 'required_name': 'trials'},
167167
{'name': 'units', 'child': True, 'required_name': 'units'},
168168
{'name': 'subject', 'child': True, 'required_name': 'subject'},
169+
{'name': 'sweep_table', 'child': True, 'required_name': 'sweep_table'},
169170
'epoch_tags',)
170171

171172
@docval({'name': 'session_description', 'type': str,
@@ -234,6 +235,8 @@ class NWBFile(MultiContainerInterface):
234235
'doc': 'the ElectrodeGroups that belong to this NWBFile', 'default': None},
235236
{'name': 'ic_electrodes', 'type': (list, tuple),
236237
'doc': 'IntracellularElectrodes that belong to this NWBFile', 'default': None},
238+
{'name': 'sweep_table', 'type': SweepTable,
239+
'doc': 'the SweepTable that belong to this NWBFile', 'default': None},
237240
{'name': 'imaging_planes', 'type': (list, tuple),
238241
'doc': 'ImagingPlanes that belong to this NWBFile', 'default': None},
239242
{'name': 'ogen_sites', 'type': (list, tuple),
@@ -288,6 +291,7 @@ def __init__(self, **kwargs):
288291
self.ogen_sites = getargs('ogen_sites', kwargs)
289292
self.time_intervals = getargs('time_intervals', kwargs)
290293
self.subject = getargs('subject', kwargs)
294+
self.sweep_table = getargs('sweep_table', kwargs)
291295

292296
recommended = [
293297
'experimenter',
@@ -500,6 +504,38 @@ def set_electrode_table(self, **kwargs):
500504
electrode_table = getargs('electrode_table', kwargs)
501505
self.electrodes = electrode_table
502506

507+
def _check_sweep_table(self):
508+
"""
509+
Create a SweepTable if not yet done.
510+
"""
511+
if self.sweep_table is None:
512+
self.sweep_table = SweepTable(name='sweep_table')
513+
514+
def _update_sweep_table(self, nwbdata):
515+
"""
516+
Add all PatchClampSeries with a valid sweep number to the sweep_table
517+
"""
518+
519+
if isinstance(nwbdata, PatchClampSeries):
520+
if nwbdata.sweep_number is not None:
521+
self._check_sweep_table()
522+
self.sweep_table.add_entry(nwbdata)
523+
524+
@docval({'name': 'nwbdata', 'type': NWBDataInterface})
525+
def add_acquisition(self, nwbdata):
526+
self._add_acquisition_internal(nwbdata)
527+
self._update_sweep_table(nwbdata)
528+
529+
@docval({'name': 'timeseries', 'type': TimeSeries})
530+
def add_stimulus(self, timeseries):
531+
self._add_stimulus_internal(timeseries)
532+
self._update_sweep_table(timeseries)
533+
534+
@docval({'name': 'timeseries', 'type': TimeSeries})
535+
def add_stimulus_template(self, timeseries):
536+
self._add_stimulus_template_internal(timeseries)
537+
self._update_sweep_table(timeseries)
538+
503539

504540
def _add_missing_timezone(date):
505541
"""

src/pynwb/icephys.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from collections import Iterable
22

3-
from .form.utils import docval, popargs, fmt_docval_args
3+
from .form.utils import docval, popargs, fmt_docval_args, call_docval_func
44

55
from . import register_class, CORE_NAMESPACE
66
from .base import TimeSeries, _default_resolution, _default_conversion
7-
from .core import NWBContainer
7+
from .core import NWBContainer, DynamicTable, ElementIdentifiers
88
from .device import Device
99

1010

@@ -420,3 +420,65 @@ def __init__(self, **kwargs):
420420
name, data, unit = popargs('name', 'data', 'unit', kwargs)
421421
electrode, gain = popargs('electrode', 'gain', kwargs)
422422
super(VoltageClampStimulusSeries, self).__init__(name, data, unit, electrode, gain, **kwargs)
423+
424+
425+
@register_class('SweepTable', CORE_NAMESPACE)
426+
class SweepTable(DynamicTable):
427+
"""
428+
A SweepTable allows to group PatchClampSeries together which stem from the same sweep.
429+
"""
430+
431+
__columns__ = (
432+
{'name': 'series', 'description': 'PatchClampSeries with the same sweep number',
433+
'required': True, 'vector_data': True},
434+
{'name': 'sweep_number', 'description': 'Sweep number of the entries in that row', 'required': True}
435+
)
436+
437+
@docval({'name': 'name', 'type': str, 'doc': 'name of this SweepTable', 'default': 'sweep_table'},
438+
{'name': 'description', 'type': str, 'doc': 'Description of this SweepTable',
439+
'default': "A sweep table groups different PatchClampSeries together."},
440+
{'name': 'id', 'type': ('array_data', ElementIdentifiers), 'doc': 'the identifiers for this table',
441+
'default': None},
442+
{'name': 'columns', 'type': (tuple, list), 'doc': 'the columns in this table', 'default': None},
443+
{'name': 'colnames', 'type': 'array_data', 'doc': 'the names of the columns in this table',
444+
'default': None})
445+
def __init__(self, **kwargs):
446+
call_docval_func(super(SweepTable, self).__init__, kwargs)
447+
448+
@docval({'name': 'pcs', 'type': PatchClampSeries, 'doc': 'PatchClampSeries to add to the table ' +
449+
'must have a valid sweep_number'})
450+
def add_entry(self, pcs):
451+
"""
452+
Add the passed PatchClampSeries to the sweep table.
453+
"""
454+
455+
kwargs = {'sweep_number': pcs.sweep_number, 'series': [pcs]}
456+
457+
# FIXME appending to an existing entry would be nicer
458+
# but this seems to be not possible
459+
self.add_row(**kwargs)
460+
461+
def get_series(self, sweep_number):
462+
"""
463+
Return a list of PatchClampSeries for the given sweep number.
464+
"""
465+
466+
ids = self.__get_row_ids(sweep_number)
467+
468+
if len(ids) == 0:
469+
return None
470+
471+
matches = []
472+
473+
for x in ids:
474+
for y in self[(x, 'series')]:
475+
matches.append(y)
476+
477+
return matches
478+
479+
def __get_row_ids(self, sweep_number):
480+
"""
481+
Return the row ids for the given sweep number.
482+
"""
483+
484+
return [index for index, elem in enumerate(self['sweep_number'].data) if elem == sweep_number]

src/pynwb/io/file.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def __init__(self, spec):
2828
icephys_spec = general_spec.get_group('intracellular_ephys')
2929
self.map_spec('ic_electrodes', icephys_spec.get_neurodata_type('IntracellularElectrode'))
3030
ecephys_spec = general_spec.get_group('extracellular_ephys')
31+
self.map_spec('sweep_table', icephys_spec.get_neurodata_type('SweepTable'))
3132
self.map_spec('electrodes', ecephys_spec.get_group('electrodes'))
3233
self.map_spec('electrode_groups', ecephys_spec.get_neurodata_type('ElectrodeGroup'))
3334
self.map_spec(

src/pynwb/io/icephys.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from .. import register_map
2+
3+
from pynwb.icephys import SweepTable
4+
from .core import DynamicTableMap
5+
6+
7+
@register_map(SweepTable)
8+
class SweepTableMap(DynamicTableMap):
9+
pass

0 commit comments

Comments
 (0)