Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into brain_scraper
Browse files Browse the repository at this point in the history
* upstream/master:
  MRG, ENH: Automatically fix magnetometers when maxwell filtering (mne-tools#7929)
  MRG: Prepare migration to PyVista 0.25 (mne-tools#7791)
  MAINT: Simpler VTK [circle front] (mne-tools#7931)
  • Loading branch information
larsoner committed Jun 25, 2020
2 parents 7ef4df3 + a408881 commit 758b0c8
Show file tree
Hide file tree
Showing 19 changed files with 189 additions and 99 deletions.
5 changes: 2 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ jobs:
- run:
name: Get Python running
command: |
python -m pip install --user --upgrade --progress-bar off pip numpy setuptools
python -m pip install --user --upgrade --progress-bar off -f "https://vtk.org/download" "vtk>=9"
python -m pip install --user --upgrade --progress-bar off pip setuptools
python -m pip install --user --upgrade --progress-bar off -r requirements.txt
python -m pip uninstall -yq pysurfer mayavi
python -m pip install --user --upgrade --progress-bar off --pre sphinx
Expand All @@ -103,7 +102,7 @@ jobs:
which python
QT_DEBUG_PLUGINS=1 mne sys_info
python -c "import numpy; numpy.show_config()"
LIBGL_DEBUG=verbose python -c "import pyvista; pyvista.BackgroundPlotter(show=True)"
LIBGL_DEBUG=verbose python -c "import pyvistaqt; pyvistaqt.BackgroundPlotter(show=True)"
python -c "import mne; mne.set_config('MNE_USE_CUDA', 'false')" # this is needed for the config tutorial
python -c "import mne; mne.set_config('MNE_LOGGING_LEVEL', 'info')"
python -c "import mne; level = mne.get_config('MNE_LOGGING_LEVEL'); assert level.lower() == 'info', repr(level)"
Expand Down
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ before_install:
pip uninstall -yq numpy
pip install -i "https://pypi.anaconda.org/scipy-wheels-nightly/simple" --pre numpy
pip install -f "https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com" scipy pandas scikit-learn matplotlib h5py Pillow
pip install -f "https://vtk.org/download" "vtk>=9"
pip install --upgrade -r requirements.txt
else
git clone https://github.com/astropy/ci-helpers.git
Expand Down
2 changes: 2 additions & 0 deletions doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ Changelog

- Add generic reader function :func:`mne.io.read_raw` that loads files based on their extensions (it wraps the underlying specific ``read_raw_xxx`` functions) by `Clemens Brunner`_

- Add automatic T3 magnetometer detection and application of :meth:`mne.io.Raw.fix_mag_coil_types` to :func:`mne.preprocessing.maxwell_filter` by `Eric Larson`_

- Add ``'auto'`` option to :meth:`mne.preprocessing.ICA.find_bads_ecg` to automatically determine the threshold for CTPS method by `Yu-Han Luo`_

- Add a ``notebook`` 3d backend for visualization in jupyter notebook with :func:`mne.viz.set_3d_backend` by`Guillaume Favelier`_
Expand Down
6 changes: 3 additions & 3 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,8 @@ def reset_warnings(gallery_conf, fname):
'ignore', '.*semaphore_tracker: process died unexpectedly.*')
warnings.filterwarnings( # needed until SciPy 1.2.0 is released
'ignore', '.*will be interpreted as an array index.*', module='scipy')
warnings.filterwarnings(
'ignore', 'VTK 9 no longer accepts an offset array', UserWarning)
warnings.filterwarnings( # PyVista needs to be updated (?)
'ignore', '.*VTK 9 no longer accepts an offset array.*')
for key in ('HasTraits', r'numpy\.testing', 'importlib', r'np\.loads',
'Using or importing the ABCs from', # internal modules on 3.7
r"it will be an error for 'np\.bool_'", # ndimage
Expand All @@ -468,7 +468,7 @@ def reset_warnings(gallery_conf, fname):
'scipy.* is deprecated and will be removed in', # dipy
r'Converting `np\.character` to a dtype is deprecated', # vtk
r'sphinx\.util\.smartypants is deprecated',
'is a deprecated alias',
'is a deprecated alias for the builtin', # NumPy
):
warnings.filterwarnings( # deal with other modules having bad imports
'ignore', message=".*%s.*" % key, category=DeprecationWarning)
Expand Down
2 changes: 1 addition & 1 deletion doc/install/mne_python.rst
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ MNE-Python and its dependencies. Typical output looks like this::
pandas: 1.0.3
dipy: 1.1.1
mayavi: 4.7.2.dev0
pyvista: 0.24.1
pyvista: 0.25.2 {pyvistaqt=0.1.0}
vtk: 9.0.0
PyQt5: 5.14.1

Expand Down
8 changes: 4 additions & 4 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ dependencies:
- traitsui>=6
- imageio>=2.6.1
- pip:
- -i "https://pypi.org/simple/"
- -f "https://vtk.org/download"
- mne
- https://github.com/numpy/numpydoc/archive/master.zip
- imageio-ffmpeg>=0.4.1
- vtk
- pyvista==0.24.3
- vtk<8.2; platform_system == "Darwin"
- vtk; platform_system != "Darwin"
- pyvista>=0.24
- pyvistaqt
- https://github.com/enthought/mayavi/archive/master.zip
- PySurfer[save_movie]
- dipy --only-binary dipy
Expand Down
20 changes: 15 additions & 5 deletions mne/channels/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -1357,13 +1357,16 @@ def _compute_ch_adjacency(info, ch_type):
return ch_adjacency, ch_names


def fix_mag_coil_types(info):
def fix_mag_coil_types(info, use_cal=False):
"""Fix magnetometer coil types.
Parameters
----------
info : dict
The info dict to correct. Corrections are done in-place.
use_cal : bool
If True, further refine the check for old coil types by checking
``info['chs'][ii]['cal']``.
Notes
-----
Expand All @@ -1385,24 +1388,31 @@ def fix_mag_coil_types(info):
current estimates computed by the MNE software is very small.
Therefore the use of ``fix_mag_coil_types`` is not mandatory.
"""
old_mag_inds = _get_T1T2_mag_inds(info)
old_mag_inds = _get_T1T2_mag_inds(info, use_cal)

for ii in old_mag_inds:
info['chs'][ii]['coil_type'] = FIFF.FIFFV_COIL_VV_MAG_T3
logger.info('%d of %d T1/T2 magnetometer types replaced with T3.' %
logger.info('%d of %d magnetometer types replaced with T3.' %
(len(old_mag_inds), len(pick_types(info, meg='mag'))))
info._check_consistency()


def _get_T1T2_mag_inds(info):
def _get_T1T2_mag_inds(info, use_cal=False):
"""Find T1/T2 magnetometer coil types."""
picks = pick_types(info, meg='mag')
old_mag_inds = []
# From email exchanges, systems with the larger T2 coil only use the cal
# value of 2.09e-11. Newer T3 magnetometers use 4.13e-11 or 1.33e-10
# (Triux). So we can use a simple check for > 3e-11.
for ii in picks:
ch = info['chs'][ii]
if ch['coil_type'] in (FIFF.FIFFV_COIL_VV_MAG_T1,
FIFF.FIFFV_COIL_VV_MAG_T2):
old_mag_inds.append(ii)
if use_cal:
if ch['cal'] > 3e-11:
old_mag_inds.append(ii)
else:
old_mag_inds.append(ii)
return old_mag_inds


Expand Down
6 changes: 3 additions & 3 deletions mne/chpi.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
from .cov import make_ad_hoc_cov, compute_whitener
from .dipole import _make_guesses
from .fixes import jit
from .preprocessing.maxwell import (_sss_basis, _prep_mf_coils, _get_mf_picks,
_regularize_out)
from .preprocessing.maxwell import (_sss_basis, _prep_mf_coils,
_regularize_out, _get_mf_picks_fix_mags)
from .transforms import (apply_trans, invert_transform, _angle_between_quats,
quat_to_rot, rot_to_quat, _fit_matched_points,
_quat_to_affine)
Expand Down Expand Up @@ -510,7 +510,7 @@ def _reorder_inv_model(inv_model, n_freqs):
def _setup_ext_proj(info, ext_order):
meg_picks = pick_types(info, meg=True, eeg=False, exclude='bads')
info = pick_info(_simplify_info(info), meg_picks) # makes a copy
_, _, _, _, mag_or_fine = _get_mf_picks(
_, _, _, _, mag_or_fine = _get_mf_picks_fix_mags(
info, int_order=0, ext_order=ext_order, ignore_ref=True,
verbose='error')
mf_coils = _prep_mf_coils(info, verbose='error')
Expand Down
1 change: 1 addition & 0 deletions mne/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def pytest_configure(config):
ignore:.*sphinx\.util\.smartypants is deprecated.*:
ignore:.*pandas\.util\.testing is deprecated.*:
ignore:.*tostring.*is deprecated.*:DeprecationWarning
ignore:VTK 9 no longer accepts an offset array:UserWarning
always:.*get_data.* is deprecated in favor of.*:DeprecationWarning
""" # noqa: E501
for warning_line in warning_lines.split('\n'):
Expand Down
14 changes: 7 additions & 7 deletions mne/epochs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3149,7 +3149,7 @@ def average_movements(epochs, head_pos=None, orig_sfreq=None, picks=None,
""" # noqa: E501
from .preprocessing.maxwell import (_trans_sss_basis, _reset_meg_bads,
_check_usable, _col_norm_pinv,
_get_n_moments, _get_mf_picks,
_get_n_moments, _get_mf_picks_fix_mags,
_prep_mf_coils, _check_destination,
_remove_meg_projs, _get_coil_scale)
if head_pos is None:
Expand All @@ -3172,20 +3172,20 @@ def average_movements(epochs, head_pos=None, orig_sfreq=None, picks=None,
% (len(epochs.events)))
if not np.array_equal(epochs.events[:, 0], np.unique(epochs.events[:, 0])):
raise RuntimeError('Epochs must have monotonically increasing events')
info_to = epochs.info.copy()
meg_picks, mag_picks, grad_picks, good_mask, _ = \
_get_mf_picks(epochs.info, int_order, ext_order, ignore_ref)
_get_mf_picks_fix_mags(info_to, int_order, ext_order, ignore_ref)
coil_scale, mag_scale = _get_coil_scale(
meg_picks, mag_picks, grad_picks, mag_scale, epochs.info)
meg_picks, mag_picks, grad_picks, mag_scale, info_to)
n_channels, n_times = len(epochs.ch_names), len(epochs.times)
other_picks = np.setdiff1d(np.arange(n_channels), meg_picks)
data = np.zeros((n_channels, n_times))
count = 0
# keep only MEG w/bad channels marked in "info_from"
info_from = pick_info(epochs.info, meg_picks[good_mask], copy=True)
all_coils_recon = _prep_mf_coils(epochs.info, ignore_ref=ignore_ref)
info_from = pick_info(info_to, meg_picks[good_mask], copy=True)
all_coils_recon = _prep_mf_coils(info_to, ignore_ref=ignore_ref)
all_coils = _prep_mf_coils(info_from, ignore_ref=ignore_ref)
# remove MEG bads in "to" info
info_to = deepcopy(epochs.info)
_reset_meg_bads(info_to)
# set up variables
w_sum = 0.
Expand All @@ -3200,7 +3200,7 @@ def average_movements(epochs, head_pos=None, orig_sfreq=None, picks=None,
event_time = epochs.events[epochs._current - 1, 0] / orig_sfreq
use_idx = np.where(t <= event_time)[0]
if len(use_idx) == 0:
trans = epochs.info['dev_head_t']['trans']
trans = info_to['dev_head_t']['trans']
else:
use_idx = use_idx[-1]
trans = np.vstack([np.hstack([rot[use_idx], trn[[use_idx]].T]),
Expand Down
2 changes: 1 addition & 1 deletion mne/io/meas_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ def ch_names(self):
def _simplify_info(info):
"""Return a simplified info structure to speed up picking."""
chs = [{key: ch[key]
for key in ('ch_name', 'kind', 'unit', 'coil_type', 'loc')}
for key in ('ch_name', 'kind', 'unit', 'coil_type', 'loc', 'cal')}
for ch in info['chs']]
sub_info = Info(chs=chs, bads=info['bads'], comps=info['comps'],
projs=info['projs'],
Expand Down
21 changes: 11 additions & 10 deletions mne/preprocessing/maxwell.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from ..utils import (verbose, logger, _clean_names, warn, _time_mask, _pl,
_check_option, _ensure_int, _validate_type, use_log_level)
from ..fixes import _get_args, _safe_svd, einsum, bincount
from ..channels.channels import _get_T1T2_mag_inds
from ..channels.channels import _get_T1T2_mag_inds, fix_mag_coil_types


# Note: MF uses single precision and some algorithms might use
Expand Down Expand Up @@ -274,7 +274,7 @@ def _prep_maxwell_filter(
# Now we can actually get moving
info = raw.info.copy()
meg_picks, mag_picks, grad_picks, good_mask, mag_or_fine = \
_get_mf_picks(info, int_order, ext_order, ignore_ref)
_get_mf_picks_fix_mags(info, int_order, ext_order, ignore_ref)

# Magnetometers are scaled to improve numerical stability
coil_scale, mag_scale = _get_coil_scale(
Expand Down Expand Up @@ -417,7 +417,8 @@ def _run_maxwell_filter(
ctc = ctc[good_mask][:, good_mask]

add_channels = (head_pos[0] is not None) and (not st_only) and copy
raw_sss, pos_picks = _copy_preload_add_channels(raw, add_channels, copy)
raw_sss, pos_picks = _copy_preload_add_channels(
raw, add_channels, copy, info)
sfreq = info['sfreq']
del raw
if not st_only:
Expand Down Expand Up @@ -742,10 +743,11 @@ def _do_tSSS(clean_data, orig_in_data, resid, st_correlation,
clean_data -= np.dot(np.dot(clean_data, t_proj), t_proj.T)


def _copy_preload_add_channels(raw, add_channels, copy):
def _copy_preload_add_channels(raw, add_channels, copy, info):
"""Load data for processing and (maybe) add cHPI pos channels."""
if copy:
raw = raw.copy()
raw.info['chs'] = info['chs'] # updated coil types
if add_channels:
kinds = [FIFF.FIFFV_QUAT_1, FIFF.FIFFV_QUAT_2, FIFF.FIFFV_QUAT_3,
FIFF.FIFFV_QUAT_4, FIFF.FIFFV_QUAT_5, FIFF.FIFFV_QUAT_6,
Expand Down Expand Up @@ -907,14 +909,13 @@ def _regularize(regularize, exp, S_decomp, mag_or_fine, t, verbose=None):


@verbose
def _get_mf_picks(info, int_order, ext_order, ignore_ref=False, verbose=None):
"""Pick types for Maxwell filtering."""
def _get_mf_picks_fix_mags(info, int_order, ext_order, ignore_ref=False,
verbose=None):
"""Pick types for Maxwell filtering and fix magnetometers."""
# Check for T1/T2 mag types
mag_inds_T1T2 = _get_T1T2_mag_inds(info)
mag_inds_T1T2 = _get_T1T2_mag_inds(info, use_cal=True)
if len(mag_inds_T1T2) > 0:
warn('%d T1/T2 magnetometer channel types found. If using SSS, it is '
'advised to replace coil types using "fix_mag_coil_types".'
% len(mag_inds_T1T2))
fix_mag_coil_types(info, use_cal=True)
# Get indices of channels to use in multipolar moment calculation
ref = not ignore_ref
meg_picks = pick_types(info, meg=True, ref_meg=ref, exclude=[])
Expand Down
16 changes: 15 additions & 1 deletion mne/preprocessing/tests/test_maxwell.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from mne.forward import use_coil_def
from mne.io import (read_raw_fif, read_info, read_raw_bti, read_raw_kit,
BaseRaw, read_raw_ctf)
from mne.io.constants import FIFF
from mne.preprocessing.maxwell import (
maxwell_filter, _get_n_moments, _sss_basis_basic, _sh_complex_to_real,
_sh_real_to_complex, _sh_negate, _bases_complex_to_real, _trans_sss_basis,
Expand Down Expand Up @@ -117,6 +118,13 @@ def _assert_n_free(raw_sss, lower, upper=None):
'nfree fail: %s <= %s <= %s' % (lower, n_free, upper)


def _assert_mag_coil_type(info, coil_type):
__tracebackhide__ = True
picks = pick_types(info, meg='mag', exclude=())
coil_types = set(info['chs'][pick]['coil_type'] for pick in picks)
assert coil_types == {coil_type}


def read_crop(fname, lims=(0, None)):
"""Read and crop."""
return read_raw_fif(fname, allow_maxshield='yes').crop(*lims)
Expand All @@ -134,8 +142,12 @@ def test_movement_compensation(tmpdir):
#
# Movement compensation, no regularization, no tSSS
#
_assert_mag_coil_type(raw.info, FIFF.FIFFV_COIL_VV_MAG_T3)
assert_allclose(raw.info['chs'][2]['cal'], 4.14e-11, rtol=1e-6)
raw.info['chs'][2]['coil_type'] = FIFF.FIFFV_COIL_VV_MAG_T2
raw_sss = maxwell_filter(raw, head_pos=head_pos, origin=mf_head_origin,
regularize=None, bad_condition='ignore')
_assert_mag_coil_type(raw_sss.info, FIFF.FIFFV_COIL_VV_MAG_T3)
assert_meg_snr(raw_sss, read_crop(sss_movecomp_fname, lims),
4.6, 12.4, chpi_med_tol=58)
# IO
Expand Down Expand Up @@ -966,10 +978,12 @@ def test_all():
def test_triux():
"""Test TRIUX system support."""
raw = read_crop(tri_fname, (0, 0.999))
raw.fix_mag_coil_types()
_assert_mag_coil_type(raw.info, FIFF.FIFFV_COIL_VV_MAG_T1)
assert_allclose(raw.info['chs'][2]['cal'], 1.33e-10, rtol=1e-6)
# standard
with use_coil_def(elekta_def_fname):
sss_py = maxwell_filter(raw, coord_frame='meg', regularize=None)
_assert_mag_coil_type(sss_py.info, FIFF.FIFFV_COIL_VV_MAG_T3)
assert_meg_snr(sss_py, read_crop(tri_sss_fname), 37, 700)
# cross-talk
sss_py = maxwell_filter(raw, coord_frame='meg', regularize=None,
Expand Down
13 changes: 10 additions & 3 deletions mne/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ def sys_info(fid=None, show_paths=False):
pandas: 1.0.3
dipy: 1.1.1
mayavi: 4.7.2.dev0
pyvista: 0.24.1
pyvista: 0.25.2 {pyvistaqt=0.1.0}
vtk: 9.0.0
PyQt5: 5.14.1
""" # noqa: E501
Expand Down Expand Up @@ -524,9 +524,16 @@ def sys_info(fid=None, show_paths=False):
else:
extra = (' (%s)' % op.dirname(mod.__file__)) if show_paths else ''
if mod_name == 'numpy':
extra = ' {%s}%s' % (libs, extra)
extra += ' {%s}%s' % (libs, extra)
elif mod_name == 'matplotlib':
extra = ' {backend=%s}%s' % (mod.get_backend(), extra)
extra += ' {backend=%s}%s' % (mod.get_backend(), extra)
elif mod_name == 'pyvista':
try:
from pyvistaqt import __version__
except Exception:
pass
else:
extra += f' {{pyvistaqt={__version__}}}'
elif mod_name in ('mayavi', 'vtk'):
has_3d = True
if mod_name == 'vtk':
Expand Down
13 changes: 10 additions & 3 deletions mne/viz/_brain/_timeviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,7 @@ def on_pick(self, vtk_picker, event):
self.add_point(hemi, mesh, vertex_id, line, color)

def add_point(self, hemi, mesh, vertex_id, line, color):
from ..backends._pyvista import _sphere
center = mesh.GetPoints().GetPoint(vertex_id)

# from the picked renderer to the subplot coords
Expand All @@ -1000,11 +1001,17 @@ def add_point(self, hemi, mesh, vertex_id, line, color):
spheres = list()
for ri, view in enumerate(self.brain._views):
self.plotter.subplot(ri, col)
actor, sphere = self.brain._renderer.sphere(
# Using _sphere() instead of renderer.sphere() for 2 reasons:
# 1) renderer.sphere() fails on Windows in a scenario where a lot
# of picking requests are done in a short span of time (could be
# mitigated with synchronization/delay?)
# 2) the glyph filter is used in renderer.sphere() but only one
# sphere is required in this function.
actor, sphere = _sphere(
plotter=self.plotter,
center=np.array(center),
color=color,
scale=1.0,
radius=4.0
radius=4.0,
)
actors.append(actor)
spheres.append(sphere)
Expand Down
3 changes: 2 additions & 1 deletion mne/viz/_brain/tests/test_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ def test_brain(renderer):
# screenshot
brain.show_view(view=dict(azimuth=180., elevation=90.))
img = brain.screenshot(mode='rgb')
assert(img.shape == (size[0], size[1], 3))
assert_allclose(img.shape, (size[0], size[1], 3),
atol=50) # XXX undo once size is fixed

# add annotation
annots = ['aparc', 'PALS_B12_Lobes']
Expand Down
Loading

0 comments on commit 758b0c8

Please sign in to comment.