1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
"""
The **events** and :class:`~mne.Annotations` data structures
=========================================================================
Events and :class:`~mne.Annotations` are quite similar.
This tutorial highlights their differences and similarities, and tries to shed
some light on which one is preferred to use in different situations when using
MNE.
Here are the definitions from the :ref:`glossary`.
events
Events correspond to specific time points in raw data; e.g., triggers,
experimental condition events, etc. MNE represents events with integers
that are stored in numpy arrays of shape (n_events, 3). Such arrays are
classically obtained from a trigger channel, also referred to as stim
channel.
annotations
An annotation is defined by an onset, a duration, and a string
description. It can contain information about the experiment, but
also details on signals marked by a human: bad data segments,
sleep scores, sleep events (spindles, K-complex) etc.
Both events and :class:`~mne.Annotations` can be seen as triplets
where the first element answers to **when** something happens and the last
element refers to **what** it is.
The main difference is that events represent the onset in samples taking into
account the first sample value
(:attr:`raw.first_samp <mne.io.Raw.first_samp>`), and the description is
an integer value.
In contrast, :class:`~mne.Annotations` represents the
``onset`` in seconds (relative to the reference ``orig_time``),
and the ``description`` is an arbitrary string.
There is no correspondence between the second element of events and
:class:`~mne.Annotations`.
For events, the second element corresponds to the previous value on the
stimulus channel from which events are extracted. In practice, the second
element is therefore in most cases zero.
The second element of :class:`~mne.Annotations` is a float
indicating its duration in seconds.
See :ref:`sphx_glr_auto_examples_io_plot_read_events.py`
for a complete example of how to read, select, and visualize **events**;
and :ref:`sphx_glr_auto_tutorials_plot_artifacts_correction_rejection.py` to
learn how :class:`~mne.Annotations` are used to mark bad segments
of data.
An example of events and annotations
------------------------------------
The following example shows the recorded events in `sample_audvis_raw.fif` and
marks bad segments due to eye blinks.
"""
import os.path as op
import numpy as np
import mne
# Load the data
data_path = mne.datasets.sample.data_path()
fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw.fif')
raw = mne.io.read_raw_fif(fname)
###############################################################################
# First we'll create and plot events associated with the experimental paradigm:
# extract the events array from the stim channel
events = mne.find_events(raw)
# Specify event_id dictionary based on the meaning of experimental triggers
event_id = {'Auditory/Left': 1, 'Auditory/Right': 2,
'Visual/Left': 3, 'Visual/Right': 4,
'smiley': 5, 'button': 32}
color = {1: 'green', 2: 'yellow', 3: 'red', 4: 'c', 5: 'black', 32: 'blue'}
mne.viz.plot_events(events, raw.info['sfreq'], raw.first_samp, color=color,
event_id=event_id)
###############################################################################
# Next, we're going to detect eye blinks and turn them into
# :class:`~mne.Annotations`:
# find blinks
annotated_blink_raw = raw.copy()
eog_events = mne.preprocessing.find_eog_events(raw)
n_blinks = len(eog_events)
# Turn blink events into Annotations of 0.5 seconds duration,
# each centered on the blink event:
onset = eog_events[:, 0] / raw.info['sfreq'] - 0.25
duration = np.repeat(0.5, n_blinks)
description = ['bad blink'] * n_blinks
annot = mne.Annotations(onset, duration, description,
orig_time=raw.info['meas_date'])
annotated_blink_raw.set_annotations(annot)
# plot the annotated raw
annotated_blink_raw.plot()
###############################################################################
# Working with Annotations
# ------------------------
#
# An important element of :class:`~mne.Annotations` is
# ``orig_time`` which is the time reference for the ``onset``.
# It is key to understand that when calling
# :func:`raw.set_annotations <mne.io.Raw.set_annotations>`, given
# annotations are copied and transformed so that
# :class:`raw.annotations.orig_time <mne.Annotations>`
# matches the recording time of the raw object.
# Refer to the documentation of :class:`~mne.Annotations` to see
# the expected behavior depending on ``meas_date`` and ``orig_time``.
# Where ``meas_date`` is the recording time stored in
# :class:`Info <mne.Info>`.
# You can find more information about :class:`Info <mne.Info>` in
# :ref:`sphx_glr_auto_tutorials_plot_info.py`.
#
# We'll now manipulate some simulated annotations.
# The first annotations has ``orig_time`` set to ``None`` while the
# second is set to a chosen POSIX timestamp for illustration purposes.
# Note that both annotations have different ``onset`` values.
###############################################################################
# Create an annotation object with orig_time undefined (default)
annot_none = mne.Annotations(onset=[0, 2, 9], duration=[0.5, 4, 0],
description=['foo', 'bar', 'foo'],
orig_time=None)
print(annot_none)
# Create an annotation object with orig_time
orig_time = '2002-12-03 19:01:31.676071'
annot_orig = mne.Annotations(onset=[22, 24, 31], duration=[0.5, 4, 0],
description=['foo', 'bar', 'foo'],
orig_time=orig_time)
print(annot_orig)
###############################################################################
# Now we create two raw objects and set each with different annotations.
# Then we plot both raw objects to compare the annotations.
# Create two cropped copies of raw with the two previous annotations
raw_a = raw.copy().crop(tmax=12).set_annotations(annot_none)
raw_b = raw.copy().crop(tmax=12).set_annotations(annot_orig)
# Plot the raw objects
raw_a.plot()
raw_b.plot()
###############################################################################
# Note that although the ``onset`` values of both annotations were different,
# due to complementary ``orig_time`` they are now identical. This is because
# the first one (``annot_none``), once set in raw, adopted its ``orig_time``.
# The second one (``annot_orig``) already had an ``orig_time``, so its
# ``orig_time`` was changed to match the onset time of the raw. Changing an
# already defined ``orig_time`` of annotations caused its ``onset`` to be
# recalibrated with respect to the new ``orig_time``. As a result both
# annotations have now identical ``onset`` and identical ``orig_time``:
# Show the annotations in the raw objects
print(raw_a.annotations)
print(raw_b.annotations)
# Show that the onsets are the same
np.set_printoptions(precision=6)
print(raw_a.annotations.onset)
print(raw_b.annotations.onset)
###############################################################################
# Notice again that for the case where ``orig_time`` is ``None``,
# it is assumed that the ``orig_time`` is the time of the first sample of data.
raw_delta = (1 / raw.info['sfreq'])
print('raw.first_sample is {}'.format(raw.first_samp * raw_delta))
print('annot_none.onset[0] is {}'.format(annot_none.onset[0]))
print('raw_a.annotations.onset[0] is {}'.format(raw_a.annotations.onset[0]))
###############################################################################
# It is possible to concatenate two annotations with the + operator (just like
# lists) if both share the same ``orig_time``
annot = mne.Annotations(onset=[10], duration=[0.5],
description=['foobar'],
orig_time=orig_time)
annot = annot_orig + annot # concatenation
print(annot)
###############################################################################
# Note that you can also save annotations to disk in FIF format::
#
# >>> annot.save('my-annot.fif')
#
# Or as CSV with onsets in (absolute) ISO timestamps::
#
# >>> annot.save('my-annot.csv')
#
# Or in plain text with onsets relative to ``orig_time``::
#
# >>> annot.save('my-annot.txt')
#
|