Extracellular electrophysiology data

The following examples will reference variables that may not be defined within the block they are used in. For clarity, we define them here:

import numpy as np

Creating and Writing NWB files

When creating a NWB file, the first step is to create the NWBFile. The first argument is the name of the NWB file, and the second argument is a brief description of the dataset.

from datetime import datetime
from dateutil.tz import tzlocal
from pynwb import NWBFile

nwbfile = NWBFile('my first synthetic recording', 'EXAMPLE_ID', datetime.now(tzlocal()),
                  experimenter='Dr. Bilbo Baggins',
                  lab='Bag End Laboratory',
                  institution='University of Middle Earth at the Shire',
                  experiment_description='I went on an adventure with thirteen dwarves to reclaim vast treasures.',
                  session_id='LONELYMTN')

Electrode metadata

Electrode groups (i.e. experimentally relevant groupings of channels) are represented by ElectrodeGroup objects. To create an electrode group, you can use the NWBFile instance method create_electrode_group.

Before creating an ElectrodeGroup, you need to provide some information about the device that was used to record from the electrode. This is done by creating a Device object using the instance method create_device.

device = nwbfile.create_device(name='trodes_rig123')

Once you have created the Device, you can create an ElectrodeGroup.

electrode_name = 'tetrode1'
description = "an example tetrode"
location = "somewhere in the hippocampus"

electrode_group = nwbfile.create_electrode_group(electrode_name,
                                                 description=description,
                                                 location=location,
                                                 device=device)

After setting up electrode group metadata, you should add metadata about the individual electrodes comprising each electrode group. This is done with add_electrode.

The first argument to add_electrode is a unique identifier that the user should assign. For details on the rest of the arguments, please see the API documentation.

for idx in [1, 2, 3, 4]:
    nwbfile.add_electrode(id=idx,
                          x=1.0, y=2.0, z=3.0,
                          imp=float(-idx),
                          location='CA1', filtering='none',
                          group=electrode_group)

Extracellular recordings

The main classes for storing extracellular recordings are ElectricalSeries and SpikeEventSeries. ElectricalSeries should be used for storing raw voltage traces, local-field potential and filtered voltage traces and SpikeEventSeries is meant for storing spike waveforms (typically in preparation for clustering). The results of spike clustering (e.g. per-unit metadata and spike times) should be stored in the top-level Units table.

In addition to the data and timestamps fields inherited from TimeSeries class, these two classs will require metadata about the elctrodes from which data was generated. This is done by providing an DynamicTableRegion, which you can create using the create_electrode_table_region

The first argument to create_electrode_table_region a list of the indices of the electrodes you want in the region..

electrode_table_region = nwbfile.create_electrode_table_region([0, 2], 'the first and third electrodes')

Now that we have a DynamicTableRegion, we can create an ElectricalSeries and add it to our NWBFile.

from pynwb.ecephys import ElectricalSeries

rate = 10.0
np.random.seed(1234)
data_len = 1000
ephys_data = np.random.rand(data_len * 2).reshape((data_len, 2))
ephys_timestamps = np.arange(data_len) / rate

ephys_ts = ElectricalSeries('test_ephys_data',
                            ephys_data,
                            electrode_table_region,
                            timestamps=ephys_timestamps,
                            # Alternatively, could specify starting_time and rate as follows
                            # starting_time=ephys_timestamps[0],
                            # rate=rate,
                            resolution=0.001,
                            comments="This data was randomly generated with numpy, using 1234 as the seed",
                            description="Random numbers generated with numpy.random.rand")
nwbfile.add_acquisition(ephys_ts)

Associate electrodes with units

The PyNWB Basics tutorial demonstrates how to add data about units and specifying custom metadata about units. As mentioned here, there are some optional fields for units, one of these is electrodes. This field takes a list of indices into the electrode table for the electrodes that the unit corresponds to. For example, if two units were inferred from the first electrode (id = 1, index = 0), you would specify that like so:

nwbfile.add_unit(id=1, electrodes=[0])
nwbfile.add_unit(id=2, electrodes=[0])

Designating electrophysiology data

As mentioned above, ElectricalSeries and SpikeEventSeries are meant for storing specific types of extracellular recordings. In addition to these two TimeSeries classes, NWB provides some data interfaces for designating the type of data you are storing. We will briefly discuss them here, and refer the reader to API documentation and PyNWB Basics tutorial for more details on using these objects.

For storing spike data, there are two options. Which one you choose depends on what data you have available. If you need to store the complete, continuous raw voltage traces, you should store your the traces with ElectricalSeries objects as acquisition data, and use the EventDetection class for identifying the spike events in your raw traces. If you do not want to store the raw voltage traces and only the waveform ‘snippets’ surrounding spike events, you should use the EventWaveform class, which can store one or more SpikeEventSeries objects.

The results of spike sorting (or clustering) should be stored in the top-level Units table. Note that it is not required to store spike waveforms in order to store spike events or waveforms–if you only want to store the spike times of clustered units you can use only the Units table.

For local field potential data, there are two options. Again, which one you choose depends on what data you have available. With both options, you should store your traces with ElectricalSeries objects. If you are storing unfiltered local field potential data, you should store the ElectricalSeries objects in LFP data interface object(s). If you have filtered LFP data, you should store the ElectricalSeries objects in FilteredEphys data interface object(s).

Once you have finished adding all of your data to the NWBFile, write the file with NWBHDF5IO.

from pynwb import NWBHDF5IO

with NWBHDF5IO('ecephys_example.nwb', 'w') as io:
    io.write(nwbfile)

For more details on NWBHDF5IO, see the basic tutorial.

Reading electrophysiology data

Now that you have written some electrophysiology data, you can read it back in.

io = NWBHDF5IO('ecephys_example.nwb', 'r')
nwbfile = io.read()

For details on retrieving data from an NWBFile, we refer the reader to the basic tutorial. For this tutorial, we will just get back our the ElectricalSeries object we added above.

First, get the ElectricalSeries.

ephys_ts = nwbfile.acquisition['test_ephys_data']

The second dimension of the data attribute should be the electrodes the data was recorded with. We can get the electrodes for each column in data from the electrodes attribute. For example, information about the electrode in the second index can be retrieved like so:

elec2 = ephys_ts.electrodes[1]

Gallery generated by Sphinx-Gallery