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)
Exemple #2
0
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)
Exemple #3
0
    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)]
Exemple #4
0
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
Exemple #5
0
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)]
Exemple #7
0
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)