Ejemplo n.º 1
0
def fix_channel_pos(inst):
    '''Scale channel positions to default mne head radius.
    FIXME - add docs'''
    import borsar
    from mne.bem import _fit_sphere

    # get channel positions matrix
    pos = borsar.channels.get_ch_pos(inst)

    # remove channels without positions
    no_pos = np.isnan(pos).any(axis=1) | (pos == 0).any(axis=1)
    pos = pos[~no_pos, :]

    # fit sphere to channel positions
    radius, origin = _fit_sphere(pos)

    default_sphere = 0.095
    scale = radius / default_sphere

    info = get_info(inst)
    for idx, chs in enumerate(info['chs']):
        if chs['kind'] == 2:
            chs['loc'][:3] -= origin
            chs['loc'][:3] /= scale

    return inst
Ejemplo n.º 2
0
def _interpolate_bads_eeg(inst, picks=None, verbose=None):
    """ Interpolate bad EEG channels.

    Operates in place.

    Parameters
    ----------
    inst : mne.io.Raw, mne.Epochs or mne.Evoked
        The data to interpolate. Must be preloaded.
    picks: np.ndarray, shape(n_channels, ) | list | None
        The channel indices to be used for interpolation.
    """
    from mne.bem import _fit_sphere
    from mne.utils import logger, warn
    from mne.channels.interpolation import _do_interp_dots
    from mne.channels.interpolation import _make_interpolation_matrix
    import numpy as np

    if picks is None:
        picks = pick_types(inst.info, meg=False, eeg=True, exclude=[])

    bads_idx = np.zeros(len(inst.ch_names), dtype=np.bool)
    goods_idx = np.zeros(len(inst.ch_names), dtype=np.bool)

    inst.info._check_consistency()
    bads_idx[picks] = [inst.ch_names[ch] in inst.info['bads'] for ch in picks]

    if len(picks) == 0 or bads_idx.sum() == 0:
        return

    goods_idx[picks] = True
    goods_idx[bads_idx] = False

    pos = inst._get_channel_positions(picks)

    # Make sure only good EEG are used
    bads_idx_pos = bads_idx[picks]
    goods_idx_pos = goods_idx[picks]
    pos_good = pos[goods_idx_pos]
    pos_bad = pos[bads_idx_pos]

    # test spherical fit
    radius, center = _fit_sphere(pos_good)
    distance = np.sqrt(np.sum((pos_good - center)**2, 1))
    distance = np.mean(distance / radius)
    if np.abs(1. - distance) > 0.1:
        warn('Your spherical fit is poor, interpolation results are '
             'likely to be inaccurate.')

    logger.info('Computing interpolation matrix from {0} sensor '
                'positions'.format(len(pos_good)))

    interpolation = _make_interpolation_matrix(pos_good, pos_bad)

    logger.info('Interpolating {0} sensors'.format(len(pos_bad)))
    _do_interp_dots(inst, interpolation, goods_idx, bads_idx)
Ejemplo n.º 3
0
def _interpolate_bads_eeg(inst, picks=None, verbose=None):
    """ Interpolate bad EEG channels.

    Operates in place.

    Parameters
    ----------
    inst : mne.io.Raw, mne.Epochs or mne.Evoked
        The data to interpolate. Must be preloaded.
    picks: np.ndarray, shape(n_channels, ) | list | None
        The channel indices to be used for interpolation.
    """
    from mne.bem import _fit_sphere
    from mne.utils import logger, warn
    from mne.channels.interpolation import _do_interp_dots
    from mne.channels.interpolation import _make_interpolation_matrix
    import numpy as np

    if picks is None:
        picks = pick_types(inst.info, meg=False, eeg=True, exclude=[])

    bads_idx = np.zeros(len(inst.ch_names), dtype=np.bool)
    goods_idx = np.zeros(len(inst.ch_names), dtype=np.bool)

    inst.info._check_consistency()
    bads_idx[picks] = [inst.ch_names[ch] in inst.info['bads'] for ch in picks]

    if len(picks) == 0 or bads_idx.sum() == 0:
        return

    goods_idx[picks] = True
    goods_idx[bads_idx] = False

    pos = inst._get_channel_positions(picks)

    # Make sure only good EEG are used
    bads_idx_pos = bads_idx[picks]
    goods_idx_pos = goods_idx[picks]
    pos_good = pos[goods_idx_pos]
    pos_bad = pos[bads_idx_pos]

    # test spherical fit
    radius, center = _fit_sphere(pos_good)
    distance = np.sqrt(np.sum((pos_good - center) ** 2, 1))
    distance = np.mean(distance / radius)
    if np.abs(1. - distance) > 0.1:
        warn('Your spherical fit is poor, interpolation results are '
             'likely to be inaccurate.')

    logger.info('Computing interpolation matrix from {0} sensor '
                'positions'.format(len(pos_good)))

    interpolation = _make_interpolation_matrix(pos_good, pos_bad)

    logger.info('Interpolating {0} sensors'.format(len(pos_bad)))
    _do_interp_dots(inst, interpolation, goods_idx, bads_idx)
Ejemplo n.º 4
0
def test_set_montage():
    """Test setting a montage."""
    raw = read_raw_fif(fif_fname)
    orig_pos = np.array([ch['loc'][:3] for ch in raw.info['chs']
                         if ch['ch_name'].startswith('EEG')])
    raw.set_montage('mgh60')  # test loading with string argument
    new_pos = np.array([ch['loc'][:3] for ch in raw.info['chs']
                        if ch['ch_name'].startswith('EEG')])
    assert_true((orig_pos != new_pos).all())
    r0 = _fit_sphere(new_pos)[1]
    assert_allclose(r0, [0., -0.016, 0.], atol=1e-3)
Ejemplo n.º 5
0
def test_set_montage():
    """Test setting a montage."""
    raw = read_raw_fif(fif_fname)
    mon = read_montage('mgh60')
    orig_pos = np.array([ch['loc'][:3] for ch in raw.info['chs']
                         if ch['ch_name'].startswith('EEG')])
    raw.set_montage(mon)
    new_pos = np.array([ch['loc'][:3] for ch in raw.info['chs']
                        if ch['ch_name'].startswith('EEG')])
    assert_true((orig_pos != new_pos).all())
    r0 = _fit_sphere(new_pos)[1]
    assert_allclose(r0, [0., -0.016, 0.], atol=1e-3)
Ejemplo n.º 6
0
def forward_to_tangential(fwd, center=None):
    """Convert a free orientation forward solution to a tangential one.

    Places two source dipoles at each vertex that are oriented tangentially to
    a sphere with its origin at the center of the brain. Recomputes the forward
    model according to the new dipoles.

    Parameters
    ----------
    fwd : instance of Forward
        The forward solution to convert.
    center : tuple of float (x, y, z) | None
        The carthesian coordinates of the center of the brain. By default, a
        sphere is fitted through all the points in the source space.

    Returns
    -------
    fwd_out : instance of Forward
        The tangential forward solution.
    """
    if fwd['source_ori'] != FIFF.FIFFV_MNE_FREE_ORI:
        raise ValueError('Forward solution needs to have free orientation.')

    n_sources, n_channels = fwd['nsource'], fwd['nchan']

    if fwd['sol']['ncol'] // n_sources == 2:
        raise ValueError('Forward solution already seems to be in tangential '
                         'orientation.')

    # Compute two dipole directions tangential to a sphere that has its origin
    # in the center of the brain.
    if center is None:
        _, center = _fit_sphere(fwd['source_rr'], disp=False)
        _, tan1, tan2 = _make_radial_coord_system(fwd['source_rr'], center)

    # Make sure the forward solution is in head orientation for this
    fwd_out = convert_forward_solution(fwd, surf_ori=False, copy=True)
    G = fwd_out['sol']['data'].reshape(n_channels, n_sources, 3)

    # Compute the forward solution for the new dipoles
    Phi = np.einsum('ijk,ljk->ijl', G, [tan1, tan2])
    fwd_out['sol']['data'] = Phi.reshape(n_channels, 2 * n_sources)
    fwd_out['sol']['ncol'] = 2 * n_sources

    # Store the source orientations
    fwd_out['source_nn'] = np.stack((tan1, tan2), axis=1).reshape(-1, 3)

    # Mark the orientation as free for now. In the future we should add a
    # new constant to indicate "tangential" orientations.
    fwd_out['source_ori'] = FIFF.FIFFV_MNE_FREE_ORI

    return fwd_out
Ejemplo n.º 7
0
def test_set_montage():
    """Test setting 'mgh60' montage to old fif."""
    raw = read_raw_fif(fif_fname)
    raw.rename_channels(lambda x: x.replace('EEG ', 'EEG'))

    orig_pos = np.array([ch['loc'][:3] for ch in raw.info['chs']
                         if ch['ch_name'].startswith('EEG')])

    raw.set_montage('mgh60')  # test loading with string argument
    new_pos = np.array([ch['loc'][:3] for ch in raw.info['chs']
                        if ch['ch_name'].startswith('EEG')])
    assert ((orig_pos != new_pos).all())

    r0 = _fit_sphere(new_pos)[1]
    assert_allclose(r0, [0.000775, 0.006881, 0.047398], atol=1e-3)
Ejemplo n.º 8
0
def test_set_montage():
    """Test setting a montage."""
    raw = read_raw_fif(fif_fname)
    orig_pos = np.array([ch['loc'][:3] for ch in raw.info['chs']
                         if ch['ch_name'].startswith('EEG')])
    raw.set_montage('mgh60')  # test loading with string argument
    new_pos = np.array([ch['loc'][:3] for ch in raw.info['chs']
                        if ch['ch_name'].startswith('EEG')])
    assert_true((orig_pos != new_pos).all())
    r0 = _fit_sphere(new_pos)[1]
    assert_allclose(r0, [0., -0.016, 0.], atol=1e-3)
    # mgh70 has no 61/62/63/64 (these are EOG/ECG)
    mon = read_montage('mgh70')
    assert 'EEG061' not in mon.ch_names
    assert 'EEG074' in mon.ch_names
Ejemplo n.º 9
0
def test_set_montage():
    """Test setting a montage."""
    raw = read_raw_fif(fif_fname)
    orig_pos = np.array([ch['loc'][:3] for ch in raw.info['chs']
                         if ch['ch_name'].startswith('EEG')])
    raw.set_montage('mgh60')  # test loading with string argument
    new_pos = np.array([ch['loc'][:3] for ch in raw.info['chs']
                        if ch['ch_name'].startswith('EEG')])
    assert ((orig_pos != new_pos).all())
    r0 = _fit_sphere(new_pos)[1]
    assert_allclose(r0, [0., -0.016, 0.], atol=1e-3)
    # mgh70 has no 61/62/63/64 (these are EOG/ECG)
    mon = read_montage('mgh70')
    assert 'EEG061' not in mon.ch_names
    assert 'EEG074' in mon.ch_names
Ejemplo n.º 10
0
def _make_interpolator(inst, bad_channels):
    """Find indexes and interpolation matrix to interpolate bad channels

    Parameters
    ----------
    inst : mne.io.Raw, mne.Epochs or mne.Evoked
        The data to interpolate. Must be preloaded.
    """
    logger = logging.getLogger(__name__)

    bads_idx = np.zeros(len(inst.ch_names), dtype=np.bool)
    goods_idx = np.zeros(len(inst.ch_names), dtype=np.bool)

    picks = pick_types(inst.info, meg=False, eeg=True, exclude=[])
    bads_idx[picks] = [inst.ch_names[ch] in bad_channels for ch in picks]
    goods_idx[picks] = True
    goods_idx[bads_idx] = False

    pos = get_channel_positions(inst, picks)

    # Make sure only EEG are used
    bads_idx_pos = bads_idx[picks]
    goods_idx_pos = goods_idx[picks]

    pos_good = pos[goods_idx_pos]
    pos_bad = pos[bads_idx_pos]

    # test spherical fit
    radius, center = _fit_sphere(pos_good, False)
    distance = np.sqrt(np.sum((pos_good - center) ** 2, 1))
    distance = np.mean(distance / radius)
    if np.abs(1. - distance) > 0.1:
        logger.warning('Your spherical fit is poor, interpolation results are '
                       'likely to be inaccurate.')

    logger.info('Computing interpolation matrix from {0} sensor '
                'positions'.format(len(pos_good)))

    interpolation = _make_interpolation_matrix(pos_good, pos_bad)

    return goods_idx, bads_idx, interpolation
Ejemplo n.º 11
0
def _make_interpolator(inst, bad_channels):
    """Find indexes and interpolation matrix to interpolate bad channels

    Parameters
    ----------
    inst : mne.io.Raw, mne.Epochs or mne.Evoked
        The data to interpolate. Must be preloaded.
    """
    logger = logging.getLogger(__name__)

    bads_idx = np.zeros(len(inst.ch_names), dtype=np.bool)
    goods_idx = np.zeros(len(inst.ch_names), dtype=np.bool)

    picks = pick_types(inst.info, meg=False, eeg=True, exclude=[])
    bads_idx[picks] = [inst.ch_names[ch] in bad_channels for ch in picks]
    goods_idx[picks] = True
    goods_idx[bads_idx] = False

    pos = get_channel_positions(inst, picks)

    # Make sure only EEG are used
    bads_idx_pos = bads_idx[picks]
    goods_idx_pos = goods_idx[picks]

    pos_good = pos[goods_idx_pos]
    pos_bad = pos[bads_idx_pos]

    # test spherical fit
    radius, center = _fit_sphere(pos_good, False)
    distance = np.sqrt(np.sum((pos_good - center) ** 2, 1))
    distance = np.mean(distance / radius)
    if np.abs(1. - distance) > 0.1:
        logger.warning('Your spherical fit is poor, interpolation results are '
                       'likely to be inaccurate.')

    logger.info('Computing interpolation matrix from {0} sensor '
                'positions'.format(len(pos_good)))

    interpolation = _make_interpolation_matrix(pos_good, pos_bad)

    return goods_idx, bads_idx, interpolation
Ejemplo n.º 12
0
def test_set_montage_mgh(rename):
    """Test setting 'mgh60' montage to old fif."""
    raw = read_raw_fif(fif_fname)
    eeg_picks = pick_types(raw.info, meg=False, eeg=True, exclude=())
    assert list(eeg_picks) == [ii for ii, name in enumerate(raw.ch_names)
                               if name.startswith('EEG')]
    orig_pos = np.array([raw.info['chs'][pick]['loc'][:3]
                         for pick in eeg_picks])
    atol = 1e-6
    if rename == 'raw':
        raw.rename_channels(lambda x: x.replace('EEG ', 'EEG'))
        raw.set_montage('mgh60')  # test loading with string argument
    elif rename == 'montage':
        mon = make_standard_montage('mgh60')
        mon.rename_channels(lambda x: x.replace('EEG', 'EEG '))
        assert [raw.ch_names[pick] for pick in eeg_picks] == mon.ch_names
        raw.set_montage(mon)
    else:
        atol = 3e-3  # XXX old defs here apparently (maybe not realistic)?
        assert rename == 'custom'
        assert len(_MGH60) == 60
        mon = make_standard_montage('standard_1020')

        def renamer(x):
            try:
                return 'EEG %03d' % (_MGH60.index(x) + 1,)
            except ValueError:
                return x

        mon.rename_channels(renamer)
        raw.set_montage(mon)

    new_pos = np.array([ch['loc'][:3] for ch in raw.info['chs']
                        if ch['ch_name'].startswith('EEG')])
    assert ((orig_pos != new_pos).all())

    r0 = _fit_sphere(new_pos)[1]
    assert_allclose(r0, [0.000775, 0.006881, 0.047398], atol=1e-3)
    # spot check
    assert_allclose(new_pos[:2], [[0.000273, 0.084920, 0.105838],
                                  [0.028822, 0.083529, 0.099164]], atol=atol)
Ejemplo n.º 13
0
def test_read_raw_curry_rfDC(fname, tol, mock_dev_head_t, tmpdir):
    """Test reading CURRY files."""
    if mock_dev_head_t:
        if 'Curry 7' in fname:  # not supported yet
            return
        # copy files to tmpdir
        base = op.splitext(fname)[0]
        for ext in ('.cdt', '.cdt.dpa'):
            src = base + ext
            dst = op.join(tmpdir, op.basename(base) + ext)
            copyfile(src, dst)
            if ext == '.cdt.dpa':
                with open(dst, 'a') as fid:
                    fid.write(LM_CONTENT)
        fname = op.join(tmpdir, op.basename(fname))
        with open(fname + '.hpi', 'w') as fid:
            fid.write(HPI_CONTENT)

    # check data
    bti_rfDC = read_raw_bti(pdf_fname=bti_rfDC_file, head_shape_fname=None)
    with catch_logging() as log:
        raw = read_raw_curry(fname, verbose=True)
    log = log.getvalue()
    if mock_dev_head_t:
        assert 'Composing device' in log
    else:
        assert 'Leaving device' in log
        assert 'no landmark' in log

    # test on the eeg chans, since these were not renamed by curry
    eeg_names = [
        ch["ch_name"] for ch in raw.info["chs"]
        if ch["kind"] == FIFF.FIFFV_EEG_CH
    ]

    assert_allclose(raw.get_data(eeg_names),
                    bti_rfDC.get_data(eeg_names),
                    rtol=tol)
    assert bti_rfDC.info['dev_head_t'] is not None  # XXX probably a BTI bug
    if mock_dev_head_t:
        assert raw.info['dev_head_t'] is not None
        assert_allclose(raw.info['dev_head_t']['trans'], WANT_TRANS, atol=1e-5)
    else:
        assert raw.info['dev_head_t'] is None

    # check that most MEG sensors are approximately oriented outward from
    # the device origin
    n_meg = n_eeg = n_other = 0
    pos = list()
    nn = list()
    for ch in raw.info['chs']:
        if ch['kind'] == FIFF.FIFFV_MEG_CH:
            assert ch['coil_type'] == FIFF.FIFFV_COIL_CTF_GRAD
            t = _loc_to_coil_trans(ch['loc'])
            pos.append(t[:3, 3])
            nn.append(t[:3, 2])
            assert_allclose(np.linalg.norm(nn[-1]), 1.)
            n_meg += 1
        elif ch['kind'] == FIFF.FIFFV_EEG_CH:
            assert ch['coil_type'] == FIFF.FIFFV_COIL_EEG
            n_eeg += 1
        else:
            assert ch['coil_type'] == FIFF.FIFFV_COIL_NONE
            n_other += 1
    assert n_meg == 148
    assert n_eeg == 31
    assert n_other == 15
    pos = np.array(pos)
    nn = np.array(nn)
    rad, origin = _fit_sphere(pos, disp=False)
    assert 0.11 < rad < 0.13
    pos -= origin
    pos /= np.linalg.norm(pos, axis=1, keepdims=True)
    angles = np.abs(np.rad2deg(np.arccos((pos * nn).sum(-1))))
    assert (angles < 20).sum() > 100
Ejemplo n.º 14
0
def homologous_pairs(inst):
    '''
    Construct homologous channel pairs based on channel names or positions.

    Parameters
    ----------
    inst : mne object instance
        Mne object like mne.Raw or mne.Epochs.

    Returns
    -------
    selection: dict of {str -> list of int} mappings
        Dictionary mapping hemisphere ('left' or 'right') to array of channel
        indices.
    '''
    from borsar.channels import get_ch_pos, get_ch_names

    ch_names = get_ch_names(inst)
    ch_pos = get_ch_pos(inst)

    labels = ['right', 'left']
    selection = {label: list() for label in labels}
    has_1020_names = 'Cz' in ch_names and 'F3' in ch_names

    if has_1020_names:
        # find homologues by channel names
        left_chans = ch_pos[:, 0] < 0
        y_ord = np.argsort(ch_pos[left_chans, 1])[::-1]
        check_chans = [
            ch for ch in list(np.array(ch_names)[left_chans][y_ord])
            if 'z' not in ch
        ]

        for ch in check_chans:
            chan_base = ''.join([char for char in ch if not char.isdigit()])
            chan_value = int(''.join([char for char in ch if char.isdigit()]))

            if (chan_value % 2) == 1:
                # sometimes homologous channels are missing in the cap
                homologous_ch = chan_base + str(chan_value + 1)
                if homologous_ch in ch_names:
                    selection['left'].append(ch_names.index(ch))
                    selection['right'].append(ch_names.index(homologous_ch))
    else:
        # channel names do not come from 10-20 system
        # constructing homologues from channel position
        # (this will not work well for digitized channel positions)
        from mne.bem import _fit_sphere

        # fit sphere to channel positions and calculate median distance
        # of the channels to the sphere origin
        radius, origin = _fit_sphere(ch_pos)
        origin_distance = ch_pos - origin[np.newaxis, :]
        dist = np.linalg.norm(origin_distance, axis=1)
        median_dist = np.median(dist)

        # find channels on the left from sphere origin
        left_x_val = origin[0] - median_dist * 0.05
        sel = ch_pos[:, 0] < left_x_val
        left_chans = ch_pos[sel, :]
        sel_idx = np.where(sel)[0]

        for idx, pos in enumerate(left_chans):
            # represent channel position with respect to the origin
            this_distance = pos - origin

            # find similar origin-relative position on the right side
            this_distance[0] *= -1
            this_simil = origin_distance - this_distance[np.newaxis, :]
            similar = np.linalg.norm(this_simil, axis=1).argmin()

            # fill selection dictionary
            selection['left'].append(sel_idx[idx])
            selection['right'].append(similar)

    selection['left'] = np.array(selection['left'])
    selection['right'] = np.array(selection['right'])
    return selection
Ejemplo n.º 15
0
def _interpolate_bads_eeg(inst, picks=None, verbose=False):
    """ Interpolate bad EEG channels.

    Operates in place.

    Parameters
    ----------
    inst : mne.io.Raw, mne.Epochs or mne.Evoked
        The data to interpolate. Must be preloaded.
    picks : str | list | slice | None
        Channels to include for interpolation. Slices and lists of integers
        will be interpreted as channel indices. In lists, channel *name*
        strings (e.g., ``['EEG 01', 'EEG 02']``) will pick the given channels.
        None (default) will pick all EEG channels. Note that channels in
        ``info['bads']`` *will be included* if their names or indices are
        explicitly provided.
    """
    from mne.bem import _fit_sphere
    from mne.utils import logger, warn
    from mne.channels.interpolation import _do_interp_dots
    from mne.channels.interpolation import _make_interpolation_matrix
    import numpy as np

    inst.info._check_consistency()
    if picks is None:
        picks = pick_types(inst.info, meg=False, eeg=True, exclude=[])
    else:
        picks = _handle_picks(inst.info, picks)

    bads_idx = np.zeros(len(inst.ch_names), dtype=np.bool)
    goods_idx = np.zeros(len(inst.ch_names), dtype=np.bool)
    bads_idx[picks] = [inst.ch_names[ch] in inst.info['bads'] for ch in picks]

    if len(picks) == 0 or bads_idx.sum() == 0:
        return

    goods_idx[picks] = True
    goods_idx[bads_idx] = False

    pos = inst._get_channel_positions(picks)

    # Make sure only good EEG are used
    bads_idx_pos = bads_idx[picks]
    goods_idx_pos = goods_idx[picks]
    pos_good = pos[goods_idx_pos]
    pos_bad = pos[bads_idx_pos]

    # test spherical fit
    radius, center = _fit_sphere(pos_good)
    distance = np.sqrt(np.sum((pos_good - center)**2, 1))
    distance = np.mean(distance / radius)
    if np.abs(1. - distance) > 0.1:
        warn('Your spherical fit is poor, interpolation results are '
             'likely to be inaccurate.')

    logger.info('Computing interpolation matrix from {0} sensor '
                'positions'.format(len(pos_good)))

    interpolation = _make_interpolation_matrix(pos_good, pos_bad)

    logger.info('Interpolating {0} sensors'.format(len(pos_bad)))
    _do_interp_dots(inst, interpolation, goods_idx, bads_idx)
Ejemplo n.º 16
0
from config import fname, subjects

mne.set_log_level('ERROR')

fwd = mne.read_forward_solution(fname.fwd(subject=subjects[0]))
fwd_r = mne.read_forward_solution(fname.fwd_r(subject=subjects[0]))
trans = fname.trans(subject=subjects[0])

fig1 = mne.viz.plot_alignment(fwd['info'],
                              trans=trans,
                              src=fwd_r['src'],
                              meg='sensors',
                              surfaces='white')
fig1.scene.background = (1, 1, 1)  # white
fig1.children[-1].children[0].children[0].glyph.glyph.scale_factor = 0.008
mlab.view(135, 120, 0.3, [0.01, 0.015, 0.058])
mlab.savefig('../paper/figures/forward1.png', magnification=4, figure=fig1)

radius, center = _fit_sphere(fwd_r['source_rr'])
rad, tan1, tan2 = conpy.forward._make_radial_coord_system(
    fwd_r['source_rr'], center)
fig2 = conpy.forward._plot_coord_system(fwd_r['source_rr'],
                                        rad,
                                        tan1,
                                        tan2,
                                        scale=0.003,
                                        n_ori=2)
fig2.scene.background = (1, 1, 1)  # white
mlab.view(75.115, 47.534, 0.311, [0.00068598, 0.01360262, 0.03581326])
mlab.savefig('../paper/figures/forward2.png', magnification=4, figure=fig2)