Skip to content

Commit d214798

Browse files
larsoneragramfort
authored andcommitted
ENH: Add support for Info-as-Raw (#5869)
1 parent 203a96c commit d214798

File tree

3 files changed

+83
-57
lines changed

3 files changed

+83
-57
lines changed

doc/whats_new.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ Changelog
3939

4040
- Add :func:`mne.labels_to_stc` to facilitate working with label data, by `Eric Larson`_
4141

42+
- Add support for using :class:`mne.Info` and ``duration`` in :func:`mne.simulation.simulate_raw` instead of :class:`mne.io.Raw` by `Eric Larson`_
43+
4244
- Add ``overlap`` argument to :func:`mne.make_fixed_length_events` by `Eric Larson`_
4345

4446
- Add 448-labels subdivided aparc cortical parcellation by `Denis Engemann`_ and `Sheraz Khan`_

mne/simulation/raw.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from ..source_estimate import VolSourceEstimate
1818
from ..cov import make_ad_hoc_cov, read_cov, Covariance
1919
from ..bem import fit_sphere_to_headshape, make_sphere_model, read_bem_solution
20-
from ..io import RawArray, BaseRaw
20+
from ..io import RawArray, BaseRaw, Info
2121
from ..chpi import (read_head_pos, head_pos_to_trans_rot_t, _get_hpi_info,
2222
_get_hpi_initial_fit)
2323
from ..io.constants import FIFF
@@ -30,7 +30,8 @@
3030
from ..source_space import (_ensure_src, _points_outside_surface,
3131
_adjust_patch_info)
3232
from ..source_estimate import _BaseSourceEstimate
33-
from ..utils import logger, verbose, check_random_state, warn, _pl
33+
from ..utils import (logger, verbose, check_random_state, warn, _pl,
34+
_validate_type)
3435
from ..parallel import check_n_jobs
3536

3637

@@ -65,17 +66,20 @@ def _log_ch(start, info, ch):
6566
def simulate_raw(raw, stc, trans, src, bem, cov='simple',
6667
blink=False, ecg=False, chpi=False, head_pos=None,
6768
mindist=1.0, interp='cos2', iir_filter=None, n_jobs=1,
68-
random_state=None, use_cps=True, forward=None, verbose=None):
69+
random_state=None, use_cps=True, forward=None,
70+
duration=None, verbose=None):
6971
u"""Simulate raw data.
7072
7173
Head movements can optionally be simulated using the ``head_pos``
7274
parameter.
7375
7476
Parameters
7577
----------
76-
raw : instance of Raw
78+
raw : instance of Raw | instance of Info
7779
The raw template to use for simulation. The ``info``, ``times``,
78-
and potentially ``first_samp`` properties will be used.
80+
and ``first_samp`` properties will be used.
81+
Can be ``info`` if ``duration`` is also supplied, in which case
82+
``first_samp`` will be set to zero.
7983
stc : instance of SourceEstimate
8084
The source estimate to use to simulate data. Must have the same
8185
sample rate as the raw data.
@@ -136,6 +140,13 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
136140
forward : instance of Forward | None
137141
The forward operator to use. If None (default) it will be computed
138142
using `bem`, `trans`, and `src`.
143+
144+
.. versionadded:: 0.17
145+
duration : float | None
146+
The duration to simulate. Can be None to use the duration of ``raw``.
147+
Must be supplied if ``raw`` is an instance of :class:`mne.Info`.
148+
149+
.. versionadded:: 0.18
139150
verbose : bool, str, int, or None
140151
If not None, override default verbose level (see :func:`mne.verbose`
141152
and :ref:`Logging documentation <tut_logging>` for more).
@@ -203,10 +214,22 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
203214
.. [1] Bentivoglio et al. "Analysis of blink rate patterns in normal
204215
subjects" Movement Disorders, 1997 Nov;12(6):1028-34.
205216
"""
206-
if not isinstance(raw, BaseRaw):
207-
raise TypeError('raw should be an instance of Raw')
208-
times, info, first_samp = raw.times, raw.info, raw.first_samp
209-
raw_verbose = raw.verbose
217+
_validate_type(raw, (BaseRaw, Info), 'raw', 'Raw or Info')
218+
if duration is not None:
219+
duration = float(duration)
220+
if isinstance(raw, Info):
221+
if duration is None:
222+
raise ValueError('duration cannot be None if raw is an instance '
223+
'of Info')
224+
info, first_samp = raw, 0
225+
raw_verbose = verbose
226+
else:
227+
info, first_samp = raw.info, raw.first_samp
228+
raw_verbose = raw.verbose
229+
if duration is None:
230+
duration = raw.times[-1] + 1. / info['sfreq']
231+
times = np.arange(int(round(info['sfreq'] * duration))) / info['sfreq']
232+
del raw
210233

211234
# Check for common flag errors and try to override
212235
if not isinstance(stc, _BaseSourceEstimate):
@@ -227,7 +250,7 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
227250
raise ValueError('If forward is not None then trans, src, bem, '
228251
'and head_pos must all be None')
229252
if not np.allclose(forward['info']['dev_head_t']['trans'],
230-
raw.info['dev_head_t']['trans'], atol=1e-6):
253+
info['dev_head_t']['trans'], atol=1e-6):
231254
raise ValueError('The forward meg<->head transform '
232255
'forward["info"]["dev_head_t"] does not match '
233256
'the one in raw.info["dev_head_t"]')
@@ -241,7 +264,8 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
241264
head_pos = head_pos_to_trans_rot_t(head_pos)
242265
if isinstance(head_pos, tuple): # can be quats converted to trans, rot, t
243266
transs, rots, ts = head_pos
244-
ts -= raw._first_time # MF files need reref
267+
first_time = first_samp / info['sfreq']
268+
ts -= first_time # MF files need reref
245269
dev_head_ts = [np.r_[np.c_[r, t[:, np.newaxis]], [[0, 0, 0, 1]]]
246270
for r, t in zip(rots, transs)]
247271
del transs, rots
@@ -267,7 +291,7 @@ def simulate_raw(raw, stc, trans, src, bem, cov='simple',
267291
dev_head_ts = [{'trans': d, 'to': info['dev_head_t']['to'],
268292
'from': info['dev_head_t']['from']}
269293
for d in dev_head_ts]
270-
offsets = raw.time_as_index(ts)
294+
offsets = np.round(ts * info['sfreq']).astype(int)
271295
offsets = np.concatenate([offsets, [len(times)]])
272296
assert offsets[-2] != offsets[-1]
273297
assert np.array_equal(offsets, np.unique(offsets))

mne/simulation/tests/test_raw.py

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -99,32 +99,30 @@ def test_simulate_raw_sphere():
9999
#
100100
raw_sim = simulate_raw(raw, stc, trans, src, sphere, read_cov(cov_fname),
101101
head_pos=head_pos_sim,
102-
blink=True, ecg=True, random_state=seed,
103-
use_cps=True)
102+
blink=True, ecg=True, random_state=seed)
104103
raw_sim_2 = simulate_raw(raw, stc, trans_fname, src_fname, sphere,
105104
cov_fname, head_pos=head_pos_sim,
106-
blink=True, ecg=True, random_state=seed,
107-
use_cps=True)
105+
blink=True, ecg=True, random_state=seed)
108106
assert_array_equal(raw_sim_2[:][0], raw_sim[:][0])
109107
std = dict(grad=2e-13, mag=10e-15, eeg=0.1e-6)
110108
raw_sim = simulate_raw(raw, stc, trans, src, sphere,
111109
make_ad_hoc_cov(raw.info, std=std),
112110
head_pos=head_pos_sim, blink=True, ecg=True,
113-
random_state=seed, use_cps=True)
111+
random_state=seed)
114112
raw_sim_2 = simulate_raw(raw, stc, trans_fname, src_fname, sphere,
115113
cov=std, head_pos=head_pos_sim, blink=True,
116-
ecg=True, random_state=seed, use_cps=True)
114+
ecg=True, random_state=seed)
117115
assert_array_equal(raw_sim_2[:][0], raw_sim[:][0])
118116
sphere_norad = make_sphere_model('auto', None, raw.info)
119117
raw_meg = raw.copy().pick_types()
120118
raw_sim = simulate_raw(raw_meg, stc, trans, src, sphere_norad,
121119
make_ad_hoc_cov(raw.info, std=None),
122120
head_pos=head_pos_sim, blink=True, ecg=True,
123-
random_state=seed, use_cps=True)
121+
random_state=seed)
124122
raw_sim_2 = simulate_raw(raw_meg, stc, trans_fname, src_fname,
125123
sphere_norad,
126124
cov='simple', head_pos=head_pos_sim, blink=True,
127-
ecg=True, random_state=seed, use_cps=True)
125+
ecg=True, random_state=seed)
128126
assert_array_equal(raw_sim_2[:][0], raw_sim[:][0])
129127
# Test IO on processed data
130128
tempdir = _TempDir()
@@ -139,12 +137,10 @@ def test_simulate_raw_sphere():
139137
for ecg, eog in ((True, False), (False, True), (True, True)):
140138
raw_sim_3 = simulate_raw(raw, stc, trans, src, sphere,
141139
cov=None, head_pos=head_pos_sim,
142-
blink=eog, ecg=ecg, random_state=seed,
143-
use_cps=True)
140+
blink=eog, ecg=ecg, random_state=seed)
144141
raw_sim_4 = simulate_raw(raw, stc, trans, src, sphere,
145142
cov=None, head_pos=head_pos_sim,
146-
blink=False, ecg=False, random_state=seed,
147-
use_cps=True)
143+
blink=False, ecg=False, random_state=seed)
148144
picks = np.arange(len(raw.ch_names))
149145
diff_picks = pick_types(raw.info, meg=False, ecg=ecg, eog=eog)
150146
these_picks = np.setdiff1d(picks, diff_picks)
@@ -159,68 +155,74 @@ def test_simulate_raw_sphere():
159155
# make sure it works with EEG-only and MEG-only
160156
raw_sim_meg = simulate_raw(raw.copy().pick_types(meg=True, eeg=False),
161157
stc, trans, src, sphere, cov=None,
162-
ecg=True, blink=True, random_state=seed,
163-
use_cps=True)
158+
ecg=True, blink=True, random_state=seed)
164159
raw_sim_eeg = simulate_raw(raw.copy().pick_types(meg=False, eeg=True),
165160
stc, trans, src, sphere, cov=None,
166-
ecg=True, blink=True, random_state=seed,
167-
use_cps=True)
161+
ecg=True, blink=True, random_state=seed)
168162
raw_sim_meeg = simulate_raw(raw.copy().pick_types(meg=True, eeg=True),
169163
stc, trans, src, sphere, cov=None,
170-
ecg=True, blink=True, random_state=seed,
171-
use_cps=True)
164+
ecg=True, blink=True, random_state=seed)
172165
assert_allclose(np.concatenate((raw_sim_meg[:][0], raw_sim_eeg[:][0])),
173166
raw_sim_meeg[:][0], rtol=1e-7, atol=1e-20)
174167
del raw_sim_meg, raw_sim_eeg, raw_sim_meeg
175168

169+
# check that raw-as-info is supported
170+
raw_sim = simulate_raw(raw, stc, trans, src, sphere, cov=None)
171+
n_samp = int(round(raw.info['sfreq']))
172+
for use_raw in (raw, raw.info):
173+
raw_sim_2 = simulate_raw(use_raw, stc, trans, src, sphere, cov=None,
174+
duration=1.)
175+
assert len(raw_sim_2.times) == n_samp
176+
assert_allclose(raw_sim[:, :n_samp][0],
177+
raw_sim_2[:, :n_samp][0], rtol=1e-5, atol=1e-30)
178+
del raw_sim, raw_sim_2
179+
176180
# check that different interpolations are similar given small movements
177181
raw_sim = simulate_raw(raw, stc, trans, src, sphere, cov=None,
178-
head_pos=head_pos_sim, interp='linear',
179-
use_cps=True)
182+
head_pos=head_pos_sim, interp='linear')
180183
raw_sim_hann = simulate_raw(raw, stc, trans, src, sphere, cov=None,
181-
head_pos=head_pos_sim, interp='hann',
182-
use_cps=True)
184+
head_pos=head_pos_sim, interp='hann')
183185
assert_allclose(raw_sim[:][0], raw_sim_hann[:][0], rtol=1e-1, atol=1e-14)
184186
del raw_sim, raw_sim_hann
185187

186188
# Make impossible transform (translate up into helmet) and ensure failure
187189
head_pos_sim_err = deepcopy(head_pos_sim)
188190
head_pos_sim_err[1.][2, 3] -= 0.1 # z trans upward 10cm
189191
pytest.raises(RuntimeError, simulate_raw, raw, stc, trans, src, sphere,
190-
ecg=False, blink=False, head_pos=head_pos_sim_err,
191-
use_cps=True)
192+
ecg=False, blink=False, head_pos=head_pos_sim_err)
192193
pytest.raises(RuntimeError, simulate_raw, raw, stc, trans, src,
193194
bem_fname, ecg=False, blink=False,
194-
head_pos=head_pos_sim_err, use_cps=True)
195+
head_pos=head_pos_sim_err)
195196
# other degenerate conditions
196-
pytest.raises(TypeError, simulate_raw, 'foo', stc, trans, src, sphere,
197-
use_cps=True)
198-
pytest.raises(TypeError, simulate_raw, raw, 'foo', trans, src, sphere,
199-
use_cps=True)
197+
pytest.raises(TypeError, simulate_raw, 'foo', stc, trans, src, sphere)
198+
pytest.raises(TypeError, simulate_raw, raw, 'foo', trans, src, sphere)
200199
pytest.raises(ValueError, simulate_raw, raw, stc.copy().crop(0, 0),
201-
trans, src, sphere, use_cps=True)
200+
trans, src, sphere)
201+
with pytest.raises(ValueError, match='duration cannot be None'):
202+
simulate_raw(raw.info, stc, trans, src, sphere)
203+
with pytest.raises(TypeError, match='must be an instance of Raw or Info'):
204+
simulate_raw(0, stc, trans, src, sphere)
202205
stc_bad = stc.copy()
203206
stc_bad.tstep += 0.1
204-
pytest.raises(ValueError, simulate_raw, raw, stc_bad, trans, src, sphere,
205-
use_cps=True)
207+
pytest.raises(ValueError, simulate_raw, raw, stc_bad, trans, src, sphere)
206208
pytest.raises(TypeError, simulate_raw, raw, stc, trans, src, sphere,
207-
cov=0, use_cps=True) # wrong covariance type
209+
cov=0) # wrong covariance type
208210
pytest.raises(RuntimeError, simulate_raw, raw, stc, trans, src, sphere,
209-
chpi=True, use_cps=True) # no cHPI info
211+
chpi=True) # no cHPI info
210212
pytest.raises(ValueError, simulate_raw, raw, stc, trans, src, sphere,
211-
interp='foo', use_cps=True)
213+
interp='foo')
212214
pytest.raises(TypeError, simulate_raw, raw, stc, trans, src, sphere,
213-
head_pos=1., use_cps=True)
215+
head_pos=1.)
214216
pytest.raises(RuntimeError, simulate_raw, raw, stc, trans, src, sphere,
215-
head_pos=pos_fname, use_cps=True) # ends up with t>t_end
217+
head_pos=pos_fname) # ends up with t>t_end
216218
head_pos_sim_err = deepcopy(head_pos_sim)
217219
head_pos_sim_err[-1.] = head_pos_sim_err[1.] # negative time
218220
pytest.raises(RuntimeError, simulate_raw, raw, stc, trans, src, sphere,
219-
head_pos=head_pos_sim_err, use_cps=True)
221+
head_pos=head_pos_sim_err)
220222
raw_bad = raw.copy()
221223
raw_bad.info['dig'] = None
222224
pytest.raises(RuntimeError, simulate_raw, raw_bad, stc, trans, src, sphere,
223-
blink=True, use_cps=True)
225+
blink=True)
224226

225227

226228
@pytest.mark.slowtest
@@ -238,10 +240,9 @@ def test_simulate_raw_bem():
238240
vertices = [s['vertno'] for s in src]
239241
stc = SourceEstimate(np.eye(sum(len(v) for v in vertices)), vertices,
240242
0, 1. / raw.info['sfreq'])
241-
raw_sim_sph = simulate_raw(raw, stc, trans, src, sphere, cov=None,
242-
use_cps=True)
243+
raw_sim_sph = simulate_raw(raw, stc, trans, src, sphere, cov=None)
243244
raw_sim_bem = simulate_raw(raw, stc, trans, src, bem_fname, cov=None,
244-
n_jobs=2, use_cps=True)
245+
n_jobs=2)
245246
# some components (especially radial) might not match that well,
246247
# so just make sure that most components have high correlation
247248
assert_array_equal(raw_sim_sph.ch_names, raw_sim_bem.ch_names)
@@ -335,11 +336,10 @@ def test_simulate_raw_chpi():
335336
stc = _make_stc(raw, src)
336337
# simulate data with cHPI on
337338
raw_sim = simulate_raw(raw, stc, None, src, sphere, cov=None, chpi=False,
338-
interp='zero', use_cps=True)
339+
interp='zero')
339340
# need to trim extra samples off this one
340341
raw_chpi = simulate_raw(raw, stc, None, src, sphere, cov=None, chpi=True,
341-
head_pos=pos_fname, interp='zero',
342-
use_cps=True)
342+
head_pos=pos_fname, interp='zero')
343343
# test cHPI indication
344344
hpi_freqs, hpi_pick, hpi_ons = _get_hpi_info(raw.info)
345345
assert_allclose(raw_sim[hpi_pick][0], 0.)

0 commit comments

Comments
 (0)