1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
# -*- coding: utf-8 -*-
# Authors: Alex Rockhill <aprockhill@mailbox.org>
#
# License: BSD-3-clause
import os.path as op
import numpy as np
from numpy.testing import assert_allclose
import pytest
import mne
from mne.datasets import testing
from mne.transforms import apply_trans
from mne.utils import requires_nibabel, requires_version, use_log_level
from mne.viz.utils import _fake_click
data_path = testing.data_path(download=False)
subject = 'sample'
subjects_dir = op.join(data_path, 'subjects')
sample_dir = op.join(data_path, 'MEG', subject)
raw_path = op.join(sample_dir, 'sample_audvis_trunc_raw.fif')
fname_trans = op.join(sample_dir, 'sample_audvis_trunc-trans.fif')
@requires_nibabel()
@pytest.fixture
def _fake_CT_coords(skull_size=5, contact_size=2):
"""Make somewhat realistic CT data with contacts."""
import nibabel as nib
brain = nib.load(
op.join(subjects_dir, subject, 'mri', 'brain.mgz'))
verts = mne.read_surface(
op.join(subjects_dir, subject, 'bem', 'outer_skull.surf'))[0]
verts = apply_trans(np.linalg.inv(brain.header.get_vox2ras_tkr()), verts)
x, y, z = np.array(brain.shape).astype(int) // 2
coords = [(x, y - 14, z), (x - 10, y - 15, z),
(x - 20, y - 16, z + 1), (x - 30, y - 16, z + 1)]
center = np.array(brain.shape) / 2
# make image
np.random.seed(99)
ct_data = np.random.random(brain.shape).astype(np.float32) * 100
# make skull
for vert in verts:
x, y, z = np.round(vert).astype(int)
ct_data[slice(x - skull_size, x + skull_size + 1),
slice(y - skull_size, y + skull_size + 1),
slice(z - skull_size, z + skull_size + 1)] = 1000
# add electrode with contacts
for (x, y, z) in coords:
# make sure not in skull
assert np.linalg.norm(center - np.array((x, y, z))) < 50
ct_data[slice(x - contact_size, x + contact_size + 1),
slice(y - contact_size, y + contact_size + 1),
slice(z - contact_size, z + contact_size + 1)] = \
1000 - np.linalg.norm(np.array(np.meshgrid(
*[range(-contact_size, contact_size + 1)] * 3)), axis=0)
ct = nib.MGHImage(ct_data, brain.affine)
coords = apply_trans(ct.header.get_vox2ras_tkr(), np.array(coords))
return ct, coords
@pytest.fixture
def _locate_ieeg(renderer_interactive_pyvistaqt):
# Use a fixture to create these classes so we can ensure that they
# are closed at the end of the test
guis = list()
def fun(*args, **kwargs):
guis.append(mne.gui.locate_ieeg(*args, **kwargs))
return guis[-1]
yield fun
for gui in guis:
try:
gui.close()
except Exception:
pass
def test_ieeg_elec_locate_io(_locate_ieeg):
"""Test the input/output of the intracranial location GUI."""
import nibabel as nib
info = mne.create_info([], 1000)
aligned_ct = nib.MGHImage(np.zeros((256, 256, 256), dtype=np.float32),
np.eye(4))
trans = mne.transforms.Transform('head', 'mri')
with pytest.raises(ValueError,
match='No channels found in `info` to locate'):
_locate_ieeg(info, trans, aligned_ct, subject, subjects_dir)
@requires_version('sphinx_gallery')
@testing.requires_testing_data
def test_locate_scraper(_locate_ieeg, _fake_CT_coords, tmp_path):
"""Test sphinx-gallery scraping of the GUI."""
raw = mne.io.read_raw_fif(raw_path)
raw.pick_types(eeg=True)
ch_dict = {'EEG 001': 'LAMY 1', 'EEG 002': 'LAMY 2',
'EEG 003': 'LSTN 1', 'EEG 004': 'LSTN 2'}
raw.pick_channels(list(ch_dict.keys()))
raw.rename_channels(ch_dict)
raw.set_montage(None)
aligned_ct, _ = _fake_CT_coords
trans = mne.read_trans(fname_trans)
with pytest.warns(RuntimeWarning, match='`pial` surface not found'):
gui = _locate_ieeg(raw.info, trans, aligned_ct,
subject=subject, subjects_dir=subjects_dir)
(tmp_path / '_images').mkdir()
image_path = str(tmp_path / '_images' / 'temp.png')
gallery_conf = dict(builder_name='html', src_dir=str(tmp_path))
block_vars = dict(
example_globals=dict(gui=gui),
image_path_iterator=iter([image_path]))
assert not op.isfile(image_path)
assert not getattr(gui, '_scraped', False)
mne.gui._GUIScraper()(None, block_vars, gallery_conf)
assert op.isfile(image_path)
assert gui._scraped
@testing.requires_testing_data
def test_ieeg_elec_locate_display(_locate_ieeg, _fake_CT_coords):
"""Test that the intracranial location GUI displays properly."""
raw = mne.io.read_raw_fif(raw_path, preload=True)
raw.pick_types(eeg=True)
ch_dict = {'EEG 001': 'LAMY 1', 'EEG 002': 'LAMY 2',
'EEG 003': 'LSTN 1', 'EEG 004': 'LSTN 2'}
raw.pick_channels(list(ch_dict.keys()))
raw.rename_channels(ch_dict)
raw.set_eeg_reference('average')
raw.set_channel_types({name: 'seeg' for name in raw.ch_names})
raw.set_montage(None)
aligned_ct, coords = _fake_CT_coords
trans = mne.read_trans(fname_trans)
with pytest.warns(RuntimeWarning, match='`pial` surface not found'):
gui = _locate_ieeg(raw.info, trans, aligned_ct,
subject=subject, subjects_dir=subjects_dir,
verbose=True)
with pytest.raises(ValueError, match='read-only'):
gui._ras[:] = coords[0] # start in the right position
gui._set_ras(coords[0])
gui._mark_ch()
assert not gui._lines and not gui._lines_2D # no lines for one contact
for ci, coord in enumerate(coords[1:], 1):
coord_vox = apply_trans(gui._ras_vox_t, coord)
with use_log_level('debug'):
_fake_click(gui._figs[2], gui._figs[2].axes[0],
coord_vox[:-1], xform='data', kind='release')
assert_allclose(coord[:2], gui._ras[:2], atol=0.1,
err_msg=f'coords[{ci}][:2]')
assert_allclose(coord[2], gui._ras[2], atol=2,
err_msg=f'coords[{ci}][2]')
gui._mark_ch()
# ensure a 3D line was made for each group
assert len(gui._lines) == 2
# test snap to center
gui._ch_index = 0
gui._set_ras(coords[0]) # move to first position
gui._mark_ch()
assert_allclose(coords[0], gui._chs['LAMY 1'], atol=0.2)
gui._snap_button.click()
assert gui._snap_button.text() == 'Off'
# now make sure no snap happens
gui._ch_index = 0
gui._set_ras(coords[1] + 1)
gui._mark_ch()
assert_allclose(coords[1] + 1, gui._chs['LAMY 1'], atol=0.01)
# check that it turns back on
gui._snap_button.click()
assert gui._snap_button.text() == 'On'
# test remove
gui._ch_index = 1
gui._update_ch_selection()
gui._remove_ch()
assert np.isnan(gui._chs['LAMY 2']).all()
# check that raw object saved
assert not np.isnan(raw.info['chs'][0]['loc'][:3]).any() # LAMY 1
assert np.isnan(raw.info['chs'][1]['loc'][:3]).all() # LAMY 2 (removed)
# move sliders
gui._alpha_slider.setValue(75)
assert gui._ch_alpha == 0.75
gui._radius_slider.setValue(5)
assert gui._radius == 5
ct_sum_before = np.nansum(gui._images['ct'][0].get_array().data)
gui._ct_min_slider.setValue(500)
assert np.nansum(gui._images['ct'][0].get_array().data) < ct_sum_before
# test buttons
gui._toggle_show_brain()
assert 'mri' in gui._images
assert 'local_max' not in gui._images
gui._toggle_show_max()
assert 'local_max' in gui._images
assert 'mip' not in gui._images
gui._toggle_show_mip()
assert 'mip' in gui._images
assert 'mip_chs' in gui._images
assert len(gui._lines_2D) == 1 # LAMY only has one contact
|