From 80c6966b762545b611337fe3312845d7fa81c0f4 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 15 Oct 2018 15:26:50 +0200 Subject: [PATCH 01/21] Add tutorial --- doc/documentation.rst | 1 + tutorials/plot_events_and_annotations.py | 156 +++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 tutorials/plot_events_and_annotations.py diff --git a/doc/documentation.rst b/doc/documentation.rst index 4fc83b269e3..0db6d769484 100644 --- a/doc/documentation.rst +++ b/doc/documentation.rst @@ -115,6 +115,7 @@ There are also **examples**, which contain a short use-case to highlight MNE-fun auto_tutorials/plot_object_evoked.rst auto_tutorials/plot_object_source_estimate.rst auto_tutorials/plot_info.rst + auto_tutorials/plot_events_and_annotations.rst .. raw:: html diff --git a/tutorials/plot_events_and_annotations.py b/tutorials/plot_events_and_annotations.py new file mode 100644 index 00000000000..1a1c99ce0ec --- /dev/null +++ b/tutorials/plot_events_and_annotations.py @@ -0,0 +1,156 @@ +""" +.. _tut_events_and_annotation_objects: + +The **events** and :class:`Annotations ` data structures +========================================================================= + +XXX bla + +1. `find_events`, with event masking +2. `events` column values +3. `raw.first_samp`, what it is, how it changes with `.crop`, how to get event times relative to the start of the instance +4. `Annotations` and `orig_time` + +Events and annotations are quite similar. This tutorial highlights their +differences and similitudes and tries to shade some light to which one is +preferred to use in different situations when using MNE. +Here follows both terms definition from the glossary. +We refer the reader to :ref:`sphx_glr_auto_examples_io_plot_read_events.py` +for a complete example in how to read, select and visualize **events**; +and ref:`marking_bad_segments` to know how :class:`mne.Annotations` are used to +mark bad segments of data. + + 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 + One annotation is defined by an onset, a duration and a string + description. It can contain information about the experiments, but + also details on signals marked by a human: bad data segments, + sleep scores, sleep events (spindles, K-complex) etc. + + +What are events, annotations and how to load / interpret them +------------------------------------------------------------- +""" +# :ref:`sphx_glr_auto_tutorials_plot_epoching_and_averaging.py` + +# XXX I should put somewhere which file formats use stim channel and which ones prefer annotation +# see agramfort comment: +# bst is coming from a ctf system +# so .ds files +# that also have stim channels usually +# annotations are from EEG files / vendors + +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) + +# Specify event_id dictionary based on the experiment +event_id = {'Auditory/Left': 1, 'Auditory/Right': 2, + 'Visual/Left': 3, 'Visual/Right': 4, + 'smiley': 5, 'button': 32} + + +# plot the events +color = {1: 'green', 2: 'yellow', 3: 'red', 4: 'c', 5: 'black', 32: 'blue'} +events = mne.find_events(raw) +print(events[:5]) +mne.viz.plot_events(events, raw.info['sfreq'], raw.first_samp, color=color, + event_id=event_id) + +# create some annotations +eog_events = mne.preprocessing.find_eog_events(raw) +n_blinks = len(eog_events) +# Center to cover the whole blink with full duration of 0.5s: +onset = eog_events[:, 0] / raw.info['sfreq'] - 0.25 +duration = np.repeat(0.5, n_blinks) +annot = mne.Annotations(onset, duration, ['bad blink'] * n_blinks, + orig_time=raw.info['meas_date']) +raw.set_annotations(annot) + + +print(raw.annotations) # to get information about what annotations we have +raw.plot(events=eog_events) # To see the annotated segments. + +############################################################################### +# links +# :ref:`sphx_glr_auto_tutorials_plot_epoching_and_averaging.py` +# :ref:`sphx_glr_download_auto_examples_io_plot_read_events.py` +# https://mne-tools.github.io/stable/auto_tutorials/plot_artifacts_correction_rejection.html?highlight=marking%20bad%20segments +# :class:`mne.Annotations` and :ref:`marking_bad_segments`. To see all the + +############################################################################### +# Other + +# ############################################################################### +# # To illustrate the events object, lets see its first values. +# # +# # an event is a triplet of (trigger, XXX +# print('Found %s events, first five:' % len(events)) +# print(events[:5]) + +# ############################################################################### +# # The main usage until MNE-v.0.17 of Annotations was to mark bad segments in the data. +# # .. _marking_bad_segments: +# # https://mne-tools.github.io/stable/auto_tutorials/plot_visualize_raw.html#drawing-annotations +# # +# # lets load a version of the same example but this time use a modified version for illustration +# # purposes that lacks stim channel but has annotations encoding the events +# # (plus, some fake bad segments). +# fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw_with_annotations_no_events.fif') +# raw_with_annotations = mne.io.read_raw_fif(fname) + +# # illustrate that `find_events` returns an empty list since there is no stim channel +# assert mne.find_evets(raw_with_annotations) == [] +# raw_with_annotations.annotations + +# # plot something XXXX + +# # Specify colors and an event_id dictionary as before for the legend. +# 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'} + +# events = mne.events_from_annotations(raw_with_annotations) +# mne.viz.plot_events(events, raw.info['sfreq'], raw.first_samp, color=color, +# event_id=event_id) + +# ############################################################################### +# # annotations can be loaded and treated on their own. +# # fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw_with_no_annotations_no_events.fif') +# # annotations_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_annotations.fif') + +# # raw_no_annotations_no_events = mne.io.read_raw_fif(fname) +# # assert mne.find_evets(raw_with_no_annotations_no_events) == [] +# # assert not len(raw_with_no_annotations_no_events.annotations) + +# # inspect the annotations +# # annot = mne.read_annotations(annotations_fname) +# # print(annot) +# # print(annot.orig_time) + +# # observe that annotations on its own, cannot be converted into event and they need a raw like object +# # xxx = raw_no_annotations_no_events.copy() +# # xxx.set_annotations(annot) + + + + + + + + + + From ec6d9ad13a3bf265faf1f90d1f596116699178ed Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Fri, 9 Nov 2018 16:19:33 +0100 Subject: [PATCH 02/21] illustrate orig_time --- tutorials/plot_events_and_annotations.py | 182 ++++++++++++----------- 1 file changed, 99 insertions(+), 83 deletions(-) diff --git a/tutorials/plot_events_and_annotations.py b/tutorials/plot_events_and_annotations.py index 1a1c99ce0ec..875a1cd51a3 100644 --- a/tutorials/plot_events_and_annotations.py +++ b/tutorials/plot_events_and_annotations.py @@ -4,13 +4,6 @@ The **events** and :class:`Annotations ` data structures ========================================================================= -XXX bla - -1. `find_events`, with event masking -2. `events` column values -3. `raw.first_samp`, what it is, how it changes with `.crop`, how to get event times relative to the start of the instance -4. `Annotations` and `orig_time` - Events and annotations are quite similar. This tutorial highlights their differences and similitudes and tries to shade some light to which one is preferred to use in different situations when using MNE. @@ -37,7 +30,6 @@ What are events, annotations and how to load / interpret them ------------------------------------------------------------- """ -# :ref:`sphx_glr_auto_tutorials_plot_epoching_and_averaging.py` # XXX I should put somewhere which file formats use stim channel and which ones prefer annotation # see agramfort comment: @@ -46,104 +38,128 @@ # that also have stim channels usually # annotations are from EEG files / vendors +# - [ ] 1. `find_events`, with event masking +# - [ ] 2. `events` column values +# - [ ] 3. `raw.first_samp`, what it is, how it changes with `.crop`, how to get +# event times relative to the start of the instance +# - [ ] 4. `Annotations` and `orig_time` + +############################################################################### +# links +# :ref:`sphx_glr_auto_tutorials_plot_epoching_and_averaging.py` +# :ref:`sphx_glr_download_auto_examples_io_plot_read_events.py` +# https://mne-tools.github.io/stable/auto_tutorials/plot_artifacts_correction_rejection.html?highlight=marking%20bad%20segments +# :class:`mne.Annotations` and :ref:`marking_bad_segments`. To see all the + 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) +# Define some helper functions & constants +def _get_blink_annotations(raw): + eog_events = mne.preprocessing.find_eog_events(raw) + n_blinks = len(eog_events) + # Center to cover the whole blink with full duration of 0.5s: + onset = eog_events[:, 0] / raw.info['sfreq'] - 0.25 + duration = np.repeat(0.5, n_blinks) + return mne.Annotations(onset, duration, ['bad blink'] * n_blinks, + orig_time=raw.info['meas_date']) # Specify event_id dictionary based on the experiment 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'} + +# 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) # plot the events -color = {1: 'green', 2: 'yellow', 3: 'red', 4: 'c', 5: 'black', 32: 'blue'} events = mne.find_events(raw) -print(events[:5]) mne.viz.plot_events(events, raw.info['sfreq'], raw.first_samp, color=color, event_id=event_id) # create some annotations -eog_events = mne.preprocessing.find_eog_events(raw) -n_blinks = len(eog_events) -# Center to cover the whole blink with full duration of 0.5s: -onset = eog_events[:, 0] / raw.info['sfreq'] - 0.25 -duration = np.repeat(0.5, n_blinks) -annot = mne.Annotations(onset, duration, ['bad blink'] * n_blinks, - orig_time=raw.info['meas_date']) -raw.set_annotations(annot) +annotated_blink_raw = raw.copy() +annot = _get_blink_annotations(annotated_blink_raw) +annotated_blink_raw.set_annotations(annot) +print(annotated_blink_raw.annotations) # check the annotations +annotated_blink_raw.plot() # plot the annotated raw -print(raw.annotations) # to get information about what annotations we have -raw.plot(events=eog_events) # To see the annotated segments. ############################################################################### -# links -# :ref:`sphx_glr_auto_tutorials_plot_epoching_and_averaging.py` -# :ref:`sphx_glr_download_auto_examples_io_plot_read_events.py` -# https://mne-tools.github.io/stable/auto_tutorials/plot_artifacts_correction_rejection.html?highlight=marking%20bad%20segments -# :class:`mne.Annotations` and :ref:`marking_bad_segments`. To see all the +# Annotations +# +# An important element of the :class:`mne.Annotations` is ``orig_time`` which +# is the time reference for the ``onset``. It is key to understand that when +# calling `raw.set_annotation`, the given annotations is copied and transformed +# so that `raw.annotations.orig_time` matches meas_date. (check +# :class:`mne.Annotations` documentation notes to see the expected behavior +# depending of `meas_date` and `orig_time`) + +# empty_annot = mne.Annotations(onset=list(), duration=list(), +# description=list(), orig_time=None) + + +# XXXX This should not be done like that, I should be able to get something +# printable using MNE. +def _print_meas_date(stamp): + if stamp is None: + print('None') + else: + from datetime import datetime + stamp = mne.annotations._handle_meas_date(stamp) + print(datetime.utcfromtimestamp(stamp)) + +annot_none = mne.Annotations(onset=[0, 2, 9], duration=[0.5, 4, 0], + description=['AA', 'BB', 'CC'], + orig_time=None) +print('annotation without orig_time') +_print_meas_date(annot_none.orig_time) + + +annot_orig = mne.Annotations(onset=[22, 24, 31], duration=[0.5, 4, 0], + description=['AA', 'BB', 'CC'], + orig_time=1038942091.6760709) +print('annotation with orig_time') +_print_meas_date(annot_orig.orig_time) + +print('raw.info[\'meas_date\']') +_print_meas_date(raw.info['meas_date']) + + +raw_a = raw.copy().crop(tmax=12).set_annotations(annot_none) +raw_b = raw.copy().crop(tmax=12).set_annotations(annot_orig) + +# Plot both raw files side to side to see they are the same +# +# fig, axs = plt.subplots(1, 2, figsize=(15, 5)) +# raw_a.plot(axes=axs[0]) +# axs[0].set(title="using None") +# raw_b.plot(axes=axs[0]) +# axs[0].set(title="using orig_time") +# plt.tight_layout() +# plt.show() +# +raw_a.plot() +raw_b.plot() + +# show the new origin +print('raw_a.annotation.orig_time') +_print_meas_date(raw_a.annotations.orig_time) +print('raw_b.annotation.orig_time') +_print_meas_date(raw_b.annotations.orig_time) + +print(raw_a.annotations.onset == raw_b.annotations.onset) + + + -############################################################################### -# Other - -# ############################################################################### -# # To illustrate the events object, lets see its first values. -# # -# # an event is a triplet of (trigger, XXX -# print('Found %s events, first five:' % len(events)) -# print(events[:5]) - -# ############################################################################### -# # The main usage until MNE-v.0.17 of Annotations was to mark bad segments in the data. -# # .. _marking_bad_segments: -# # https://mne-tools.github.io/stable/auto_tutorials/plot_visualize_raw.html#drawing-annotations -# # -# # lets load a version of the same example but this time use a modified version for illustration -# # purposes that lacks stim channel but has annotations encoding the events -# # (plus, some fake bad segments). -# fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw_with_annotations_no_events.fif') -# raw_with_annotations = mne.io.read_raw_fif(fname) - -# # illustrate that `find_events` returns an empty list since there is no stim channel -# assert mne.find_evets(raw_with_annotations) == [] -# raw_with_annotations.annotations - -# # plot something XXXX - -# # Specify colors and an event_id dictionary as before for the legend. -# 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'} - -# events = mne.events_from_annotations(raw_with_annotations) -# mne.viz.plot_events(events, raw.info['sfreq'], raw.first_samp, color=color, -# event_id=event_id) - -# ############################################################################### -# # annotations can be loaded and treated on their own. -# # fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw_with_no_annotations_no_events.fif') -# # annotations_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_annotations.fif') - -# # raw_no_annotations_no_events = mne.io.read_raw_fif(fname) -# # assert mne.find_evets(raw_with_no_annotations_no_events) == [] -# # assert not len(raw_with_no_annotations_no_events.annotations) - -# # inspect the annotations -# # annot = mne.read_annotations(annotations_fname) -# # print(annot) -# # print(annot.orig_time) - -# # observe that annotations on its own, cannot be converted into event and they need a raw like object -# # xxx = raw_no_annotations_no_events.copy() -# # xxx.set_annotations(annot) From c63b559d825f1e066890643c30b90ec4f1b3ca63 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Fri, 9 Nov 2018 17:46:48 +0100 Subject: [PATCH 03/21] Add orig_time in annotations __repr__ --- mne/annotations.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index ddd28026b57..d16c219eed0 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -180,8 +180,12 @@ def __repr__(self): for kind in kinds] kinds = ', '.join(kinds[:3]) + ('' if len(kinds) <= 3 else '...') kinds = (': ' if len(kinds) > 0 else '') + kinds - return ('' - % (len(self.onset), _pl(len(self.onset)), kinds)) + if self.orig_time is None: + orig = 'orig_time : None' + else: + orig = 'orig_time : %s' % datetime.utcfromtimestamp(self.orig_time) + return ('' + % (len(self.onset), _pl(len(self.onset)), kinds, orig)) def __len__(self): """Return the number of annotations.""" From 5e835078b1da939fc55d70b2154429ec55d8ac9c Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Fri, 9 Nov 2018 18:12:25 +0100 Subject: [PATCH 04/21] clean up --- tutorials/plot_events_and_annotations.py | 54 +++++++----------------- 1 file changed, 15 insertions(+), 39 deletions(-) diff --git a/tutorials/plot_events_and_annotations.py b/tutorials/plot_events_and_annotations.py index 875a1cd51a3..16c3335df1e 100644 --- a/tutorials/plot_events_and_annotations.py +++ b/tutorials/plot_events_and_annotations.py @@ -42,7 +42,7 @@ # - [ ] 2. `events` column values # - [ ] 3. `raw.first_samp`, what it is, how it changes with `.crop`, how to get # event times relative to the start of the instance -# - [ ] 4. `Annotations` and `orig_time` +# - [x] 4. `Annotations` and `orig_time` ############################################################################### # links @@ -102,62 +102,38 @@ def _get_blink_annotations(raw): # :class:`mne.Annotations` documentation notes to see the expected behavior # depending of `meas_date` and `orig_time`) -# empty_annot = mne.Annotations(onset=list(), duration=list(), -# description=list(), orig_time=None) - - -# XXXX This should not be done like that, I should be able to get something -# printable using MNE. -def _print_meas_date(stamp): - if stamp is None: - print('None') - else: - from datetime import datetime - stamp = mne.annotations._handle_meas_date(stamp) - print(datetime.utcfromtimestamp(stamp)) +# check raw.info['meas_date'] +print(raw.info) +# create annotation object without orig_time annot_none = mne.Annotations(onset=[0, 2, 9], duration=[0.5, 4, 0], description=['AA', 'BB', 'CC'], orig_time=None) -print('annotation without orig_time') -_print_meas_date(annot_none.orig_time) +print(annot_none) +# create annotation object with orig_time annot_orig = mne.Annotations(onset=[22, 24, 31], duration=[0.5, 4, 0], description=['AA', 'BB', 'CC'], orig_time=1038942091.6760709) -print('annotation with orig_time') -_print_meas_date(annot_orig.orig_time) - -print('raw.info[\'meas_date\']') -_print_meas_date(raw.info['meas_date']) +print(annot_orig) +# create two cropped copies of raw with the 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 both raw files side to side to see they are the same -# -# fig, axs = plt.subplots(1, 2, figsize=(15, 5)) -# raw_a.plot(axes=axs[0]) -# axs[0].set(title="using None") -# raw_b.plot(axes=axs[0]) -# axs[0].set(title="using orig_time") -# plt.tight_layout() -# plt.show() -# +# plot the raw objects raw_a.plot() raw_b.plot() -# show the new origin -print('raw_a.annotation.orig_time') -_print_meas_date(raw_a.annotations.orig_time) -print('raw_b.annotation.orig_time') -_print_meas_date(raw_b.annotations.orig_time) - -print(raw_a.annotations.onset == raw_b.annotations.onset) - +# show the annotations in the raw objects +print(raw_a.annotations) +print(raw_b.annotations) +# show that the onsets are the same +print(raw_a.annotations.onset) +print(raw_b.annotations.onset) From c82b367b584a578fa5938a2681af42fe04bd0161 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Fri, 9 Nov 2018 18:35:31 +0100 Subject: [PATCH 05/21] clean up --- tutorials/plot_events_and_annotations.py | 74 ++++++++++-------------- 1 file changed, 30 insertions(+), 44 deletions(-) diff --git a/tutorials/plot_events_and_annotations.py b/tutorials/plot_events_and_annotations.py index 16c3335df1e..3992126503c 100644 --- a/tutorials/plot_events_and_annotations.py +++ b/tutorials/plot_events_and_annotations.py @@ -27,52 +27,18 @@ sleep scores, sleep events (spindles, K-complex) etc. -What are events, annotations and how to load / interpret them -------------------------------------------------------------- -""" - -# XXX I should put somewhere which file formats use stim channel and which ones prefer annotation -# see agramfort comment: -# bst is coming from a ctf system -# so .ds files -# that also have stim channels usually -# annotations are from EEG files / vendors +What are events vs annotations +------------------------------ -# - [ ] 1. `find_events`, with event masking -# - [ ] 2. `events` column values -# - [ ] 3. `raw.first_samp`, what it is, how it changes with `.crop`, how to get -# event times relative to the start of the instance -# - [x] 4. `Annotations` and `orig_time` - -############################################################################### -# links -# :ref:`sphx_glr_auto_tutorials_plot_epoching_and_averaging.py` -# :ref:`sphx_glr_download_auto_examples_io_plot_read_events.py` -# https://mne-tools.github.io/stable/auto_tutorials/plot_artifacts_correction_rejection.html?highlight=marking%20bad%20segments -# :class:`mne.Annotations` and :ref:`marking_bad_segments`. To see all the +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 -# Define some helper functions & constants -def _get_blink_annotations(raw): - eog_events = mne.preprocessing.find_eog_events(raw) - n_blinks = len(eog_events) - # Center to cover the whole blink with full duration of 0.5s: - onset = eog_events[:, 0] / raw.info['sfreq'] - 0.25 - duration = np.repeat(0.5, n_blinks) - return mne.Annotations(onset, duration, ['bad blink'] * n_blinks, - orig_time=raw.info['meas_date']) - -# Specify event_id dictionary based on the experiment -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'} - # load the data data_path = mne.datasets.sample.data_path() fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw.fif') @@ -80,20 +46,33 @@ def _get_blink_annotations(raw): # plot the events events = mne.find_events(raw) + +# Specify event_id dictionary based on the experiment +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) # create some annotations annotated_blink_raw = raw.copy() -annot = _get_blink_annotations(annotated_blink_raw) +eog_events = mne.preprocessing.find_eog_events(raw) +n_blinks = len(eog_events) +# Center to cover the whole blink with full duration of 0.5s: +onset = eog_events[:, 0] / raw.info['sfreq'] - 0.25 +duration = np.repeat(0.5, n_blinks) +annot = mne.Annotations(onset, duration, ['bad blink'] * n_blinks, + orig_time=raw.info['meas_date']) annotated_blink_raw.set_annotations(annot) -print(annotated_blink_raw.annotations) # check the annotations annotated_blink_raw.plot() # plot the annotated raw ############################################################################### -# Annotations +# Working with Annotations +# ------------------------ # # An important element of the :class:`mne.Annotations` is ``orig_time`` which # is the time reference for the ``onset``. It is key to understand that when @@ -102,8 +81,8 @@ def _get_blink_annotations(raw): # :class:`mne.Annotations` documentation notes to see the expected behavior # depending of `meas_date` and `orig_time`) -# check raw.info['meas_date'] -print(raw.info) + +############################################################################### # create annotation object without orig_time annot_none = mne.Annotations(onset=[0, 2, 9], duration=[0.5, 4, 0], @@ -112,12 +91,15 @@ def _get_blink_annotations(raw): print(annot_none) +############################################################################### + # create annotation object with orig_time annot_orig = mne.Annotations(onset=[22, 24, 31], duration=[0.5, 4, 0], description=['AA', 'BB', 'CC'], orig_time=1038942091.6760709) print(annot_orig) +############################################################################### # create two cropped copies of raw with the previous annotations raw_a = raw.copy().crop(tmax=12).set_annotations(annot_none) @@ -127,10 +109,14 @@ def _get_blink_annotations(raw): raw_a.plot() raw_b.plot() +############################################################################### + # show the annotations in the raw objects print(raw_a.annotations) print(raw_b.annotations) +############################################################################### + # show that the onsets are the same print(raw_a.annotations.onset) print(raw_b.annotations.onset) From ac146d51c943cf234183db110c75dada9c7b0b18 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Fri, 9 Nov 2018 18:42:25 +0100 Subject: [PATCH 06/21] more clean up --- tutorials/plot_events_and_annotations.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tutorials/plot_events_and_annotations.py b/tutorials/plot_events_and_annotations.py index 3992126503c..4591452c0f2 100644 --- a/tutorials/plot_events_and_annotations.py +++ b/tutorials/plot_events_and_annotations.py @@ -8,10 +8,6 @@ differences and similitudes and tries to shade some light to which one is preferred to use in different situations when using MNE. Here follows both terms definition from the glossary. -We refer the reader to :ref:`sphx_glr_auto_examples_io_plot_read_events.py` -for a complete example in how to read, select and visualize **events**; -and ref:`marking_bad_segments` to know how :class:`mne.Annotations` are used to -mark bad segments of data. events Events correspond to specific time points in raw data; e.g., @@ -26,9 +22,13 @@ also details on signals marked by a human: bad data segments, sleep scores, sleep events (spindles, K-complex) etc. +See :ref:`sphx_glr_auto_examples_io_plot_read_events.py` +for a complete example in how to read, select and visualize **events**; +and ref:`sphx_glr_auto_tutorials_plot_artifacts_correction_rejection.py` to +know how :class:`mne.Annotations` are used to mark bad segments of data. -What are events vs annotations ------------------------------- +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. From 06c715e030f63ea7a026714f07623cfc4690c67b Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Fri, 9 Nov 2018 18:45:23 +0100 Subject: [PATCH 07/21] Add whatsnew --- doc/whats_new.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/whats_new.rst b/doc/whats_new.rst index ad39f268976..f783146734f 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -19,6 +19,8 @@ Current Changelog ~~~~~~~~~ +- New tutorial in the documentation regarding :class:`mne.Annotations` by `Joan Massich`_. + - Add :meth:`mne.Epochs.shift_time` that shifts the time axis of :class:`mne.Epochs` by `Thomas Hartmann`_ - Add :func:`mne.viz.plot_arrowmap` computes arrowmaps using Hosaka-Cohen transformation from magnetometer or gradiometer data, these arrows represents an estimation of the current flow underneath the MEG sensors by `Sheraz Khan`_ From 70e8a803ff39b0d1da7d6d5840f8559b6c6be6a8 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Fri, 9 Nov 2018 19:24:26 +0100 Subject: [PATCH 08/21] fix broken link --- tutorials/plot_events_and_annotations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/plot_events_and_annotations.py b/tutorials/plot_events_and_annotations.py index 4591452c0f2..46a7395cd62 100644 --- a/tutorials/plot_events_and_annotations.py +++ b/tutorials/plot_events_and_annotations.py @@ -24,7 +24,7 @@ See :ref:`sphx_glr_auto_examples_io_plot_read_events.py` for a complete example in how to read, select and visualize **events**; -and ref:`sphx_glr_auto_tutorials_plot_artifacts_correction_rejection.py` to +and :ref:`sphx_glr_auto_tutorials_plot_artifacts_correction_rejection.py` to know how :class:`mne.Annotations` are used to mark bad segments of data. An example of events and annotations From 0fa243dfd2372643707b74f142912d22e4ad3511 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Sat, 10 Nov 2018 13:01:02 +0100 Subject: [PATCH 09/21] remove old link reference --- tutorials/plot_events_and_annotations.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tutorials/plot_events_and_annotations.py b/tutorials/plot_events_and_annotations.py index 46a7395cd62..bd992592197 100644 --- a/tutorials/plot_events_and_annotations.py +++ b/tutorials/plot_events_and_annotations.py @@ -1,6 +1,4 @@ """ -.. _tut_events_and_annotation_objects: - The **events** and :class:`Annotations ` data structures ========================================================================= From cdb11835a65bc59a60dc46b3e7d8649b05ad31a1 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Sat, 10 Nov 2018 13:09:50 +0100 Subject: [PATCH 10/21] pep8 --- tutorials/plot_events_and_annotations.py | 26 +++++++----------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/tutorials/plot_events_and_annotations.py b/tutorials/plot_events_and_annotations.py index bd992592197..161157ddeca 100644 --- a/tutorials/plot_events_and_annotations.py +++ b/tutorials/plot_events_and_annotations.py @@ -8,11 +8,11 @@ Here follows both terms definition from the 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. + 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 One annotation is defined by an onset, a duration and a string @@ -61,8 +61,8 @@ # Center to cover the whole blink with full duration of 0.5s: onset = eog_events[:, 0] / raw.info['sfreq'] - 0.25 duration = np.repeat(0.5, n_blinks) -annot = mne.Annotations(onset, duration, ['bad blink'] * n_blinks, - orig_time=raw.info['meas_date']) +annot = mne.Annotations(onset, duration, ['bad blink'] * n_blinks, + orig_time=raw.info['meas_date']) annotated_blink_raw.set_annotations(annot) annotated_blink_raw.plot() # plot the annotated raw @@ -118,15 +118,3 @@ # show that the onsets are the same print(raw_a.annotations.onset) print(raw_b.annotations.onset) - - - - - - - - - - - - From e92fdbfe02a45f7b6f25667908936e7b8c8d97d4 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Sun, 11 Nov 2018 15:56:11 +0100 Subject: [PATCH 11/21] a deeper explanation of the tripplets --- tutorials/plot_events_and_annotations.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tutorials/plot_events_and_annotations.py b/tutorials/plot_events_and_annotations.py index 161157ddeca..1c836d493a2 100644 --- a/tutorials/plot_events_and_annotations.py +++ b/tutorials/plot_events_and_annotations.py @@ -20,6 +20,16 @@ also details on signals marked by a human: bad data segments, sleep scores, sleep events (spindles, K-complex) etc. +They both can be seen as triplets where the first element answers to **when** +something happens and the last element refers to **what** is it. The +main differnce is that events the when is samples with respect to the first +sample and the what is an integer id; while in annotations the when is in +seconds with respect to an origin and the what is an arbitrary string. +The second element of the triplets have no direct relation between the two +structures. For the events case, the second element corresponds to id of the +previous active event. Whereas, the second element of the +: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 in how to read, select and visualize **events**; and :ref:`sphx_glr_auto_tutorials_plot_artifacts_correction_rejection.py` to From 8de310b4e3cc9c68f62846bda8047a75740068a8 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Sun, 11 Nov 2018 16:03:30 +0100 Subject: [PATCH 12/21] remove meas_date from the explanation, and point where to read about it --- tutorials/plot_events_and_annotations.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tutorials/plot_events_and_annotations.py b/tutorials/plot_events_and_annotations.py index 1c836d493a2..1c9a76cfea4 100644 --- a/tutorials/plot_events_and_annotations.py +++ b/tutorials/plot_events_and_annotations.py @@ -85,9 +85,11 @@ # An important element of the :class:`mne.Annotations` is ``orig_time`` which # is the time reference for the ``onset``. It is key to understand that when # calling `raw.set_annotation`, the given annotations is copied and transformed -# so that `raw.annotations.orig_time` matches meas_date. (check -# :class:`mne.Annotations` documentation notes to see the expected behavior -# depending of `meas_date` and `orig_time`) +# so that `raw.annotations.orig_time` matches the recording time of the raw +# object. (check :class:`mne.Annotations` documentation notes to see the +# expected behavior depending of `meas_date` and `orig_time`. Notice that +# `meas_date` is the :class:`Info ` attribute of the recording time. +# Find more in :ref:`sphx_glr_auto_tutorials_plot_info.py`) ############################################################################### From 94eee8a4baf9acafb7cbf6b4af704863c9b8b58a Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Sun, 11 Nov 2018 16:22:02 +0100 Subject: [PATCH 13/21] more --- doc/documentation.rst | 2 +- doc/glossary.rst | 3 +- doc/whats_new.rst | 2 +- mne/annotations.py | 2 +- ...otations.py => plot_object_annotations.py} | 33 +++++++++++-------- 5 files changed, 24 insertions(+), 18 deletions(-) rename tutorials/{plot_events_and_annotations.py => plot_object_annotations.py} (86%) diff --git a/doc/documentation.rst b/doc/documentation.rst index 0db6d769484..8011d771194 100644 --- a/doc/documentation.rst +++ b/doc/documentation.rst @@ -115,7 +115,7 @@ There are also **examples**, which contain a short use-case to highlight MNE-fun auto_tutorials/plot_object_evoked.rst auto_tutorials/plot_object_source_estimate.rst auto_tutorials/plot_info.rst - auto_tutorials/plot_events_and_annotations.rst + auto_tutorials/plot_object_annotations.rst .. raw:: html diff --git a/doc/glossary.rst b/doc/glossary.rst index 4b1f1c013a9..5b793b4125f 100644 --- a/doc/glossary.rst +++ b/doc/glossary.rst @@ -27,7 +27,8 @@ MNE-Python core terminology and general concepts sleep scores, sleep events (spindles, K-complex) etc. An :class:`Annotations` object is a container of multiple annotations. See :class:`Annotations` page for the API of the corresponding - object class. + object class and :ref:`sphx_glr_auto_tutorials_plot_object_annotations.py` + for a tutorial on how to manipulate such objects. channels Channels refer to MEG sensors, EEG electrodes or any extra electrode diff --git a/doc/whats_new.rst b/doc/whats_new.rst index f783146734f..749c90647cb 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -19,7 +19,7 @@ Current Changelog ~~~~~~~~~ -- New tutorial in the documentation regarding :class:`mne.Annotations` by `Joan Massich`_. +- New tutorial in the documentation regarding :class:`mne.Annotations` by `Joan Massich`_ and `Alex Gramfort`_. - Add :meth:`mne.Epochs.shift_time` that shifts the time axis of :class:`mne.Epochs` by `Thomas Hartmann`_ diff --git a/mne/annotations.py b/mne/annotations.py index d16c219eed0..b2bbd93a03d 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -184,7 +184,7 @@ def __repr__(self): orig = 'orig_time : None' else: orig = 'orig_time : %s' % datetime.utcfromtimestamp(self.orig_time) - return ('' + return ('' % (len(self.onset), _pl(len(self.onset)), kinds, orig)) def __len__(self): diff --git a/tutorials/plot_events_and_annotations.py b/tutorials/plot_object_annotations.py similarity index 86% rename from tutorials/plot_events_and_annotations.py rename to tutorials/plot_object_annotations.py index 1c9a76cfea4..2248682ac6b 100644 --- a/tutorials/plot_events_and_annotations.py +++ b/tutorials/plot_object_annotations.py @@ -5,7 +5,7 @@ Events and annotations are quite similar. This tutorial highlights their differences and similitudes and tries to shade some light to which one is preferred to use in different situations when using MNE. -Here follows both terms definition from the glossary. +Here follows both terms definition from the :ref:`glossary`. events Events correspond to specific time points in raw data; e.g., triggers, @@ -90,28 +90,26 @@ # expected behavior depending of `meas_date` and `orig_time`. Notice that # `meas_date` is the :class:`Info ` attribute of the recording time. # Find more in :ref:`sphx_glr_auto_tutorials_plot_info.py`) - - -############################################################################### - -# create annotation object without orig_time +# +# We'll now manipulate some simulated annotations objects. +# +# First let's create an annotation object without orig_time. It this case +# one assumes that the orig_time is the time of the first sample of data. annot_none = mne.Annotations(onset=[0, 2, 9], duration=[0.5, 4, 0], - description=['AA', 'BB', 'CC'], + description=['foo', 'bar', 'foo'], orig_time=None) print(annot_none) ############################################################################### - -# create annotation object with orig_time +# Now let's create annotation object with orig_time annot_orig = mne.Annotations(onset=[22, 24, 31], duration=[0.5, 4, 0], - description=['AA', 'BB', 'CC'], + description=['foo', 'bar', 'foo'], orig_time=1038942091.6760709) print(annot_orig) ############################################################################### - -# create two cropped copies of raw with the previous 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) @@ -120,13 +118,20 @@ raw_b.plot() ############################################################################### - # show the annotations in the raw objects print(raw_a.annotations) print(raw_b.annotations) ############################################################################### - # show that the onsets are the same print(raw_a.annotations.onset) print(raw_b.annotations.onset) + +############################################################################### +# It is possible to concatenate two annotations with the + like for lists. + +annot = mne.Annotations(onset=[10], duration=[0.5], + description=['foobar'], + orig_time=1038942091.6760709) +annot = annot_orig + annot # concatenation +print(annot) From bc3ca0693880ff87809cd56129ce018d5b2481e7 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Sun, 11 Nov 2018 18:55:50 +0100 Subject: [PATCH 14/21] format tutorial --- tutorials/plot_object_annotations.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tutorials/plot_object_annotations.py b/tutorials/plot_object_annotations.py index 2248682ac6b..356cdf002e7 100644 --- a/tutorials/plot_object_annotations.py +++ b/tutorials/plot_object_annotations.py @@ -102,6 +102,7 @@ ############################################################################### + # Now let's create annotation object with orig_time annot_orig = mne.Annotations(onset=[22, 24, 31], duration=[0.5, 4, 0], description=['foo', 'bar', 'foo'], @@ -109,6 +110,7 @@ print(annot_orig) ############################################################################### + # 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) @@ -118,6 +120,7 @@ raw_b.plot() ############################################################################### + # show the annotations in the raw objects print(raw_a.annotations) print(raw_b.annotations) @@ -128,6 +131,7 @@ print(raw_b.annotations.onset) ############################################################################### +# # It is possible to concatenate two annotations with the + like for lists. annot = mne.Annotations(onset=[10], duration=[0.5], From a9c0e6616709415b21e0f120719263b333d36091 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 12 Nov 2018 16:18:28 +0100 Subject: [PATCH 15/21] more --- doc/glossary.rst | 2 +- doc/whats_new.rst | 2 +- tutorials/plot_object_annotations.py | 115 +++++++++++++++++---------- 3 files changed, 75 insertions(+), 44 deletions(-) diff --git a/doc/glossary.rst b/doc/glossary.rst index 5b793b4125f..9a8335b073b 100644 --- a/doc/glossary.rst +++ b/doc/glossary.rst @@ -21,7 +21,7 @@ MNE-Python core terminology and general concepts annotations - One annotation is defined by an onset, a duration and a string + An annotation is defined by an onset, a duration, and a string description. It can contain information about the experiments, but also details on signals marked by a human: bad data segments, sleep scores, sleep events (spindles, K-complex) etc. diff --git a/doc/whats_new.rst b/doc/whats_new.rst index 749c90647cb..690c813f3fe 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -19,7 +19,7 @@ Current Changelog ~~~~~~~~~ -- New tutorial in the documentation regarding :class:`mne.Annotations` by `Joan Massich`_ and `Alex Gramfort`_. +- New tutorial in the documentation regarding :class:`mne.Annotations` by `Joan Massich`_ and `Alex Gramfort`_ - Add :meth:`mne.Epochs.shift_time` that shifts the time axis of :class:`mne.Epochs` by `Thomas Hartmann`_ diff --git a/tutorials/plot_object_annotations.py b/tutorials/plot_object_annotations.py index 356cdf002e7..bcf4cdb2e8a 100644 --- a/tutorials/plot_object_annotations.py +++ b/tutorials/plot_object_annotations.py @@ -2,10 +2,12 @@ The **events** and :class:`Annotations ` data structures ========================================================================= -Events and annotations are quite similar. This tutorial highlights their -differences and similitudes and tries to shade some light to which one is -preferred to use in different situations when using MNE. -Here follows both terms definition from the :ref:`glossary`. +Events and :class:`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, @@ -15,25 +17,32 @@ channel. annotations - One annotation is defined by an onset, a duration and a string + An annotation is defined by an onset, a duration, and a string description. It can contain information about the experiments, but also details on signals marked by a human: bad data segments, sleep scores, sleep events (spindles, K-complex) etc. -They both can be seen as triplets where the first element answers to **when** -something happens and the last element refers to **what** is it. The -main differnce is that events the when is samples with respect to the first -sample and the what is an integer id; while in annotations the when is in -seconds with respect to an origin and the what is an arbitrary string. -The second element of the triplets have no direct relation between the two -structures. For the events case, the second element corresponds to id of the -previous active event. Whereas, the second element of the -:class:`mne.Annotations` is a float indicating its duration in seconds. +Both events and :class:`Annotations ` be seen as triplets +where the first element answers to **when** something happens and the last +element refers to **what** is it. +The main difference is that events represent the onset in samples relative to +the first sample value (:attr:`raw.first_samp `), and +the description is an integer value. +In contrast, :class:`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:`Annotations `. +For events, the second element corresponds to the ID of the previously active +event. +The second element of :class:`Annotations ` is a float +indicating its duration in seconds. See :ref:`sphx_glr_auto_examples_io_plot_read_events.py` -for a complete example in how to read, select and visualize **events**; +for a complete example of how to read, select, and visualize **events**; and :ref:`sphx_glr_auto_tutorials_plot_artifacts_correction_rejection.py` to -know how :class:`mne.Annotations` are used to mark bad segments of data. +learn how :class:`Annotations ` are used to mark bad segments +of data. An example of events and annotations ------------------------------------ @@ -44,15 +53,16 @@ import os.path as op import numpy as np +from datetime import datetime import mne -# load the data +# 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) -# plot the events +# Plot the events events = mne.find_events(raw) # Specify event_id dictionary based on the experiment @@ -64,7 +74,7 @@ mne.viz.plot_events(events, raw.info['sfreq'], raw.first_samp, color=color, event_id=event_id) -# create some annotations +# Create some annotations annotated_blink_raw = raw.copy() eog_events = mne.preprocessing.find_eog_events(raw) n_blinks = len(eog_events) @@ -82,57 +92,78 @@ # Working with Annotations # ------------------------ # -# An important element of the :class:`mne.Annotations` is ``orig_time`` which -# is the time reference for the ``onset``. It is key to understand that when -# calling `raw.set_annotation`, the given annotations is copied and transformed -# so that `raw.annotations.orig_time` matches the recording time of the raw -# object. (check :class:`mne.Annotations` documentation notes to see the -# expected behavior depending of `meas_date` and `orig_time`. Notice that -# `meas_date` is the :class:`Info ` attribute of the recording time. -# Find more in :ref:`sphx_glr_auto_tutorials_plot_info.py`) -# -# We'll now manipulate some simulated annotations objects. +# An important element of :class:`Annotations ` is +# ``orig_time`` which is the time reference for the ``onset``. +# It is key to understand that when calling +# :func:`raw.set_annotations `, the given +# annotations are copied and transformed so that +# :class:`raw.annotations.orig_time ` +# matches the recording time of the raw object. +# Refer to the documentation of :class:`Annotations ` to see +# the expected behavior depending on ``meas_date`` and ``orig_time``. +# Where ``meas_date`` is the recording time of the stored in +# :class:`Info `. +# You can find more information about :class:`Info ` in +# :ref:`sphx_glr_auto_tutorials_plot_info.py`. # -# First let's create an annotation object without orig_time. It this case -# one assumes that the orig_time is the time of the first sample of data. +# 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. + +############################################################################### + +# Create an annotation object without orig_time 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' +my_datetime = datetime.strptime(orig_time, '%Y-%m-%d %H:%M:%S.%f') +posix_timestamp = (my_datetime - datetime(1970, 1, 1)).total_seconds() +print('{0:f} is the POSIX timestamp of {1:s}'.format(posix_timestamp, + orig_time)) -############################################################################### - -# Now let's create annotation object with orig_time annot_orig = mne.Annotations(onset=[22, 24, 31], duration=[0.5, 4, 0], description=['foo', 'bar', 'foo'], orig_time=1038942091.6760709) print(annot_orig) ############################################################################### +# Now we create two raw objects, set the annotations and plot them to compare +# them. -# create two cropped copies of raw with the two previous 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 +# Plot the raw objects raw_a.plot() raw_b.plot() -############################################################################### - -# show the annotations in the raw objects +# Show the annotations in the raw objects print(raw_a.annotations) print(raw_b.annotations) -############################################################################### -# show that the onsets are the same +# Show that the onsets are the same print(raw_a.annotations.onset) print(raw_b.annotations.onset) ############################################################################### # -# It is possible to concatenate two annotations with the + like for lists. +# Notice that for the case where ``orig_time`` is ``None``, +# one assumes 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 like for +# lists if both share the same ``orig_time`` annot = mne.Annotations(onset=[10], duration=[0.5], description=['foobar'], From 762284480865b32485b3a5d5f20b92bc458bd606 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 12 Nov 2018 13:39:14 -0500 Subject: [PATCH 16/21] FIX: Minor fixes --- tutorials/plot_object_annotations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorials/plot_object_annotations.py b/tutorials/plot_object_annotations.py index bcf4cdb2e8a..e0e481b1b6b 100644 --- a/tutorials/plot_object_annotations.py +++ b/tutorials/plot_object_annotations.py @@ -24,13 +24,13 @@ Both events and :class:`Annotations ` be seen as triplets where the first element answers to **when** something happens and the last -element refers to **what** is it. +element refers to **what** it is. The main difference is that events represent the onset in samples relative to the first sample value (:attr:`raw.first_samp `), and the description is an integer value. In contrast, :class:`Annotations ` represents the ``onset`` in seconds (relative to the reference ``orig_time``), - and the ``description`` is an arbitrary string. +and the ``description`` is an arbitrary string. There is no correspondence between the second element of events and :class:`Annotations `. For events, the second element corresponds to the ID of the previously active From f2918304454c7883c2ab3333ffbdaaa4bf4ff01e Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Mon, 12 Nov 2018 21:57:07 +0100 Subject: [PATCH 17/21] pass on text --- tutorials/plot_object_annotations.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tutorials/plot_object_annotations.py b/tutorials/plot_object_annotations.py index e0e481b1b6b..1333f128c06 100644 --- a/tutorials/plot_object_annotations.py +++ b/tutorials/plot_object_annotations.py @@ -22,19 +22,21 @@ also details on signals marked by a human: bad data segments, sleep scores, sleep events (spindles, K-complex) etc. -Both events and :class:`Annotations ` be seen as triplets +Both events and :class:`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 relative to -the first sample value (:attr:`raw.first_samp `), and -the description is an integer value. +The main difference is that events represent the onset in samples taking into +account the first sample value +(:attr:`raw.first_samp `), and the description is +an integer value. In contrast, :class:`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:`Annotations `. -For events, the second element corresponds to the ID of the previously active -event. +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:`Annotations ` is a float indicating its duration in seconds. @@ -74,14 +76,15 @@ mne.viz.plot_events(events, raw.info['sfreq'], raw.first_samp, color=color, event_id=event_id) -# Create some annotations +# Create some annotations specifying onset, duration and description annotated_blink_raw = raw.copy() eog_events = mne.preprocessing.find_eog_events(raw) n_blinks = len(eog_events) # Center to cover the whole blink with full duration of 0.5s: onset = eog_events[:, 0] / raw.info['sfreq'] - 0.25 duration = np.repeat(0.5, n_blinks) -annot = mne.Annotations(onset, duration, ['bad blink'] * 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) @@ -101,7 +104,7 @@ # matches the recording time of the raw object. # Refer to the documentation of :class:`Annotations ` to see # the expected behavior depending on ``meas_date`` and ``orig_time``. -# Where ``meas_date`` is the recording time of the stored in +# Where ``meas_date`` is the recording time stored in # :class:`Info `. # You can find more information about :class:`Info ` in # :ref:`sphx_glr_auto_tutorials_plot_info.py`. From cce8cb72bf9299054ec7771b3b23199dc666d28f Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Thu, 8 Nov 2018 16:02:14 +0100 Subject: [PATCH 18/21] Add str support to _handle_meas_date --- mne/annotations.py | 17 ++++++++++++++++- mne/tests/test_annotations.py | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/mne/annotations.py b/mne/annotations.py index b2bbd93a03d..22338ac1703 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -362,9 +362,24 @@ def _combine_annotations(one, two, one_n_samples, one_first_samp, def _handle_meas_date(meas_date): - """Convert meas_date to seconds.""" + """Convert meas_date to seconds. + + if meas_date is string, it should conform to '%Y-%m-%d %H:%M:%S.%f' + otherwise it would be return 0. Take into account that the iso8601 allows + for ' ' or 'T' as delimiter between date and time. + """ if meas_date is None: meas_date = 0 + elif isinstance(meas_date, string_types): + ACCEPTED_ISO8601 = '%Y-%m-%d %H:%M:%S.%f' + try: + meas_date = datetime.strptime(meas_date, ACCEPTED_ISO8601) + except ValueError: + meas_date = 0 + else: + unix_ref_time = datetime.utcfromtimestamp(0) + meas_date = (meas_date - unix_ref_time).total_seconds() + elif not np.isscalar(meas_date): if len(meas_date) > 1: meas_date = meas_date[0] + meas_date[1] / 1000000. diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py index 3e45d4a0868..996aa70a350 100644 --- a/mne/tests/test_annotations.py +++ b/mne/tests/test_annotations.py @@ -635,4 +635,18 @@ def _constant_id(*args, **kwargs): assert event_id == expected_event_id +@pytest.mark.parametrize('meas_date, out', [ + pytest.param('toto', 0, id='invalid string'), + pytest.param(None, 0, id='None'), + pytest.param(42, 42.0, id='Scalar'), + pytest.param(3.14, 3.14, id='Float'), + # pytest.param((3, 1400000), 3.14, id='Scalar touple') + pytest.param('2002-12-03 19:01:11.720100', 1038942071.7201, + id='valid iso8601 string'), + pytest.param('2002-12-03T19:01:11.720100', 0, + id='invalid iso8601 string')]) +def test_handle_meas_date(meas_date, out): + assert _handle_meas_date(meas_date) == out + + run_tests_if_main() From 390106584c66bb4fc9f15cabec60c3496c9c6153 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Mon, 12 Nov 2018 22:24:40 +0100 Subject: [PATCH 19/21] allow passing orig_time in ISO8601 format --- mne/annotations.py | 19 +++++++++---------- tutorials/plot_object_annotations.py | 10 ++-------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index 22338ac1703..b72970dd4bb 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -50,14 +50,16 @@ class Annotations(object): Array of strings containing description for each annotation. If a string, all the annotations are given the same description. To reject epochs, use description starting with keyword 'bad'. See example above. - orig_time : float | int | instance of datetime | array of int | None + orig_time : float | int | instance of datetime | array of int | None | str A POSIX Timestamp, datetime or an array containing the timestamp as the first element and microseconds as the second element. Determines the starting time of annotation acquisition. If None (default), starting time is determined from beginning of raw data acquisition. In general, ``raw.info['meas_date']`` (or None) can be used for syncing the annotations with raw data if their acquisiton is started at the - same time. + same time. If it is a string, it should conform to the ISO8601 format. + More precisely to this '%Y-%m-%d %H:%M:%S.%f' particular case of the + ISO8601 format where the delimiter between date and time is ' '. Notes ----- @@ -143,12 +145,7 @@ class Annotations(object): def __init__(self, onset, duration, description, orig_time=None): # noqa: D102 if orig_time is not None: - if isinstance(orig_time, datetime): - orig_time = float(time.mktime(orig_time.timetuple())) - elif not np.isscalar(orig_time): - orig_time = orig_time[0] + orig_time[1] / 1000000. - else: # isscalar - orig_time = float(orig_time) # np.int not serializable + orig_time = _handle_meas_date(orig_time) self.orig_time = orig_time onset = np.array(onset, dtype=float) @@ -379,13 +376,15 @@ def _handle_meas_date(meas_date): else: unix_ref_time = datetime.utcfromtimestamp(0) meas_date = (meas_date - unix_ref_time).total_seconds() - + meas_date = round(meas_date, 6) # round that 6th decimal + elif isinstance(meas_date, datetime): + meas_date = float(time.mktime(meas_date.timetuple())) elif not np.isscalar(meas_date): if len(meas_date) > 1: meas_date = meas_date[0] + meas_date[1] / 1000000. else: meas_date = meas_date[0] - return meas_date + return float(meas_date) def _sync_onset(raw, onset, inverse=False): diff --git a/tutorials/plot_object_annotations.py b/tutorials/plot_object_annotations.py index 1333f128c06..f906fec71f2 100644 --- a/tutorials/plot_object_annotations.py +++ b/tutorials/plot_object_annotations.py @@ -55,7 +55,6 @@ import os.path as op import numpy as np -from datetime import datetime import mne @@ -123,14 +122,9 @@ # Create an annotation object with orig_time orig_time = '2002-12-03 19:01:31.676071' -my_datetime = datetime.strptime(orig_time, '%Y-%m-%d %H:%M:%S.%f') -posix_timestamp = (my_datetime - datetime(1970, 1, 1)).total_seconds() -print('{0:f} is the POSIX timestamp of {1:s}'.format(posix_timestamp, - orig_time)) - annot_orig = mne.Annotations(onset=[22, 24, 31], duration=[0.5, 4, 0], description=['foo', 'bar', 'foo'], - orig_time=1038942091.6760709) + orig_time=orig_time) print(annot_orig) ############################################################################### @@ -170,6 +164,6 @@ annot = mne.Annotations(onset=[10], duration=[0.5], description=['foobar'], - orig_time=1038942091.6760709) + orig_time=orig_time) annot = annot_orig + annot # concatenation print(annot) From 174d94acb4aac712b6d086085b89b1aee6246f46 Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Mon, 12 Nov 2018 22:32:15 +0100 Subject: [PATCH 20/21] Migrate whatever was missing in _handle_meas_date form #5699 --- mne/annotations.py | 9 ++++++--- mne/tests/test_annotations.py | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mne/annotations.py b/mne/annotations.py index b72970dd4bb..3b4e393e0b5 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -361,9 +361,12 @@ def _combine_annotations(one, two, one_n_samples, one_first_samp, def _handle_meas_date(meas_date): """Convert meas_date to seconds. - if meas_date is string, it should conform to '%Y-%m-%d %H:%M:%S.%f' - otherwise it would be return 0. Take into account that the iso8601 allows - for ' ' or 'T' as delimiter between date and time. + If `meas_date` is a string, it should conform to the ISO8601 format. + More precisely to this '%Y-%m-%d %H:%M:%S.%f' particular case of the + ISO8601 format where the delimiter between date and time is ' '. + + Otherwise, this function returns 0. Note that ISO8601 allows for ' ' or 'T' + as delimiters between date and time. """ if meas_date is None: meas_date = 0 diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py index 996aa70a350..1a639b74a1f 100644 --- a/mne/tests/test_annotations.py +++ b/mne/tests/test_annotations.py @@ -640,12 +640,13 @@ def _constant_id(*args, **kwargs): pytest.param(None, 0, id='None'), pytest.param(42, 42.0, id='Scalar'), pytest.param(3.14, 3.14, id='Float'), - # pytest.param((3, 1400000), 3.14, id='Scalar touple') + pytest.param((3, 140000), 3.14, id='Scalar touple'), pytest.param('2002-12-03 19:01:11.720100', 1038942071.7201, id='valid iso8601 string'), pytest.param('2002-12-03T19:01:11.720100', 0, id='invalid iso8601 string')]) def test_handle_meas_date(meas_date, out): + """Test meas date formats.""" assert _handle_meas_date(meas_date) == out From 60912ed8db9d7a39b799688c0ff92130b14abbad Mon Sep 17 00:00:00 2001 From: Joan Massich Date: Tue, 13 Nov 2018 13:57:35 +0100 Subject: [PATCH 21/21] print 6 decimals --- tutorials/plot_object_annotations.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tutorials/plot_object_annotations.py b/tutorials/plot_object_annotations.py index f906fec71f2..02801cf4e00 100644 --- a/tutorials/plot_object_annotations.py +++ b/tutorials/plot_object_annotations.py @@ -144,6 +144,7 @@ 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)