Note
Go to the end to download the full example code.
Annotating Time Intervals¶
Annotating events in time is a common need in neuroscience, e.g. to describes epochs, trials, and
invalid times during an experimental session. NWB supports annotation of time intervals via the
TimeIntervals
type. The TimeIntervals
type is
a DynamicTable
with the following columns:
start_time
andstop_time
describe the start and stop times of intervals as floating point offsets in seconds relative to thetimestamps_reference_time
of the file. In addition,tags
is an optional, indexed column used to associate user-defined string tags with intervals (0 or more tags per time interval)timeseries
is an optional, indexedTimeSeriesReferenceVectorData
column to map intervals directly to ranges in select, relevantTimeSeries
(0 or more per time interval)as a
DynamicTable
user may add additional columns toTimeIntervals
viaadd_column
Hint
TimeIntervals
is intended for storing general annotations of time ranges.
Depending on the application (e.g., when intervals are generated by data acquisition or automatic
data processing), it can be useful to describe intervals (or instantaneous events) in time
as TimeSeries
. NWB provides several types for this purposes, e.g.,
IntervalSeries
, BehavioralEpochs
,
BehavioralEvents
, EventDetection
, or
SpikeEventSeries
.
Setup: Creating an example NWB file for the tutorial¶
from datetime import datetime
from uuid import uuid4
import numpy as np
from dateutil.tz import tzlocal
from pynwb import NWBFile, TimeSeries
# create the NWBFile
nwbfile = NWBFile(
session_description="my first synthetic recording", # required
identifier=str(uuid4()), # required
session_start_time=datetime(2017, 4, 3, 11, tzinfo=tzlocal()), # required
experimenter="Baggins, Bilbo", # optional
lab="Bag End Laboratory", # optional
institution="University of Middle Earth at the Shire", # optional
experiment_description="I went on an adventure with thirteen dwarves to reclaim vast treasures.", # optional
session_id="LONELYMTN", # optional
)
# create some example TimeSeries
test_ts = TimeSeries(
name="series1",
data=np.arange(1000),
unit="m",
timestamps=np.linspace(0.5, 601, 1000),
)
rate_ts = TimeSeries(
name="series2", data=np.arange(600), unit="V", starting_time=0.0, rate=1.0
)
# Add the TimeSeries to the file
nwbfile.add_acquisition(test_ts)
nwbfile.add_acquisition(rate_ts)
Adding Time Intervals to a NWBFile¶
NWB provides a set of pre-defined TimeIntervals
tables for epochs
, trials
, and
invalid_times
.
Trials¶
Trials can be added to an NWB file using the methods add_trial
By default, NWBFile only requires trial start_time
and stop_time
. The tags
and timeseries
are
optional. For timeseries
we only need to supply the TimeSeries
.
PyNWB automatically calculates the corresponding index range (described by idx_start
and count
) for
the supplied TimeSeries
based on the given start_time
and stop_time
and
the timestamps
(or starting_time
and rate
) of the given TimeSeries
.
Additional columns can be added using add_trial_column
. This method takes a name
for the column and a description of what the column stores. You do not need to supply data
type, as this will inferred. Once all columns have been added, trial data can be populated using
add_trial
. Note that if you add a custom column, you must
add at least one row to write the table to a file.
Lets add an additional column and some trial data with tags and timeseries references.
nwbfile.add_trial_column(name="stim", description="the visual stimuli during the trial")
nwbfile.add_trial(
start_time=0.0,
stop_time=2.0,
stim="dog",
tags=["animal"],
timeseries=[test_ts, rate_ts],
)
nwbfile.add_trial(
start_time=3.0,
stop_time=5.0,
stim="mountain",
tags=["landscape"],
timeseries=[test_ts, rate_ts],
)
nwbfile.add_trial(
start_time=6.0,
stop_time=8.0,
stim="desert",
tags=["landscape"],
timeseries=[test_ts, rate_ts],
)
nwbfile.add_trial(
start_time=9.0,
stop_time=11.0,
stim="tree",
tags=["landscape", "plant"],
timeseries=[test_ts, rate_ts],
)
nwbfile.add_trial(
start_time=12.0,
stop_time=14.0,
stim="bird",
tags=["animal"],
timeseries=[test_ts, rate_ts],
)
nwbfile.add_trial(
start_time=15.0,
stop_time=17.0,
stim="flower",
tags=["animal"],
timeseries=[test_ts, rate_ts],
)
Epochs¶
Similarly, epochs can be added to an NWB file using the method add_epoch
and
add_epoch_column
.
nwbfile.add_epoch(
2.0,
4.0,
["first", "example"],
[
test_ts,
],
)
nwbfile.add_epoch(
6.0,
8.0,
["second", "example"],
[
test_ts,
],
)
Invalid Times¶
Similarly, invalid times can be added using the method add_invalid_time_interval
and
add_invalid_times_column
.
nwbfile.add_epoch(
2.0,
4.0,
["first", "example"],
[
test_ts,
],
)
nwbfile.add_epoch(
6.0,
8.0,
["second", "example"],
[
test_ts,
],
)
Custom Time Intervals¶
To define custom, experiment-specific TimeIntervals
we can add them
either: 1) when creating the NWBFile
by defining the
intervals
constructor argument or 2) via the
add_time_intervals
or create_time_intervals
after the NWBFile
has been created.
from pynwb.epoch import TimeIntervals
sleep_stages = TimeIntervals(
name="sleep_stages",
description="intervals for each sleep stage as determined by EEG",
)
sleep_stages.add_column(name="stage", description="stage of sleep")
sleep_stages.add_column(name="confidence", description="confidence in stage (0-1)")
sleep_stages.add_row(start_time=0.3, stop_time=0.5, stage=1, confidence=0.5)
sleep_stages.add_row(start_time=0.7, stop_time=0.9, stage=2, confidence=0.99)
sleep_stages.add_row(start_time=1.3, stop_time=3.0, stage=3, confidence=0.7)
_ = nwbfile.add_time_intervals(sleep_stages)
Accessing Time Intervals¶
We can access the predefined TimeIntervals
tables via the corresponding
epochs
, trials
, and
invalid_times
properties and for custom TimeIntervals
via the get_time_intervals
method. E.g.:
Like any DynamicTable
, we can conveniently convert any
TimeIntervals
table to a pandas.DataFrame
via
to_dataframe
, such as:
nwbfile.trials.to_dataframe()
This approach makes it easy to query the data to, e.g., locate all time intervals within a certain time range
trials_df = nwbfile.trials.to_dataframe()
trials_df.query("(start_time > 2.0) & (stop_time < 9.0)")
Accessing referenced TimeSeries¶
As mentioned earlier, the timeseries
column is defined by a TimeSeriesReferenceVectorData
which stores references to the corresponding ranges in TimeSeries
. Individual references
to TimeSeries
are described via TimeSeriesReference
tuples
with the idx_start
, count
,
and timeseries
.
Using TimeSeriesReference
we can easily access the relevant
data
and timestamps
for the corresponding time range from the TimeSeries
.
# Get a single example TimeSeriesReference from the trials table
example_tsr = nwbfile.trials["timeseries"][0][0]
# Get the data values from the timeseries. This is a shorthand for:
# _ = example_tsr.timeseries.data[example_tsr.idx_start: (example_tsr.idx_start + example_tsr.count)]
_ = example_tsr.data
# Get the timestamps. Timestamps are either loaded from the TimeSeries or
# computed from the starting_time and rate
example_tsr.timestamps
array([0.5 , 1.1011011, 1.7022022])
Using isvalid
we can further check if the reference is valid.
A TimeSeriesReference
is defined as invalid if both
idx_start
, count
are
set to -1
. isvalid
further also checks that the indicated
index range and types are valid, raising IndexError
and TypeError
respectively, if bad
idx_start
, count
or
timeseries
are found.
example_tsr.isvalid()
True
Adding TimeSeries references to other tables¶
Since TimeSeriesReferenceVectorData
is a regular VectorData
type, we can use it to add references to intervals in TimeSeries
to any
DynamicTable
. In the IntracellularRecordingsTable
, e.g.,
it is used to reference the recording of the stimulus and response associated with a particular intracellular
electrophysiology recording.
Reading/Writing TimeIntervals to file¶
Reading and writing the data is as usual:
from pynwb import NWBHDF5IO
# write the file
with NWBHDF5IO("example_timeintervals_file.nwb", "w") as io:
io.write(nwbfile)
# read the file
with NWBHDF5IO("example_timeintervals_file.nwb", "r") as io:
nwbfile_in = io.read()
# plot the sleep stages TimeIntervals table
nwbfile_in.get_time_intervals("sleep_stages").to_dataframe()