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
|
# Authors: Jaakko Leppakangas <jaeilepp@student.jyu.fi>
#
# License: BSD (3-clause)
from datetime import datetime
import time
import numpy as np
from .externals.six import string_types
class Annotations(object):
"""Annotation object for annotating segments of raw data.
Annotations are added to instance of :class:`mne.io.Raw` as an attribute
named ``annotations``. See the example below. To reject bad epochs using
annotations, use annotation description starting with 'bad' keyword. The
epochs with overlapping bad segments are then rejected automatically by
default.
To remove epochs with blinks you can do::
>>> eog_events = mne.preprocessing.find_eog_events(raw) # doctest: +SKIP
>>> n_blinks = len(eog_events) # doctest: +SKIP
>>> onset = eog_events[:, 0] / raw.info['sfreq'] - 0.25 # doctest: +SKIP
>>> duration = np.repeat(0.5, n_blinks) # doctest: +SKIP
>>> description = ['bad blink'] * n_blinks # doctest: +SKIP
>>> annotations = mne.Annotations(onset, duration, description) # doctest: +SKIP
>>> raw.annotations = annotations # doctest: +SKIP
>>> epochs = mne.Epochs(raw, events, event_id, tmin, tmax) # doctest: +SKIP
Parameters
----------
onset : array of float, shape (n_annotations,)
Annotation time onsets from the beginning of the recording in seconds.
duration : array of float, shape (n_annotations,)
Durations of the annotations in seconds.
description : array of str, shape (n_annotations,) | str
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
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.
Notes
-----
If ``orig_time`` is None, the annotations are synced to the start of the
data (0 seconds). Otherwise the annotations are synced to sample 0 and
``raw.first_samp`` is taken into account the same way as with events.
""" # noqa
def __init__(self, onset, duration, description, orig_time=None):
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
self.orig_time = orig_time
onset = np.array(onset, dtype=float)
if onset.ndim != 1:
raise ValueError('Onset must be a one dimensional array.')
duration = np.array(duration, dtype=float)
if isinstance(description, string_types):
description = np.repeat(description, len(onset))
if duration.ndim != 1:
raise ValueError('Duration must be a one dimensional array.')
if not (len(onset) == len(duration) == len(description)):
raise ValueError('Onset, duration and description must be '
'equal in sizes.')
if any([';' in desc for desc in description]):
raise ValueError('Semicolons in descriptions not supported.')
self.onset = onset
self.duration = duration
self.description = np.array(description)
def _combine_annotations(annotations, last_samps, first_samps, sfreq):
"""Helper for combining a tuple of annotations."""
if not any(annotations):
return None
elif annotations[1] is None:
return annotations[0]
elif annotations[0] is None:
old_onset = list()
old_duration = list()
old_description = list()
old_orig_time = None
else:
old_onset = annotations[0].onset
old_duration = annotations[0].duration
old_description = annotations[0].description
old_orig_time = annotations[0].orig_time
extra_samps = len(first_samps) - 1 # Account for sample 0
onset = (annotations[1].onset + (sum(last_samps[:-1]) + extra_samps -
sum(first_samps[:-1])) / sfreq)
onset = np.concatenate([old_onset, onset])
duration = np.concatenate([old_duration, annotations[1].duration])
description = np.concatenate([old_description, annotations[1].description])
return Annotations(onset, duration, description, old_orig_time)
def _onset_to_seconds(raw, onset):
"""Helper function for adjusting annotation onsets in relation to raw data.
"""
meas_date = raw.info['meas_date']
if meas_date is None:
meas_date = 0
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]
if raw.annotations.orig_time is None:
orig_time = meas_date
else:
orig_time = (raw.annotations.orig_time -
raw.first_samp / raw.info['sfreq'])
annot_start = orig_time - meas_date + onset
return annot_start
|