Skip to content

Commit

Permalink
IntracellularRecordingsTable: infer electrode (#1598)
Browse files Browse the repository at this point in the history
* IntracellularRecordingsTable: allow electrode to be inferred by response or stimulus and add tests
* Update CHANGELOG.md

Co-authored-by: Oliver Ruebel <[email protected]>
  • Loading branch information
bendichter and oruebel authored Dec 12, 2022
1 parent cf6759a commit f4bd3bc
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 27 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Enhancements and minor changes
- `Subject.age` can be input as a `timedelta`. @bendichter [#1590](https://github.com/NeurodataWithoutBorders/pynwb/pull/1590)
- `IntracellularRecordingsTable.add_recording`: the `electrode` arg is now optional, and is automatically populated from the stimulus or response.
[#1597](https://github.com/NeurodataWithoutBorders/pynwb/pull/1597)
- Add module `pynwb.testing.mock.icephys` and corresponding tests. @bendichter
[1595](https://github.com/NeurodataWithoutBorders/pynwb/pull/1595)
- Remove redundant object mapper code. @rly [#1600](https://github.com/NeurodataWithoutBorders/pynwb/pull/1600)
Expand Down
61 changes: 39 additions & 22 deletions src/pynwb/icephys.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,26 +506,33 @@ def __init__(self, **kwargs):

super().__init__(**kwargs)

@docval({'name': 'electrode', 'type': IntracellularElectrode, 'doc': 'The intracellular electrode used'},
{'name': 'stimulus_start_index', 'type': int, 'doc': 'Start index of the stimulus', 'default': None},
{'name': 'stimulus_index_count', 'type': int, 'doc': 'Stop index of the stimulus', 'default': None},
{'name': 'stimulus', 'type': TimeSeries,
'doc': 'The TimeSeries (usually a PatchClampSeries) with the stimulus',
'default': None},
{'name': 'response_start_index', 'type': int, 'doc': 'Start index of the response', 'default': None},
{'name': 'response_index_count', 'type': int, 'doc': 'Stop index of the response', 'default': None},
{'name': 'response', 'type': TimeSeries,
'doc': 'The TimeSeries (usually a PatchClampSeries) with the response',
'default': None},
{'name': 'electrode_metadata', 'type': dict,
'doc': 'Additional electrode metadata to be stored in the electrodes table', 'default': None},
{'name': 'stimulus_metadata', 'type': dict,
'doc': 'Additional stimulus metadata to be stored in the stimuli table', 'default': None},
{'name': 'response_metadata', 'type': dict,
'doc': 'Additional resposnse metadata to be stored in the responses table', 'default': None},
returns='Integer index of the row that was added to this table',
rtype=int,
allow_extra=True)
@docval(
{
"name": "electrode",
"type": IntracellularElectrode,
"doc": "The intracellular electrode used",
"default": None,
},
{'name': 'stimulus_start_index', 'type': int, 'doc': 'Start index of the stimulus', 'default': None},
{'name': 'stimulus_index_count', 'type': int, 'doc': 'Stop index of the stimulus', 'default': None},
{'name': 'stimulus', 'type': TimeSeries,
'doc': 'The TimeSeries (usually a PatchClampSeries) with the stimulus',
'default': None},
{'name': 'response_start_index', 'type': int, 'doc': 'Start index of the response', 'default': None},
{'name': 'response_index_count', 'type': int, 'doc': 'Stop index of the response', 'default': None},
{'name': 'response', 'type': TimeSeries,
'doc': 'The TimeSeries (usually a PatchClampSeries) with the response',
'default': None},
{'name': 'electrode_metadata', 'type': dict,
'doc': 'Additional electrode metadata to be stored in the electrodes table', 'default': None},
{'name': 'stimulus_metadata', 'type': dict,
'doc': 'Additional stimulus metadata to be stored in the stimuli table', 'default': None},
{'name': 'response_metadata', 'type': dict,
'doc': 'Additional resposnse metadata to be stored in the responses table', 'default': None},
returns='Integer index of the row that was added to this table',
rtype=int,
allow_extra=True,
)
def add_recording(self, **kwargs):
"""
Add a single recording to the IntracellularRecordingsTable table.
Expand All @@ -546,6 +553,14 @@ def add_recording(self, **kwargs):
'response',
kwargs)
electrode = popargs('electrode', kwargs)

# if electrode is not provided, take from stimulus or response object
if electrode is None:
if stimulus:
electrode = stimulus.electrode
elif response:
electrode = response.electrode

# Confirm that we have at least a valid stimulus or response
if stimulus is None and response is None:
raise ValueError("stimulus and response cannot both be None.")
Expand Down Expand Up @@ -580,8 +595,10 @@ def add_recording(self, **kwargs):
# warnings.warn("sweep_number are usually expected to be the same for PatchClampSeries type "
# "stimulus and response pairs in an intracellular recording.")
if response.electrode != stimulus.electrode:
raise ValueError("electrodes are usually expected to be the same for PatchClampSeries type "
"stimulus and response pairs in an intracellular recording.")
raise ValueError(
"electrodes are usually expected to be the same for PatchClampSeries type stimulus and response "
"pairs in an intracellular recording."
)

# Compile the electrodes table data
electrodes = copy(popargs('electrode_metadata', kwargs))
Expand Down
12 changes: 10 additions & 2 deletions tests/unit/test_icephys.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import numpy as np

from pynwb.icephys import (PatchClampSeries, CurrentClampSeries, IZeroClampSeries, CurrentClampStimulusSeries,
VoltageClampSeries, VoltageClampStimulusSeries, IntracellularElectrode, SweepTable)
from pynwb.icephys import (
PatchClampSeries,
CurrentClampSeries,
IZeroClampSeries,
CurrentClampStimulusSeries,
VoltageClampSeries,
VoltageClampStimulusSeries,
IntracellularElectrode,
SweepTable,
)
from pynwb.device import Device
from pynwb.testing import TestCase
from pynwb.file import NWBFile # Needed to test icephys functionality defined on NWBFile
Expand Down
65 changes: 62 additions & 3 deletions tests/unit/test_icephys_metadata_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,20 @@

from pynwb.testing import TestCase, remove_test_file, create_icephys_stimulus_and_response
from pynwb.file import NWBFile
from pynwb.icephys import (VoltageClampStimulusSeries, VoltageClampSeries, CurrentClampStimulusSeries,
IZeroClampSeries, IntracellularRecordingsTable, SimultaneousRecordingsTable,
SequentialRecordingsTable, RepetitionsTable, ExperimentalConditionsTable)
from pynwb.icephys import (
VoltageClampStimulusSeries,
VoltageClampSeries,
CurrentClampStimulusSeries,
IZeroClampSeries,
SimultaneousRecordingsTable,
SequentialRecordingsTable,
RepetitionsTable,
ExperimentalConditionsTable,
IntracellularElectrode,
CurrentClampSeries,
IntracellularRecordingsTable
)
from pynwb.device import Device
from pynwb.base import TimeSeriesReferenceVectorData
from pynwb import NWBHDF5IO
from hdmf.utils import docval, popargs
Expand Down Expand Up @@ -629,6 +640,54 @@ def test_write_with_stimulus_template(self):
with NWBHDF5IO(self.path, 'w') as io:
io.write(local_nwbfile)

def test_no_electrode(self):
device = Device(name='device_name')
elec = IntracellularElectrode(
name='test_iS',
device=device,
description='description',
slice='slice',
seal='seal',
location='location',
resistance='resistance',
filtering='filtering',
initial_access_resistance='initial_access_resistance',
cell_id='this_cell',
)

cCSS = CurrentClampStimulusSeries(
name="test_cCSS",
data=np.ones((30,)),
electrode=elec,
gain=1.0,
rate=100_000.,
)

cCS = CurrentClampSeries(
name="test_cCS",
data=np.ones((30,)),
electrode=elec,
gain=1.0,
rate=100_000.,
)

# test retrieve electrode from stimulus (when both stimulus and response are given)
itr = IntracellularRecordingsTable()
itr.add_recording(stimulus=cCSS, response=cCS)
self.assertEqual(itr["electrodes"].values[0], elec)
del itr

# test retrieve electrode from stimulus (when only stimulus is given)
itr = IntracellularRecordingsTable()
itr.add_recording(stimulus=cCSS, response=None)
self.assertEqual(itr["electrodes"].values[0], elec)
del itr

# test retrieve electrode from response (when only response is given)
itr = IntracellularRecordingsTable()
itr.add_recording(stimulus=None, response=cCS)
self.assertEqual(itr["electrodes"].values[0], elec)


class SimultaneousRecordingsTableTests(ICEphysMetaTestBase):
"""
Expand Down

0 comments on commit f4bd3bc

Please sign in to comment.