def test_auto_topomap_coords(): """Test mapping of coordinates in 3D space to 2D""" info = Raw(fif_fname).info.copy() picks = pick_types(info, meg=False, eeg=True, eog=False, stim=False) # Remove extra digitization point, so EEG digitization points match up # with the EEG channels del info['dig'][85] # Remove head origin from channel locations, so mapping with digitization # points yields the same result dig_kinds = (FIFF.FIFFV_POINT_CARDINAL, FIFF.FIFFV_POINT_EEG, FIFF.FIFFV_POINT_EXTRA) _, origin_head, _ = fit_sphere_to_headshape(info, dig_kinds, units='m') for ch in info['chs']: ch['loc'][:3] -= origin_head # Use channel locations l0 = _auto_topomap_coords(info, picks) # Remove electrode position information, use digitization points from now # on. for ch in info['chs']: ch['loc'].fill(0) l1 = _auto_topomap_coords(info, picks) assert_allclose(l1, l0, atol=1e-3) # Test plotting mag topomap without channel locations: it should fail mag_picks = pick_types(info, meg='mag') assert_raises(ValueError, _auto_topomap_coords, info, mag_picks) # Test function with too many EEG digitization points: it should fail info['dig'].append({'r': [1, 2, 3], 'kind': FIFF.FIFFV_POINT_EEG}) assert_raises(ValueError, _auto_topomap_coords, info, picks) # Test function with too little EEG digitization points: it should fail info['dig'] = info['dig'][:-2] assert_raises(ValueError, _auto_topomap_coords, info, picks) # Electrode positions must be unique info['dig'].append(info['dig'][-1]) assert_raises(ValueError, _auto_topomap_coords, info, picks) # Test function without EEG digitization points: it should fail info['dig'] = [d for d in info['dig'] if d['kind'] != FIFF.FIFFV_POINT_EEG] assert_raises(RuntimeError, _auto_topomap_coords, info, picks) # Test function without any digitization points, it should fail info['dig'] = None assert_raises(RuntimeError, _auto_topomap_coords, info, picks) info['dig'] = [] assert_raises(RuntimeError, _auto_topomap_coords, info, picks)
def test_auto_topomap_coords(): """Test mapping of coordinates in 3D space to 2D.""" info = read_info(fif_fname) picks = pick_types(info, meg=False, eeg=True, eog=False, stim=False) # Remove extra digitization point, so EEG digitization points match up # with the EEG channels del info['dig'][85] # Remove head origin from channel locations, so mapping with digitization # points yields the same result dig_kinds = (FIFF.FIFFV_POINT_CARDINAL, FIFF.FIFFV_POINT_EEG, FIFF.FIFFV_POINT_EXTRA) _, origin_head, _ = fit_sphere_to_headshape(info, dig_kinds, units='m') for ch in info['chs']: ch['loc'][:3] -= origin_head # Use channel locations l0 = _auto_topomap_coords(info, picks) # Remove electrode position information, use digitization points from now # on. for ch in info['chs']: ch['loc'].fill(np.nan) l1 = _auto_topomap_coords(info, picks) assert_allclose(l1, l0, atol=1e-3) # Test plotting mag topomap without channel locations: it should fail mag_picks = pick_types(info, meg='mag') pytest.raises(ValueError, _auto_topomap_coords, info, mag_picks) # Test function with too many EEG digitization points: it should fail info['dig'].append({'r': [1, 2, 3], 'kind': FIFF.FIFFV_POINT_EEG}) pytest.raises(ValueError, _auto_topomap_coords, info, picks) # Test function with too little EEG digitization points: it should fail info['dig'] = info['dig'][:-2] pytest.raises(ValueError, _auto_topomap_coords, info, picks) # Electrode positions must be unique info['dig'].append(info['dig'][-1]) pytest.raises(ValueError, _auto_topomap_coords, info, picks) # Test function without EEG digitization points: it should fail info['dig'] = [d for d in info['dig'] if d['kind'] != FIFF.FIFFV_POINT_EEG] pytest.raises(RuntimeError, _auto_topomap_coords, info, picks) # Test function without any digitization points, it should fail info['dig'] = None pytest.raises(RuntimeError, _auto_topomap_coords, info, picks) info['dig'] = [] pytest.raises(RuntimeError, _auto_topomap_coords, info, picks)
def build_cmd(self, in_fname, out_fname, origin='0 0 40', frame='head', bad=None, autobad='off', skip=None, force=False, st=False, st_buflen=16.0, st_corr=0.96, trans=None, movecomp=False, headpos=False, hp=None, hpistep=None, hpisubt=None, hpicons=True, linefreq=None, cal=None, ctc=None, mx_args='', maxfilter_bin='/neuro/bin/util/maxfilter', logfile=None, n_threads=4): """Build a NeuroMag MaxFilter command for later execution. See the Maxfilter manual for details on the different options! Things to implement * check that cal-file matches date in infile! * check that maxfilter binary is OK Parameters ---------- in_fname : str Input file name out_fname : str Output file name n_threads : int Number of parallel threads to execute on (default: 4) maxfilter_bin : str Full path to the maxfilter-executable logfile : str Full path to the output logfile force : bool Overwrite existing output (default: False) origin : array-like or str Head origin in mm. If None it will be estimated from headshape points. frame : str ('device' or 'head') Coordinate frame for head center bad : str, list (or None) List of static bad channels. Can be a list with channel names, or a string with channels (with or without the preceding 'MEG') autobad : string ('on', 'off', 'n') Sets automated bad channel detection on or off skip : string or a list of float-tuples (or None) Skips raw data sequences, time intervals pairs in sec, e.g.: 0 30 120 150 force : bool Ignore program warnings st : bool Apply the time-domain SSS extension (tSSS) st_buflen : float tSSS buffer length in sec (disabled if st is False) st_corr : float tSSS subspace correlation limit (disabled if st is False) movecomp : bool (or 'inter') Estimates and compensates head movements in continuous raw data. trans : str(filename or 'default') (or None) Transforms the data into the coil definitions of in_fname, or into the default frame. If None, and movecomp is True, data will be movement compensated to initial head position. headpos : bool Estimates and stores head position parameters, but does not compensate movements hp : string (or None) Stores head position data in an ascii file hpistep : float (or None) Sets head position update interval in ms hpisubt : str('amp', 'base', 'off') (or None) Subtracts hpi signals: sine amplitudes, amp + baseline, or switch off hpicons : bool Check initial consistency isotrak vs hpifit linefreq : int (50, 60) (or None) Sets the basic line interference frequency (50 or 60 Hz) (None: do not use line filter) cal : str Path to calibration file ctc : str Path to Cross-talk compensation file mx_args : str Additional command line arguments to pass to MaxFilter """ if not check_source_readable(in_fname): raise IOError('Input file {} not readable!'.format(in_fname)) if check_destination_exists(out_fname): if not force: raise IOError('Output file {} exists, use force=True to ' 'overwrite!'.format(out_fname)) elif not check_destination_writable(out_fname): raise IOError('Output file {} not writable!'.format(out_fname)) output_dir = op.dirname(out_fname) # determine the head origin if necessary if origin is None: self.logger.info('Estimating head origin from headshape points..') raw = Raw(in_fname, preload=False) with warnings.filterwarnings('error', category=RuntimeWarning): r, o_head, o_dev = fit_sphere_to_headshape(raw.info, dig_kind='auto', units='m') raw.close() self.logger.info('Fitted sphere: r = {.1f} mm'.format(r)) self.logger.info( 'Origin head coordinates: {.1f} {.1f} {.1f} mm'.format( o_head[0], o_head[1], o_head[2])) self.logger.info( 'Origin device coordinates: {.1f} {.1f} {.1f} mm'.format( o_dev[0], o_dev[1], o_dev[2])) self.logger.info('[done]') if frame == 'head': origin = o_head elif frame == 'device': origin = o_dev else: RuntimeError('invalid frame for origin') # Start building command cmd = (maxfilter_bin + ' -f {:s} -o {:s} -v '.format(in_fname, out_fname)) if isinstance(origin, (np.ndarray, list, tuple)): origin = '{:.1f} {:.1f} {:.1f}'.format(origin[0], origin[1], origin[2]) elif not isinstance(origin, str): raise (ValueError('origin must be list-like or string')) cmd += ' -frame {:s} -origin {:s} -v '.format(frame, origin) if bad is not None: # format the channels if isinstance(bad, str): bad = bad.split() bad += self.info['bad'] # combine the two else: bad = self.info['bad'] # NB add check here that all bads in list actually exist in raw! if len(bad) > 0: # now assume we have a list of str with channel names bad_logic = [ch[3:] if ch.startswith('MEG') else ch for ch in bad] bad_str = ' '.join(bad_logic) cmd += '-bad {:s} '.format(bad_str) cmd += '-autobad {:s} '.format(autobad) if skip is not None: if isinstance(skip, list): skip = ' '.join( ['{:.3f} {:.3f}'.format(s[0], s[1]) for s in skip]) cmd += '-skip {:s} '.format(skip) if force: cmd += '-force ' if st: cmd += '-st ' cmd += ' {:.0f} '.format(st_buflen) cmd += '-corr {:.4f} '.format(st_corr) if trans is not None: cmd += '-trans {:s} '.format(trans) if movecomp: cmd += '-movecomp ' if movecomp == 'inter': cmd += ' inter ' if headpos: if movecomp: raise RuntimeError('movecomp and headpos mutually exclusive') cmd += '-headpos ' if hp is not None: cmd += '-hp {:s} '.format(hp) if hpisubt is not None: cmd += 'hpisubt {:s} '.format(hpisubt) if hpicons: cmd += '-hpicons ' if linefreq is not None: cmd += '-linefreq {:d} '.format(linefreq) if cal is not None: cmd += '-cal {:s} '.format(cal) if ctc is not None: cmd += '-ctc {:s} '.format(ctc) cmd += mx_args if logfile: cmd += ' | tee ' + logfile # NB maxfilter.q hard-coded here, remember to change if cluster changes self.add_job(cmd, queue='maxfilter.q', n_threads=n_threads, job_name='maxfilter', log_dir=self.info['log_dir'], working_dir=output_dir) self.info['io_mapping'] += [dict(input=in_fname, output=out_fname)]
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
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
def build_maxfilter_cmd(self, in_fname, out_fname, origin='0 0 40', frame='head', bad=None, autobad='off', skip=None, force=False, st=False, st_buflen=16.0, st_corr=0.96, trans=None, movecomp=False, headpos=False, hp=None, hpistep=None, hpisubt=None, hpicons=True, linefreq=None, cal=None, ctc=None, mx_args='', maxfilter_bin='/neuro/bin/util/maxfilter', logfile=None): """Build a NeuroMag MaxFilter command for later execution. See the Maxfilter manual for details on the different options! Things to implement * check that cal-file matches date in infile! * check that maxfilter binary is OK Parameters ---------- in_fname : str Input file name out_fname : str Output file name maxfilter_bin : str Full path to the maxfilter-executable logfile : str Full path to the output logfile force : bool Overwrite existing output (default: False) origin : array-like or str Head origin in mm. If None it will be estimated from headshape points. frame : str ('device' or 'head') Coordinate frame for head center bad : str, list (or None) List of static bad channels. Can be a list with channel names, or a string with channels (with or without the preceding 'MEG') autobad : string ('on', 'off', 'n') Sets automated bad channel detection on or off skip : string or a list of float-tuples (or None) Skips raw data sequences, time intervals pairs in sec, e.g.: 0 30 120 150 force : bool Ignore program warnings st : bool Apply the time-domain SSS extension (tSSS) st_buflen : float tSSS buffer length in sec (disabled if st is False) st_corr : float tSSS subspace correlation limit (disabled if st is False) movecomp : bool (or 'inter') Estimates and compensates head movements in continuous raw data. trans : str(filename or 'default') (or None) Transforms the data into the coil definitions of in_fname, or into the default frame. If None, and movecomp is True, data will be movement compensated to initial head position. headpos : bool Estimates and stores head position parameters, but does not compensate movements hp : string (or None) Stores head position data in an ascii file hpistep : float (or None) Sets head position update interval in ms hpisubt : str('amp', 'base', 'off') (or None) Subtracts hpi signals: sine amplitudes, amp + baseline, or switch off hpicons : bool Check initial consistency isotrak vs hpifit linefreq : int (50, 60) (or None) Sets the basic line interference frequency (50 or 60 Hz) (None: do not use line filter) cal : str Path to calibration file ctc : str Path to Cross-talk compensation file mx_args : str Additional command line arguments to pass to MaxFilter """ # determine the head origin if necessary if origin is None: self.logger.info('Estimating head origin from headshape points..') raw = Raw(in_fname, preload=False) with warnings.filterwarnings('error', category=RuntimeWarning): r, o_head, o_dev = fit_sphere_to_headshape(raw.info, dig_kind='auto', units='m') raw.close() self.logger.info('Fitted sphere: r = {.1f} mm'.format(r)) self.logger.info('Origin head coordinates: {.1f} {.1f} {.1f} mm'. format(o_head[0], o_head[1], o_head[2])) self.logger.info('Origin device coordinates: {.1f} {.1f} {.1f} mm'. format(o_dev[0], o_dev[1], o_dev[2])) self.logger.info('[done]') if frame == 'head': origin = o_head elif frame == 'device': origin = o_dev else: RuntimeError('invalid frame for origin') # Start building command cmd = (maxfilter_bin + ' -f {:s} -o {:s} -v '.format(in_fname, out_fname)) if isinstance(origin, (np.ndarray, list, tuple)): origin = '{:.1f} {:.1f} {:.1f}'.format(origin[0], origin[1], origin[2]) elif not isinstance(origin, str): raise(ValueError('origin must be list-like or string')) cmd += ' -frame {:s} -origin {:s} -v '.format(frame, origin) if bad is not None: # format the channels if isinstance(bad, str): bad = bad.split() bad += self.info['bad'] # combine the two else: bad = self.info['bad'] if len(bad) > 0: # now assume we have a list of str with channel names bad_logic = [ch[3:] if ch.startswith('MEG') else ch for ch in bad] bad_str = ' '.join(bad_logic) cmd += '-bad {:s} '.format(bad_str) cmd += '-autobad {:s} '.format(autobad) if skip is not None: if isinstance(skip, list): skip = ' '.join(['{:.3f} {:.3f}'.format(s[0], s[1]) for s in skip]) cmd += '-skip {:s} '.format(skip) if force: cmd += '-force ' if st: cmd += '-st ' cmd += ' {:.0f} '.format(st_buflen) cmd += '-corr {:.4f} '.format(st_corr) if trans is not None: cmd += '-trans {:s} '.format(trans) if movecomp: cmd += '-movecomp ' if movecomp == 'inter': cmd += ' inter ' if headpos: if movecomp: raise RuntimeError('movecomp and headpos mutually exclusive') cmd += '-headpos ' if hp is not None: cmd += '-hp {:s} '.format(hp) if hpisubt is not None: cmd += 'hpisubt {:s} '.format(hpisubt) if hpicons: cmd += '-hpicons ' if linefreq is not None: cmd += '-linefreq {:d} '.format(linefreq) if cal is not None: cmd += '-cal {:s} '.format(cal) if ctc is not None: cmd += '-ctc {:s} '.format(ctc) cmd += mx_args if logfile: cmd += ' | tee ' + logfile self.info['cmd'] += [cmd] self.info['io_mapping'] += [dict(input=in_fname, output=out_fname)]
def push_raw_files(p, subjects, run_indices): """Push raw files to SSS workstation""" from ._sss import calc_median_hp if len(subjects) == 0: return print(' Pushing raw files to SSS workstation...') # do all copies at once to avoid multiple logins shutil.copy2(op.join(op.dirname(__file__), 'run_sss.sh'), p.work_dir) includes = ['--include', op.sep + 'run_sss.sh'] if not isinstance(p.trans_to, str): raise TypeError(' Illegal head transformation argument to MaxFilter.') elif p.trans_to not in ('default', 'median'): _check_trans_file(p) includes += ['--include', op.sep + p.trans_to] for si, subj in enumerate(subjects): subj_dir = op.join(p.work_dir, subj) raw_dir = op.join(subj_dir, p.raw_dir) out_pos = op.join(raw_dir, subj + '_center.txt') if not op.isfile(out_pos): print(' Determining head center for %s... ' % subj, end='') in_fif = op.join( raw_dir, safe_inserter(p.run_names[0], subj) + p.raw_fif_tag) if p.dig_with_eeg: dig_kinds = (FIFF.FIFFV_POINT_EXTRA, FIFF.FIFFV_POINT_LPA, FIFF.FIFFV_POINT_NASION, FIFF.FIFFV_POINT_RPA, FIFF.FIFFV_POINT_EEG) else: dig_kinds = (FIFF.FIFFV_POINT_EXTRA, ) origin_head = fit_sphere_to_headshape(read_info(in_fif), dig_kinds=dig_kinds, units='mm')[1] out_string = ' '.join( ['%0.0f' % np.round(number) for number in origin_head]) with open(out_pos, 'w') as fid: fid.write(out_string) med_pos = op.join(raw_dir, subj + '_median_pos.fif') if not op.isfile(med_pos): calc_median_hp(p, subj, med_pos, run_indices[si]) root = op.sep + subj raw_root = op.join(root, p.raw_dir) includes += [ '--include', root, '--include', raw_root, '--include', op.join(raw_root, op.basename(out_pos)), '--include', op.join(raw_root, op.basename(med_pos)) ] prebad_file = _prebad(p, subj) includes += ['--include', op.join(raw_root, op.basename(prebad_file))] fnames = get_raw_fnames(p, subj, 'raw', True, True, run_indices[si]) assert len(fnames) > 0 for fname in fnames: assert op.isfile(fname), fname includes += ['--include', op.join(raw_root, op.basename(fname))] assert ' ' not in p.sws_dir assert ' ' not in p.sws_ssh cmd = (['rsync', '-aLve', 'ssh -p %s' % p.sws_port, '--partial'] + includes + ['--exclude', '*']) cmd += ['.', '%s:%s' % (p.sws_ssh, op.join(p.sws_dir, ''))] run_subprocess(cmd, cwd=p.work_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)