In [ ]:
%matplotlib inline
This tutorial describes adding annotations to a :class:~mne.io.Raw
object,
and how annotations are used in later stages of data processing.
:depth: 1
As usual we'll start by importing the modules we need, loading some
example data <sample-dataset>
, and (since we won't actually analyze the
raw data in this tutorial) cropping the :class:~mne.io.Raw
object to just 60
seconds before loading it into RAM to save memory:
In [ ]:
import os
from datetime import datetime
import mne
sample_data_folder = mne.datasets.sample.data_path()
sample_data_raw_file = os.path.join(sample_data_folder, 'MEG', 'sample',
'sample_audvis_raw.fif')
raw = mne.io.read_raw_fif(sample_data_raw_file, verbose=False)
raw.crop(tmax=60).load_data()
:class:~mne.Annotations
in MNE-Python are a way of storing short strings of
information about temporal spans of a :class:~mne.io.Raw
object. Below the
surface, :class:~mne.Annotations
are :class:list-like <list>
objects,
where each element comprises three pieces of information: an onset
time
(in seconds), a duration
(also in seconds), and a description
(a text
string). Additionally, the :class:~mne.Annotations
object itself also keeps
track of orig_time
, which is a POSIX timestamp
_ denoting a real-world
time relative to which the annotation onsets should be interpreted.
Creating annotations programmatically ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you know in advance what spans of the :class:~mne.io.Raw
object you want
to annotate, :class:~mne.Annotations
can be created programmatically, and
you can even pass lists or arrays to the :class:~mne.Annotations
constructor to annotate multiple spans at once:
In [ ]:
my_annot = mne.Annotations(onset=[3, 5, 7],
duration=[1, 0.5, 0.25],
description=['AAA', 'BBB', 'CCC'])
print(my_annot)
Notice that orig_time
is None
, because we haven't specified it. In
those cases, when you add the annotations to a :class:~mne.io.Raw
object,
it is assumed that the orig_time
matches the time of the first sample of
the recording, so orig_time
will be set to match the recording
measurement date (raw.info['meas_date']
).
In [ ]:
raw.set_annotations(my_annot)
print(raw.annotations)
# convert meas_date (a tuple of seconds, microseconds) into a float:
meas_date = raw.info['meas_date'][0] + raw.info['meas_date'][1] / 1e6
orig_time = raw.annotations.orig_time
print(meas_date == orig_time)
Since the example data comes from a Neuromag system that starts counting
sample numbers before the recording begins, adding my_annot
to the
:class:~mne.io.Raw
object also involved another automatic change: an offset
equalling the time of the first recorded sample (raw.first_samp /
raw.info['sfreq']
) was added to the onset
values of each annotation
(see time-as-index
for more info on raw.first_samp
):
In [ ]:
time_of_first_sample = raw.first_samp / raw.info['sfreq']
print(my_annot.onset + time_of_first_sample)
print(raw.annotations.onset)
If you know that your annotation onsets are relative to some other time, you
can set orig_time
before you call :meth:~mne.io.Raw.set_annotations
,
and the onset times will get adjusted based on the time difference between
your specified orig_time
and raw.info['meas_date']
, but without the
additional adjustment for raw.first_samp
. orig_time
can be specified
in various ways (see the documentation of :class:~mne.Annotations
for the
options); here we'll use an ISO 8601
_ formatted string, and set it to be 50
seconds later than raw.info['meas_date']
.
In [ ]:
time_format = '%Y-%m-%d %H:%M:%S.%f'
new_orig_time = datetime.utcfromtimestamp(meas_date + 50).strftime(time_format)
print(new_orig_time)
later_annot = mne.Annotations(onset=[3, 5, 7],
duration=[1, 0.5, 0.25],
description=['DDD', 'EEE', 'FFF'],
orig_time=new_orig_time)
raw2 = raw.copy().set_annotations(later_annot)
print(later_annot.onset)
print(raw2.annotations.onset)
If your annotations fall outside the range of data times in the :class:`~mne.io.Raw` object, the annotations outside the data range will not be added to ``raw.annotations``, and a warning will be issued.
Now that your annotations have been added to a :class:~mne.io.Raw
object,
you can see them when you visualize the :class:~mne.io.Raw
object:
In [ ]:
fig = raw.plot(start=2, duration=6)
The three annotations appear as differently colored rectangles because they
have different description
values (which are printed along the top
edge of the plot area). Notice also that colored spans appear in the small
scroll bar at the bottom of the plot window, making it easy to quickly view
where in a :class:~mne.io.Raw
object the annotations are so you can easily
browse through the data to find and examine them.
Annotating Raw objects interactively ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Annotations can also be added to a :class:~mne.io.Raw
object interactively
by clicking-and-dragging the mouse in the plot window. To do this, you must
first enter "annotation mode" by pressing :kbd:a
while the plot window is
focused; this will bring up the annotation controls window:
In [ ]:
fig.canvas.key_press_event('a')
The colored rings are clickable, and determine which existing label will be
created by the next click-and-drag operation in the main plot window. New
annotation descriptions can be added by typing the new description,
clicking the :guilabel:Add label
button; the new description will be added
to the list of descriptions and automatically selected.
During interactive annotation it is also possible to adjust the start and end times of existing annotations, by clicking-and-dragging on the left or right edges of the highlighting rectangle corresponding to that annotation.
Calling :meth:`~mne.io.Raw.set_annotations` **replaces** any annotations currently stored in the :class:`~mne.io.Raw` object, so be careful when working with annotations that were created interactively (you could lose a lot of work if you accidentally overwrite your interactive annotations). A good safeguard is to run ``interactive_annot = raw.annotations`` after you finish an interactive annotation session, so that the annotations are stored in a separate variable outside the :class:`~mne.io.Raw` object.
How annotations affect preprocessing and analysis ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You may have noticed that the description for new labels in the annotation
controls window defaults to BAD_
. The reason for this is that annotation
is often used to mark bad temporal spans of data (such as movement artifacts
or environmental interference that cannot be removed in other ways such as
projection <tut-projectors-background>
or filtering). Several
MNE-Python operations
are "annotation aware" and will avoid using data that is annotated with a
description that begins with "bad" or "BAD"; such operations typically have a
boolean reject_by_annotation
parameter. Examples of such operations are
independent components analysis (:class:mne.preprocessing.ICA
), functions
for finding heartbeat and blink artifacts
(:func:~mne.preprocessing.find_ecg_events
,
:func:~mne.preprocessing.find_eog_events
), and creation of epoched data
from continuous data (:class:mne.Epochs
). See tut-reject-data-spans
for details.
Operations on Annotations objects ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:class:~mne.Annotations
objects can be combined by simply adding them with
the +
operator, as long as they share the same orig_time
:
In [ ]:
new_annot = mne.Annotations(onset=3.75, duration=0.75, description='AAA')
raw.set_annotations(my_annot + new_annot)
raw.plot(start=2, duration=6)
Notice that it is possible to create overlapping annotations, even when they share the same description. This is not possible when annotating interactively; click-and-dragging to create a new annotation that overlaps with an existing annotation with the same description will cause the old and new annotations to be merged.
Individual annotations can be accessed by indexing an
:class:~mne.Annotations
object, and subsets of the annotations can be
achieved by either slicing or indexing with a list, tuple, or array of
indices:
In [ ]:
print(raw.annotations[0]) # just the first annotation
print(raw.annotations[:2]) # the first two annotations
print(raw.annotations[(3, 2)]) # the fourth and third annotations
You can also iterate over the annotations within an :class:~mne.Annotations
object:
In [ ]:
for ann in raw.annotations:
descr = ann['description']
start = ann['onset']
end = ann['onset'] + ann['duration']
print("'{}' goes from {} to {}".format(descr, start, end))
Note that iterating, indexing and slicing :class:~mne.Annotations
all
return a copy, so changes to an indexed, sliced, or iterated element will not
modify the original :class:~mne.Annotations
object.
In [ ]:
# later_annot WILL be changed, because we're modifying the first element of
# later_annot.onset directly:
later_annot.onset[0] = 99
# later_annot WILL NOT be changed, because later_annot[0] returns a copy
# before the 'onset' field is changed:
later_annot[0]['onset'] = 77
print(later_annot[0]['onset'])
Reading and writing Annotations to/from a file ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:class:~mne.Annotations
objects have a :meth:~mne.Annotations.save
method
which can write :file:.fif
, :file:.csv
, and :file:.txt
formats (the
format to write is inferred from the file extension in the filename you
provide). There is a corresponding :func:~mne.read_annotations
function to
load them from disk:
In [ ]:
raw.annotations.save('saved-annotations.csv')
annot_from_file = mne.read_annotations('saved-annotations.csv')
print(annot_from_file)
.. LINKS