def run(): t0 = time.time() parser = get_optparser(__file__) parser.add_option("--raw", dest="raw_in", help="Input raw FIF file", metavar="FILE") parser.add_option("--pos", dest="pos", default=None, help="Position definition text file. Can be 'constant' " "to hold the head position fixed", metavar="FILE") parser.add_option("--dipoles", dest="dipoles", default=None, help="Dipole definition file", metavar="FILE") parser.add_option("--cov", dest="cov", help="Covariance to use for noise generation. Can be " "'simple' to use a diagonal covariance, or 'off' to " "omit noise", metavar="FILE", default='simple') parser.add_option("--duration", dest="duration", default=None, help="Duration of each epoch (sec). If omitted, the last" " time point in the dipole definition file plus 200 ms " "will be used", type="float") parser.add_option("-j", "--jobs", dest="n_jobs", help="Number of jobs to" " run in parallel", type="int", default=1) parser.add_option("--out", dest="raw_out", help="Output raw filename", metavar="FILE") parser.add_option("--plot-dipoles", dest="plot_dipoles", help="Plot " "input dipole positions", action="store_true") parser.add_option("--plot-raw", dest="plot_raw", help="Plot the resulting " "raw traces", action="store_true") parser.add_option("--plot-evoked", dest="plot_evoked", help="Plot evoked " "data", action="store_true") parser.add_option("-p", "--plot", dest="plot", help="Plot dipoles, raw, " "and evoked", action="store_true") parser.add_option("--overwrite", dest="overwrite", help="Overwrite the" "output file if it exists", action="store_true") options, args = parser.parse_args() raw_in = options.raw_in pos = options.pos raw_out = options.raw_out dipoles = options.dipoles n_jobs = options.n_jobs plot = options.plot plot_dipoles = options.plot_dipoles or plot plot_raw = options.plot_raw or plot plot_evoked = options.plot_evoked or plot overwrite = options.overwrite duration = options.duration cov = options.cov # check parameters if not (raw_out or plot_raw or plot_evoked): raise ValueError('data must either be saved (--out) or ' 'plotted (--plot-raw or --plot_evoked)') if raw_out and op.isfile(raw_out) and not overwrite: raise ValueError('output file exists, use --overwrite (%s)' % raw_out) if raw_in is None or pos is None or dipoles is None: parser.print_help() sys.exit(1) s = 'Simulate raw data with head movements' print('\n%s\n%s\n%s\n' % ('-' * len(s), s, '-' * len(s))) # setup the simulation with printer('Reading dipole definitions'): if not op.isfile(dipoles): raise IOError('dipole file not found:\n%s' % dipoles) dipoles = np.loadtxt(dipoles, skiprows=1, dtype=float) n_dipoles = dipoles.shape[0] if dipoles.shape[1] != 8: raise ValueError('dipoles must have 8 columns') rr = dipoles[:, :3] * 1e-3 nn = dipoles[:, 3:6] t = dipoles[:, 6:8] duration = t.max() + 0.2 if duration is None else duration if (t[:, 0] > t[:, 1]).any(): raise ValueError('found tmin > tmax in dipole file') if (t < 0).any(): raise ValueError('found t < 0 in dipole file') if (t > duration).any(): raise ValueError('found t > duration in dipole file') amp = np.sqrt(np.sum(nn * nn, axis=1)) * 1e-9 mne.surface._normalize_vectors(nn) nn[(nn == 0).all(axis=1)] = (1, 0, 0) src = mne.SourceSpaces([ dict(rr=rr, nn=nn, inuse=np.ones(n_dipoles, int), coord_frame=FIFF.FIFFV_COORD_HEAD) ]) for key in ['pinfo', 'nuse_tri', 'use_tris', 'patch_inds']: src[0][key] = None trans = { 'from': FIFF.FIFFV_COORD_HEAD, 'to': FIFF.FIFFV_COORD_MRI, 'trans': np.eye(4) } if (amp > 100e-9).any(): print('') warnings.warn('Largest dipole amplitude %0.1f > 100 nA' % (amp.max() * 1e9)) if pos == 'constant': print('Holding head position constant') pos = None else: with printer('Loading head positions'): pos = mne.get_chpi_positions(pos) with printer('Loading raw data file'): with warnings.catch_warnings(record=True): raw = mne.io.Raw(raw_in, preload=False, allow_maxshield=True, verbose=False) if cov == 'simple': print('Using diagonal covariance for brain noise') elif cov == 'off': print('Omitting brain noise in the simulation') cov = None else: with printer('Loading covariance file for brain noise'): cov = mne.read_cov(cov) with printer('Setting up spherical model'): bem = mne.bem.make_sphere_model('auto', 'auto', raw.info, verbose=False) # check that our sources are reasonable rad = bem['layers'][0]['rad'] r0 = bem['r0'] outside = np.sqrt(np.sum((rr - r0)**2, axis=1)) >= rad n_outside = outside.sum() if n_outside > 0: print('') raise ValueError( '%s dipole%s outside the spherical model, are your positions ' 'in mm?' % (n_outside, 's were' if n_outside != 1 else ' was')) with printer('Constructing source estimate'): tmids = t.mean(axis=1) t = np.round(t * raw.info['sfreq']).astype(int) t[:, 1] += 1 # make it inclusive n_samp = int(np.ceil(duration * raw.info['sfreq'])) data = np.zeros((n_dipoles, n_samp)) for di, (t_, amp_) in enumerate(zip(t, amp)): data[di, t_[0]:t_[1]] = amp_ * np.hanning(t_[1] - t_[0]) stc = mne.VolSourceEstimate(data, np.arange(n_dipoles), 0, 1. / raw.info['sfreq']) # do the simulation print('') raw_mv = simulate_raw(raw, stc, trans, src, bem, cov=cov, head_pos=pos, chpi=True, n_jobs=n_jobs, verbose=True) print('') if raw_out: with printer('Saving data'): raw_mv.save(raw_out, overwrite=overwrite) # plot results -- must be *after* save because we low-pass filter if plot_dipoles: with printer('Plotting dipoles'): fig, axs = plt.subplots(1, 3, figsize=(10, 3), facecolor='w') fig.canvas.set_window_title('Dipoles') meg_info = mne.pick_info( raw.info, mne.pick_types(raw.info, meg=True, eeg=False)) helmet_rr = [ ch['coil_trans'][:3, 3].copy() for ch in meg_info['chs'] ] helmet_nn = np.zeros_like(helmet_rr) helmet_nn[:, 2] = 1. surf = dict(rr=helmet_rr, nn=helmet_nn, coord_frame=FIFF.FIFFV_COORD_DEVICE) helmet_rr = mne.surface.transform_surface_to( surf, 'head', meg_info['dev_head_t'])['rr'] p = np.linspace(0, 2 * np.pi, 40) x_sphere, y_sphere = rad * np.sin(p), rad * np.cos(p) for ai, ax in enumerate(axs): others = np.setdiff1d(np.arange(3), [ai]) ax.plot(helmet_rr[:, others[0]], helmet_rr[:, others[1]], marker='o', linestyle='none', alpha=0.1, markeredgecolor='none', markerfacecolor='b', zorder=-2) ax.plot(x_sphere + r0[others[0]], y_sphere + r0[others[1]], color='y', alpha=0.25, zorder=-1) ax.quiver(rr[:, others[0]], rr[:, others[1]], amp * nn[:, others[0]], amp * nn[:, others[1]], angles='xy', units='x', color='k', alpha=0.5) ax.set_aspect('equal') ax.set_xlabel(' - ' + 'xyz'[others[0]] + ' + ') ax.set_ylabel(' - ' + 'xyz'[others[1]] + ' + ') ax.set_xticks([]) ax.set_yticks([]) plt.setp(list(ax.spines.values()), color='none') plt.tight_layout() if plot_raw or plot_evoked: with printer('Low-pass filtering simulated data'): events = mne.find_events(raw_mv, 'STI101', verbose=False) b, a = signal.butter(4, 40. / (raw.info['sfreq'] / 2.), 'low', analog=False) raw_mv.filter(None, 40., method='iir', iir_params=dict(b=b, a=a), verbose=False, n_jobs=n_jobs) if plot_raw: with printer('Plotting raw data'): raw_mv.plot(clipping='transparent', events=events, show=False) if plot_evoked: with printer('Plotting evoked data'): picks = mne.pick_types(raw_mv.info, meg=True, eeg=True) events[:, 2] = 1 evoked = mne.Epochs(raw_mv, events, { 'Simulated': 1 }, 0, duration, None, picks).average() evoked.plot_topomap(np.unique(tmids), show=False) print('\nTotal time: %0.1f sec' % (time.time() - t0)) sys.stdout.flush() if any([plot_dipoles, plot_raw, plot_evoked]): plt.show(block=True)
def run(): t0 = time.time() parser = get_optparser(__file__) parser.add_option("--raw", dest="raw_in", help="Input raw FIF file", metavar="FILE") parser.add_option("--pos", dest="pos", default=None, help="Position definition text file. Can be 'constant' " "to hold the head position fixed", metavar="FILE") parser.add_option("--dipoles", dest="dipoles", default=None, help="Dipole definition file", metavar="FILE") parser.add_option("--cov", dest="cov", help="Covariance to use for noise generation. Can be " "'simple' to use a diagonal covariance, or 'off' to " "omit noise", metavar="FILE", default='simple') parser.add_option("--duration", dest="duration", default=None, help="Duration of each epoch (sec). If omitted, the last" " time point in the dipole definition file plus 200 ms " "will be used", type="float") parser.add_option("-j", "--jobs", dest="n_jobs", help="Number of jobs to" " run in parallel", type="int", default=1) parser.add_option("--out", dest="raw_out", help="Output raw filename", metavar="FILE") parser.add_option("--plot-dipoles", dest="plot_dipoles", help="Plot " "input dipole positions", action="store_true") parser.add_option("--plot-raw", dest="plot_raw", help="Plot the resulting " "raw traces", action="store_true") parser.add_option("--plot-evoked", dest="plot_evoked", help="Plot evoked " "data", action="store_true") parser.add_option("-p", "--plot", dest="plot", help="Plot dipoles, raw, " "and evoked", action="store_true") parser.add_option("--overwrite", dest="overwrite", help="Overwrite the" "output file if it exists", action="store_true") options, args = parser.parse_args() raw_in = options.raw_in pos = options.pos raw_out = options.raw_out dipoles = options.dipoles n_jobs = options.n_jobs plot = options.plot plot_dipoles = options.plot_dipoles or plot plot_raw = options.plot_raw or plot plot_evoked = options.plot_evoked or plot overwrite = options.overwrite duration = options.duration cov = options.cov # check parameters if not (raw_out or plot_raw or plot_evoked): raise ValueError('data must either be saved (--out) or ' 'plotted (--plot-raw or --plot_evoked)') if raw_out and op.isfile(raw_out) and not overwrite: raise ValueError('output file exists, use --overwrite (%s)' % raw_out) if raw_in is None or pos is None or dipoles is None: parser.print_help() sys.exit(1) s = 'Simulate raw data with head movements' print('\n%s\n%s\n%s\n' % ('-' * len(s), s, '-' * len(s))) # setup the simulation with printer('Reading dipole definitions'): if not op.isfile(dipoles): raise IOError('dipole file not found:\n%s' % dipoles) dipoles = np.loadtxt(dipoles, skiprows=1, dtype=float) n_dipoles = dipoles.shape[0] if dipoles.shape[1] != 8: raise ValueError('dipoles must have 8 columns') rr = dipoles[:, :3] * 1e-3 nn = dipoles[:, 3:6] t = dipoles[:, 6:8] duration = t.max() + 0.2 if duration is None else duration if (t[:, 0] > t[:, 1]).any(): raise ValueError('found tmin > tmax in dipole file') if (t < 0).any(): raise ValueError('found t < 0 in dipole file') if (t > duration).any(): raise ValueError('found t > duration in dipole file') amp = np.sqrt(np.sum(nn * nn, axis=1)) * 1e-9 mne.surface._normalize_vectors(nn) nn[(nn == 0).all(axis=1)] = (1, 0, 0) src = mne.SourceSpaces([ dict(rr=rr, nn=nn, inuse=np.ones(n_dipoles, int), coord_frame=FIFF.FIFFV_COORD_HEAD)]) for key in ['pinfo', 'nuse_tri', 'use_tris', 'patch_inds']: src[0][key] = None trans = {'from': FIFF.FIFFV_COORD_HEAD, 'to': FIFF.FIFFV_COORD_MRI, 'trans': np.eye(4)} if (amp > 100e-9).any(): print('') warnings.warn('Largest dipole amplitude %0.1f > 100 nA' % (amp.max() * 1e9)) if pos == 'constant': print('Holding head position constant') pos = None else: with printer('Loading head positions'): pos = mne.get_chpi_positions(pos) with printer('Loading raw data file'): with warnings.catch_warnings(record=True): raw = mne.io.Raw(raw_in, preload=False, allow_maxshield=True, verbose=False) if cov == 'simple': print('Using diagonal covariance for brain noise') elif cov == 'off': print('Omitting brain noise in the simulation') cov = None else: with printer('Loading covariance file for brain noise'): cov = mne.read_cov(cov) with printer('Setting up spherical model'): bem = mne.bem.make_sphere_model('auto', 'auto', raw.info, verbose=False) # check that our sources are reasonable rad = bem['layers'][0]['rad'] r0 = bem['r0'] outside = np.sqrt(np.sum((rr - r0) ** 2, axis=1)) >= rad n_outside = outside.sum() if n_outside > 0: print('') raise ValueError( '%s dipole%s outside the spherical model, are your positions ' 'in mm?' % (n_outside, 's were' if n_outside != 1 else ' was')) with printer('Constructing source estimate'): tmids = t.mean(axis=1) t = np.round(t * raw.info['sfreq']).astype(int) t[:, 1] += 1 # make it inclusive n_samp = int(np.ceil(duration * raw.info['sfreq'])) data = np.zeros((n_dipoles, n_samp)) for di, (t_, amp_) in enumerate(zip(t, amp)): data[di, t_[0]:t_[1]] = amp_ * np.hanning(t_[1] - t_[0]) stc = mne.VolSourceEstimate(data, np.arange(n_dipoles), 0, 1. / raw.info['sfreq']) # do the simulation print('') raw_mv = simulate_raw(raw, stc, trans, src, bem, cov=cov, head_pos=pos, chpi=True, n_jobs=n_jobs, verbose=True) print('') if raw_out: with printer('Saving data'): raw_mv.save(raw_out, overwrite=overwrite) # plot results -- must be *after* save because we low-pass filter if plot_dipoles: with printer('Plotting dipoles'): fig, axs = plt.subplots(1, 3, figsize=(10, 3), facecolor='w') fig.canvas.set_window_title('Dipoles') meg_info = mne.pick_info(raw.info, mne.pick_types(raw.info, meg=True, eeg=False)) helmet_rr = [ch['coil_trans'][:3, 3].copy() for ch in meg_info['chs']] helmet_nn = np.zeros_like(helmet_rr) helmet_nn[:, 2] = 1. surf = dict(rr=helmet_rr, nn=helmet_nn, coord_frame=FIFF.FIFFV_COORD_DEVICE) helmet_rr = mne.surface.transform_surface_to( surf, 'head', meg_info['dev_head_t'])['rr'] p = np.linspace(0, 2 * np.pi, 40) x_sphere, y_sphere = rad * np.sin(p), rad * np.cos(p) for ai, ax in enumerate(axs): others = np.setdiff1d(np.arange(3), [ai]) ax.plot(helmet_rr[:, others[0]], helmet_rr[:, others[1]], marker='o', linestyle='none', alpha=0.1, markeredgecolor='none', markerfacecolor='b', zorder=-2) ax.plot(x_sphere + r0[others[0]], y_sphere + r0[others[1]], color='y', alpha=0.25, zorder=-1) ax.quiver(rr[:, others[0]], rr[:, others[1]], amp * nn[:, others[0]], amp * nn[:, others[1]], angles='xy', units='x', color='k', alpha=0.5) ax.set_aspect('equal') ax.set_xlabel(' - ' + 'xyz'[others[0]] + ' + ') ax.set_ylabel(' - ' + 'xyz'[others[1]] + ' + ') ax.set_xticks([]) ax.set_yticks([]) plt.setp(list(ax.spines.values()), color='none') plt.tight_layout() if plot_raw or plot_evoked: with printer('Low-pass filtering simulated data'): events = mne.find_events(raw_mv, 'STI101', verbose=False) b, a = signal.butter(4, 40. / (raw.info['sfreq'] / 2.), 'low', analog=False) raw_mv.filter(None, 40., method='iir', iir_params=dict(b=b, a=a), verbose=False, n_jobs=n_jobs) if plot_raw: with printer('Plotting raw data'): raw_mv.plot(clipping='transparent', events=events, show=False) if plot_evoked: with printer('Plotting evoked data'): picks = mne.pick_types(raw_mv.info, meg=True, eeg=True) events[:, 2] = 1 evoked = mne.Epochs(raw_mv, events, {'Simulated': 1}, 0, duration, None, picks).average() evoked.plot_topomap(np.unique(tmids), show=False) print('\nTotal time: %0.1f sec' % (time.time() - t0)) sys.stdout.flush() if any([plot_dipoles, plot_raw, plot_evoked]): plt.show(block=True)
# ############################################################################ # Simulate data # Simulate data with movement with warnings.catch_warnings(record=True): raw = Raw(fname_raw, allow_maxshield=True) raw_movement = simulate_movement(raw, fname_pos_orig, stc, trans, src, bem, interp='zero', n_jobs=6, verbose=True) # Simulate data with no movement (use initial head position) raw_stationary = simulate_movement(raw, None, stc, trans, src, bem, interp='zero', n_jobs=6, verbose=True) # Extract positions trans_move, rot_move, t_move = get_chpi_positions(fname_pos_move) trans_stat, rot_stat, t_stat = get_chpi_positions(fname_pos_stat) trans_orig, rot_orig, t_orig = get_chpi_positions(fname_pos_orig) # ############################################################################ # Let's look at the results, just translation for simplicity axes = 'XYZ' fig = plt.figure(dpi=200) ts = [t_orig, t_stat, t_move] transs = [trans_orig, trans_stat, trans_move] labels = ['original', 'stationary', 'simulated'] sizes = [10, 5, 5] colors = 'kyr' for ai, axis in enumerate(axes): ax = plt.subplot(3, 1, ai + 1)
def simulate_movement(raw, pos, stc, trans, src, bem, cov='simple', mindist=1.0, interp='linear', random_state=None, n_jobs=1, verbose=None): """Simulate raw data with head movements Parameters ---------- raw : instance of Raw The raw instance to use. The measurement info, including the head positions, will be used to simulate data. pos : str | dict | None Name of the position estimates file. Should be in the format of the files produced by maxfilter-produced. If dict, keys should be the time points and entries should be 4x3 ``dev_head_t`` matrices. If None, the original head position (from ``raw.info['dev_head_t']``) will be used. stc : instance of SourceEstimate The source estimate to use to simulate data. Must have the same sample rate as the raw data. trans : dict | str Either a transformation filename (usually made using mne_analyze) or an info dict (usually opened using read_trans()). If string, an ending of `.fif` or `.fif.gz` will be assumed to be in FIF format, any other ending will be assumed to be a text file with a 4x4 transformation matrix (like the `--trans` MNE-C option). src : str | instance of SourceSpaces If string, should be a source space filename. Can also be an instance of loaded or generated SourceSpaces. bem : str Filename of the BEM (e.g., "sample-5120-5120-5120-bem-sol.fif"). cov : instance of Covariance | 'simple' | None The sensor covariance matrix used to generate noise. If None, no noise will be added. If 'simple', a basic (diagonal) ad-hoc noise covariance will be used. mindist : float Minimum distance between sources and the inner skull boundary to use during forward calculation. interp : str Either 'linear' or 'zero', the type of forward-solution interpolation to use between provided time points. random_state : None | int | np.random.RandomState To specify the random generator state. n_jobs : int Number of jobs to use. verbose : bool, str, int, or None If not None, override default verbose level (see mne.verbose). Returns ------- raw : instance of Raw The simulated raw file. Notes ----- Events coded with the number of the forward solution used will be placed in the raw files in the trigger channel STI101 at the t=0 times of the SourceEstimates. The resulting SNR will be determined by the structure of the noise covariance, and the amplitudes of the SourceEstimate. Note that this will vary as a function of position. """ if isinstance(raw, string_types): with warnings.catch_warnings(record=True): raw = Raw(raw, allow_maxshield=True, preload=True, verbose=False) else: raw = raw.copy() if not isinstance(stc, _BaseSourceEstimate): raise TypeError('stc must be a SourceEstimate') if not np.allclose(raw.info['sfreq'], 1. / stc.tstep): raise ValueError('stc and raw must have same sample rate') rng = check_random_state(random_state) if interp not in ('linear', 'zero'): raise ValueError('interp must be "linear" or "zero"') if pos is None: # use pos from file dev_head_ts = [raw.info['dev_head_t']] * 2 offsets = np.array([0, raw.n_times]) interp = 'zero' else: if isinstance(pos, string_types): pos = get_chpi_positions(pos, verbose=False) if isinstance(pos, tuple): # can be an already-loaded pos file transs, rots, ts = pos ts -= raw.first_samp / raw.info['sfreq'] # MF files need reref dev_head_ts = [np.r_[np.c_[r, t[:, np.newaxis]], [[0, 0, 0, 1]]] for r, t in zip(rots, transs)] del transs, rots elif isinstance(pos, dict): ts = np.array(list(pos.keys()), float) ts.sort() dev_head_ts = [pos[float(tt)] for tt in ts] else: raise TypeError('unknown pos type %s' % type(pos)) if not (ts >= 0).all(): # pathological if not raise RuntimeError('Cannot have t < 0 in transform file') tend = raw.times[-1] assert not (ts < 0).any() assert not (ts > tend).any() if ts[0] > 0: ts = np.r_[[0.], ts] dev_head_ts.insert(0, raw.info['dev_head_t']['trans']) dev_head_ts = [{'trans': d, 'to': raw.info['dev_head_t']['to'], 'from': raw.info['dev_head_t']['from']} for d in dev_head_ts] if ts[-1] < tend: dev_head_ts.append(dev_head_ts[-1]) ts = np.r_[ts, [tend]] offsets = raw.time_as_index(ts) offsets[-1] = raw.n_times # fix for roundoff error assert offsets[-2] != offsets[-1] del ts if isinstance(cov, string_types): assert cov == 'simple' cov = make_ad_hoc_cov(raw.info, verbose=False) assert np.array_equal(offsets, np.unique(offsets)) assert len(offsets) == len(dev_head_ts) approx_events = int((raw.n_times / raw.info['sfreq']) / (stc.times[-1] - stc.times[0])) logger.info('Provided parameters will provide approximately %s event%s' % (approx_events, '' if approx_events == 1 else 's')) # get HPI freqs and reorder hpi_freqs = np.array([x['custom_ref'][0] for x in raw.info['hpi_meas'][0]['hpi_coils']]) n_freqs = len(hpi_freqs) order = [x['number'] - 1 for x in raw.info['hpi_meas'][0]['hpi_coils']] assert np.array_equal(np.unique(order), np.arange(n_freqs)) hpi_freqs = hpi_freqs[order] hpi_order = raw.info['hpi_results'][0]['order'] - 1 assert np.array_equal(np.unique(hpi_order), np.arange(n_freqs)) hpi_freqs = hpi_freqs[hpi_order] # extract necessary info picks = pick_types(raw.info, meg=True, eeg=True) # for simulation meg_picks = pick_types(raw.info, meg=True, eeg=False) # for CHPI fwd_info = pick_info(raw.info, picks) fwd_info['projs'] = [] logger.info('Setting up raw data simulation using %s head position%s' % (len(dev_head_ts), 's' if len(dev_head_ts) != 1 else '')) raw.preload_data(verbose=False) if isinstance(stc, VolSourceEstimate): verts = [stc.vertices] else: verts = stc.vertices src = _restrict_source_space_to(src, verts) # figure out our cHPI, ECG, and EOG dipoles dig = raw.info['dig'] assert all([d['coord_frame'] == FIFF.FIFFV_COORD_HEAD for d in dig if d['kind'] == FIFF.FIFFV_POINT_HPI]) chpi_rrs = [d['r'] for d in dig if d['kind'] == FIFF.FIFFV_POINT_HPI] R, r0 = fit_sphere_to_headshape(raw.info, verbose=False)[:2] R /= 1000. r0 /= 1000. ecg_rr = np.array([[-R, 0, -3 * R]]) eog_rr = [d['r'] for d in raw.info['dig'] if d['ident'] == FIFF.FIFFV_POINT_NASION][0] eog_rr = eog_rr - r0 eog_rr = (eog_rr / np.sqrt(np.sum(eog_rr * eog_rr)) * 0.98 * R)[np.newaxis, :] eog_rr += r0 eog_bem = make_sphere_model(r0, head_radius=R, relative_radii=(0.99, 1.), sigmas=(0.33, 0.33), verbose=False) # let's oscillate between resting (17 bpm) and reading (4.5 bpm) rate # http://www.ncbi.nlm.nih.gov/pubmed/9399231 blink_rate = np.cos(2 * np.pi * 1. / 60. * raw.times) blink_rate *= 12.5 / 60. blink_rate += 4.5 / 60. blink_data = rng.rand(raw.n_times) < blink_rate / raw.info['sfreq'] blink_data = blink_data * (rng.rand(raw.n_times) + 0.5) # vary amplitudes blink_kernel = np.hanning(int(0.25 * raw.info['sfreq'])) eog_data = np.convolve(blink_data, blink_kernel, 'same')[np.newaxis, :] eog_data += rng.randn(eog_data.shape[1]) * 0.05 eog_data *= 100e-6 del blink_data, max_beats = int(np.ceil(raw.times[-1] * 70. / 60.)) cardiac_idx = np.cumsum(rng.uniform(60. / 70., 60. / 50., max_beats) * raw.info['sfreq']).astype(int) cardiac_idx = cardiac_idx[cardiac_idx < raw.n_times] cardiac_data = np.zeros(raw.n_times) cardiac_data[cardiac_idx] = 1 cardiac_kernel = np.concatenate([ 2 * np.hanning(int(0.04 * raw.info['sfreq'])), -0.3 * np.hanning(int(0.05 * raw.info['sfreq'])), 0.2 * np.hanning(int(0.26 * raw.info['sfreq']))], axis=-1) ecg_data = np.convolve(cardiac_data, cardiac_kernel, 'same')[np.newaxis, :] ecg_data += rng.randn(ecg_data.shape[1]) * 0.05 ecg_data *= 3e-4 del cardiac_data # Add to data file, then rescale for simulation for data, scale, exg_ch in zip([eog_data, ecg_data], [1e-3, 5e-4], ['EOG062', 'ECG063']): ch = pick_channels(raw.ch_names, [exg_ch]) if len(ch) == 1: raw._data[ch[0], :] = data data *= scale evoked = EvokedArray(np.zeros((len(picks), len(stc.times))), fwd_info, stc.tmin, verbose=False) stc_event_idx = np.argmin(np.abs(stc.times)) event_ch = pick_channels(raw.info['ch_names'], ['STI101'])[0] used = np.zeros(raw.n_times, bool) stc_indices = np.arange(raw.n_times) % len(stc.times) raw._data[event_ch, ].fill(0) hpi_mag = 25e-9 last_fwd = last_fwd_chpi = last_fwd_eog = last_fwd_ecg = src_sel = None for fi, (fwd, fwd_eog, fwd_ecg, fwd_chpi) in \ enumerate(_make_forward_solutions( fwd_info, trans, src, bem, eog_bem, dev_head_ts, mindist, chpi_rrs, eog_rr, ecg_rr, n_jobs)): # must be fixed orientation fwd = convert_forward_solution(fwd, surf_ori=True, force_fixed=True, verbose=False) # just use one arbitrary direction fwd_eog = fwd_eog['sol']['data'][:, ::3] fwd_ecg = fwd_ecg['sol']['data'][:, ::3] fwd_chpi = fwd_chpi[:, ::3] if src_sel is None: src_sel = _stc_src_sel(fwd['src'], stc) if isinstance(stc, VolSourceEstimate): verts = [stc.vertices] else: verts = stc.vertices diff_ = sum([len(v) for v in verts]) - len(src_sel) if diff_ != 0: warnings.warn('%s STC vertices omitted due to fwd calculation' % (diff_,)) if last_fwd is None: last_fwd, last_fwd_eog, last_fwd_ecg, last_fwd_chpi = \ fwd, fwd_eog, fwd_ecg, fwd_chpi continue n_time = offsets[fi] - offsets[fi-1] time_slice = slice(offsets[fi-1], offsets[fi]) assert not used[time_slice].any() stc_idxs = stc_indices[time_slice] event_idxs = np.where(stc_idxs == stc_event_idx)[0] + offsets[fi-1] used[time_slice] = True logger.info(' Simulating data for %0.3f-%0.3f sec with %s event%s' % (tuple(offsets[fi-1:fi+1] / raw.info['sfreq']) + (len(event_idxs), '' if len(event_idxs) == 1 else 's'))) # simulate brain data stc_data = stc.data[:, stc_idxs][src_sel] data = _interp(last_fwd['sol']['data'], fwd['sol']['data'], stc_data, interp) simulated = EvokedArray(data, evoked.info, 0) if cov is not None: noise = generate_noise_evoked(simulated, cov, [1, -1, 0.2], rng) simulated.data += noise.data assert simulated.data.shape[0] == len(picks) assert simulated.data.shape[1] == len(stc_idxs) raw._data[picks, time_slice] = simulated.data # add ECG, EOG, and CHPI traces raw._data[picks, time_slice] += \ _interp(last_fwd_eog, fwd_eog, eog_data[:, time_slice], interp) raw._data[meg_picks, time_slice] += \ _interp(last_fwd_ecg, fwd_ecg, ecg_data[:, time_slice], interp) this_t = np.arange(offsets[fi-1], offsets[fi]) / raw.info['sfreq'] sinusoids = np.zeros((n_freqs, n_time)) for fi, freq in enumerate(hpi_freqs): sinusoids[fi] = 2 * np.pi * freq * this_t sinusoids[fi] = hpi_mag * np.sin(sinusoids[fi]) raw._data[meg_picks, time_slice] += \ _interp(last_fwd_chpi, fwd_chpi, sinusoids, interp) # add events raw._data[event_ch, event_idxs] = fi # prepare for next iteration last_fwd, last_fwd_eog, last_fwd_ecg, last_fwd_chpi = \ fwd, fwd_eog, fwd_ecg, fwd_chpi assert used.all() logger.info('Done') return raw
head_pos=fname_pos_orig, n_jobs=6, verbose=True) # Simulate data with no movement (use initial head position) raw_stationary = simulate_raw(raw, stc, trans, src, bem, chpi=True, n_jobs=6, verbose=True) # Extract positions trans_orig, rot_orig, t_orig = get_chpi_positions(fname_pos_orig) t_orig -= raw.first_samp / raw.info['sfreq'] trans_move, rot_move, t_move = _calculate_chpi_positions(raw_movement) trans_stat, rot_stat, t_stat = _calculate_chpi_positions(raw_stationary) # ############################################################################ # Let's look at the results, just translation for simplicity axes = 'XYZ' fig = plt.figure(dpi=200) ts = [t_orig, t_stat, t_move] transs = [trans_orig, trans_stat, trans_move] labels = ['original', 'stationary', 'simulated'] sizes = [10, 5, 5] colors = 'kyr' for ai, axis in enumerate(axes):
n_jobs=6, verbose=True) # Simulate data with no movement (use initial head position) raw_stationary = simulate_movement(raw, None, stc, trans, src, bem, interp='zero', n_jobs=6, verbose=True) # Extract positions trans_move, rot_move, t_move = get_chpi_positions(fname_pos_move) trans_stat, rot_stat, t_stat = get_chpi_positions(fname_pos_stat) trans_orig, rot_orig, t_orig = get_chpi_positions(fname_pos_orig) # ############################################################################ # Let's look at the results, just translation for simplicity axes = 'XYZ' fig = plt.figure(dpi=200) ts = [t_orig, t_stat, t_move] transs = [trans_orig, trans_stat, trans_move] labels = ['original', 'stationary', 'simulated'] sizes = [10, 5, 5] colors = 'kyr' for ai, axis in enumerate(axes): ax = plt.subplot(3, 1, ai + 1)
def simulate_movement(raw, pos, stc, trans, src, bem, cov='simple', mindist=1.0, interp='linear', random_state=None, n_jobs=1, verbose=None): """Simulate raw data with head movements Parameters ---------- raw : instance of Raw The raw instance to use. The measurement info, including the head positions, will be used to simulate data. pos : str | dict | None Name of the position estimates file. Should be in the format of the files produced by maxfilter-produced. If dict, keys should be the time points and entries should be 4x3 ``dev_head_t`` matrices. If None, the original head position (from ``raw.info['dev_head_t']``) will be used. stc : instance of SourceEstimate The source estimate to use to simulate data. Must have the same sample rate as the raw data. trans : dict | str Either a transformation filename (usually made using mne_analyze) or an info dict (usually opened using read_trans()). If string, an ending of `.fif` or `.fif.gz` will be assumed to be in FIF format, any other ending will be assumed to be a text file with a 4x4 transformation matrix (like the `--trans` MNE-C option). src : str | instance of SourceSpaces If string, should be a source space filename. Can also be an instance of loaded or generated SourceSpaces. bem : str Filename of the BEM (e.g., "sample-5120-5120-5120-bem-sol.fif"). cov : instance of Covariance | 'simple' | None The sensor covariance matrix used to generate noise. If None, no noise will be added. If 'simple', a basic (diagonal) ad-hoc noise covariance will be used. mindist : float Minimum distance between sources and the inner skull boundary to use during forward calculation. interp : str Either 'linear' or 'zero', the type of forward-solution interpolation to use between provided time points. random_state : None | int | np.random.RandomState To specify the random generator state. n_jobs : int Number of jobs to use. verbose : bool, str, int, or None If not None, override default verbose level (see mne.verbose). Returns ------- raw : instance of Raw The simulated raw file. Notes ----- Events coded with the number of the forward solution used will be placed in the raw files in the trigger channel STI101 at the t=0 times of the SourceEstimates. The resulting SNR will be determined by the structure of the noise covariance, and the amplitudes of the SourceEstimate. Note that this will vary as a function of position. """ if isinstance(raw, string_types): with warnings.catch_warnings(record=True): raw = Raw(raw, allow_maxshield=True, preload=True, verbose=False) else: raw = raw.copy() if not isinstance(stc, _BaseSourceEstimate): raise TypeError('stc must be a SourceEstimate') if not np.allclose(raw.info['sfreq'], 1. / stc.tstep): raise ValueError('stc and raw must have same sample rate') rng = check_random_state(random_state) if interp not in ('linear', 'zero'): raise ValueError('interp must be "linear" or "zero"') if pos is None: # use pos from file dev_head_ts = [raw.info['dev_head_t']] * 2 offsets = np.array([0, raw.n_times]) interp = 'zero' else: if isinstance(pos, string_types): pos = get_chpi_positions(pos, verbose=False) if isinstance(pos, tuple): # can be an already-loaded pos file transs, rots, ts = pos ts -= raw.first_samp / raw.info['sfreq'] # MF files need reref dev_head_ts = [ np.r_[np.c_[r, t[:, np.newaxis]], [[0, 0, 0, 1]]] for r, t in zip(rots, transs) ] del transs, rots elif isinstance(pos, dict): ts = np.array(list(pos.keys()), float) ts.sort() dev_head_ts = [pos[float(tt)] for tt in ts] else: raise TypeError('unknown pos type %s' % type(pos)) if not (ts >= 0).all(): # pathological if not raise RuntimeError('Cannot have t < 0 in transform file') tend = raw.times[-1] assert not (ts < 0).any() assert not (ts > tend).any() if ts[0] > 0: ts = np.r_[[0.], ts] dev_head_ts.insert(0, raw.info['dev_head_t']['trans']) dev_head_ts = [{ 'trans': d, 'to': raw.info['dev_head_t']['to'], 'from': raw.info['dev_head_t']['from'] } for d in dev_head_ts] if ts[-1] < tend: dev_head_ts.append(dev_head_ts[-1]) ts = np.r_[ts, [tend]] offsets = raw.time_as_index(ts) offsets[-1] = raw.n_times # fix for roundoff error assert offsets[-2] != offsets[-1] del ts if isinstance(cov, string_types): assert cov == 'simple' cov = make_ad_hoc_cov(raw.info, verbose=False) assert np.array_equal(offsets, np.unique(offsets)) assert len(offsets) == len(dev_head_ts) approx_events = int( (raw.n_times / raw.info['sfreq']) / (stc.times[-1] - stc.times[0])) logger.info('Provided parameters will provide approximately %s event%s' % (approx_events, '' if approx_events == 1 else 's')) # get HPI freqs and reorder hpi_freqs = np.array( [x['custom_ref'][0] for x in raw.info['hpi_meas'][0]['hpi_coils']]) n_freqs = len(hpi_freqs) order = [x['number'] - 1 for x in raw.info['hpi_meas'][0]['hpi_coils']] assert np.array_equal(np.unique(order), np.arange(n_freqs)) hpi_freqs = hpi_freqs[order] hpi_order = raw.info['hpi_results'][0]['order'] - 1 assert np.array_equal(np.unique(hpi_order), np.arange(n_freqs)) hpi_freqs = hpi_freqs[hpi_order] # extract necessary info picks = pick_types(raw.info, meg=True, eeg=True) # for simulation meg_picks = pick_types(raw.info, meg=True, eeg=False) # for CHPI fwd_info = pick_info(raw.info, picks) fwd_info['projs'] = [] logger.info('Setting up raw data simulation using %s head position%s' % (len(dev_head_ts), 's' if len(dev_head_ts) != 1 else '')) raw.preload_data(verbose=False) if isinstance(stc, VolSourceEstimate): verts = [stc.vertices] else: verts = stc.vertices src = _restrict_source_space_to(src, verts) # figure out our cHPI, ECG, and EOG dipoles dig = raw.info['dig'] assert all([ d['coord_frame'] == FIFF.FIFFV_COORD_HEAD for d in dig if d['kind'] == FIFF.FIFFV_POINT_HPI ]) chpi_rrs = [d['r'] for d in dig if d['kind'] == FIFF.FIFFV_POINT_HPI] R, r0 = fit_sphere_to_headshape(raw.info, verbose=False)[:2] R /= 1000. r0 /= 1000. ecg_rr = np.array([[-R, 0, -3 * R]]) eog_rr = [ d['r'] for d in raw.info['dig'] if d['ident'] == FIFF.FIFFV_POINT_NASION ][0] eog_rr = eog_rr - r0 eog_rr = (eog_rr / np.sqrt(np.sum(eog_rr * eog_rr)) * 0.98 * R)[np.newaxis, :] eog_rr += r0 eog_bem = make_sphere_model(r0, head_radius=R, relative_radii=(0.99, 1.), sigmas=(0.33, 0.33), verbose=False) # let's oscillate between resting (17 bpm) and reading (4.5 bpm) rate # http://www.ncbi.nlm.nih.gov/pubmed/9399231 blink_rate = np.cos(2 * np.pi * 1. / 60. * raw.times) blink_rate *= 12.5 / 60. blink_rate += 4.5 / 60. blink_data = rng.rand(raw.n_times) < blink_rate / raw.info['sfreq'] blink_data = blink_data * (rng.rand(raw.n_times) + 0.5) # vary amplitudes blink_kernel = np.hanning(int(0.25 * raw.info['sfreq'])) eog_data = np.convolve(blink_data, blink_kernel, 'same')[np.newaxis, :] eog_data += rng.randn(eog_data.shape[1]) * 0.05 eog_data *= 100e-6 del blink_data, max_beats = int(np.ceil(raw.times[-1] * 70. / 60.)) cardiac_idx = np.cumsum( rng.uniform(60. / 70., 60. / 50., max_beats) * raw.info['sfreq']).astype(int) cardiac_idx = cardiac_idx[cardiac_idx < raw.n_times] cardiac_data = np.zeros(raw.n_times) cardiac_data[cardiac_idx] = 1 cardiac_kernel = np.concatenate([ 2 * np.hanning(int(0.04 * raw.info['sfreq'])), -0.3 * np.hanning(int(0.05 * raw.info['sfreq'])), 0.2 * np.hanning(int(0.26 * raw.info['sfreq'])) ], axis=-1) ecg_data = np.convolve(cardiac_data, cardiac_kernel, 'same')[np.newaxis, :] ecg_data += rng.randn(ecg_data.shape[1]) * 0.05 ecg_data *= 3e-4 del cardiac_data # Add to data file, then rescale for simulation for data, scale, exg_ch in zip([eog_data, ecg_data], [1e-3, 5e-4], ['EOG062', 'ECG063']): ch = pick_channels(raw.ch_names, [exg_ch]) if len(ch) == 1: raw._data[ch[0], :] = data data *= scale evoked = EvokedArray(np.zeros((len(picks), len(stc.times))), fwd_info, stc.tmin, verbose=False) stc_event_idx = np.argmin(np.abs(stc.times)) event_ch = pick_channels(raw.info['ch_names'], ['STI101'])[0] used = np.zeros(raw.n_times, bool) stc_indices = np.arange(raw.n_times) % len(stc.times) raw._data[event_ch, ].fill(0) hpi_mag = 25e-9 last_fwd = last_fwd_chpi = last_fwd_eog = last_fwd_ecg = src_sel = None for fi, (fwd, fwd_eog, fwd_ecg, fwd_chpi) in \ enumerate(_make_forward_solutions( fwd_info, trans, src, bem, eog_bem, dev_head_ts, mindist, chpi_rrs, eog_rr, ecg_rr, n_jobs)): # must be fixed orientation fwd = convert_forward_solution(fwd, surf_ori=True, force_fixed=True, verbose=False) # just use one arbitrary direction fwd_eog = fwd_eog['sol']['data'][:, ::3] fwd_ecg = fwd_ecg['sol']['data'][:, ::3] fwd_chpi = fwd_chpi[:, ::3] if src_sel is None: src_sel = _stc_src_sel(fwd['src'], stc) if isinstance(stc, VolSourceEstimate): verts = [stc.vertices] else: verts = stc.vertices diff_ = sum([len(v) for v in verts]) - len(src_sel) if diff_ != 0: warnings.warn( '%s STC vertices omitted due to fwd calculation' % (diff_, )) if last_fwd is None: last_fwd, last_fwd_eog, last_fwd_ecg, last_fwd_chpi = \ fwd, fwd_eog, fwd_ecg, fwd_chpi continue n_time = offsets[fi] - offsets[fi - 1] time_slice = slice(offsets[fi - 1], offsets[fi]) assert not used[time_slice].any() stc_idxs = stc_indices[time_slice] event_idxs = np.where(stc_idxs == stc_event_idx)[0] + offsets[fi - 1] used[time_slice] = True logger.info(' Simulating data for %0.3f-%0.3f sec with %s event%s' % (tuple(offsets[fi - 1:fi + 1] / raw.info['sfreq']) + (len(event_idxs), '' if len(event_idxs) == 1 else 's'))) # simulate brain data stc_data = stc.data[:, stc_idxs][src_sel] data = _interp(last_fwd['sol']['data'], fwd['sol']['data'], stc_data, interp) simulated = EvokedArray(data, evoked.info, 0) if cov is not None: noise = generate_noise_evoked(simulated, cov, [1, -1, 0.2], rng) simulated.data += noise.data assert simulated.data.shape[0] == len(picks) assert simulated.data.shape[1] == len(stc_idxs) raw._data[picks, time_slice] = simulated.data # add ECG, EOG, and CHPI traces raw._data[picks, time_slice] += \ _interp(last_fwd_eog, fwd_eog, eog_data[:, time_slice], interp) raw._data[meg_picks, time_slice] += \ _interp(last_fwd_ecg, fwd_ecg, ecg_data[:, time_slice], interp) this_t = np.arange(offsets[fi - 1], offsets[fi]) / raw.info['sfreq'] sinusoids = np.zeros((n_freqs, n_time)) for fi, freq in enumerate(hpi_freqs): sinusoids[fi] = 2 * np.pi * freq * this_t sinusoids[fi] = hpi_mag * np.sin(sinusoids[fi]) raw._data[meg_picks, time_slice] += \ _interp(last_fwd_chpi, fwd_chpi, sinusoids, interp) # add events raw._data[event_ch, event_idxs] = fi # prepare for next iteration last_fwd, last_fwd_eog, last_fwd_ecg, last_fwd_chpi = \ fwd, fwd_eog, fwd_ecg, fwd_chpi assert used.all() logger.info('Done') return raw
stc.data.fill(0) stc.data[:, (stc.times >= pulse_tmin) & (stc.times <= pulse_tmax)] = 10e-9 # ############################################################################ # Simulate data # Simulate data with movement with warnings.catch_warnings(record=True): raw = Raw(fname_raw, allow_maxshield=True) raw_movement = simulate_raw(raw, stc, trans, src, bem, chpi=True, head_pos=fname_pos_orig, n_jobs=6, verbose=True) # Simulate data with no movement (use initial head position) raw_stationary = simulate_raw(raw, stc, trans, src, bem, chpi=True, n_jobs=6, verbose=True) # Extract positions trans_orig, rot_orig, t_orig = get_chpi_positions(fname_pos_orig) t_orig -= raw.first_samp / raw.info["sfreq"] trans_move, rot_move, t_move = _calculate_chpi_positions(raw_movement) trans_stat, rot_stat, t_stat = _calculate_chpi_positions(raw_stationary) # ############################################################################ # Let's look at the results, just translation for simplicity axes = "XYZ" fig = plt.figure(dpi=200) ts = [t_orig, t_stat, t_move] transs = [trans_orig, trans_stat, trans_move] labels = ["original", "stationary", "simulated"] sizes = [10, 5, 5] colors = "kyr" for ai, axis in enumerate(axes):