def test_plot_topomap_bads(): """Test plotting topomap with bad channels (gh-7213).""" import matplotlib.pyplot as plt data = np.random.RandomState(0).randn(3, 1000) raw = RawArray(data, create_info(3, 1000., 'eeg')) ch_pos_dict = {name: pos for name, pos in zip(raw.ch_names, np.eye(3))} raw.info.set_montage(make_dig_montage(ch_pos_dict, coord_frame='head')) for count in range(3): raw.info['bads'] = raw.ch_names[:count] raw.info._check_consistency() plot_topomap(data[:, 0], raw.info) plt.close('all')
def make_montage(info, kind, check=False): from mne.utils import _clean_names import mnefun assert kind in ('mgh60', 'mgh70', 'uw_70', 'uw_60') picks = pick_types(info, meg=False, eeg=True, exclude=()) sphere = make_sphere_model('auto', 'auto', info) info = pick_info(info, picks) to_names = info['ch_names'] if kind in ('mgh60', 'mgh70'): if kind == 'mgh60': assert len(to_names) in (59, 60) else: assert len(to_names) in (70, ) montage = make_standard_montage(kind, head_size=sphere.radius) from_names = _clean_names(to_names, remove_whitespace=True) else: assert len(to_names) == 60 from_names = getattr(mnefun, 'ch_names_' + kind) montage = make_standard_montage('standard_1020', head_size=sphere.radius) assert len(from_names) == len(to_names) montage_pos = montage._get_ch_pos() montage = make_dig_montage( {to: montage_pos[fro] for fro, to in zip(from_names, to_names)}, coord_frame='head') eeg_pos = np.array([ch['loc'][:3] for ch in info['chs']]) montage_pos = montage._get_ch_pos() montage_pos = np.array([montage_pos[name] for name in to_names]) assert len(eeg_pos) == len(montage_pos) if check: from mayavi import mlab mlab.figure(size=(800, 800)) mlab.points3d(*sphere['r0'], scale_factor=2 * sphere.radius, color=(0., 0., 1.), opacity=0.1, mode='sphere') mlab.points3d(*montage_pos.T, scale_factor=0.01, color=(1, 0, 0), mode='sphere', opacity=0.5) mlab.points3d(*eeg_pos.T, scale_factor=0.005, color=(1, 1, 1), mode='sphere', opacity=1) return montage, sphere
def test_get_montage_volume_labels(): """Test finding ROI labels near montage channel locations.""" ch_coords = np.array([[-8.7040273, 17.99938754, 10.29604017], [-14.03007764, 19.69978401, 12.07236939], [-21.1130506, 21.98310911, 13.25658887]]) ch_pos = dict(zip(['1', '2', '3'], ch_coords / 1000)) # mm -> m montage = make_dig_montage(ch_pos, coord_frame='mri') labels, colors = get_montage_volume_labels(montage, 'sample', subjects_dir, aseg='aseg', dist=1) assert labels == { '1': ['Unknown'], '2': ['Left-Cerebral-Cortex'], '3': ['Left-Cerebral-Cortex'] } assert 'Unknown' in colors assert 'Left-Cerebral-Cortex' in colors np.testing.assert_almost_equal( colors['Left-Cerebral-Cortex'], (0.803921568627451, 0.24313725490196078, 0.3058823529411765, 1.0)) np.testing.assert_almost_equal(colors['Unknown'], (0.0, 0.0, 0.0, 1.0)) # test inputs with pytest.raises(RuntimeError, match='`aseg` file path must end with "aseg"'): get_montage_volume_labels(montage, 'sample', subjects_dir, aseg='foo') fail_montage = make_dig_montage(ch_pos, coord_frame='head') with pytest.raises(RuntimeError, match='Coordinate frame not supported'): get_montage_volume_labels(fail_montage, 'sample', subjects_dir, aseg='aseg') with pytest.raises(ValueError, match='between 0 and 10'): get_montage_volume_labels(montage, 'sample', subjects_dir, dist=11)
def test_set_montage_with_mismatching_ch_names(): """Test setting a DigMontage with mismatching ch_names.""" raw = read_raw_fif(fif_fname) montage = make_standard_montage('mgh60') # 'EEG 001' and 'EEG001' won't match missing_err = '60 channel positions not present' with pytest.raises(ValueError, match=missing_err): raw.set_montage(montage) montage.ch_names = [ # modify the names in place name.replace('EEG', 'EEG ') for name in montage.ch_names ] raw.set_montage(montage) # does not raise # Case sensitivity raw.rename_channels(lambda x: x.lower()) with pytest.raises(ValueError, match=missing_err): raw.set_montage(montage) # should work raw.set_montage(montage, match_case=False) raw.rename_channels(lambda x: x.upper()) # restore assert 'EEG 001' in raw.ch_names and 'eeg 001' not in raw.ch_names raw.rename_channels({'EEG 002': 'eeg 001'}) assert 'EEG 001' in raw.ch_names and 'eeg 001' in raw.ch_names raw.set_channel_types({'eeg 001': 'misc'}) raw.set_montage(montage) raw.set_channel_types({'eeg 001': 'eeg'}) with pytest.raises(ValueError, match='1 channel position not present'): raw.set_montage(montage) with pytest.raises(ValueError, match='match_case=False as 1 channel name'): raw.set_montage(montage, match_case=False) info = create_info(['EEG 001'], 1000., 'eeg') mon = make_dig_montage({ 'EEG 001': np.zeros(3), 'eeg 001': np.zeros(3) }, nasion=[0, 1., 0], rpa=[1., 0, 0], lpa=[-1., 0, 0]) info.set_montage(mon) with pytest.raises(ValueError, match='match_case=False as 1 montage name'): info.set_montage(mon, match_case=False)
def test_set_montage_with_missing_coordinates(): """Test set montage with missing coordinates.""" N_CHANNELS, NaN = 3, np.nan raw = _make_toy_raw(N_CHANNELS) raw.set_channel_types({ch: 'ecog' for ch in raw.ch_names}) # don't include all the channels ch_names = raw.ch_names[1:] n_channels = len(ch_names) ch_coords = np.arange(n_channels * 3).reshape(n_channels, 3) montage_in_mri = make_dig_montage( ch_pos=dict(zip( ch_names, ch_coords, )), coord_frame='unknown', nasion=[0, 1, 0], lpa=[1, 0, 0], rpa=[-1, 0, 0], ) with pytest.raises(ValueError, match='DigMontage is ' 'only a subset of info'): raw.set_montage(montage_in_mri) with pytest.raises(ValueError, match='Invalid value'): raw.set_montage(montage_in_mri, on_missing=True) with pytest.warns(RuntimeWarning, match='DigMontage is ' 'only a subset of info'): raw.set_montage(montage_in_mri, on_missing='warn') raw.set_montage(montage_in_mri, on_missing='ignore') assert_allclose( actual=np.array([ch['loc'] for ch in raw.info['chs']]), desired=[ [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN], [0., 1., -2., 0., 0., 0., NaN, NaN, NaN, NaN, NaN, NaN], [-3., 4., -5., 0., 0., 0., NaN, NaN, NaN, NaN, NaN, NaN], ])
def test_read_dig_montage_using_polhemus_fastscan(): """Test FastScan.""" N_EEG_CH = 10 my_electrode_positions = read_polhemus_fastscan( op.join(kit_dir, 'test_elp.txt') ) montage = make_dig_montage( # EEG_CH ch_pos=dict(zip(ascii_lowercase[:N_EEG_CH], np.random.RandomState(0).rand(N_EEG_CH, 3))), # NO NAMED points nasion=my_electrode_positions[0], lpa=my_electrode_positions[1], rpa=my_electrode_positions[2], hpi=my_electrode_positions[3:], hsp=read_polhemus_fastscan(op.join(kit_dir, 'test_hsp.txt')), # Other defaults coord_frame='unknown' ) assert repr(montage) == ( '<DigMontage | ' '500 extras (headshape), 5 HPIs, 3 fiducials, 10 channels>' ) # XXX: is this wrong? extra is not in headspace, is it? assert set([d['coord_frame'] for d in montage.dig]) == { FIFF.FIFFV_COORD_UNKNOWN } # XXX: so far we build everything in 'unknown' EXPECTED_FID_IN_POLHEMUS = { 'nasion': [0.001393, 0.0131613, -0.0046967], 'lpa': [-0.0624997, -0.0737271, 0.07996], 'rpa': [-0.0748957, 0.0873785, 0.0811943], } fiducials, fid_coordframe = _get_fid_coords(montage.dig) assert fid_coordframe == FIFF.FIFFV_COORD_UNKNOWN for kk, val in fiducials.items(): assert_allclose(val, EXPECTED_FID_IN_POLHEMUS[kk])
assert_equal(len(set(kinds)), len(kinds), err_msg=str(sorted(kinds))) assert_equal(set(montages), set(kinds)) @pytest.mark.parametrize('reader, file_content, expected_dig, ext', [ pytest.param( partial(read_custom_montage, head_size=None, unit='m'), ('FidNz 0 9.071585155 -2.359754454\n' 'FidT9 -6.711765 0.040402876 -3.251600355\n' 'very_very_very_long_name -5.831241498 -4.494821698 4.955347697\n' 'Cz 0 0 8.899186843'), make_dig_montage( ch_pos={ 'very_very_very_long_name': [-5.8312416, -4.4948215, 4.9553475], # noqa 'Cz': [0., 0., 8.899187], }, nasion=[0., 9.071585, -2.3597546], lpa=[-6.711765, 0.04040287, -3.2516003], rpa=None, ), 'sfp', id='sfp'), pytest.param( partial(read_custom_montage, head_size=1, unit='n/a'), ('1 0 0.50669 FPz\n' '2 23 0.71 EOG1\n' '3 -39.947 0.34459 F3\n' '4 0 0.25338 Fz\n'), make_dig_montage( ch_pos={ 'EOG1': [0.30873816, 0.72734152, -0.61290705],
def test_plot_topomap_basic(monkeypatch): """Test basics of topomap plotting.""" evoked = read_evokeds(evoked_fname, 'Left Auditory', baseline=(None, 0)) res = 8 fast_test = dict(res=res, contours=0, sensors=False, time_unit='s') fast_test_noscale = dict(res=res, contours=0, sensors=False) ev_bad = evoked.copy().pick_types(meg=False, eeg=True) ev_bad.pick_channels(ev_bad.ch_names[:2]) plt_topomap = partial(ev_bad.plot_topomap, **fast_test) plt_topomap(times=ev_bad.times[:2] - 1e-6) # auto, plots EEG pytest.raises(ValueError, plt_topomap, ch_type='mag') pytest.raises(ValueError, plt_topomap, times=[-100]) # bad time pytest.raises(ValueError, plt_topomap, times=[[0]]) # bad time evoked.plot_topomap([0.1], ch_type='eeg', scalings=1, res=res, contours=[-100, 0, 100], time_unit='ms') # extrapolation to the edges of the convex hull or the head circle evoked.plot_topomap([0.1], ch_type='eeg', scalings=1, res=res, contours=[-100, 0, 100], time_unit='ms', extrapolate='local') evoked.plot_topomap([0.1], ch_type='eeg', scalings=1, res=res, contours=[-100, 0, 100], time_unit='ms', extrapolate='head') evoked.plot_topomap([0.1], ch_type='eeg', scalings=1, res=res, contours=[-100, 0, 100], time_unit='ms', extrapolate='head', outlines='skirt') # extrapolation options when < 4 channels: temp_data = np.random.random(3) picks = channel_indices_by_type(evoked.info)['mag'][:3] info_sel = pick_info(evoked.info, picks) plot_topomap(temp_data, info_sel, extrapolate='local', res=res) plot_topomap(temp_data, info_sel, extrapolate='head', res=res) # make sure extrapolation works for 3 channels with border='mean' # (if extra points are placed incorrectly some of them have only # other extra points as neighbours and border='mean' fails) plot_topomap(temp_data, info_sel, extrapolate='local', border='mean', res=res) # border=0 and border='mean': # --------------------------- ch_pos = np.array( sum(([[0, 0, r], [r, 0, 0], [-r, 0, 0], [0, -r, 0], [0, r, 0]] for r in np.linspace(0.2, 1.0, 5)), [])) rng = np.random.RandomState(23) data = np.full(len(ch_pos), 5) + rng.randn(len(ch_pos)) info = create_info(len(ch_pos), 250, 'eeg') ch_pos_dict = {name: pos for name, pos in zip(info['ch_names'], ch_pos)} dig = make_dig_montage(ch_pos_dict, coord_frame='head') info.set_montage(dig) # border=0 ax, _ = plot_topomap(data, info, extrapolate='head', border=0, sphere=1) img_data = ax.get_array().data assert np.abs(img_data[31, 31] - data[0]) < 0.12 assert np.abs(img_data[0, 0]) < 1.5 # border='mean' ax, _ = plot_topomap(data, info, extrapolate='head', border='mean', sphere=1) img_data = ax.get_array().data assert np.abs(img_data[31, 31] - data[0]) < 0.12 assert img_data[0, 0] > 5 # error when not numeric or str: error_msg = 'border must be an instance of numeric or str' with pytest.raises(TypeError, match=error_msg): plot_topomap(data, info, extrapolate='head', border=[1, 2, 3]) # error when str is not 'mean': error_msg = "The only allowed value is 'mean', but got 'fancy' instead." with pytest.raises(ValueError, match=error_msg): plot_topomap(data, info, extrapolate='head', border='fancy') # test channel placement when only 'grad' are picked: # --------------------------------------------------- info_grad = evoked.copy().pick('grad').info n_grads = len(info_grad['ch_names']) data = np.random.randn(n_grads) img, _ = plot_topomap(data, info_grad) # check that channels are scattered around x == 0 pos = img.axes.collections[-1].get_offsets() prop_channels_on_the_right = (pos[:, 0] > 0).mean() assert prop_channels_on_the_right < 0.6 # other: # ------ plt_topomap = partial(evoked.plot_topomap, **fast_test) plt.close('all') axes = [plt.subplot(221), plt.subplot(222)] plt_topomap(axes=axes, colorbar=False) plt.close('all') plt_topomap(times=[-0.1, 0.2]) plt.close('all') evoked_grad = evoked.copy().crop(0, 0).pick_types(meg='grad') mask = np.zeros((204, 1), bool) mask[[0, 3, 5, 6]] = True names = [] def proc_names(x): names.append(x) return x[4:] evoked_grad.plot_topomap(ch_type='grad', times=[0], mask=mask, show_names=proc_names, **fast_test) assert_equal(sorted(names), ['MEG 011x', 'MEG 012x', 'MEG 013x', 'MEG 014x']) mask = np.zeros_like(evoked.data, dtype=bool) mask[[1, 5], :] = True plt_topomap(ch_type='mag', outlines=None) times = [0.1] plt_topomap(times, ch_type='grad', mask=mask) plt_topomap(times, ch_type='planar1') plt_topomap(times, ch_type='planar2') plt_topomap(times, ch_type='grad', mask=mask, show_names=True, mask_params={'marker': 'x'}) plt.close('all') with pytest.raises(ValueError, match='number of seconds; got -'): plt_topomap(times, ch_type='eeg', average=-1e3) with pytest.raises(TypeError, match='number of seconds; got type'): plt_topomap(times, ch_type='eeg', average='x') p = plt_topomap(times, ch_type='grad', image_interp='bilinear', show_names=lambda x: x.replace('MEG', '')) subplot = [x for x in p.get_children() if 'Subplot' in str(type(x))] assert len(subplot) >= 1, [type(x) for x in p.get_children()] subplot = subplot[0] have_all = all('MEG' not in x.get_text() for x in subplot.get_children() if isinstance(x, matplotlib.text.Text)) assert have_all # Plot array for ch_type in ('mag', 'grad'): evoked_ = evoked.copy().pick_types(eeg=False, meg=ch_type) plot_topomap(evoked_.data[:, 0], evoked_.info, **fast_test_noscale) # fail with multiple channel types pytest.raises(ValueError, plot_topomap, evoked.data[0, :], evoked.info) # Test title def get_texts(p): return [ x.get_text() for x in p.get_children() if isinstance(x, matplotlib.text.Text) ] p = plt_topomap(times, ch_type='eeg', average=0.01) assert_equal(len(get_texts(p)), 0) p = plt_topomap(times, ch_type='eeg', title='Custom') texts = get_texts(p) assert_equal(len(texts), 1) assert_equal(texts[0], 'Custom') plt.close('all') # delaunay triangulation warning plt_topomap(times, ch_type='mag') # projs have already been applied pytest.raises(RuntimeError, plot_evoked_topomap, evoked, 0.1, 'mag', proj='interactive', time_unit='s') # change to no-proj mode evoked = read_evokeds(evoked_fname, 'Left Auditory', baseline=(None, 0), proj=False) fig1 = evoked.plot_topomap('interactive', 'mag', proj='interactive', **fast_test) _fake_click(fig1, fig1.axes[1], (0.5, 0.5)) # click slider data_max = np.max(fig1.axes[0].images[0]._A) fig2 = plt.gcf() _fake_click(fig2, fig2.axes[0], (0.075, 0.775)) # toggle projector # make sure projector gets toggled assert (np.max(fig1.axes[0].images[0]._A) != data_max) with monkeypatch.context() as m: # speed it up by not actually plotting m.setattr(topomap, '_plot_topomap', lambda *args, **kwargs: (None, None, None)) with pytest.warns(RuntimeWarning, match='More than 25 topomaps plots'): plot_evoked_topomap(evoked, [0.1] * 26, colorbar=False) pytest.raises(ValueError, plot_evoked_topomap, evoked, [-3e12, 15e6], time_unit='s') for ch in evoked.info['chs']: if ch['coil_type'] == FIFF.FIFFV_COIL_EEG: ch['loc'].fill(0) # Remove extra digitization point, so EEG digitization points # correspond with the EEG electrodes del evoked.info['dig'][85] # Plot skirt evoked.plot_topomap(times, ch_type='eeg', outlines='skirt', **fast_test) # Pass custom outlines without patch eeg_picks = pick_types(evoked.info, meg=False, eeg=True) pos, outlines = _get_pos_outlines(evoked.info, eeg_picks, 0.1) evoked.plot_topomap(times, ch_type='eeg', outlines=outlines, **fast_test) plt.close('all') # Test interactive cmap fig = plot_evoked_topomap(evoked, times=[0., 0.1], ch_type='eeg', cmap=('Reds', True), title='title', **fast_test) fig.canvas.key_press_event('up') fig.canvas.key_press_event(' ') fig.canvas.key_press_event('down') cbar = fig.get_axes()[0].CB # Fake dragging with mouse. ax = cbar.cbar.ax _fake_click(fig, ax, (0.1, 0.1)) _fake_click(fig, ax, (0.1, 0.2), kind='motion') _fake_click(fig, ax, (0.1, 0.3), kind='release') _fake_click(fig, ax, (0.1, 0.1), button=3) _fake_click(fig, ax, (0.1, 0.2), button=3, kind='motion') _fake_click(fig, ax, (0.1, 0.3), kind='release') fig.canvas.scroll_event(0.5, 0.5, -0.5) # scroll down fig.canvas.scroll_event(0.5, 0.5, 0.5) # scroll up plt.close('all') # Pass custom outlines with patch callable def patch(): return Circle((0.5, 0.4687), radius=.46, clip_on=True, transform=plt.gca().transAxes) outlines['patch'] = patch plot_evoked_topomap(evoked, times, ch_type='eeg', outlines=outlines, **fast_test) # Remove digitization points. Now topomap should fail evoked.info['dig'] = None pytest.raises(RuntimeError, plot_evoked_topomap, evoked, times, ch_type='eeg', time_unit='s') plt.close('all') # Error for missing names n_channels = len(pos) data = np.ones(n_channels) pytest.raises(ValueError, plot_topomap, data, pos, show_names=True) # Test error messages for invalid pos parameter pos_1d = np.zeros(n_channels) pos_3d = np.zeros((n_channels, 2, 2)) pytest.raises(ValueError, plot_topomap, data, pos_1d) pytest.raises(ValueError, plot_topomap, data, pos_3d) pytest.raises(ValueError, plot_topomap, data, pos[:3, :]) pos_x = pos[:, :1] pos_xyz = np.c_[pos, np.zeros(n_channels)[:, np.newaxis]] pytest.raises(ValueError, plot_topomap, data, pos_x) pytest.raises(ValueError, plot_topomap, data, pos_xyz) # An #channels x 4 matrix should work though. In this case (x, y, width, # height) is assumed. pos_xywh = np.c_[pos, np.zeros((n_channels, 2))] plot_topomap(data, pos_xywh) plt.close('all') # Test peak finder axes = [plt.subplot(131), plt.subplot(132)] evoked.plot_topomap(times='peaks', axes=axes, **fast_test) plt.close('all') evoked.data = np.zeros(evoked.data.shape) evoked.data[50][1] = 1 assert_array_equal(_find_peaks(evoked, 10), evoked.times[1]) evoked.data[80][100] = 1 assert_array_equal(_find_peaks(evoked, 10), evoked.times[[1, 100]]) evoked.data[2][95] = 2 assert_array_equal(_find_peaks(evoked, 10), evoked.times[[1, 95]]) assert_array_equal(_find_peaks(evoked, 1), evoked.times[95]) # Test excluding bads channels evoked_grad.info['bads'] += [evoked_grad.info['ch_names'][0]] orig_bads = evoked_grad.info['bads'] evoked_grad.plot_topomap(ch_type='grad', times=[0], time_unit='ms') assert_array_equal(evoked_grad.info['bads'], orig_bads) plt.close('all')
def test_plot_digmontage(): """Test plot DigMontage.""" montage = make_dig_montage(ch_pos=dict(zip(list('abc'), np.eye(3)))) montage.plot() plt.close('all')
def test_array_raw(): """Test creating raw from array.""" # creating raw = read_raw_fif(fif_fname).crop(2, 5) data, times = raw[:, :] sfreq = raw.info['sfreq'] ch_names = [(ch[4:] if 'STI' not in ch else ch) for ch in raw.info['ch_names']] # change them, why not types = list() for ci in range(101): types.extend(('grad', 'grad', 'mag')) types.extend(['ecog', 'seeg', 'hbo']) # really 3 meg channels types.extend(['stim'] * 9) types.extend(['eeg'] * 60) picks = np.concatenate([ pick_types(raw.info)[::20], pick_types(raw.info, meg=False, stim=True), pick_types(raw.info, meg=False, eeg=True)[::20] ]) del raw data = data[picks] ch_names = np.array(ch_names)[picks].tolist() types = np.array(types)[picks].tolist() types.pop(-1) # wrong length pytest.raises(ValueError, create_info, ch_names, sfreq, types) # bad entry types.append('foo') pytest.raises(KeyError, create_info, ch_names, sfreq, types) types[-1] = 'eog' # default type info = create_info(ch_names, sfreq) assert_equal(info['chs'][0]['kind'], _kind_dict['misc'][0]) # use real types info = create_info(ch_names, sfreq, types) raw2 = _test_raw_reader(RawArray, test_preloading=False, data=data, info=info, first_samp=2 * data.shape[1]) data2, times2 = raw2[:, :] assert_allclose(data, data2) assert_allclose(times, times2) assert ('RawArray' in repr(raw2)) pytest.raises(TypeError, RawArray, info, data) # filtering picks = pick_types(raw2.info, misc=True, exclude='bads')[:4] assert_equal(len(picks), 4) raw_lp = raw2.copy() kwargs = dict(fir_design='firwin', picks=picks) raw_lp.filter(None, 4.0, h_trans_bandwidth=4., **kwargs) raw_hp = raw2.copy() raw_hp.filter(16.0, None, l_trans_bandwidth=4., **kwargs) raw_bp = raw2.copy() raw_bp.filter(8.0, 12.0, l_trans_bandwidth=4., h_trans_bandwidth=4., **kwargs) raw_bs = raw2.copy() raw_bs.filter(16.0, 4.0, l_trans_bandwidth=4., h_trans_bandwidth=4., **kwargs) data, _ = raw2[picks, :] lp_data, _ = raw_lp[picks, :] hp_data, _ = raw_hp[picks, :] bp_data, _ = raw_bp[picks, :] bs_data, _ = raw_bs[picks, :] sig_dec = 15 assert_array_almost_equal(data, lp_data + bp_data + hp_data, sig_dec) assert_array_almost_equal(data, bp_data + bs_data, sig_dec) # plotting raw2.plot() raw2.plot_psd(tmax=2., average=True, n_fft=1024, spatial_colors=False) plt.close('all') # epoching events = find_events(raw2, stim_channel='STI 014') events[:, 2] = 1 assert len(events) > 2 epochs = Epochs(raw2, events, 1, -0.2, 0.4, preload=True) evoked = epochs.average() assert_equal(evoked.nave, len(events) - 1) # complex data rng = np.random.RandomState(0) data = rng.randn(1, 100) + 1j * rng.randn(1, 100) raw = RawArray(data, create_info(1, 1000., 'eeg')) assert_allclose(raw._data, data) # Using digital montage to give MNI electrode coordinates n_elec = 10 ts_size = 10000 Fs = 512. ch_names = [str(i) for i in range(n_elec)] ch_pos_loc = np.random.randint(60, size=(n_elec, 3)).tolist() data = np.random.rand(n_elec, ts_size) montage = make_dig_montage(ch_pos=dict(zip(ch_names, ch_pos_loc)), coord_frame='head') info = create_info(ch_names, Fs, 'ecog') raw = RawArray(data, info) raw.set_montage(montage) raw.plot_psd(average=False) # looking for nonexistent layout raw.plot_psd_topo()
def to_mne_eeg(eegstream=None, line_freq=None, filenames=None, nasion=None, lpa=None, rpa=None): '''Convert recordings to MNE format. Args: eegstream : array EEG streams previously imported. line_freq : int Powerline frequency (50 or 60). filenames : array Full path of XDF files. Used for recording identification. nasion : array, shape(3,) Position of the nasion fiducial point. If specified, the array must have the same lenght of eegstream. Format for every recording (X, Y, Z) in meters: [0,0,0] lpa : array, shape(3,) Position of the left periauricular fiducial point. If specified, the array must have the same lenght of eegstream. Format for every recording (X, Y, Z) in meters: [0,0,0] rpa : array, shape(3,) Position of the right periauricular fiducial point. If specified, the array must have the same lenght of eegstream. Format for every recording (X, Y, Z) in meters: [0,0,0] Returns: Array of MNE RawArray instances with the recordings specified in eegstream. Raises: ValueError: if no stream is specified in eegstream or powerline frequency is not 50 or 60. See also: read_raw_xdf read_raw_xdf_dir ''' if eegstream is None: raise (ValueError('Enter parameter array of EEG recordings.')) if line_freq is None or line_freq not in [50, 60]: raise (ValueError( 'Enter the powerline frequency of your region (50 Hz or 60 Hz).')) eegstream = [eegstream] if not isinstance(eegstream, list) else eegstream raweeg = [] # Get the names of the channels ch_names = [ eegstream[0]['info']['desc'][0]['channels'][0]['channel'][i]['label'] [0] for i in range(len(eegstream[0]['time_series'][0])) ] # Define sensor coordinates sensor_coord = [[-0.0856192, -0.0465147, -0.0457070], [-0.0548397, 0.0685722, -0.0105900], [0.0557433, 0.0696568, -0.0107550], [0.0861618, -0.0470353, -0.0458690]] for index, stream in enumerate(eegstream): # Get channels position dig_montage = channels.make_dig_montage( ch_pos=dict(zip(ch_names, sensor_coord)), nasion=nasion[index] if nasion is not None else None, lpa=lpa[index] if lpa is not None else None, rpa=rpa[index] if rpa is not None else None, coord_frame='head') # Create raw info for processing info = create_info(ch_names=dig_montage.ch_names, sfreq=float(stream['info']['nominal_srate'][0]), ch_types='eeg') # Add channels position to info info.set_montage(dig_montage) # Convert data from microvolts to volts conv_data = stream["time_series"] * 1e-6 # Reorder channels ord_data = [[sublist[1][item] for item in [1, 2, 3, 0]] for sublist in enumerate(conv_data)] # Create raw data for mne raw = io.RawArray(np.array(ord_data).T, info) # Get the information of each stream stream_info = stream['info']['name'][0][:9] + ' ' + ( filenames[index] if filenames is not None else '') # Print the information of each stream print('\nInfo: ' + str(index) + ' ' + stream_info + '\n') # Add the powerline frequency of each stream raw.info['line_freq'] = line_freq # Create annotation to store the name of the device and the filenames annotations = Annotations(0, 0, stream_info) # Add the annotations to raw data raw.set_annotations(annotations) # Add the RawArray object to list of eeg recordings raweeg.append(raw) return raweeg
def test_combining_digmontage_objects(): """Test combining different DigMontage objects.""" rng = np.random.RandomState(0) fiducials = dict(zip(('nasion', 'lpa', 'rpa'), rng.rand(3, 3))) # hsp positions are [1X, 1X, 1X] hsp1 = make_dig_montage(**fiducials, hsp=np.full((2, 3), 11.)) hsp2 = make_dig_montage(**fiducials, hsp=np.full((2, 3), 12.)) hsp3 = make_dig_montage(**fiducials, hsp=np.full((2, 3), 13.)) # hpi positions are [2X, 2X, 2X] hpi1 = make_dig_montage(**fiducials, hpi=np.full((2, 3), 21.)) hpi2 = make_dig_montage(**fiducials, hpi=np.full((2, 3), 22.)) hpi3 = make_dig_montage(**fiducials, hpi=np.full((2, 3), 23.)) # channels have positions at 40s, 50s, and 60s. ch_pos1 = make_dig_montage(**fiducials, ch_pos={ 'h': [41, 41, 41], 'b': [42, 42, 42], 'g': [43, 43, 43] }) ch_pos2 = make_dig_montage(**fiducials, ch_pos={ 'n': [51, 51, 51], 'y': [52, 52, 52], 'p': [53, 53, 53] }) ch_pos3 = make_dig_montage(**fiducials, ch_pos={ 'v': [61, 61, 61], 'a': [62, 62, 62], 'l': [63, 63, 63] }) montage = (DigMontage() + hsp1 + hsp2 + hsp3 + hpi1 + hpi2 + hpi3 + ch_pos1 + ch_pos2 + ch_pos3) assert repr(montage) == ( '<DigMontage | ' '6 extras (headshape), 6 HPIs, 3 fiducials, 9 channels>') EXPECTED_MONTAGE = make_dig_montage(**fiducials, hsp=np.concatenate([ np.full((2, 3), 11.), np.full((2, 3), 12.), np.full((2, 3), 13.) ]), hpi=np.concatenate([ np.full((2, 3), 21.), np.full((2, 3), 22.), np.full((2, 3), 23.) ]), ch_pos={ 'h': [41, 41, 41], 'b': [42, 42, 42], 'g': [43, 43, 43], 'n': [51, 51, 51], 'y': [52, 52, 52], 'p': [53, 53, 53], 'v': [61, 61, 61], 'a': [62, 62, 62], 'l': [63, 63, 63], }) # Do some checks to ensure they are the same DigMontage assert len(montage.ch_names) == len(EXPECTED_MONTAGE.ch_names) assert all([c in montage.ch_names for c in EXPECTED_MONTAGE.ch_names]) actual_occurrences = _count_points_by_type(montage.dig) expected_occurrences = _count_points_by_type(EXPECTED_MONTAGE.dig) assert actual_occurrences == expected_occurrences
def test_warp_montage_volume(): """Test warping an montage based on intracranial electrode positions.""" import nibabel as nib subject_brain = nib.load( op.join(subjects_dir, 'sample', 'mri', 'brain.mgz')) template_brain = nib.load( op.join(subjects_dir, 'fsaverage', 'mri', 'brain.mgz')) zooms = dict(translation=10, rigid=10, sdr=10) reg_affine, sdr_morph = compute_volume_registration( subject_brain, template_brain, zooms=zooms, niter=[3, 3, 3], pipeline=('translation', 'rigid', 'sdr')) # make an info object with three channels with positions ch_coords = np.array([[-8.7040273, 17.99938754, 10.29604017], [-14.03007764, 19.69978401, 12.07236939], [-21.1130506, 21.98310911, 13.25658887]]) ch_pos = dict(zip(['1', '2', '3'], ch_coords / 1000)) # mm -> m lpa, nasion, rpa = get_mni_fiducials('sample', subjects_dir) montage = make_dig_montage(ch_pos, lpa=lpa['r'], nasion=nasion['r'], rpa=rpa['r'], coord_frame='mri') # make fake image based on the info CT_data = np.zeros(subject_brain.shape) # convert to voxels ch_coords_vox = apply_trans( np.linalg.inv(subject_brain.header.get_vox2ras_tkr()), ch_coords) for (x, y, z) in ch_coords_vox.round().astype(int): # make electrode contact hyperintensities # first, make the surrounding voxels high intensity CT_data[x - 1:x + 2, y - 1:y + 2, z - 1:z + 2] = 500 # then, make the center even higher intensity CT_data[x, y, z] = 1000 CT = nib.Nifti1Image(CT_data, subject_brain.affine) ch_coords = np.array([[-8.7040273, 17.99938754, 10.29604017], [-14.03007764, 19.69978401, 12.07236939], [-21.1130506, 21.98310911, 13.25658887]]) ch_pos = dict(zip(['1', '2', '3'], ch_coords / 1000)) # mm -> m lpa, nasion, rpa = get_mni_fiducials('sample', subjects_dir) montage = make_dig_montage(ch_pos, lpa=lpa['r'], nasion=nasion['r'], rpa=rpa['r'], coord_frame='mri') montage_warped, image_from, image_to = warp_montage_volume( montage, CT, reg_affine, sdr_morph, 'sample', subjects_dir_from=subjects_dir, thresh=0.99) # checked with nilearn plot from `tut-ieeg-localize` # check montage in surface RAS ground_truth_warped = np.array([[-0.009, -0.00133333, -0.033], [-0.01445455, 0.00127273, -0.03163636], [-0.022, 0.00285714, -0.031]]) for i, d in enumerate(montage_warped.dig): assert np.linalg.norm( # off by less than 1.5 cm d['r'] - ground_truth_warped[i]) < 0.015 # check image_from for idx, contact in enumerate(range(1, len(ch_pos) + 1)): voxels = np.array(np.where(np.array(image_from.dataobj) == contact)).T assert ch_coords_vox.round()[idx] in voxels assert ch_coords_vox.round()[idx] + 5 not in voxels # check image_to, too many, just check center ground_truth_warped_voxels = np.array( [[135.5959596, 161.97979798, 123.83838384], [143.11111111, 159.71428571, 125.61904762], [150.53982301, 158.38053097, 127.31858407]]) for i in range(len(montage.ch_names)): assert np.linalg.norm( np.array(np.where(np.array(image_to.dataobj) == i + 1) ).mean(axis=1) - ground_truth_warped_voxels[i]) < 8 # test inputs with pytest.raises(ValueError, match='`thresh` must be between 0 and 1'): warp_montage_volume( montage, CT, reg_affine, sdr_morph, 'sample', thresh=11.) with pytest.raises(ValueError, match='subject folder is incorrect'): warp_montage_volume( montage, CT, reg_affine, sdr_morph, subject_from='foo') CT_unaligned = nib.Nifti1Image(CT_data, template_brain.affine) with pytest.raises(RuntimeError, match='not aligned to Freesurfer'): warp_montage_volume(montage, CT_unaligned, reg_affine, sdr_morph, 'sample', subjects_dir_from=subjects_dir) bad_montage = montage.copy() for d in bad_montage.dig: d['coord_frame'] = 99 with pytest.raises(RuntimeError, match='Coordinate frame not supported'): warp_montage_volume(bad_montage, CT, reg_affine, sdr_morph, 'sample', subjects_dir_from=subjects_dir) # check channel not warped ch_pos_doubled = ch_pos.copy() ch_pos_doubled.update(zip(['4', '5', '6'], ch_coords / 1000)) doubled_montage = make_dig_montage( ch_pos_doubled, lpa=lpa['r'], nasion=nasion['r'], rpa=rpa['r'], coord_frame='mri') with pytest.warns(RuntimeWarning, match='not assigned'): warp_montage_volume(doubled_montage, CT, reg_affine, None, 'sample', subjects_dir_from=subjects_dir)
def _fake_montage(ch_names): pos = np.random.RandomState(42).randn(len(ch_names), 3) return make_dig_montage(ch_pos=dict(zip(ch_names, pos)), coord_frame='head')
def test_warp_montage_volume(): """Test warping an montage based on intracranial electrode positions.""" import nibabel as nib subject_T1 = nib.load(op.join(subjects_dir, 'sample', 'mri', 'T1.mgz')) subject_brain = nib.load( op.join(subjects_dir, 'sample', 'mri', 'brain.mgz')) template_brain = nib.load( op.join(subjects_dir, 'fsaverage', 'mri', 'brain.mgz')) reg_affine, sdr_morph = compute_volume_registration( subject_brain, template_brain, zooms=5, niter=dict(translation=[5, 5, 5], rigid=[5, 5, 5], sdr=[3, 3, 3]), pipeline=('translation', 'rigid', 'sdr')) # make fake image with three coordinates CT_data = np.zeros(subject_brain.shape) # make electrode contact hyperintensities CT_data[45:47, 39:41, 49:50] = 500 # surround high intensity CT_data[46, 40, 49] = 1000 # center even higher intensity CT_data[47:49, 39:40, 49:50] = 500 CT_data[48, 39, 50] = 1000 CT_data[50:52, 38:40, 50:51] = 500 CT_data[50, 39, 50] = 1000 CT = nib.Nifti1Image(CT_data, subject_T1.affine) ch_coords = np.array([[-8.7040273, 17.99938754, 10.29604017], [-14.03007764, 19.69978401, 12.07236939], [-21.1130506, 21.98310911, 13.25658887]]) ch_pos = dict(zip(['1', '2', '3'], ch_coords / 1000)) # mm -> m montage = make_dig_montage(ch_pos, coord_frame='mri') montage_warped, image_from, image_to = warp_montage_volume( montage, CT, reg_affine, sdr_morph, 'sample', subjects_dir=subjects_dir, thresh=0.99) # checked with nilearn plot from `tut-ieeg-localize` # check montage in surface RAS ground_truth_warped = np.array([[-0.27778788, 0.24251515, -0.35693939], [-0.30033333, 0.24785714, -0.35014286], [-0.32261947, 0.25295575, -0.34614159]]) for i in range(len(montage.ch_names)): assert np.linalg.norm( # off by less than 1.5 cm montage_warped.dig[i]['r'] - ground_truth_warped[i]) < 0.015 # check image_from assert_array_equal(np.array(np.where(_get_img_fdata(image_from) == 1)), np.array([[45, 46, 46], [40, 39, 40], [49, 49, 49]])) assert_array_equal(np.array(np.where(_get_img_fdata(image_from) == 2)), np.array([[48, 48], [39, 39], [49, 50]])) assert_array_equal(np.array(np.where(_get_img_fdata(image_from) == 3)), np.array([[50, 50, 51], [38, 39, 39], [50, 50, 50]])) # check image_to, too many, just check center ground_truth_warped_voxels = np.array( [[135.5959596, 161.97979798, 123.83838384], [143.11111111, 159.71428571, 125.61904762], [150.53982301, 158.38053097, 127.31858407]]) for i in range(len(montage.ch_names)): assert np.linalg.norm( np.array(np.where(_get_img_fdata(image_to) == i + 1)).mean( axis=1) - ground_truth_warped_voxels[i]) < 5 # test inputs with pytest.raises(ValueError, match='`thresh` must be between 0 and 1'): warp_montage_volume(montage, CT, reg_affine, sdr_morph, 'sample', thresh=11.) with pytest.raises(ValueError, match='subject folder is incorrect'): warp_montage_volume(montage, CT, reg_affine, sdr_morph, subject_from='foo') CT_unaligned = nib.Nifti1Image(CT_data, subject_brain.affine) with pytest.raises(RuntimeError, match='not aligned to Freesurfer'): warp_montage_volume(montage, CT_unaligned, reg_affine, sdr_morph, 'sample', subjects_dir=subjects_dir) bad_montage = make_dig_montage(ch_pos, coord_frame='mri') bad_montage.dig[0]['coord_frame'] = 99 with pytest.raises(RuntimeError, match='Only single coordinate frame in ' 'dig is supported'): warp_montage_volume(bad_montage, CT, reg_affine, sdr_morph, 'sample', subjects_dir=subjects_dir) wrong_montage = make_dig_montage(ch_pos, coord_frame='head') with pytest.raises(RuntimeError, match='Coordinate frame not supported'): warp_montage_volume(wrong_montage, CT, reg_affine, sdr_morph, 'sample', subjects_dir=subjects_dir) # check channel not warped ch_pos_doubled = ch_pos.copy() ch_pos_doubled.update(zip(['4', '5', '6'], ch_coords / 1000)) doubled_montage = make_dig_montage(ch_pos_doubled, coord_frame='mri') with pytest.warns(RuntimeWarning, match='not assigned'): warp_montage_volume(doubled_montage, CT, reg_affine, sdr_morph, 'sample', subjects_dir=subjects_dir)
def test_transform_to_head_and_compute_dev_head_t(): """Test transform_to_head and compute_dev_head_t.""" EXPECTED_DEV_HEAD_T = \ [[-3.72201691e-02, -9.98212167e-01, -4.67667497e-02, -7.31583414e-04], [8.98064989e-01, -5.39382685e-02, 4.36543170e-01, 1.60134431e-02], [-4.38285221e-01, -2.57513699e-02, 8.98466990e-01, 6.13035748e-02], [0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]] EXPECTED_FID_IN_POLHEMUS = { 'nasion': np.array([0.001393, 0.0131613, -0.0046967]), 'lpa': np.array([-0.0624997, -0.0737271, 0.07996]), 'rpa': np.array([-0.0748957, 0.0873785, 0.0811943]), } EXPECTED_FID_IN_HEAD = { 'nasion': np.array([-8.94466792e-18, 1.10559624e-01, -3.85185989e-34]), 'lpa': np.array([-8.10816716e-02, 6.56321671e-18, 0]), 'rpa': np.array([8.05048781e-02, -6.47441364e-18, 0]), } hpi_dev = np.array( [[ 2.13951493e-02, 8.47444056e-02, -5.65431188e-02], # noqa [ 2.10299433e-02, -8.03141101e-02, -6.34420259e-02], # noqa [ 1.05916829e-01, 8.18485672e-05, 1.19928083e-02], # noqa [ 9.26595105e-02, 4.64804385e-02, 8.45141253e-03], # noqa [ 9.42554419e-02, -4.35206589e-02, 8.78999363e-03]] # noqa ) hpi_polhemus = np.array( [[-0.0595004, -0.0704836, 0.075893 ], # noqa [-0.0646373, 0.0838228, 0.0762123], # noqa [-0.0135035, 0.0072522, -0.0268405], # noqa [-0.0202967, -0.0351498, -0.0129305], # noqa [-0.0277519, 0.0452628, -0.0222407]] # noqa ) montage_polhemus = make_dig_montage( **EXPECTED_FID_IN_POLHEMUS, hpi=hpi_polhemus, coord_frame='unknown' ) montage_meg = make_dig_montage(hpi=hpi_dev, coord_frame='meg') # Test regular worflow to get dev_head_t montage = montage_polhemus + montage_meg fids, _ = _get_fid_coords(montage.dig) for kk in fids: assert_allclose(fids[kk], EXPECTED_FID_IN_POLHEMUS[kk], atol=1e-5) with pytest.raises(ValueError, match='set to head coordinate system'): _ = compute_dev_head_t(montage) montage = transform_to_head(montage) fids, _ = _get_fid_coords(montage.dig) for kk in fids: assert_allclose(fids[kk], EXPECTED_FID_IN_HEAD[kk], atol=1e-5) dev_head_t = compute_dev_head_t(montage) assert_allclose(dev_head_t['trans'], EXPECTED_DEV_HEAD_T, atol=1e-7) # Test errors when number of HPI points do not match EXPECTED_ERR_MSG = 'Device-to-Head .*Got 0 .*device and 5 points in head' with pytest.raises(ValueError, match=EXPECTED_ERR_MSG): _ = compute_dev_head_t(transform_to_head(montage_polhemus)) EXPECTED_ERR_MSG = 'Device-to-Head .*Got 5 .*device and 0 points in head' with pytest.raises(ValueError, match=EXPECTED_ERR_MSG): _ = compute_dev_head_t(transform_to_head( montage_meg + make_dig_montage(**EXPECTED_FID_IN_POLHEMUS) )) EXPECTED_ERR_MSG = 'Device-to-Head .*Got 3 .*device and 5 points in head' with pytest.raises(ValueError, match=EXPECTED_ERR_MSG): _ = compute_dev_head_t(transform_to_head( DigMontage(dig=_format_dig_points(montage_meg.dig[:3])) + montage_polhemus ))
def test_plot_ch_adjacency(): """Test plotting of adjacency matrix.""" xyz_pos = np.array([[-0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0., 0., 0.12], [-0.1, -0.1, 0.1], [0.1, -0.1, 0.1]]) info = create_info(list('abcde'), 23, ch_types='eeg') montage = make_dig_montage( ch_pos={ch: pos for ch, pos in zip(info.ch_names, xyz_pos)}, coord_frame='head') info.set_montage(montage) # construct adjacency adj_sparse, ch_names = find_ch_adjacency(info, 'eeg') # plot adjacency fig = plot_ch_adjacency(info, adj_sparse, ch_names, kind='2d', edit=True) # find channel positions collection = fig.axes[0].collections[0] pos = collection.get_offsets().data # get adjacency lines lines = fig.axes[0].lines[4:] # (first four lines are head outlines) # make sure lines match adjacency relations in the matrix for line in lines: x, y = line.get_data() ch_idx = [np.where((pos == [[x[ix], y[ix]]]).all(axis=1))[0][0] for ix in range(2)] assert adj_sparse[ch_idx[0], ch_idx[1]] # make sure additional point is generated after clicking a channel _fake_click(fig, fig.axes[0], pos[0], xform='data') collections = fig.axes[0].collections assert len(collections) == 2 # make sure the point is green green = matplotlib.colors.to_rgba('tab:green') assert (collections[1].get_facecolor() == green).all() # make sure adjacency entry is modified after second click on another node assert adj_sparse[0, 1] assert adj_sparse[1, 0] n_lines_before = len(lines) _fake_click(fig, fig.axes[0], pos[1], xform='data') assert not adj_sparse[0, 1] assert not adj_sparse[1, 0] # and there is one line less lines = fig.axes[0].lines[4:] n_lines_after = len(lines) assert n_lines_after == n_lines_before - 1 # make sure there is still one green point ... collections = fig.axes[0].collections assert len(collections) == 2 assert (collections[1].get_facecolor() == green).all() # ... but its at a different location point_pos = collections[1].get_offsets().data assert (point_pos == pos[1]).all() # check that clicking again removes the green selection point _fake_click(fig, fig.axes[0], pos[1], xform='data') collections = fig.axes[0].collections assert len(collections) == 1 # clicking the points again adds a green line _fake_click(fig, fig.axes[0], pos[1], xform='data') _fake_click(fig, fig.axes[0], pos[0], xform='data') lines = fig.axes[0].lines[4:] assert len(lines) == n_lines_after + 1 assert lines[-1].get_color() == 'tab:green' # smoke test for 3d option adj = adj_sparse.toarray() fig = plot_ch_adjacency(info, adj, ch_names, kind='3d') # test errors # ----------- # number of channels in the adjacency matrix and info must match msg = ("``adjacency`` must have the same number of rows as the number of " "channels in ``info``") with pytest.raises(ValueError, match=msg): plot_ch_adjacency(info, adj_sparse, ch_names[:3], kind='2d') # edition mode only available for 2d plot msg = "Editing a 3d adjacency plot is not supported." with pytest.raises(ValueError, match=msg): plot_ch_adjacency(info, adj, ch_names, kind='3d', edit=True)