[MRG] use set_annotations method for Raw objects (#4870)

This commit is contained in:
Joan Massich
2018-08-03 16:28:39 +02:00
committed by Alexandre Gramfort
parent 83e16e22b7
commit b9c712b237
17 changed files with 106 additions and 68 deletions
+2
View File
@@ -98,6 +98,8 @@ API
- `src.kind` now equals to `'mixed'` (and not `'combined'`) for a mixed source space (made of surfaces and volume grids) by `Alex Gramfort`_
- Deprecation of :meth:`mne.io.BaseRaw.annotations` property in favor of :meth:`mne.io.BaseRaw.set_annotations` by `Joan Massich`_
.. _changes_0_16:
Version 0.16
+1 -1
View File
@@ -34,7 +34,7 @@ class Annotations(object):
>>> 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
>>> raw.set_annotations(annotations) # doctest: +SKIP
>>> epochs = mne.Epochs(raw, events, event_id, tmin, tmax) # doctest: +SKIP
Parameters
+2 -1
View File
@@ -80,7 +80,8 @@ class FunctionMaker(object):
self.module = func.__module__
if inspect.isfunction(func):
argspec = getfullargspec(func)
self.annotations = getattr(func, '__annotations__', {})
if hasattr(self, 'set_annotations'):
self.set_annotations(getattr(func, '__annotations__', {}))
for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs',
'kwonlydefaults'):
setattr(self, a, getattr(argspec, a))
+40 -21
View File
@@ -11,6 +11,7 @@ import copy
from copy import deepcopy
import os
import os.path as op
import warnings
import numpy as np
@@ -365,7 +366,7 @@ class BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
self._projectors = list()
self._projector = None
self._dtype_ = dtype
self.annotations = None
self.set_annotations(None)
# If we have True or a string, actually do the preloading
self._update_times()
if load_from_disk:
@@ -666,6 +667,13 @@ class BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
@annotations.setter
def annotations(self, annotations, emit_warning=True):
warnings.warn('setting the annotations attribute by assignation is'
' deprecated since 0.17, and would be removed in 0.19.'
' Please use raw.set_annotations() instead.',
category=DeprecationWarning)
self.set_annotations(annotations, emit_warning=emit_warning)
def set_annotations(self, annotations, emit_warning=True):
"""Setter for annotations.
This setter checks if they are inside the data range.
@@ -677,19 +685,24 @@ class BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
emit_warning : bool
Whether to emit warnings when limiting or omitting annotations.
"""
new_annot = None
if annotations is not None:
if not isinstance(annotations, Annotations):
raise ValueError('Annotations must be an instance of '
'mne.Annotations. Got %s.' % annotations)
new_annot = annotations.copy()
meas_date = _handle_meas_date(self.info['meas_date'])
if annotations.orig_time is not None:
offset = (annotations.orig_time - meas_date -
if new_annot.orig_time is not None:
offset = (new_annot.orig_time - meas_date -
self.first_samp / self.info['sfreq'])
else:
offset = 0
omit_ind = list()
omitted = limited = 0
for ind, onset in enumerate(annotations.onset):
for ind, onset in enumerate(new_annot.onset):
onset += offset
if onset > self.times[-1]:
omitted += 1
@@ -697,29 +710,28 @@ class BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
logger.debug('Omitting %d @ %s > %s'
% (ind, onset, self.times[-1]))
elif onset < self.times[0]:
if onset + annotations.duration[ind] < self.times[0]:
if onset + new_annot.duration[ind] < self.times[0]:
omitted += 1
omit_ind.append(ind)
logger.debug('Omitting %d @ %s < %s'
% (ind, onset, self.times[0]))
else:
limited += 1
duration = annotations.duration[ind] + onset
annotations.duration[ind] = duration
annotations.onset[ind] = self.times[0] - offset
elif (onset + annotations.duration[ind] >
duration = new_annot.duration[ind] + onset
new_annot.duration[ind] = duration
new_annot.onset[ind] = self.times[0] - offset
elif (onset + new_annot.duration[ind] >
self.times[-1] + 1.1 / self.info['sfreq']):
# We have to permit onset+duration to appear to go one past
# the last sample in order to actually include the last
# sample...
limited += 1
annotations.duration[ind] = (self.times[-1] +
1. / self.info['sfreq'] -
onset)
annotations.onset = np.delete(annotations.onset, omit_ind)
annotations.duration = np.delete(annotations.duration, omit_ind)
annotations.description = np.delete(annotations.description,
omit_ind)
new_annot.duration[ind] = (self.times[-1] +
1. / self.info['sfreq'] -
onset)
new_annot.onset = np.delete(new_annot.onset, omit_ind)
new_annot.duration = np.delete(new_annot.duration, omit_ind)
new_annot.description = np.delete(new_annot.description, omit_ind)
if emit_warning:
if omitted > 0:
warn('Omitted %s annotation(s) that were outside data '
@@ -727,7 +739,8 @@ class BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
if limited > 0:
warn('Limited %s annotation(s) that were expanding '
'outside the data range.' % limited)
self._annotations = annotations
self._annotations = new_annot
def __del__(self): # noqa: D105
# remove file for memmap
@@ -1611,11 +1624,10 @@ class BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
self._update_times()
if self.annotations is not None:
annotations = self.annotations
# XXX there might be a cleaner way to do this someday
if self.annotations.orig_time is None:
self.annotations.onset -= tmin
BaseRaw.annotations.fset(self, annotations, emit_warning=False)
# now call setter to filter out annotations outside of interval
self.set_annotations(self.annotations, False)
return self
@@ -2036,7 +2048,7 @@ class BaseRaw(ProjMixin, ContainsMixin, UpdateChannelsMixin,
self._update_times()
if annotations is None:
annotations = Annotations([], [], [])
self.annotations = annotations
self.set_annotations(annotations)
for edge_samp in edge_samps:
onset = _sync_onset(self, (edge_samp) / self.info['sfreq'], True)
self.annotations.append(onset, 0., 'BAD boundary')
@@ -2164,6 +2176,13 @@ class _RawShell():
def n_times(self): # noqa: D102
return self.last_samp - self.first_samp + 1
@property
def annotations(self): # noqa: D102
return self._annotations
def set_annotations(self, annotations):
self._annotations = annotations
###############################################################################
# Writing
+1 -1
View File
@@ -154,7 +154,7 @@ class RawCTF(BaseRaw):
# Add bad segments as Annotations (correct for start time)
start_time = -res4['pre_trig_pts'] / float(info['sfreq'])
self.annotations = _annotate_bad_segments(directory, start_time)
self.set_annotations(_annotate_bad_segments(directory, start_time))
if clean_names:
self._clean_names()
+1 -1
View File
@@ -555,7 +555,7 @@ class RawMff(BaseRaw):
data_view = data[n_data1_channels:, -1] = 0
warn('This file has the EGI PSG sample bug')
if self.annotations is None:
self.annotations = Annotations((), (), ())
self.set_annotations(Annotations((), (), ()))
an_start = current_data_sample
self.annotations.append(
_sync_onset(self, an_start / self.info['sfreq']),
+4 -4
View File
@@ -105,7 +105,7 @@ class Raw(BaseRaw):
verbose=verbose)
# combine annotations
BaseRaw.annotations.fset(self, raws[0].annotations, False)
self.set_annotations(raws[0].annotations, False)
if any([r.annotations for r in raws[1:]]):
n_samples = np.sum(self._last_samps - self._first_samps + 1)
for r in raws:
@@ -113,7 +113,7 @@ class Raw(BaseRaw):
self.annotations, r.annotations,
n_samples, self.first_samp, r.first_samp,
r.info['sfreq'], self.info['meas_date'])
BaseRaw.annotations.fset(self, annotations, False)
self.set_annotations(annotations, False)
n_samples += r.last_samp - r.first_samp + 1
# Add annotations for in-data skips
@@ -123,7 +123,7 @@ class Raw(BaseRaw):
for skip in extra:
if skip['ent'] is None: # these are skips
if self.annotations is None:
self.annotations = Annotations((), (), ())
self.set_annotations(Annotations((), (), ()))
start = skip['first'] - first_samp + offset
stop = skip['last'] - first_samp + offset
self.annotations.append(
@@ -196,7 +196,7 @@ class Raw(BaseRaw):
raw = _RawShell()
raw.filename = fname
raw.first_samp = first_samp
raw.annotations = annotations
raw.set_annotations(annotations)
# Go through the remaining tags in the directory
raw_extras = list()
+4 -4
View File
@@ -404,7 +404,7 @@ def test_split_files():
assert_allclose(raw_1.buffer_size_sec, 10., atol=1e-2) # samp rate
split_fname = op.join(tempdir, 'split_raw.fif')
raw_1.annotations = Annotations([2.], [5.5], 'test')
raw_1.set_annotations(Annotations([2.], [5.5], 'test'))
raw_1.save(split_fname, buffer_size_sec=1.0, split_size='10MB')
raw_2 = read_raw_fif(split_fname)
@@ -1241,7 +1241,7 @@ def test_save():
annot = Annotations([10], [5], ['test'],
raw.info['meas_date'] +
raw.first_samp / raw.info['sfreq'])
raw.annotations = annot
raw.set_annotations(annot)
new_fname = op.join(op.abspath(op.curdir), 'break_raw.fif')
raw.save(op.join(tempdir, new_fname), overwrite=True)
new_raw = read_raw_fif(op.join(tempdir, new_fname), preload=False)
@@ -1259,7 +1259,7 @@ def test_annotation_crop():
new_fname = op.join(op.abspath(op.curdir), 'break_raw.fif')
annot = Annotations([5., 11., 15.], [2., 1., 3.], ['test', 'test', 'test'])
raw = read_raw_fif(fif_fname, preload=False)
raw.annotations = annot
raw.set_annotations(annot)
with warnings.catch_warnings(record=True) as w:
r1 = raw.copy().crop(2.5, 7.5)
r2 = raw.copy().crop(12.5, 17.5)
@@ -1277,7 +1277,7 @@ def test_annotation_crop():
annot = Annotations([0., raw.times[-1]], [2., 2.], 'test',
raw.info['meas_date'] + raw.first_samp / sfreq - 1.)
with warnings.catch_warnings(record=True) as w: # outside range
raw.annotations = annot
raw.set_annotations(annot)
assert (all('data range' in str(ww.message) for ww in w))
assert_allclose(raw.annotations.duration,
[1., 1. + 1. / raw.info['sfreq']], atol=1e-3)
+12 -2
View File
@@ -2,13 +2,14 @@
from os import path as op
import math
import pytest
import numpy as np
from numpy.testing import (assert_allclose, assert_array_almost_equal,
assert_equal, assert_array_equal)
from mne import concatenate_raws
from mne import concatenate_raws, create_info
from mne.datasets import testing
from mne.io import read_raw_fif
from mne.io import read_raw_fif, RawArray
from mne.utils import _TempDir
@@ -143,3 +144,12 @@ def test_time_index():
# Test new (rounding) indexing behavior
new_inds = raw.time_as_index(raw.times, use_rounding=True)
assert(len(set(new_inds)) == len(new_inds))
def test_annotation_property_deprecation_warning():
"""Test that assigning annotations warns and nowhere else."""
with pytest.warns(None) as record:
raw = RawArray(np.random.rand(1, 1), create_info(1, 1))
assert len(record) is 0
with pytest.warns(DeprecationWarning, match='assignation is deprecated'):
raw.annotations = None
+1 -1
View File
@@ -13,7 +13,7 @@ proj_fname = op.join(data_path, 'test-proj.fif')
def test_find_eog():
"""Test find EOG peaks."""
raw = read_raw_fif(raw_fname)
raw.annotations = Annotations([14, 21], [1, 1], 'BAD_blink')
raw.set_annotations(Annotations([14, 21], [1, 1], 'BAD_blink'))
events = find_eog_events(raw)
assert len(events) == 4
assert not all(events[:, 0] < 29000)
+2 -2
View File
@@ -91,7 +91,7 @@ def test_ica_full_data_recovery(method):
data = raw._data[:n_channels].copy()
data_epochs = epochs.get_data()
data_evoked = evoked.data
raw.annotations = Annotations([0.5], [0.5], ['BAD'])
raw.set_annotations(Annotations([0.5], [0.5], ['BAD']))
methods = [method]
for method in methods:
stuff = [(2, n_channels, True), (2, n_channels // 2, False)]
@@ -334,7 +334,7 @@ def test_ica_additional(method):
stop2 = 500
raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
raw.del_proj() # avoid warnings
raw.annotations = Annotations([0.5], [0.5], ['BAD'])
raw.set_annotations(Annotations([0.5], [0.5], ['BAD']))
# XXX This breaks the tests :(
# raw.info['bads'] = [raw.ch_names[1]]
test_cov = read_cov(test_cov_name)
+20 -17
View File
@@ -50,8 +50,9 @@ def test_basics():
raw2.first_samp / raw2.info['sfreq'])
annot = Annotations(onset, duration, description, orig_time)
assert ' segments' in repr(annot)
raw2.annotations = annot
raw2.set_annotations(annot)
assert_array_equal(raw2.annotations.onset, onset)
assert id(raw2.annotations) != id(annot)
concatenate_raws([raw, raw2])
raw.annotations.delete(-1) # remove boundary annotations
raw.annotations.delete(-1)
@@ -70,10 +71,10 @@ def test_basics():
for first_samp in [12300, 100, 12]:
raw = RawArray(data.copy(), info, first_samp=first_samp)
ants = Annotations([1., 2.], [.5, .5], 'x', np.pi + first_samp / sfreq)
raw.annotations = ants
raw.set_annotations(ants)
raws.append(raw)
raw = RawArray(data.copy(), info)
raw.annotations = Annotations([1.], [.5], 'x', None)
raw.set_annotations(Annotations([1.], [.5], 'x', None))
raws.append(raw)
raw = concatenate_raws(raws, verbose='debug')
boundary_idx = np.where(raw.annotations.description == 'BAD boundary')[0]
@@ -100,8 +101,9 @@ def test_crop():
onset = events[events[:, 2] == 1, 0] / raw.info['sfreq']
duration = np.full_like(onset, 0.5)
description = ['bad %d' % k for k in range(len(onset))]
raw.annotations = mne.Annotations(onset, duration, description,
orig_time=raw.info['meas_date'])
annot = mne.Annotations(onset, duration, description,
orig_time=raw.info['meas_date'])
raw.set_annotations(annot)
split_time = raw.times[-1] / 2. + 2.
split_idx = len(onset) // 2 + 1
@@ -134,13 +136,13 @@ def test_crop():
getattr(raw.annotations, attr),
err_msg='Failed for %s:' % (attr,))
raw.annotations = None # undo
raw.set_annotations(None) # undo
# Test concatenating annotations with and without orig_time.
last_time = raw.last_samp / raw.info['sfreq']
raw2 = raw.copy()
raw.annotations = Annotations([45.], [3], 'test', raw.info['meas_date'])
raw2.annotations = Annotations([2.], [3], 'BAD', None)
raw.set_annotations(Annotations([45.], [3], 'test', raw.info['meas_date']))
raw2.set_annotations(Annotations([2.], [3], 'BAD', None))
raw = concatenate_raws([raw, raw2])
raw.annotations.delete(-1) # remove boundary annotations
raw.annotations.delete(-1)
@@ -164,12 +166,12 @@ def test_crop():
assert len(annot) == 0
# Test that empty annotations can be saved with an object
fname = op.join(tempdir, 'test_raw.fif')
raw.annotations = annot
raw.set_annotations(annot)
raw.save(fname)
raw_read = read_raw_fif(fname)
assert isinstance(raw_read.annotations, Annotations)
assert len(raw_read.annotations) == 0
raw.annotations = None
raw.set_annotations(None)
raw.save(fname, overwrite=True)
raw_read = read_raw_fif(fname)
assert raw_read.annotations is None
@@ -182,7 +184,7 @@ def test_crop_more():
onset = np.array([0.47058824, 2.49773765, 6.67873287, 9.15837097])
duration = np.array([0.89592767, 1.13574672, 1.09954739, 0.48868752])
annotations = mne.Annotations(onset, duration, 'BAD')
raw.annotations = annotations
raw.set_annotations(annotations)
assert len(raw.annotations) == 4
delta = 1. / raw.info['sfreq']
raw_concat = mne.concatenate_raws(
@@ -232,7 +234,8 @@ def test_raw_reject():
info = create_info(['a', 'b', 'c', 'd', 'e'], sfreq, ch_types='eeg')
raw = RawArray(np.ones((5, 15000)), info)
with warnings.catch_warnings(record=True): # one outside range
raw.annotations = Annotations([2, 100, 105, 148], [2, 8, 5, 8], 'BAD')
raw.set_annotations(Annotations([2, 100, 105, 148],
[2, 8, 5, 8], 'BAD'))
data, times = raw.get_data([0, 1, 3, 4], 100, 11200, # 1-112 sec
'omit', return_times=True)
bad_times = np.concatenate([np.arange(200, 400),
@@ -244,8 +247,8 @@ def test_raw_reject():
# with orig_time and complete overlap
raw = read_raw_fif(fif_fname)
t_0 = raw.first_samp / raw.info['sfreq']
raw.annotations = Annotations([t_0 + 1, t_0 + 4, t_0 + 5], [1, 3, 1],
'BAD', raw.info['meas_date'])
raw.set_annotations(Annotations([t_0 + 1, t_0 + 4, t_0 + 5], [1, 3, 1],
'BAD', raw.info['meas_date']))
t_stop = 18.
assert raw.times[-1] > t_stop
n_stop = int(round(t_stop * raw.info['sfreq']))
@@ -314,7 +317,7 @@ def test_annotation_filtering():
# Let's try another one
raw = raws[0].copy()
raw.annotations = Annotations([0.], [0.5], ['BAD_ACQ_SKIP'])
raw.set_annotations(Annotations([0.], [0.5], ['BAD_ACQ_SKIP']))
my_data, times = raw.get_data(reject_by_annotation='omit',
return_times=True)
assert_allclose(times, raw.times[500:])
@@ -326,7 +329,7 @@ def test_annotation_filtering():
assert_allclose(raw_filt[:][0], expected, atol=1e-14)
raw = raws[0].copy()
raw.annotations = Annotations([0.5], [0.5], ['BAD_ACQ_SKIP'])
raw.set_annotations(Annotations([0.5], [0.5], ['BAD_ACQ_SKIP']))
my_data, times = raw.get_data(reject_by_annotation='omit',
return_times=True)
assert_allclose(times, raw.times[:500])
@@ -343,7 +346,7 @@ def test_annotation_omit():
data = np.concatenate([np.ones((1, 1000)), 2 * np.ones((1, 1000))], -1)
info = create_info(1, 1000., 'eeg')
raw = RawArray(data, info)
raw.annotations = Annotations([0.5], [1], ['bad'])
raw.set_annotations(Annotations([0.5], [1], ['bad']))
expected = raw[0][0]
assert_allclose(raw.get_data(reject_by_annotation=None), expected)
# nan
+3 -3
View File
@@ -292,8 +292,8 @@ def test_reject():
first_time = (raw.info['meas_date'][0] + raw.info['meas_date'][1] *
0.000001 + raw.first_samp / sfreq)
for orig_time in [None, first_time]:
raw.annotations = Annotations(onsets, [0.5, 0.5, 0.5], 'BAD',
orig_time)
annot = Annotations(onsets, [0.5, 0.5, 0.5], 'BAD', orig_time)
raw.set_annotations(annot)
epochs = Epochs(raw, events, event_id, tmin, tmax, picks=[0],
reject=None, preload=preload)
epochs.drop_bad()
@@ -301,7 +301,7 @@ def test_reject():
assert_equal(epochs.drop_log[0][0], 'BAD')
assert_equal(epochs.drop_log[2][0], 'BAD')
assert_equal(epochs.drop_log[4][0], 'BAD')
raw.annotations = None
raw.set_annotations(None)
def test_own_data():
+7 -5
View File
@@ -197,7 +197,7 @@ def test_plot_raw():
pytest.raises(TypeError, raw.plot, event_color={'foo': 'r'})
annot = Annotations([10, 10 + raw.first_samp / raw.info['sfreq']],
[10, 10], ['test', 'test'], raw.info['meas_date'])
raw.annotations = annot
raw.set_annotations(annot)
fig = plot_raw(raw, events=events, event_color={-1: 'r', 998: 'b'})
plt.close('all')
for group_by, order in zip(['position', 'selection'],
@@ -229,8 +229,9 @@ def test_plot_raw():
# test if meas_date has only one element
raw.info['meas_date'] = np.array([raw.info['meas_date'][0]],
dtype=np.int32)
raw.annotations = Annotations([1 + raw.first_samp / raw.info['sfreq']],
[5], ['bad'])
annot = Annotations([1 + raw.first_samp / raw.info['sfreq']],
[5], ['bad'])
raw.set_annotations(annot)
raw.plot(group_by='position', order=np.arange(8))
for fig_num in plt.get_fignums():
fig = plt.figure(fig_num)
@@ -268,7 +269,8 @@ def test_plot_annotations():
_annotation_helper(raw, events=True)
with warnings.catch_warnings(record=True): # cut off
raw.annotations = Annotations([42], [1], 'test', raw.info['meas_date'])
annot = Annotations([42], [1], 'test', raw.info['meas_date'])
raw.set_annotations(annot)
_annotation_helper(raw)
@@ -330,7 +332,7 @@ def test_plot_raw_psd():
assert len(w) == 4
# test reject_by_annotation
raw = _get_raw()
raw.annotations = Annotations([1, 5], [3, 3], ['test', 'test'])
raw.set_annotations(Annotations([1, 5], [3, 3], ['test', 'test']))
raw.plot_psd(reject_by_annotation=True)
raw.plot_psd(reject_by_annotation=False)
@@ -126,8 +126,9 @@ 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)
raw.annotations = 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'])
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.
+1 -1
View File
@@ -127,7 +127,7 @@ durations = annotations_df['duration'].values / raw.info['sfreq']
descriptions = annotations_df['label'].values
annotations = mne.Annotations(onsets, durations, descriptions)
raw.annotations = annotations
raw.set_annotations(annotations)
del onsets, durations, descriptions
###############################################################################
+2 -2
View File
@@ -28,14 +28,14 @@ raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
raw = mne.io.read_raw_fif(raw_fname, preload=True)
raw.filter(1, None, fir_design='firwin') # already lowpassed @ 40
raw.annotations = mne.Annotations([1], [10], 'BAD')
raw.set_annotations(mne.Annotations([1], [10], 'BAD'))
raw.plot(block=True)
# For the sake of example we annotate first 10 seconds of the recording as
# 'BAD'. This part of data is excluded from the ICA decomposition by default.
# To turn this behavior off, pass ``reject_by_annotation=False`` to
# :meth:`mne.preprocessing.ICA.fit`.
raw.annotations = mne.Annotations([0], [10], 'BAD')
raw.set_annotations(mne.Annotations([0], [10], 'BAD'))
###############################################################################
# 1) Fit ICA model using the FastICA algorithm.