def test_set_dig_montage(): """Test applying DigMontage to inst.""" # Extensive testing of applying `dig` to info is done in test_meas_info # with `test_make_dig_points`. names = ["nasion", "lpa", "rpa", "1", "2", "3", "4", "5"] hsp_points = _read_dig_points(hsp) elp_points = _read_dig_points(elp) nasion, lpa, rpa = elp_points[:3] nm_trans = get_ras_to_neuromag_trans(nasion, lpa, rpa) elp_points = apply_trans(nm_trans, elp_points) nasion, lpa, rpa = elp_points[:3] hsp_points = apply_trans(nm_trans, hsp_points) montage = read_dig_montage(hsp, hpi, elp, names, transform=True, dev_head_t=True) temp_dir = _TempDir() fname_temp = op.join(temp_dir, "test.fif") montage.save(fname_temp) montage_read = read_dig_montage(fif=fname_temp) for use_mon in (montage, montage_read): info = create_info(["Test Ch"], 1e3, ["eeg"]) with warnings.catch_warnings(record=True) as w: # test ch pos not set _set_montage(info, use_mon) assert_true(all("not set" in str(ww.message) for ww in w)) hs = np.array([p["r"] for i, p in enumerate(info["dig"]) if p["kind"] == FIFF.FIFFV_POINT_EXTRA]) nasion_dig = np.array( [ p["r"] for p in info["dig"] if all([p["ident"] == FIFF.FIFFV_POINT_NASION, p["kind"] == FIFF.FIFFV_POINT_CARDINAL]) ] ) lpa_dig = np.array( [ p["r"] for p in info["dig"] if all([p["ident"] == FIFF.FIFFV_POINT_LPA, p["kind"] == FIFF.FIFFV_POINT_CARDINAL]) ] ) rpa_dig = np.array( [ p["r"] for p in info["dig"] if all([p["ident"] == FIFF.FIFFV_POINT_RPA, p["kind"] == FIFF.FIFFV_POINT_CARDINAL]) ] ) hpi_dig = np.array([p["r"] for p in info["dig"] if p["kind"] == FIFF.FIFFV_POINT_HPI]) assert_allclose(hs, hsp_points, atol=1e-7) assert_allclose(nasion_dig.ravel(), nasion, atol=1e-7) assert_allclose(lpa_dig.ravel(), lpa, atol=1e-7) assert_allclose(rpa_dig.ravel(), rpa, atol=1e-7) assert_allclose(hpi_dig, elp_points[3:], atol=1e-7)
def test_fit_matched_points(): """Test fit_matched_points: fitting two matching sets of points""" tgt_pts = np.random.uniform(size=(6, 3)) # rotation only trans = rotation(2, 6, 3) src_pts = apply_trans(trans, tgt_pts) trans_est = fit_matched_points(src_pts, tgt_pts, translate=False, out="trans") est_pts = apply_trans(trans_est, src_pts) assert_array_almost_equal(tgt_pts, est_pts, 2, "fit_matched_points with " "rotation") # rotation & scaling trans = np.dot(rotation(2, 6, 3), scaling(0.5, 0.5, 0.5)) src_pts = apply_trans(trans, tgt_pts) trans_est = fit_matched_points(src_pts, tgt_pts, translate=False, scale=1, out="trans") est_pts = apply_trans(trans_est, src_pts) assert_array_almost_equal(tgt_pts, est_pts, 2, "fit_matched_points with " "rotation and scaling.") # rotation & translation trans = np.dot(translation(2, -6, 3), rotation(2, 6, 3)) src_pts = apply_trans(trans, tgt_pts) trans_est = fit_matched_points(src_pts, tgt_pts, out="trans") est_pts = apply_trans(trans_est, src_pts) assert_array_almost_equal(tgt_pts, est_pts, 2, "fit_matched_points with " "rotation and translation.") # rotation & translation & scaling trans = reduce(np.dot, (translation(2, -6, 3), rotation(1.5, 0.3, 1.4), scaling(0.5, 0.5, 0.5))) src_pts = apply_trans(trans, tgt_pts) trans_est = fit_matched_points(src_pts, tgt_pts, scale=1, out="trans") est_pts = apply_trans(trans_est, src_pts) assert_array_almost_equal(tgt_pts, est_pts, 2, "fit_matched_points with " "rotation, translation and scaling.") # test exceeding tolerance tgt_pts[0, :] += 20 assert_raises(RuntimeError, fit_matched_points, tgt_pts, src_pts, tol=10)
def plot_coregistration(subject, subjects_dir, hcp_path, recordings_path, info_from=(('data_type', 'rest'), ('run_index', 0)), view_init=(('azim', 0), ('elev', 0))): """A diagnostic plot to show the HCP coregistration Parameters ---------- subject : str The subject subjects_dir : str The path corresponding to MNE/freesurfer SUBJECTS_DIR (to be created) hcp_path : str The path where the HCP files can be found. recordings_path : str The path to converted data (including the head<->device transform). info_from : tuple of tuples | dict The reader info concerning the data from which sensor positions should be read. Must not be empty room as sensor positions are in head coordinates for 4D systems, hence not available in that case. Note that differences between the sensor positions across runs are smaller than 12 digits, hence negligible. view_init : tuple of tuples | dict The initival view, defaults to azimuth and elevation of 0, a simple lateral view Returns ------- fig : matplotlib.figure.Figure The figure object. """ import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D # noqa if isinstance(info_from, tuple): info_from = dict(info_from) if isinstance(view_init, tuple): view_init = dict(view_init) head_mri_t = read_trans( op.join(recordings_path, subject, '{}-head_mri-trans.fif'.format(subject))) info = read_info(subject=subject, hcp_path=hcp_path, **info_from) info = pick_info(info, _pick_data_channels(info, with_ref_meg=False)) sens_pnts = np.array([c['loc'][:3] for c in info['chs']]) sens_pnts = apply_trans(head_mri_t, sens_pnts) sens_pnts *= 1e3 # put in mm scale pnts, tris = read_surface( op.join(subjects_dir, subject, 'bem', 'inner_skull.surf')) fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(*sens_pnts.T, color='purple', marker='o') ax.scatter(*pnts.T, color='green', alpha=0.3) ax.view_init(**view_init) fig.tight_layout() return fig
def _compute_depth(dip, fname_bem, fname_trans, subject, subjects_dir): """Compute dipole depth.""" trans = _get_trans(fname_trans)[0] bem = read_bem_solution(fname_bem) surf = _bem_find_surface(bem, 'inner_skull') points = surf['rr'] points = apply_trans(trans['trans'], points) depth = _compute_nearest(points, dip.pos, return_dists=True)[1][0] return np.ravel(depth)
def test_fit_point_cloud(): """Test fit_point_cloud: fitting a set of points to a point cloud""" # evenly spaced target points on a sphere u = np.linspace(0, np.pi, 150) v = np.linspace(0, np.pi, 150) x = np.outer(np.cos(u), np.sin(v)).reshape((-1, 1)) y = np.outer(np.sin(u), np.sin(v)).reshape((-1, 1)) z = np.outer(np.ones(np.size(u)), np.cos(v)).reshape((-1, 1)) * 3 tgt_pts = np.hstack((x, y, z)) tgt_pts = _decimate_points(tgt_pts, .05) # pick some points to fit some_tgt_pts = tgt_pts[::362] # rotation only trans = rotation(1.5, .3, -0.4) src_pts = apply_trans(trans, some_tgt_pts) trans_est = fit_point_cloud(src_pts, tgt_pts, rotate=True, translate=False, scale=0, out='trans') est_pts = apply_trans(trans_est, src_pts) err = _point_cloud_error(est_pts, tgt_pts) assert_array_less(err, .1, "fit_point_cloud with rotation.") # rotation and translation trans = np.dot(rotation(0.5, .3, -0.4), translation(.3, .2, -.2)) src_pts = apply_trans(trans, some_tgt_pts) trans_est = fit_point_cloud(src_pts, tgt_pts, rotate=True, translate=True, scale=0, out='trans') est_pts = apply_trans(trans_est, src_pts) err = _point_cloud_error(est_pts, tgt_pts) assert_array_less(err, .1, "fit_point_cloud with rotation and " "translation.") # rotation and 1 scale parameter trans = np.dot(rotation(0.5, .3, -0.4), scaling(1.5, 1.5, 1.5)) src_pts = apply_trans(trans, some_tgt_pts) trans_est = fit_point_cloud(src_pts, tgt_pts, rotate=True, translate=False, scale=1, out='trans') est_pts = apply_trans(trans_est, src_pts) err = _point_cloud_error(est_pts, tgt_pts) assert_array_less(err, .1, "fit_point_cloud with rotation and 1 scaling " "parameter.") # rotation and 3 scale parameter trans = np.dot(rotation(0.5, .3, -0.4), scaling(1.5, 1.7, 1.1)) src_pts = apply_trans(trans, some_tgt_pts) trans_est = fit_point_cloud(src_pts, tgt_pts, rotate=True, translate=False, scale=3, out='trans') est_pts = apply_trans(trans_est, src_pts) err = _point_cloud_error(est_pts, tgt_pts) assert_array_less(err, .1, "fit_point_cloud with rotation and 3 scaling " "parameters.")
def test_hsp_elp(): """Test KIT usage of *.elp and *.hsp files against *.txt files.""" raw_txt = read_raw_kit(sqd_path, mrk_path, elp_txt_path, hsp_txt_path) raw_elp = read_raw_kit(sqd_path, mrk_path, elp_path, hsp_path) # head points pts_txt = np.array([dig_point['r'] for dig_point in raw_txt.info['dig']]) pts_elp = np.array([dig_point['r'] for dig_point in raw_elp.info['dig']]) assert_array_almost_equal(pts_elp, pts_txt, decimal=5) # transforms trans_txt = raw_txt.info['dev_head_t']['trans'] trans_elp = raw_elp.info['dev_head_t']['trans'] assert_array_almost_equal(trans_elp, trans_txt, decimal=5) # head points in device space pts_txt_in_dev = apply_trans(linalg.inv(trans_txt), pts_txt) pts_elp_in_dev = apply_trans(linalg.inv(trans_elp), pts_elp) assert_array_almost_equal(pts_elp_in_dev, pts_txt_in_dev, decimal=5)
def test_set_dig_montage(): """Test applying DigMontage to inst.""" # Extensive testing of applying `dig` to info is done in test_meas_info # with `test_make_dig_points`. names = ['nasion', 'lpa', 'rpa', '1', '2', '3', '4', '5'] hsp_points = _read_dig_points(hsp) elp_points = _read_dig_points(elp) nasion, lpa, rpa = elp_points[:3] nm_trans = get_ras_to_neuromag_trans(nasion, lpa, rpa) elp_points = apply_trans(nm_trans, elp_points) nasion, lpa, rpa = elp_points[:3] hsp_points = apply_trans(nm_trans, hsp_points) montage = read_dig_montage(hsp, hpi, elp, names, transform=True, dev_head_t=True) temp_dir = _TempDir() fname_temp = op.join(temp_dir, 'test.fif') montage.save(fname_temp) montage_read = read_dig_montage(fif=fname_temp) for use_mon in (montage, montage_read): info = create_info(['Test Ch'], 1e3, ['eeg']) with pytest.warns(None): # warns on one run about not all positions _set_montage(info, use_mon) hs = np.array([p['r'] for i, p in enumerate(info['dig']) if p['kind'] == FIFF.FIFFV_POINT_EXTRA]) nasion_dig = np.array([p['r'] for p in info['dig'] if all([p['ident'] == FIFF.FIFFV_POINT_NASION, p['kind'] == FIFF.FIFFV_POINT_CARDINAL]) ]) lpa_dig = np.array([p['r'] for p in info['dig'] if all([p['ident'] == FIFF.FIFFV_POINT_LPA, p['kind'] == FIFF.FIFFV_POINT_CARDINAL])]) rpa_dig = np.array([p['r'] for p in info['dig'] if all([p['ident'] == FIFF.FIFFV_POINT_RPA, p['kind'] == FIFF.FIFFV_POINT_CARDINAL])]) hpi_dig = np.array([p['r'] for p in info['dig'] if p['kind'] == FIFF.FIFFV_POINT_HPI]) assert_allclose(hs, hsp_points, atol=1e-7) assert_allclose(nasion_dig.ravel(), nasion, atol=1e-7) assert_allclose(lpa_dig.ravel(), lpa, atol=1e-7) assert_allclose(rpa_dig.ravel(), rpa, atol=1e-7) assert_allclose(hpi_dig, elp_points[3:], atol=1e-7)
def test_get_ras_to_neuromag_trans(): """Test the coordinate transformation from ras to neuromag""" # create model points in neuromag-like space anterior = [0, 1, 0] left = [-1, 0, 0] right = [0.8, 0, 0] up = [0, 0, 1] rand_pts = np.random.uniform(-1, 1, (3, 3)) pts = np.vstack((anterior, left, right, up, rand_pts)) # change coord system rx, ry, rz, tx, ty, tz = np.random.uniform(-2 * np.pi, 2 * np.pi, 6) trans = np.dot(translation(tx, ty, tz), rotation(rx, ry, rz)) pts_changed = apply_trans(trans, pts) # transform back into original space nas, lpa, rpa = pts_changed[:3] hsp_trans = get_ras_to_neuromag_trans(nas, lpa, rpa) pts_restored = apply_trans(hsp_trans, pts_changed) err = "Neuromag transformation failed" assert_array_almost_equal(pts_restored, pts, 6, err)
def test_set_dig_montage(): """Test applying DigMontage to inst Extensive testing of applying `dig` to info is done in test_meas_info with `test_make_dig_points`. """ names = ['nasion', 'lpa', 'rpa', '1', '2', '3', '4', '5'] hsp_points = _read_dig_points(hsp) elp_points = _read_dig_points(elp) hpi_points = read_mrk(hpi) p0, p1, p2 = elp_points[:3] nm_trans = get_ras_to_neuromag_trans(p0, p1, p2) elp_points = apply_trans(nm_trans, elp_points) nasion_point, lpa_point, rpa_point = elp_points[:3] hsp_points = apply_trans(nm_trans, hsp_points) montage = read_dig_montage(hsp, hpi, elp, names, unit='m', transform=True) info = create_info(['Test Ch'], 1e3, ['eeg']) _set_montage(info, montage) hs = np.array([p['r'] for i, p in enumerate(info['dig']) if p['kind'] == FIFF.FIFFV_POINT_EXTRA]) nasion_dig = np.array([p['r'] for p in info['dig'] if all([p['ident'] == FIFF.FIFFV_POINT_NASION, p['kind'] == FIFF.FIFFV_POINT_CARDINAL])]) lpa_dig = np.array([p['r'] for p in info['dig'] if all([p['ident'] == FIFF.FIFFV_POINT_LPA, p['kind'] == FIFF.FIFFV_POINT_CARDINAL])]) rpa_dig = np.array([p['r'] for p in info['dig'] if all([p['ident'] == FIFF.FIFFV_POINT_RPA, p['kind'] == FIFF.FIFFV_POINT_CARDINAL])]) hpi_dig = np.array([p['r'] for p in info['dig'] if p['kind'] == FIFF.FIFFV_POINT_HPI]) assert_array_equal(hs, hsp_points) assert_array_equal(nasion_dig.ravel(), nasion_point) assert_array_equal(lpa_dig.ravel(), lpa_point) assert_array_equal(rpa_dig.ravel(), rpa_point) assert_array_equal(hpi_dig, hpi_points) assert_array_equal(montage.dev_head_t, info['dev_head_t']['trans'])
def test_head_to_mni(): """Test conversion of aseg vertices to MNI coordinates.""" # obtained using freeview coords = np.array([[22.52, 11.24, 17.72], [22.52, 5.46, 21.58], [16.10, 5.46, 22.23], [21.24, 8.36, 22.23]]) xfm = _read_talxfm('sample', subjects_dir) coords_MNI = apply_trans(xfm['trans'], coords) trans = read_trans(trans_fname) # head->MRI (surface RAS) mri_head_t = invert_transform(trans) # MRI (surface RAS)->head matrix # obtained from sample_audvis-meg-oct-6-mixed-fwd.fif coo_right_amygdala = np.array([[0.01745682, 0.02665809, 0.03281873], [0.01014125, 0.02496262, 0.04233755], [0.01713642, 0.02505193, 0.04258181], [0.01720631, 0.03073877, 0.03850075]]) coords_MNI_2 = head_to_mni(coo_right_amygdala, 'sample', mri_head_t, subjects_dir) # less than 1mm error assert_allclose(coords_MNI, coords_MNI_2, atol=10.0)
def test_coregister_fiducials(): """Test coreg.coregister_fiducials().""" # prepare head and MRI fiducials trans = Transform('head', 'mri', rotation(.4, .1, 0).dot(translation(.1, -.1, .1))) coords_orig = np.array([[-0.08061612, -0.02908875, -0.04131077], [0.00146763, 0.08506715, -0.03483611], [0.08436285, -0.02850276, -0.04127743]]) coords_trans = apply_trans(trans, coords_orig) def make_dig(coords, cf): return ({'coord_frame': cf, 'ident': 1, 'kind': 1, 'r': coords[0]}, {'coord_frame': cf, 'ident': 2, 'kind': 1, 'r': coords[1]}, {'coord_frame': cf, 'ident': 3, 'kind': 1, 'r': coords[2]}) mri_fiducials = make_dig(coords_trans, FIFF.FIFFV_COORD_MRI) info = {'dig': make_dig(coords_orig, FIFF.FIFFV_COORD_HEAD)} # test coregister_fiducials() trans_est = coregister_fiducials(info, mri_fiducials) assert trans_est.from_str == trans.from_str assert trans_est.to_str == trans.to_str assert_array_almost_equal(trans_est['trans'], trans['trans'])
def _interpolate_bads_meg(inst, mode='accurate', origin=None, verbose=None): """Interpolate bad channels from data in good channels. Parameters ---------- inst : mne.io.Raw, mne.Epochs or mne.Evoked The data to interpolate. Must be preloaded. mode : str Either `'accurate'` or `'fast'`, determines the quality of the Legendre polynomial expansion used for interpolation. `'fast'` should be sufficient for most applications. origin : None | list If None, origin is set to sensor center of mass, otherwise use the coordinates provided as origin. The old standard value is (0., 0., 0.04) verbose : bool, str, int, or None If not None, override default verbose level (see :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>` for more). """ picks_meg = pick_types(inst.info, meg=True, eeg=False, exclude=[]) picks_good = pick_types(inst.info, meg=True, eeg=False, exclude='bads') meg_ch_names = [inst.info['ch_names'][p] for p in picks_meg] bads_meg = [ch for ch in inst.info['bads'] if ch in meg_ch_names] # select the bad meg channel to be interpolated if len(bads_meg) == 0: picks_bad = [] else: picks_bad = pick_channels(inst.info['ch_names'], bads_meg, exclude=[]) # return without doing anything if there are no meg channels if len(picks_meg) == 0 or len(picks_bad) == 0: return info_from = pick_info(inst.info, picks_good) info_to = pick_info(inst.info, picks_bad) if origin is None: posvec = np.array([inst.info['chs'][p]['loc'][0:3] for p in picks_meg]) norvec = np.array( [inst.info['chs'][p]['loc'][9:12] for p in picks_meg]) cogpos = np.mean(posvec, axis=0) norsum = np.mean(norvec, axis=0) anorm = np.sqrt(np.dot(norsum, norsum.T)) ndir = norsum / anorm # push the position slightly (4cm) away from the helmet: altpos = cogpos - 0.04 * ndir print(">_interpolate_bads_meg\\DBG> cog(sens) = [%8.5f %8.5f %8.5f]" % \ (cogpos[0], cogpos[1], cogpos[2])) print(">_interpolate_bads_meg\\DBG> alt(sens) = [%8.5f %8.5f %8.5f]" % \ (altpos[0], altpos[1], altpos[2])) cogposhd = apply_trans(inst.info['dev_head_t']['trans'], cogpos, move=True) altposhd = apply_trans(inst.info['dev_head_t']['trans'], altpos, move=True) print(">_interpolate_bads_meg\\DBG> cog(hdcs) = [%8.5f %8.5f %8.5f]" % \ (cogposhd[0], cogposhd[1], cogposhd[2])) print(">_interpolate_bads_meg\\DBG> alt(hdcs) = [%8.5f %8.5f %8.5f]" % \ (altposhd[0], altposhd[1], altposhd[2])) print(">_interpolate_bads_meg\\DBG> calling _map_meg_channels(..., origin=(%8.5f %8.5f %8.5f))" % \ (altposhd[0], altposhd[1], altposhd[2])) origin = (altposhd[0], altposhd[1], altposhd[2]) else: origin = origin mapping = _map_meg_channels(info_from, info_to, mode=mode, origin=origin) _do_interp_dots(inst, mapping, picks_good, picks_bad)
def test_scale_mri(): """Test creating fsaverage and scaling it.""" # create fsaverage using the testing "fsaverage" instead of the FreeSurfer # one tempdir = _TempDir() fake_home = testing.data_path() create_default_subject(subjects_dir=tempdir, fs_home=fake_home, verbose=True) assert _is_mri_subject('fsaverage', tempdir), "Creating fsaverage failed" fid_path = op.join(tempdir, 'fsaverage', 'bem', 'fsaverage-fiducials.fif') os.remove(fid_path) create_default_subject(update=True, subjects_dir=tempdir, fs_home=fake_home) assert op.exists(fid_path), "Updating fsaverage" # copy MRI file from sample data (shouldn't matter that it's incorrect, # so here choose a small one) path_from = op.join(testing.data_path(), 'subjects', 'sample', 'mri', 'T1.mgz') path_to = op.join(tempdir, 'fsaverage', 'mri', 'orig.mgz') copyfile(path_from, path_to) # remove redundant label files label_temp = op.join(tempdir, 'fsaverage', 'label', '*.label') label_paths = glob(label_temp) for label_path in label_paths[1:]: os.remove(label_path) # create source space print('Creating surface source space') path = op.join(tempdir, 'fsaverage', 'bem', 'fsaverage-%s-src.fif') src = mne.setup_source_space('fsaverage', 'ico0', subjects_dir=tempdir, add_dist=False) write_source_spaces(path % 'ico-0', src) mri = op.join(tempdir, 'fsaverage', 'mri', 'orig.mgz') print('Creating volume source space') vsrc = mne.setup_volume_source_space( 'fsaverage', pos=50, mri=mri, subjects_dir=tempdir, add_interpolator=False) write_source_spaces(path % 'vol-50', vsrc) # scale fsaverage for scale in (.9, [1, .2, .8]): os.environ['_MNE_FEW_SURFACES'] = 'true' scale_mri('fsaverage', 'flachkopf', scale, True, subjects_dir=tempdir, verbose='debug') del os.environ['_MNE_FEW_SURFACES'] assert _is_mri_subject('flachkopf', tempdir), "Scaling failed" spath = op.join(tempdir, 'flachkopf', 'bem', 'flachkopf-%s-src.fif') assert op.exists(spath % 'ico-0'), "Source space ico-0 was not scaled" assert os.path.isfile(os.path.join(tempdir, 'flachkopf', 'surf', 'lh.sphere.reg')) vsrc_s = mne.read_source_spaces(spath % 'vol-50') pt = np.array([0.12, 0.41, -0.22]) assert_array_almost_equal( apply_trans(vsrc_s[0]['src_mri_t'], pt * np.array(scale)), apply_trans(vsrc[0]['src_mri_t'], pt)) scale_labels('flachkopf', subjects_dir=tempdir) # add distances to source space mne.add_source_space_distances(src) src.save(path % 'ico-0', overwrite=True) # scale with distances os.remove(spath % 'ico-0') scale_source_space('flachkopf', 'ico-0', subjects_dir=tempdir) ssrc = mne.read_source_spaces(spath % 'ico-0') assert ssrc[0]['dist'] is not None
def transform_voxels_to_RAS(aseg_hdr, pts): # Transform data to RAS coordinates trans = aseg_hdr.get_vox2ras_tkr() pts = apply_trans(trans, pts) return pts
def make_mne_anatomy(subject, subjects_dir, recordings_path=None, hcp_path=op.curdir, outputs=('label', 'mri', 'surf')): """Extract relevant anatomy and create MNE friendly directory layout The function will create the following outputs by default: $subjects_dir/$subject/bem/inner_skull.surf $subjects_dir/$subject/label/* $subjects_dir/$subject/mri/* $subjects_dir/$subject/surf/* $recordings_path/$subject/$subject-head_mri-trans.fif These can then be set as $SUBJECTS_DIR and as MEG directory, consistent with MNE examples. Parameters ---------- subject : str The subject name. subjects_dir : str The path corresponding to MNE/freesurfer SUBJECTS_DIR (to be created) hcp_path : str The path where the HCP files can be found. outputs : {'label', 'mri', 'stats', 'surf', 'touch'} The outputs of the freesrufer pipeline shipped by HCP. Defaults to ('mri', 'surf'), the minimum needed to extract MNE-friendly anatomy files and data. """ if hcp_path == op.curdir: hcp_path = op.realpath(hcp_path) if not op.isabs(subjects_dir): subjects_dir = op.realpath(subjects_dir) this_subjects_dir = op.join(subjects_dir, subject) if not op.isabs(recordings_path): recordings_path = op.realpath(recordings_path) this_recordings_path = op.join(recordings_path, subject) if not op.exists(this_recordings_path): os.makedirs(this_recordings_path) for output in outputs: if not op.exists(op.join(this_subjects_dir, output)): os.makedirs(op.join(this_subjects_dir, output)) if output == 'mri': for suboutput in ['orig', 'transforms']: if not op.exists( op.join(this_subjects_dir, output, suboutput)): os.makedirs(op.join(this_subjects_dir, output, suboutput)) files = get_file_paths( subject=subject, data_type='freesurfer', output=output, hcp_path=hcp_path) for source in files: match = [match for match in re.finditer(subject, source)][-1] split_path = source[:match.span()[1] + 1] target = op.join(this_subjects_dir, source.split(split_path)[-1]) if (not op.isfile(target) and not op.islink(target) and op.exists(source)): # don't link if it's not there. if sys.platform != 'win32': os.symlink(source, target) else: shutil.copyfile(source, target) logger.info('reading extended structural processing ...') # Step 1 ################################################################# # transform head models to expected coordinate system # make hcp trans transforms_fname = get_file_paths( subject=subject, data_type='meg_anatomy', output='transforms', hcp_path=hcp_path) transforms_fname = [k for k in transforms_fname if k.endswith('transform.txt')][0] hcp_trans = _read_trans_hcp(fname=transforms_fname, convert_to_meter=False) # get RAS freesurfer trans c_ras_trans_fname = get_file_paths( subject=subject, data_type='freesurfer', output='mri', hcp_path=hcp_path) c_ras_trans_fname = [k for k in c_ras_trans_fname if k.endswith('c_ras.mat')][0] logger.info('reading RAS freesurfer transform') # ceci n'est pas un .mat file ... with open(op.join(subjects_dir, c_ras_trans_fname)) as fid: ras_trans = np.array([ r.split() for r in fid.read().split('\n') if r], dtype=np.float64) logger.info('Combining RAS transform and coregistration') ras_trans_m = linalg.inv(ras_trans) # and the inversion logger.info('extracting head model') head_model_fname = get_file_paths( subject=subject, data_type='meg_anatomy', output='head_model', hcp_path=hcp_path)[0] pnts, faces = _get_head_model(head_model_fname=head_model_fname) logger.info('coregistring head model to MNE-HCP coordinates') pnts = apply_trans(ras_trans_m.dot(hcp_trans['bti2spm']), pnts) tri_fname = op.join(this_subjects_dir, 'bem', 'inner_skull.surf') if not op.exists(op.dirname(tri_fname)): os.makedirs(op.dirname(tri_fname)) write_surface(tri_fname, pnts, faces) # Step 2 ################################################################# # write corresponding device to MRI transform logger.info('extracting coregistration') # now convert to everything meter too here ras_trans_m[:3, 3] *= 1e-3 bti2spm = hcp_trans['bti2spm'] bti2spm[:3, 3] *= 1e-3 head_mri_t = Transform( # we're lying here for a good purpose 'head', 'mri', np.dot(ras_trans_m, bti2spm)) # it should be 'ctf_head' write_trans(op.join(this_recordings_path, '%s-head_mri-trans.fif' % subject), head_mri_t)
def test_nirx_15_2_short(): """Test reading NIRX files.""" raw = read_raw_nirx(fname_nirx_15_2_short, preload=True) # Test data import assert raw._data.shape == (26, 145) assert raw.info['sfreq'] == 12.5 assert raw.info['meas_date'] == dt.datetime(2019, 8, 23, 7, 37, 4, 540000, tzinfo=dt.timezone.utc) # Test channel naming assert raw.info['ch_names'][:4] == [ "S1_D1 760", "S1_D1 850", "S1_D9 760", "S1_D9 850" ] assert raw.info['ch_names'][24:26] == ["S5_D13 760", "S5_D13 850"] # Test frequency encoding assert raw.info['chs'][0]['loc'][9] == 760 assert raw.info['chs'][1]['loc'][9] == 850 # Test info import assert raw.info['subject_info'] == dict(sex=1, first_name="MNE", middle_name="Test", last_name="Recording", birthday=(2014, 8, 23)) # Test distance between optodes matches values from # nirsite https://github.com/mne-tools/mne-testing-data/pull/51 # step 4 figure 2 allowed_distance_error = 0.0002 distances = source_detector_distances(raw.info) assert_allclose(distances[::2], [ 0.0304, 0.0078, 0.0310, 0.0086, 0.0416, 0.0072, 0.0389, 0.0075, 0.0558, 0.0562, 0.0561, 0.0565, 0.0077 ], atol=allowed_distance_error) # Test which channels are short # These are the ones marked as red at # https://github.com/mne-tools/mne-testing-data/pull/51 step 4 figure 2 is_short = short_channels(raw.info) assert_array_equal(is_short[:9:2], [False, True, False, True, False]) is_short = short_channels(raw.info, threshold=0.003) assert_array_equal(is_short[:3:2], [False, False]) is_short = short_channels(raw.info, threshold=50) assert_array_equal(is_short[:3:2], [True, True]) # Test trigger events assert_array_equal(raw.annotations.description, ['3.0', '2.0', '1.0']) # Test location of detectors # The locations of detectors can be seen in the first # figure on this page... # https://github.com/mne-tools/mne-testing-data/pull/51 # And have been manually copied below # These values were reported in mm, but according to this page... # https://mne.tools/stable/auto_tutorials/intro/plot_40_sensor_locations.html # 3d locations should be specified in meters, so that's what's tested below # Detector locations are stored in the third three loc values allowed_dist_error = 0.0002 locs = [ch['loc'][6:9] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][3:5] == 'D1' assert_allclose(mni_locs[0], [-0.0841, -0.0464, -0.0129], atol=allowed_dist_error) assert raw.info['ch_names'][4][3:5] == 'D3' assert_allclose(mni_locs[4], [0.0846, -0.0142, -0.0156], atol=allowed_dist_error) assert raw.info['ch_names'][8][3:5] == 'D2' assert_allclose(mni_locs[8], [0.0207, -0.1062, 0.0484], atol=allowed_dist_error) assert raw.info['ch_names'][12][3:5] == 'D4' assert_allclose(mni_locs[12], [-0.0196, 0.0821, 0.0275], atol=allowed_dist_error) assert raw.info['ch_names'][16][3:5] == 'D5' assert_allclose(mni_locs[16], [-0.0360, 0.0276, 0.0778], atol=allowed_dist_error) assert raw.info['ch_names'][19][3:5] == 'D6' assert_allclose(mni_locs[19], [0.0352, 0.0283, 0.0780], atol=allowed_dist_error) assert raw.info['ch_names'][21][3:5] == 'D7' assert_allclose(mni_locs[21], [0.0388, -0.0477, 0.0932], atol=allowed_dist_error)
def test_snirf_nirsport2_w_positions(): """Test reading SNIRF files with known positions.""" raw = read_raw_snirf(nirx_nirsport2_103_2, preload=True, optode_frame="mri") # Test data import assert raw._data.shape == (40, 128) assert_almost_equal(raw.info['sfreq'], 10.2, decimal=1) # Test channel naming assert raw.info['ch_names'][:4] == ['S1_D1 760', 'S1_D1 850', 'S1_D6 760', 'S1_D6 850'] assert raw.info['ch_names'][24:26] == ['S6_D4 760', 'S6_D4 850'] # Test frequency encoding assert raw.info['chs'][0]['loc'][9] == 760 assert raw.info['chs'][1]['loc'][9] == 850 assert sum(short_channels(raw.info)) == 16 # Test distance between optodes matches values from # nirsite https://github.com/mne-tools/mne-testing-data/pull/86 # figure 3 allowed_distance_error = 0.005 distances = source_detector_distances(raw.info) assert_allclose(distances[::2][:14], [0.0304, 0.0411, 0.008, 0.0400, 0.008, 0.0310, 0.0411, 0.008, 0.0299, 0.008, 0.0370, 0.008, 0.0404, 0.008], atol=allowed_distance_error) # Test location of detectors # The locations of detectors can be seen in the first # figure on this page... # https://github.com/mne-tools/mne-testing-data/pull/86 allowed_dist_error = 0.0002 locs = [ch['loc'][6:9] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][3:5] == 'D1' assert_allclose( mni_locs[0], [-0.0841, -0.0464, -0.0129], atol=allowed_dist_error) assert raw.info['ch_names'][2][3:5] == 'D6' assert_allclose( mni_locs[2], [-0.0841, -0.0138, 0.0248], atol=allowed_dist_error) assert raw.info['ch_names'][34][3:5] == 'D5' assert_allclose( mni_locs[34], [0.0845, -0.0451, -0.0123], atol=allowed_dist_error) # Test location of sensors # The locations of sensors can be seen in the second # figure on this page... # https://github.com/mne-tools/mne-testing-data/pull/86 allowed_dist_error = 0.0002 locs = [ch['loc'][3:6] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][:2] == 'S1' assert_allclose( mni_locs[0], [-0.0848, -0.0162, -0.0163], atol=allowed_dist_error) assert raw.info['ch_names'][9][:2] == 'S2' assert_allclose( mni_locs[9], [-0.0, -0.1195, 0.0142], atol=allowed_dist_error) assert raw.info['ch_names'][34][:2] == 'S8' assert_allclose( mni_locs[34], [0.0828, -0.046, 0.0285], atol=allowed_dist_error)
def plot_visualize_mft_sources(fwdmag, stcdata, tmin, tstep, subject, subjects_dir): """ Plot the MFT sources at time point of peak. Parameters ---------- fwdmag: forward solution stcdata: stc with ||cdv|| (point sequence as in fwdmag['source_rr']) tmin, tstep, subject: passed to mne.SourceEstimate() """ print("##### Attempting to plot:") # cf. decoding/plot_decoding_spatio_temporal_source.py vertices = [s['vertno'] for s in fwdmag['src']] if len(vertices) == 1: vertices = [ fwdmag['src'][0]['vertno'] [fwdmag['src'][0]['rr'][fwdmag['src'][0]['vertno']][:, 0] <= -0.], fwdmag['src'][0]['vertno'][ fwdmag['src'][0]['rr'][fwdmag['src'][0]['vertno']][:, 0] > -0.] ] elif len(vertices) > 2: warnings.warn( 'plot_visualize_mft_sources(): Cannot handle more than two sources spaces' ) return stc_feat = SourceEstimate(stcdata, vertices=vertices, tmin=tmin, tstep=tstep, subject=subject) itmaxsum = np.argmax(np.sum(stcdata, axis=0)) twmin = tmin + tstep * float(itmaxsum - stcdata.shape[1] / 20) twmax = tmin + tstep * float(itmaxsum + stcdata.shape[1] / 20) for ihemi, hemi in enumerate(['lh', 'rh', 'both']): brain = stc_feat.plot(surface='white', hemi=hemi, subjects_dir=subjects_dir, transparent=True, clim='auto') # use peak getter to move visualization to the time point of the peak print("Restricting peak search to [%fs, %fs]" % (twmin, twmax)) if hemi == 'both': brain.show_view('parietal') vertno_max, time_idx = stc_feat.get_peak(hemi=None, time_as_index=True, tmin=twmin, tmax=twmax) else: brain.show_view('lateral') vertno_max, time_idx = stc_feat.get_peak(hemi=hemi, time_as_index=True, tmin=twmin, tmax=twmax) print("hemi=%s: setting time_idx=%d" % (hemi, time_idx)) brain.set_data_time_index(time_idx) if hemi == 'lh' or hemi == 'rh': # draw marker at maximum peaking vertex brain.add_foci(vertno_max, coords_as_verts=True, hemi=hemi, color='blue', scale_factor=0.6) if len(fwdmag['src']) > ihemi: fwds = fwdmag['src'][ihemi] comax = fwds['rr'][vertno_max] print("hemi=%s: vertno_max=%d, time_idx=%d fwdmag['src'][%d]['rr'][vertno_max] = " % \ (hemi, vertno_max, time_idx, ihemi), comax) offsets = np.append([0], [s['nuse'] for s in fwdmag['src']]) if hemi == 'lh': ifoci = [ np.nonzero([ stcdata[0:offsets[1], time_idx] >= 0.25 * np.max(stcdata[:, time_idx]) ][0]) ] elif len(fwdmag['src']) > 1: ifoci = [ np.nonzero([ stcdata[offsets[1]:, time_idx] >= 0.25 * np.max(stcdata[:, time_idx]) ][0]) ] vfoci = fwds['vertno'][ifoci[0][0]] cfoci = fwds['rr'][vfoci] print("Coords of %d sel. vfoci: " % cfoci.shape[0]) print(cfoci) print("vfoci: ") print(vfoci) print("brain.geo[%s].coords[vfoci] : " % hemi) print(brain.geo[hemi].coords[vfoci]) mrfoci = np.zeros(cfoci.shape) invmri_head_t = invert_transform(fwdmag['info']['mri_head_t']) mrfoci = apply_trans(invmri_head_t['trans'], cfoci, move=True) print("mrfoci: ") print(mrfoci) # Just some blops along the coordinate axis: # This will not yield reasonable results w an inflated brain. # bloblist = np.zeros((300,3)) # for i in xrange(100): # bloblist[i,0] = float(i) # bloblist[i+100,1] = float(i) # bloblist[i+200,2] = float(i) # mrblobs = apply_trans(invmri_head_t['trans'], bloblist, move=True) # brain.add_foci(mrblobs, coords_as_verts=False, hemi=hemi, color='yellow', scale_factor=0.3) brain.save_image('testfig_map_%s.png' % hemi) brain.close()
def write_anat(bids_root, subject, t1w, session=None, acquisition=None, raw=None, trans=None, deface=False, overwrite=False, verbose=False): """Put anatomical MRI data into a BIDS format. Given a BIDS directory and a T1 weighted MRI scan for a certain subject, format the MRI scan to be in BIDS format and put it into the correct location in the bids_dir. If a transformation matrix is supplied, a sidecar JSON file will be written for the T1 weighted data. Parameters ---------- bids_root : str Path to root of the BIDS folder subject : str Subject label as in 'sub-<label>', for example: '01' t1w : str | nibabel image object Path to a T1 weighted MRI scan of the subject. Can be in any format readable by nibabel. Can also be a nibabel image object of a T1 weighted MRI scan. Will be written as a .nii.gz file. session : str | None The session for `t1w`. Corresponds to "ses" acquisition: str | None The acquisition parameters for `t1w`. Corresponds to "acq" raw : instance of Raw | None The raw data of `subject` corresponding to `t1w`. If `raw` is None, `trans` has to be None as well trans : instance of mne.transforms.Transform | str | None The transformation matrix from head coordinates to MRI coordinates. Can also be a string pointing to a .trans file containing the transformation matrix. If None, no sidecar JSON file will be written for `t1w` deface : bool | dict If False, no defacing is performed. If True, deface with default parameters. `trans` and `raw` must not be `None` if True. If dict, accepts the following keys: `inset`: how far back in millimeters to start defacing relative to the nasion (default 20) `theta`: is the angle of the defacing shear in degrees relative to the normal to the plane passing through the anatomical landmarks (default 35). overwrite : bool Whether to overwrite existing files or data in files. Defaults to False. If overwrite is True, any existing files with the same BIDS parameters will be overwritten with the exception of the `participants.tsv` and `scans.tsv` files. For these files, parts of pre-existing data that match the current data will be replaced. If overwrite is False, no existing data will be overwritten or replaced. verbose : bool If verbose is True, this will print a snippet of the sidecar files. If False, no content will be printed. Returns ------- anat_dir : str Path to the anatomical scan in the `bids_dir` """ if not has_nibabel(): # pragma: no cover raise ImportError('This function requires nibabel.') import nibabel as nib if deface and (trans is None or raw is None): raise ValueError('The raw object, trans and raw must be provided to ' 'deface the T1') # Make directory for anatomical data anat_dir = op.join(bids_root, 'sub-{}'.format(subject)) # Session is optional if session is not None: anat_dir = op.join(anat_dir, 'ses-{}'.format(session)) anat_dir = op.join(anat_dir, 'anat') if not op.exists(anat_dir): os.makedirs(anat_dir) # Try to read our T1 file and convert to MGH representation if isinstance(t1w, str): t1w = nib.load(t1w) elif type(t1w) not in nib.all_image_classes: raise ValueError('`t1w` must be a path to a T1 weighted MRI data file ' ', or a nibabel image object, but it is of type ' '"{}"'.format(type(t1w))) t1w = nib.Nifti1Image(t1w.dataobj, t1w.affine) # XYZT_UNITS = NIFT_UNITS_MM (10 in binary or 2 in decimal) # seems to be the default for Nifti files # https://nifti.nimh.nih.gov/nifti-1/documentation/nifti1fields/nifti1fields_pages/xyzt_units.html if t1w.header['xyzt_units'] == 0: t1w.header['xyzt_units'] = np.array(10, dtype='uint8') # Now give the NIfTI file a BIDS name and write it to the BIDS location t1w_basename = make_bids_basename(subject=subject, session=session, acquisition=acquisition, prefix=anat_dir, suffix='T1w.nii.gz') # Check if we have necessary conditions for writing a sidecar JSON if trans is not None: # get trans and ensure it is from head to MRI trans, _ = _get_trans(trans, fro='head', to='mri') if not isinstance(raw, BaseRaw): raise ValueError('`raw` must be specified if `trans` is not None') # Prepare to write the sidecar JSON # extract MEG landmarks coords_dict = _extract_landmarks(raw.info['dig']) meg_landmarks = np.asarray( (coords_dict['LPA'], coords_dict['NAS'], coords_dict['RPA'])) # Transform MEG landmarks into MRI space, adjust units by * 1e3 mri_landmarks = apply_trans(trans, meg_landmarks, move=True) * 1e3 # Get landmarks in voxel space, using the mgh version of our T1 data t1_mgh = nib.MGHImage(t1w.dataobj, t1w.affine) vox2ras_tkr = t1_mgh.header.get_vox2ras_tkr() ras2vox_tkr = np.linalg.inv(vox2ras_tkr) mri_landmarks = apply_trans(ras2vox_tkr, mri_landmarks) # in vox # Write sidecar.json t1w_json = dict() t1w_json['AnatomicalLandmarkCoordinates'] = \ {'LPA': list(mri_landmarks[0, :]), 'NAS': list(mri_landmarks[1, :]), 'RPA': list(mri_landmarks[2, :])} fname = t1w_basename.replace('.nii.gz', '.json') if op.isfile(fname) and not overwrite: raise IOError( 'Wanted to write a file but it already exists and ' '`overwrite` is set to False. File: "{}"'.format(fname)) _write_json(fname, t1w_json, overwrite, verbose) if deface: t1w = _deface(t1w, mri_landmarks, deface, trans, raw) # Save anatomical data if op.exists(t1w_basename): if overwrite: os.remove(t1w_basename) else: raise IOError( 'Wanted to write a file but it already exists and ' '`overwrite` is set to False. File: "{}"'.format(t1w_basename)) nib.save(t1w, t1w_basename) return anat_dir
def test_ieeg_elec_locate_gui_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
def create_volume(subjects_dir, subject, src_space, corr_file, corr_vol): set_num_threads(4) sources = [] src_space = mne.read_source_spaces(src_space) # lh_surf_coord = src_space[0]['rr'] # Triangle Mesh coordinates # lh_triangle_idx = src_space[0]['tris'] # traingular mesh face of 3 vertices t1_fname = os.path.join(subjects_dir, subject, 'mri', 'T1.mgz') t1 = nib.load(t1_fname) vox_mri_t = t1.header.get_vox2ras_tkr() mri_vox_t = np.linalg.inv(vox_mri_t) for src_ in src_space: points = src_['rr'][src_['inuse'].astype(bool)] sources.append(apply_trans(mri_vox_t, points * 1e3)) sources = np.concatenate(sources, axis=0) sources = np.round(sources) img = np.zeros([256, 256, 256]) corr_data = np.load(corr_file) # print(len(sources), corr_data.shape) for idx, val in enumerate(sources): i, j, k = int(val[0]), int(val[1]), int(val[2]) img[i][j][k] = corr_data[idx] img[img < 0.0] = sys.float_info.epsilon # Fill neiboring voxels fill_neighbor = True if fill_neighbor: found = np.argwhere(img > 0.0) for a1, b1, c1 in found: neighbor = list(neighbors((a1, b1, c1))) for i, j, k in neighbor: img[i][j][k] = img[a1][b1][c1] found = np.argwhere(img > 0.0) for a1, b1, c1 in found: neighbor = list(neighbors((a1, b1, c1))) for i, j, k in neighbor: img[i][j][k] = img[a1][b1][c1] found = np.argwhere(img > 0.0) for a1, b1, c1 in found: neighbor = list(neighbors((a1, b1, c1))) for i, j, k in neighbor: img[i][j][k] = img[a1][b1][c1] found = np.argwhere(img > 0.0) for a1, b1, c1 in found: neighbor = list(neighbors((a1, b1, c1))) for i, j, k in neighbor: img[i][j][k] = img[a1][b1][c1] #---------------------- Convolution Nearest Neighbor-------------------------# convolution = True if convolution: print(f'Convolve 2-dimensional array') final_list = [] # Axial for dim_0_slice in img: output = eight_neighbor_average_convolve2d(dim_0_slice) final_list.append(output) img = np.dstack(final_list) final_list = [] # Coronal img = np.swapaxes(img, 0, 1) for dim_0_slice in img: output = eight_neighbor_average_convolve2d(dim_0_slice) final_list.append(output) img = np.dstack(final_list) final_list = [] # Sagittal img = np.swapaxes(img, 0, 2) for dim_0_slice in img: output = eight_neighbor_average_convolve2d(dim_0_slice) final_list.append(output) img = np.dstack(final_list) affine = t1.affine hdr = t1.header result_img = nib.Nifti1Image(img, affine, header=hdr) result_img.to_filename(corr_vol) print(corr_vol)
def boxy2mne(*, boxy_file=None, mtg_file=None, coord_file=None): # ============================================================================= # ready raw data from boxy file # ============================================================================= ###this keeps track of the line we're on### ###mostly to know the start and stop of data (probably an easier way)### line_num = 0 ###load and read data to get some meta information### ###there is alot of information at the beginning of a file### ###but this only grabs some of it### with open(boxy_file, 'r') as data: for i_line in data: line_num += 1 if '#DATA ENDS' in i_line: end_line = line_num - 1 break if 'Detector Channels' in i_line: detect_num = int(i_line.rsplit(' ')[0]) elif 'External MUX Channels' in i_line: source_num = int(i_line.rsplit(' ')[0]) elif 'Auxiliary Channels' in i_line: aux_num = int(i_line.rsplit(' ')[0]) elif 'Waveform (CCF) Frequency (Hz)' in i_line: ccf_ha = float(i_line.rsplit(' ')[0]) elif 'Update Rate (Hz)' in i_line: srate = float(i_line.rsplit(' ')[0]) elif 'Updata Rate (Hz)' in i_line: srate = float(i_line.rsplit(' ')[0]) elif '#DATA BEGINS' in i_line: start_line = line_num ###now let's go through and parse our raw data### raw_data = pd.read_csv(boxy_file, skiprows=start_line, sep='\t') ###detectors, sources, and data types### detectors = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' ] data_types = ['AC', 'DC', 'Ph'] sources = np.arange(1, source_num + 1, 1) ###since we can save boxy files in two different styles### ###this will check to see which style the data is saved### ###seems to also work with older boxy files### if 'exmux' in raw_data.columns: filetype = 'non-parsed' ###drop the last line as this is just '#DATA ENDS'### raw_data = raw_data.drop([len(raw_data) - 1]) ###store some extra info### record = raw_data['record'].to_numpy() exmux = raw_data['exmux'].to_numpy() ###make some empty variables to store our data### raw_ac = np.zeros( (detect_num * source_num, int(len(raw_data) / source_num))) raw_dc = np.zeros( (detect_num * source_num, int(len(raw_data) / source_num))) raw_ph = np.zeros( (detect_num * source_num, int(len(raw_data) / source_num))) else: filetype = 'parsed' ###drop the last line as this is just '#DATA ENDS'### ###also drop the first line since this is empty### raw_data = raw_data.drop([0, len(raw_data) - 1]) ###make some empty variables to store our data### raw_ac = np.zeros(((detect_num * source_num), len(raw_data))) raw_dc = np.zeros(((detect_num * source_num), len(raw_data))) raw_ph = np.zeros(((detect_num * source_num), len(raw_data))) ###store some extra data, might not need these though### time = raw_data['time'].to_numpy() if 'time' in raw_data.columns else [] time = raw_data['time'].to_numpy() if 'time' in raw_data.columns else [] group = raw_data['group'].to_numpy() if 'group' in raw_data.columns else [] step = raw_data['step'].to_numpy() if 'step' in raw_data.columns else [] mark = raw_data['mark'].to_numpy() if 'mark' in raw_data.columns else [] flag = raw_data['flag'].to_numpy() if 'flag' in raw_data.columns else [] aux1 = raw_data['aux-1'].to_numpy() if 'aux-1' in raw_data.columns else [] digaux = raw_data['digaux'].to_numpy( ) if 'digaux' in raw_data.columns else [] bias = np.zeros((detect_num, len(raw_data))) ###loop through detectors### for i_detect in detectors[0:detect_num]: ###older boxy files don't seem to keep track of detector bias### ###probably due to specific boxy settings actually### if 'bias-A' in raw_data.columns: bias[detectors.index(i_detect), :] = raw_data['bias-' + i_detect].to_numpy() ###loop through data types### for i_data in data_types: ###loop through sources### for i_source in sources: ###where to store our data### index_loc = detectors.index(i_detect) * source_num + ( i_source - 1) ###need to treat our filetypes differently### if filetype == 'non-parsed': ###filetype saves timepoints in groups### ###this should account for that### time_points = np.arange(i_source - 1, int(record[-1]) * source_num, source_num) ###determine which channel to look for### channel = i_detect + '-' + i_data ###save our data based on data type### if data_types.index(i_data) == 0: raw_ac[index_loc, :] = raw_data[channel][ time_points].to_numpy() elif data_types.index(i_data) == 1: raw_dc[index_loc, :] = raw_data[channel][ time_points].to_numpy() elif data_types.index(i_data) == 2: raw_ph[index_loc, :] = raw_data[channel][ time_points].to_numpy() elif filetype == 'parsed': ###determine which channel to look for### channel = i_detect + '-' + i_data + str(i_source) ###save our data based on data type### if data_types.index(i_data) == 0: raw_ac[index_loc, :] = raw_data[channel].to_numpy() elif data_types.index(i_data) == 1: raw_dc[index_loc, :] = raw_data[channel].to_numpy() elif data_types.index(i_data) == 2: raw_ph[index_loc, :] = raw_data[channel].to_numpy() # ============================================================================= # read source and electrode locations from .mtg and .tol/.elp files # ============================================================================= ###set up some variables### chan_num = [] source_label = [] detect_label = [] chan_wavelength = [] chan_modulation = [] ###load and read each line of the .mtg file### with open(mtg_file, 'r') as data: for i_ignore in range(2): next(data) for i_line in data: chan1, chan2, source, detector, wavelength, modulation = i_line.split( ) chan_num.append(chan1) source_label.append(source) detect_label.append(detector) chan_wavelength.append(wavelength) chan_modulation.append(modulation) ###check if we are given a .tol or .elp file### all_labels = [] all_coords = [] fiducial_coords = [] if coord_file[-3:].lower() == 'elp'.lower(): get_label = 0 get_coords = 0 ###load and read .elp file### with open(coord_file, 'r') as data: for i_line in data: ###first let's get our fiducial coordinates### if '%F' in i_line: fiducial_coords.append(i_line.split()[1:]) ###check where sensor info starts### if '//Sensor name' in i_line: get_label = 1 elif get_label == 1: ###grab the part after '%N' for the label### label = i_line.split()[1] all_labels.append(label) get_label = 0 get_coords = 1 elif get_coords == 1: X, Y, Z = i_line.split() all_coords.append([float(X), float(Y), float(Z)]) get_coords = 0 for i_index in range(3): fiducial_coords[i_index] = np.asarray( [float(x) for x in fiducial_coords[i_index]]) elif coord_file[-3:] == 'tol': ###load and read .tol file### with open(coord_file, 'r') as data: for i_line in data: label, X, Y, Z = i_line.split() all_labels.append(label) ###convert coordinates from mm to m## all_coords.append([(float(X) * 0.001), (float(Y) * 0.001), (float(Z) * 0.001)]) ###get coordinates for sources### source_coords = [] for i_chan in source_label: if i_chan in all_labels: chan_index = all_labels.index(i_chan) source_coords.append(all_coords[chan_index]) ###get coordinates for detectors### detect_coords = [] for i_chan in detect_label: if i_chan in all_labels: chan_index = all_labels.index(i_chan) detect_coords.append(all_coords[chan_index]) ###need to rename labels to make other functions happy### ###get our unique labels for sources and detectors### unique_source_labels = [] unique_detect_labels = [] [ unique_source_labels.append(label) for label in source_label if label not in unique_source_labels ] [ unique_detect_labels.append(label) for label in detect_label if label not in unique_detect_labels ] ###now let's label each channel in our data### ###data is channels X timepoint where the first source_num rows correspond to### ###the first detector, and each row within that group is a different source### ###should note that current .mtg files contain channels for multiple data files### ###going to move to have a single .mtg file per participant, condition, and montage### ###combine coordinates and label our channels### ###will label them based on ac, dc, and ph data### boxy_coords = [] boxy_labels = [] data_types = ['AC', 'DC', 'Ph'] total_chans = detect_num * source_num for i_type in data_types: for i_coord in range(len(source_coords[0:total_chans])): boxy_coords.append( np.mean(np.vstack((source_coords[i_coord], detect_coords[i_coord])), axis=0).tolist() + source_coords[i_coord] + detect_coords[i_coord] + [chan_wavelength[i_coord]] + [0] + [0]) boxy_labels.append( 'S' + str(unique_source_labels.index(source_label[i_coord]) + 1) + '_D' + str(unique_detect_labels.index(detect_label[i_coord]) + 1) + ' ' + chan_wavelength[i_coord] + ' ' + i_type) ###montage only wants channel coords, so need to grab those, convert to### ###array, then make a dict with labels### for i_chan in range(len(boxy_coords)): boxy_coords[i_chan] = np.asarray(boxy_coords[i_chan], dtype=np.float64) for i_chan in range(len(all_coords)): all_coords[i_chan] = np.asarray(all_coords[i_chan], dtype=np.float64) all_chan_dict = dict(zip(all_labels, all_coords)) ###make our montage### montage_orig = mne.channels.make_dig_montage(ch_pos=all_chan_dict, coord_frame='head', nasion=fiducial_coords[0], lpa=fiducial_coords[1], rpa=fiducial_coords[2]) ###for some reason make_dig_montage put our channels in a different order than what we input### ###let's fix that. should be fine to just change coords and ch_names### for i_chan in range(len(all_coords)): montage_orig.dig[i_chan + 3]['r'] = all_coords[i_chan] montage_orig.ch_names[i_chan] = all_labels[i_chan] ###add an extra channel for our triggers### boxy_labels.append('Markers') ###create info structure### info = mne.create_info(boxy_labels, srate, ch_types='fnirs_raw') info.update(dig=montage_orig.dig) ###get our fiducials and transform matrix from fsaverage### subjects_dir = op.dirname(fetch_fsaverage()) fid_path = op.join(subjects_dir, 'fsaverage', 'bem', 'fsaverage-fiducials.fif') fiducials = read_fiducials(fid_path) trans = coregister_fiducials(info, fiducials[0], tol=0.02) ###remake montage using the transformed coordinates### all_coords_trans = apply_trans(trans, all_coords) all_chan_dict_trans = dict(zip(all_labels, all_coords_trans)) fiducial_coords_trans = apply_trans(trans, fiducial_coords) ###make our montage### montage_trans = mne.channels.make_dig_montage( ch_pos=all_chan_dict_trans, coord_frame='head', nasion=fiducial_coords_trans[0], lpa=fiducial_coords_trans[1], rpa=fiducial_coords_trans[2]) ###let's fix montage order again### for i_chan in range(len(all_coords_trans)): montage_trans.dig[i_chan + 3]['r'] = all_coords_trans[i_chan] montage_trans.ch_names[i_chan] = all_labels[i_chan] ###add data type and channel wavelength to info### info.update(dig=montage_trans.dig, trans=trans) ###place our coordinates and wavelengths for each channel### for i_chan in range(len(boxy_labels) - 1): temp_chn = apply_trans(trans, boxy_coords[i_chan][0:3]) temp_src = apply_trans(trans, boxy_coords[i_chan][3:6]) temp_det = apply_trans(trans, boxy_coords[i_chan][6:9]) temp_other = np.asarray(boxy_coords[i_chan][9:], dtype=np.float64) info['chs'][i_chan]['loc'] = test = np.concatenate( (temp_chn, temp_src, temp_det, temp_other), axis=0) info['chs'][-1]['loc'] = np.zeros((12, )) ###now combine our data types into a single array### all_data = np.append(raw_ac, np.append(raw_dc, raw_ph, axis=0), axis=0) ###add our markers to the data array based on filetype### if filetype == 'non-parsed': if type(digaux) is list and digaux != []: markers = digaux[np.arange(0, len(digaux), source_num)] else: markers = np.zeros(np.size(all_data, axis=1)) elif filetype == 'parsed': markers = digaux all_data = np.vstack((all_data, markers)) ###create our raw data object### raw_data_obj = mne.io.RawArray(all_data, info) return raw_data_obj
def test_montage(): """Test making montages.""" tempdir = _TempDir() inputs = dict( sfp='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', csd= '// MatLab Sphere coordinates [degrees] Cartesian coordinates\n' # noqa: E501 '// Label Theta Phi Radius X Y Z off sphere surface\n' # noqa: E501 'E1 37.700 -14.000 1.000 0.7677 0.5934 -0.2419 -0.00000000000000011\n' # noqa: E501 'E3 51.700 11.000 1.000 0.6084 0.7704 0.1908 0.00000000000000000\n' # noqa: E501 'E31 90.000 -11.000 1.000 0.0000 0.9816 -0.1908 0.00000000000000000\n' # noqa: E501 'E61 158.000 -17.200 1.000 -0.8857 0.3579 -0.2957 -0.00000000000000022', # noqa: E501 mm_elc= '# ASA electrode file\nReferenceLabel avg\nUnitPosition mm\n' # noqa:E501 'NumberPositions= 68\n' 'Positions\n' '-86.0761 -19.9897 -47.9860\n' '85.7939 -20.0093 -48.0310\n' '0.0083 86.8110 -39.9830\n' '-86.0761 -24.9897 -67.9860\n' 'Labels\nLPA\nRPA\nNz\nDummy\n', m_elc='# ASA electrode file\nReferenceLabel avg\nUnitPosition m\n' 'NumberPositions= 68\nPositions\n-.0860761 -.0199897 -.0479860\n' # noqa:E501 '.0857939 -.0200093 -.0480310\n.0000083 .00868110 -.0399830\n' '.08 -.02 -.04\n' 'Labels\nLPA\nRPA\nNz\nDummy\n', txt='Site Theta Phi\n' 'Fp1 -92 -72\n' 'Fp2 92 72\n' 'very_very_very_long_name -92 72\n' 'O2 92 -90\n', elp='346\n' 'EEG\t F3\t -62.027\t -50.053\t 85\n' 'EEG\t Fz\t 45.608\t 90\t 85\n' 'EEG\t F4\t 62.01\t 50.103\t 85\n' 'EEG\t FCz\t 68.01\t 58.103\t 85\n', hpts='eeg Fp1 -95.0 -3. -3.\n' 'eeg AF7 -1 -1 -3\n' 'eeg A3 -2 -2 2\n' 'eeg A 0 0 0', ) # Get actual positions and save them for checking # csd comes from the string above, all others come from commit 2fa35d4 poss = dict( sfp=[[0.0, 9.07159, -2.35975], [-6.71176, 0.0404, -3.2516], [-5.83124, -4.49482, 4.95535], [0.0, 0.0, 8.89919]], mm_elc=[[-0.08608, -0.01999, -0.04799], [0.08579, -0.02001, -0.04803], [1e-05, 0.08681, -0.03998], [-0.08608, -0.02499, -0.06799]], m_elc=[[-0.08608, -0.01999, -0.04799], [0.08579, -0.02001, -0.04803], [1e-05, 0.00868, -0.03998], [0.08, -0.02, -0.04]], txt=[[-26.25044, 80.79056, -2.96646], [26.25044, 80.79056, -2.96646], [-26.25044, -80.79056, -2.96646], [0.0, -84.94822, -2.96646]], elp=[[-48.20043, 57.55106, 39.86971], [0.0, 60.73848, 59.4629], [48.1426, 57.58403, 39.89198], [41.64599, 66.91489, 31.8278]], hpts=[[-95, -3, -3], [-1, -1., -3.], [-2, -2, 2.], [0, 0, 0]], ) for key, text in inputs.items(): kind = key.split('_')[-1] fname = op.join(tempdir, 'test.' + kind) with open(fname, 'w') as fid: fid.write(text) montage = read_montage(fname) if kind in ('sfp', 'txt'): assert_true('very_very_very_long_name' in montage.ch_names) assert_equal(len(montage.ch_names), 4) assert_equal(len(montage.ch_names), len(montage.pos)) assert_equal(montage.pos.shape, (4, 3)) assert_equal(montage.kind, 'test') if kind == 'csd': dtype = [('label', 'S4'), ('theta', 'f8'), ('phi', 'f8'), ('radius', 'f8'), ('x', 'f8'), ('y', 'f8'), ('z', 'f8'), ('off_sph', 'f8')] try: table = np.loadtxt(fname, skip_header=2, dtype=dtype) except TypeError: table = np.loadtxt(fname, skiprows=2, dtype=dtype) poss['csd'] = np.c_[table['x'], table['y'], table['z']] if kind == 'elc': # Make sure points are reasonable distance from geometric centroid centroid = np.sum(montage.pos, axis=0) / montage.pos.shape[0] distance_from_centroid = np.apply_along_axis( np.linalg.norm, 1, montage.pos - centroid) assert_array_less(distance_from_centroid, 0.2) assert_array_less(0.01, distance_from_centroid) assert_array_almost_equal(poss[key], montage.pos, 4, err_msg=key) # Test reading in different letter case. ch_names = [ "F3", "FZ", "F4", "FC3", "FCz", "FC4", "C3", "CZ", "C4", "CP3", "CPZ", "CP4", "P3", "PZ", "P4", "O1", "OZ", "O2" ] montage = read_montage('standard_1020', ch_names=ch_names) assert_array_equal(ch_names, montage.ch_names) # test transform input_str = """ eeg Fp1 -95.0 -31.0 -3.0 eeg AF7 -81 -59 -3 eeg AF3 -87 -41 28 cardinal 2 -91 0 -42 cardinal 1 0 -91 -42 cardinal 3 0 91 -42 """ fname = op.join(tempdir, 'test_fid.hpts') with open(fname, 'w') as fid: fid.write(input_str) montage = read_montage(op.join(tempdir, 'test_fid.hpts'), transform=True) # check coordinate transformation pos = np.array([-95.0, -31.0, -3.0]) nasion = np.array([-91, 0, -42]) lpa = np.array([0, -91, -42]) rpa = np.array([0, 91, -42]) fids = np.vstack((nasion, lpa, rpa)) trans = get_ras_to_neuromag_trans(fids[0], fids[1], fids[2]) pos = apply_trans(trans, pos) assert_array_equal(montage.pos[0], pos) idx = montage.ch_names.index('2') assert_array_equal(montage.pos[idx, [0, 2]], [0, 0]) idx = montage.ch_names.index('1') assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) idx = montage.ch_names.index('3') assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) pos = np.array([-95.0, -31.0, -3.0]) montage_fname = op.join(tempdir, 'test_fid.hpts') montage = read_montage(montage_fname, unit='mm') assert_array_equal(montage.pos[0], pos * 1e-3) # test with last info = create_info(montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) _set_montage(info, montage) pos2 = np.array([c['loc'][:3] for c in info['chs']]) assert_array_equal(pos2, montage.pos) assert_equal(montage.ch_names, info['ch_names']) info = create_info(montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) evoked = EvokedArray(data=np.zeros((len(montage.ch_names), 1)), info=info, tmin=0) evoked.set_montage(montage) pos3 = np.array([c['loc'][:3] for c in evoked.info['chs']]) assert_array_equal(pos3, montage.pos) assert_equal(montage.ch_names, evoked.info['ch_names']) # Warning should be raised when some EEG are not specified in the montage with warnings.catch_warnings(record=True) as w: info = create_info(montage.ch_names + ['foo', 'bar'], 1e3, ['eeg'] * (len(montage.ch_names) + 2)) _set_montage(info, montage) assert_true(len(w) == 1)
def get_head_mri_trans(bids_path, extra_params=None, t1_bids_path=None, fs_subject=None, fs_subjects_dir=None, verbose=None): """Produce transformation matrix from MEG and MRI landmark points. Will attempt to read the landmarks of Nasion, LPA, and RPA from the sidecar files of (i) the MEG and (ii) the T1-weighted MRI data. The two sets of points will then be used to calculate a transformation matrix from head coordinates to MRI coordinates. .. note:: The MEG and MRI data need **not** necessarily be stored in the same session or even in the same BIDS dataset. See the ``t1_bids_path`` parameter for details. Parameters ---------- bids_path : mne_bids.BIDSPath The path of the electrophysiology recording. extra_params : None | dict Extra parameters to be passed to :func:`mne.io.read_raw` when reading the MEG file. t1_bids_path : mne_bids.BIDSPath | None If ``None`` (default), will try to discover the T1-weighted MRI file based on the name and location of the MEG recording specified via the ``bids_path`` parameter. Alternatively, you explicitly specify which T1-weighted MRI scan to use for extraction of MRI landmarks. To do that, pass a :class:`mne_bids.BIDSPath` pointing to the scan. Use this parameter e.g. if the T1 scan was recorded during a different session than the MEG. It is even possible to point to a T1 image stored in an entirely different BIDS dataset than the MEG data. fs_subject : str | None The subject identifier used for FreeSurfer. If ``None``, defaults to the ``subject`` entity in ``bids_path``. fs_subjects_dir : str | pathlib.Path | None The FreeSurfer subjects directory. If ``None``, defaults to the ``SUBJECTS_DIR`` environment variable. .. versionadded:: 0.8 %(verbose)s Returns ------- trans : mne.transforms.Transform The data transformation matrix from head to MRI coordinates. """ if not has_nibabel(): # pragma: no cover raise ImportError('This function requires nibabel.') import nibabel as nib if not isinstance(bids_path, BIDSPath): raise RuntimeError('"bids_path" must be a BIDSPath object. Please ' 'instantiate using mne_bids.BIDSPath().') # check root available meg_bids_path = bids_path.copy() del bids_path if meg_bids_path.root is None: raise ValueError('The root of the "bids_path" must be set. ' 'Please use `bids_path.update(root="<root>")` ' 'to set the root of the BIDS folder to read.') # only get this for MEG data meg_bids_path.update(datatype='meg', suffix='meg') # Get the sidecar file for MRI landmarks match_bids_path = meg_bids_path if t1_bids_path is None else t1_bids_path t1w_path = _find_matching_sidecar(match_bids_path, suffix='T1w', extension='.nii.gz') t1w_json_path = _find_matching_sidecar(match_bids_path, suffix='T1w', extension='.json') # Get MRI landmarks from the JSON sidecar with open(t1w_json_path, 'r', encoding='utf-8') as f: t1w_json = json.load(f) mri_coords_dict = t1w_json.get('AnatomicalLandmarkCoordinates', dict()) # landmarks array: rows: [LPA, NAS, RPA]; columns: [x, y, z] mri_landmarks = np.full((3, 3), np.nan) for landmark_name, coords in mri_coords_dict.items(): if landmark_name.upper() == 'LPA': mri_landmarks[0, :] = coords elif landmark_name.upper() == 'RPA': mri_landmarks[2, :] = coords elif (landmark_name.upper() == 'NAS' or landmark_name.lower() == 'nasion'): mri_landmarks[1, :] = coords else: continue if np.isnan(mri_landmarks).any(): raise RuntimeError( f'Could not extract fiducial points from T1w sidecar file: ' f'{t1w_json_path}\n\n' f'The sidecar file SHOULD contain a key ' f'"AnatomicalLandmarkCoordinates" pointing to an ' f'object with the keys "LPA", "NAS", and "RPA". ' f'Yet, the following structure was found:\n\n' f'{mri_coords_dict}') # The MRI landmarks are in "voxels". We need to convert the to the # neuromag RAS coordinate system in order to compare the with MEG landmarks # see also: `mne_bids.write.write_anat` if fs_subject is None: fs_subject = f'sub-{meg_bids_path.subject}' fs_subjects_dir = get_subjects_dir(fs_subjects_dir, raise_error=False) fs_t1_fname = Path(fs_subjects_dir) / fs_subject / 'mri' / 'T1.mgz' if not fs_t1_fname.exists(): raise ValueError( f"Could not find {fs_t1_fname}. Consider running FreeSurfer's " f"'recon-all` for subject {fs_subject}.") fs_t1_mgh = nib.load(str(fs_t1_fname)) t1_nifti = nib.load(str(t1w_path)) # Convert to MGH format to access vox2ras method t1_mgh = nib.MGHImage(t1_nifti.dataobj, t1_nifti.affine) # convert to scanner RAS mri_landmarks = apply_trans(t1_mgh.header.get_vox2ras(), mri_landmarks) # convert to FreeSurfer T1 voxels (same scanner RAS as T1) mri_landmarks = apply_trans(fs_t1_mgh.header.get_ras2vox(), mri_landmarks) # now extract transformation matrix and put back to RAS coordinates of MRI vox2ras_tkr = fs_t1_mgh.header.get_vox2ras_tkr() mri_landmarks = apply_trans(vox2ras_tkr, mri_landmarks) mri_landmarks = mri_landmarks * 1e-3 # Get MEG landmarks from the raw file _, ext = _parse_ext(meg_bids_path) if extra_params is None: extra_params = dict() if ext == '.fif': extra_params['allow_maxshield'] = True raw = read_raw_bids(bids_path=meg_bids_path, extra_params=extra_params) meg_coords_dict = _extract_landmarks(raw.info['dig']) meg_landmarks = np.asarray((meg_coords_dict['LPA'], meg_coords_dict['NAS'], meg_coords_dict['RPA'])) # Given the two sets of points, fit the transform trans_fitted = fit_matched_points(src_pts=meg_landmarks, tgt_pts=mri_landmarks) trans = mne.transforms.Transform(fro='head', to='mri', trans=trans_fitted) return trans
def test_montage(): """Test making montages""" tempdir = _TempDir() # no pep8 input_str = ["""FidNz 0.00000 10.56381 -2.05108 FidT9 -7.82694 0.45386 -3.76056 FidT10 7.82694 0.45386 -3.76056""", """// MatLab Sphere coordinates [degrees] Cartesian coordinates // Label Theta Phi Radius X Y Z off sphere surface E1 37.700 -14.000 1.000 0.7677 0.5934 -0.2419 -0.00000000000000011 E2 44.600 -0.880 1.000 0.7119 0.7021 -0.0154 0.00000000000000000 E3 51.700 11.000 1.000 0.6084 0.7704 0.1908 0.00000000000000000""", # noqa """# ASA electrode file ReferenceLabel avg UnitPosition mm NumberPositions= 68 Positions -86.0761 -19.9897 -47.9860 85.7939 -20.0093 -48.0310 0.0083 86.8110 -39.9830 Labels LPA RPA Nz """, """Site Theta Phi Fp1 -92 -72 Fp2 92 72 F3 -60 -51 """, """346 EEG F3 -62.027 -50.053 85 EEG Fz 45.608 90 85 EEG F4 62.01 50.103 85 """, """ eeg Fp1 -95.0 -31.0 -3.0 eeg AF7 -81 -59 -3 eeg AF3 -87 -41 28 """] kinds = ['test.sfp', 'test.csd', 'test.elc', 'test.txt', 'test.elp', 'test.hpts'] for kind, text in zip(kinds, input_str): fname = op.join(tempdir, kind) with open(fname, 'w') as fid: fid.write(text) montage = read_montage(fname) assert_equal(len(montage.ch_names), 3) assert_equal(len(montage.ch_names), len(montage.pos)) assert_equal(montage.pos.shape, (3, 3)) assert_equal(montage.kind, op.splitext(kind)[0]) if kind.endswith('csd'): dtype = [('label', 'S4'), ('theta', 'f8'), ('phi', 'f8'), ('radius', 'f8'), ('x', 'f8'), ('y', 'f8'), ('z', 'f8'), ('off_sph', 'f8')] try: table = np.loadtxt(fname, skip_header=2, dtype=dtype) except TypeError: table = np.loadtxt(fname, skiprows=2, dtype=dtype) pos2 = np.c_[table['x'], table['y'], table['z']] assert_array_almost_equal(pos2, montage.pos, 4) # test transform input_str = """ eeg Fp1 -95.0 -31.0 -3.0 eeg AF7 -81 -59 -3 eeg AF3 -87 -41 28 cardinal nasion -91 0 -42 cardinal lpa 0 -91 -42 cardinal rpa 0 91 -42 """ kind = 'test_fid.hpts' fname = op.join(tempdir, kind) with open(fname, 'w') as fid: fid.write(input_str) montage = read_montage(op.join(tempdir, 'test_fid.hpts'), transform=True) # check coordinate transformation pos = np.array([-95.0, -31.0, -3.0]) nasion = np.array([-91, 0, -42]) lpa = np.array([0, -91, -42]) rpa = np.array([0, 91, -42]) fids = np.vstack((nasion, lpa, rpa)) trans = get_ras_to_neuromag_trans(fids[0], fids[1], fids[2]) pos = apply_trans(trans, pos) assert_array_equal(montage.pos[0], pos) idx = montage.ch_names.index('nasion') assert_array_equal(montage.pos[idx, [0, 2]], [0, 0]) idx = montage.ch_names.index('lpa') assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) idx = montage.ch_names.index('rpa') assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) pos = np.array([-95.0, -31.0, -3.0]) montage = read_montage(op.join(tempdir, 'test_fid.hpts'), unit='mm') assert_array_equal(montage.pos[0], pos * 1e-3) # test with last info = create_info(montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) apply_montage(info, montage) pos2 = np.array([c['loc'][:3] for c in info['chs']]) pos3 = np.array([c['eeg_loc'][:, 0] for c in info['chs']]) assert_array_equal(pos2, montage.pos) assert_array_equal(pos3, montage.pos) assert_equal(montage.ch_names, info['ch_names'])
def test_montage(): """Test making montages.""" tempdir = _TempDir() inputs = dict( sfp='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', csd='// MatLab Sphere coordinates [degrees] Cartesian coordinates\n' # noqa: E501 '// Label Theta Phi Radius X Y Z off sphere surface\n' # noqa: E501 'E1 37.700 -14.000 1.000 0.7677 0.5934 -0.2419 -0.00000000000000011\n' # noqa: E501 'E3 51.700 11.000 1.000 0.6084 0.7704 0.1908 0.00000000000000000\n' # noqa: E501 'E31 90.000 -11.000 1.000 0.0000 0.9816 -0.1908 0.00000000000000000\n' # noqa: E501 'E61 158.000 -17.200 1.000 -0.8857 0.3579 -0.2957 -0.00000000000000022', # noqa: E501 mm_elc='# ASA electrode file\nReferenceLabel avg\nUnitPosition mm\n' # noqa:E501 'NumberPositions= 68\n' 'Positions\n' '-86.0761 -19.9897 -47.9860\n' '85.7939 -20.0093 -48.0310\n' '0.0083 86.8110 -39.9830\n' '-86.0761 -24.9897 -67.9860\n' 'Labels\nLPA\nRPA\nNz\nDummy\n', m_elc='# ASA electrode file\nReferenceLabel avg\nUnitPosition m\n' 'NumberPositions= 68\nPositions\n-.0860761 -.0199897 -.0479860\n' # noqa:E501 '.0857939 -.0200093 -.0480310\n.0000083 .00868110 -.0399830\n' '.08 -.02 -.04\n' 'Labels\nLPA\nRPA\nNz\nDummy\n', txt='Site Theta Phi\n' 'Fp1 -92 -72\n' 'Fp2 92 72\n' 'very_very_very_long_name -92 72\n' 'O2 92 -90\n', elp='346\n' 'EEG\t F3\t -62.027\t -50.053\t 85\n' 'EEG\t Fz\t 45.608\t 90\t 85\n' 'EEG\t F4\t 62.01\t 50.103\t 85\n' 'EEG\t FCz\t 68.01\t 58.103\t 85\n', hpts='eeg Fp1 -95.0 -3. -3.\n' 'eeg AF7 -1 -1 -3\n' 'eeg A3 -2 -2 2\n' 'eeg A 0 0 0', ) # Get actual positions and save them for checking # csd comes from the string above, all others come from commit 2fa35d4 poss = dict( sfp=[[0.0, 9.07159, -2.35975], [-6.71176, 0.0404, -3.2516], [-5.83124, -4.49482, 4.95535], [0.0, 0.0, 8.89919]], mm_elc=[[-0.08608, -0.01999, -0.04799], [0.08579, -0.02001, -0.04803], [1e-05, 0.08681, -0.03998], [-0.08608, -0.02499, -0.06799]], m_elc=[[-0.08608, -0.01999, -0.04799], [0.08579, -0.02001, -0.04803], [1e-05, 0.00868, -0.03998], [0.08, -0.02, -0.04]], txt=[[-26.25044, 80.79056, -2.96646], [26.25044, 80.79056, -2.96646], [-26.25044, -80.79056, -2.96646], [0.0, -84.94822, -2.96646]], elp=[[-48.20043, 57.55106, 39.86971], [0.0, 60.73848, 59.4629], [48.1426, 57.58403, 39.89198], [41.64599, 66.91489, 31.8278]], hpts=[[-95, -3, -3], [-1, -1., -3.], [-2, -2, 2.], [0, 0, 0]], ) for key, text in inputs.items(): kind = key.split('_')[-1] fname = op.join(tempdir, 'test.' + kind) with open(fname, 'w') as fid: fid.write(text) montage = read_montage(fname) if kind in ('sfp', 'txt'): assert_true('very_very_very_long_name' in montage.ch_names) assert_equal(len(montage.ch_names), 4) assert_equal(len(montage.ch_names), len(montage.pos)) assert_equal(montage.pos.shape, (4, 3)) assert_equal(montage.kind, 'test') if kind == 'csd': dtype = [('label', 'S4'), ('theta', 'f8'), ('phi', 'f8'), ('radius', 'f8'), ('x', 'f8'), ('y', 'f8'), ('z', 'f8'), ('off_sph', 'f8')] try: table = np.loadtxt(fname, skip_header=2, dtype=dtype) except TypeError: table = np.loadtxt(fname, skiprows=2, dtype=dtype) poss['csd'] = np.c_[table['x'], table['y'], table['z']] if kind == 'elc': # Make sure points are reasonable distance from geometric centroid centroid = np.sum(montage.pos, axis=0) / montage.pos.shape[0] distance_from_centroid = np.apply_along_axis( np.linalg.norm, 1, montage.pos - centroid) assert_array_less(distance_from_centroid, 0.2) assert_array_less(0.01, distance_from_centroid) assert_array_almost_equal(poss[key], montage.pos, 4, err_msg=key) # Test reading in different letter case. ch_names = ["F3", "FZ", "F4", "FC3", "FCz", "FC4", "C3", "CZ", "C4", "CP3", "CPZ", "CP4", "P3", "PZ", "P4", "O1", "OZ", "O2"] montage = read_montage('standard_1020', ch_names=ch_names) assert_array_equal(ch_names, montage.ch_names) # test transform input_strs = [""" eeg Fp1 -95.0 -31.0 -3.0 eeg AF7 -81 -59 -3 eeg AF3 -87 -41 28 cardinal 2 -91 0 -42 cardinal 1 0 -91 -42 cardinal 3 0 91 -42 """, """ Fp1 -95.0 -31.0 -3.0 AF7 -81 -59 -3 AF3 -87 -41 28 nasion -91 0 -42 lpa 0 -91 -42 rpa 0 91 -42 """] all_fiducials = [['2', '1', '3'], ['nasion', 'lpa', 'rpa']] kinds = ['test_fid.hpts', 'test_fid.sfp'] for kind, fiducials, input_str in zip(kinds, all_fiducials, input_strs): fname = op.join(tempdir, kind) with open(fname, 'w') as fid: fid.write(input_str) montage = read_montage(op.join(tempdir, kind), transform=True) # check coordinate transformation pos = np.array([-95.0, -31.0, -3.0]) nasion = np.array([-91, 0, -42]) lpa = np.array([0, -91, -42]) rpa = np.array([0, 91, -42]) fids = np.vstack((nasion, lpa, rpa)) trans = get_ras_to_neuromag_trans(fids[0], fids[1], fids[2]) pos = apply_trans(trans, pos) assert_array_equal(montage.pos[0], pos) idx = montage.ch_names.index(fiducials[0]) assert_array_equal(montage.pos[idx, [0, 2]], [0, 0]) idx = montage.ch_names.index(fiducials[1]) assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) idx = montage.ch_names.index(fiducials[2]) assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) pos = np.array([-95.0, -31.0, -3.0]) montage_fname = op.join(tempdir, kind) montage = read_montage(montage_fname, unit='mm') assert_array_equal(montage.pos[0], pos * 1e-3) # test with last info = create_info(montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) _set_montage(info, montage) pos2 = np.array([c['loc'][:3] for c in info['chs']]) assert_array_equal(pos2, montage.pos) assert_equal(montage.ch_names, info['ch_names']) info = create_info( montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) evoked = EvokedArray( data=np.zeros((len(montage.ch_names), 1)), info=info, tmin=0) evoked.set_montage(montage) pos3 = np.array([c['loc'][:3] for c in evoked.info['chs']]) assert_array_equal(pos3, montage.pos) assert_equal(montage.ch_names, evoked.info['ch_names']) # Warning should be raised when some EEG are not specified in montage with warnings.catch_warnings(record=True) as w: info = create_info(montage.ch_names + ['foo', 'bar'], 1e3, ['eeg'] * (len(montage.ch_names) + 2)) _set_montage(info, montage) assert_true(len(w) == 1) # Channel names can be treated case insensitive with warnings.catch_warnings(record=True) as w: info = create_info(['FP1', 'af7', 'AF3'], 1e3, ['eeg'] * 3) _set_montage(info, montage) assert_true(len(w) == 0) # Unless there is a collision in names with warnings.catch_warnings(record=True) as w: info = create_info(['FP1', 'Fp1', 'AF3'], 1e3, ['eeg'] * 3) _set_montage(info, montage) assert_true(len(w) == 1) with warnings.catch_warnings(record=True) as w: montage.ch_names = ['FP1', 'Fp1', 'AF3'] info = create_info(['fp1', 'AF3'], 1e3, ['eeg', 'eeg']) _set_montage(info, montage) assert_true(len(w) == 1)
def test_nirsport_v2(): """Test NIRSport2 file.""" raw = read_raw_nirx(nirsport2, preload=True) assert raw._data.shape == (40, 128) # Test distance between optodes matches values from # nirsite https://github.com/mne-tools/mne-testing-data/pull/86 # figure 3 allowed_distance_error = 0.005 distances = source_detector_distances(raw.info) assert_allclose(distances[::2][:14], [0.0304, 0.0411, 0.008, 0.0400, 0.008, 0.0310, 0.0411, 0.008, 0.0299, 0.008, 0.0370, 0.008, 0.0404, 0.008], atol=allowed_distance_error) # Test location of detectors # The locations of detectors can be seen in the first # figure on this page... # https://github.com/mne-tools/mne-testing-data/pull/86 allowed_dist_error = 0.0002 locs = [ch['loc'][6:9] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][3:5] == 'D1' assert_allclose( mni_locs[0], [-0.0841, -0.0464, -0.0129], atol=allowed_dist_error) assert raw.info['ch_names'][2][3:5] == 'D6' assert_allclose( mni_locs[2], [-0.0841, -0.0138, 0.0248], atol=allowed_dist_error) assert raw.info['ch_names'][34][3:5] == 'D5' assert_allclose( mni_locs[34], [0.0845, -0.0451, -0.0123], atol=allowed_dist_error) # Test location of sensors # The locations of sensors can be seen in the second # figure on this page... # https://github.com/mne-tools/mne-testing-data/pull/86 locs = [ch['loc'][3:6] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][:2] == 'S1' assert_allclose( mni_locs[0], [-0.0848, -0.0162, -0.0163], atol=allowed_dist_error) assert raw.info['ch_names'][9][:2] == 'S2' assert_allclose( mni_locs[9], [-0.0, -0.1195, 0.0142], atol=allowed_dist_error) assert raw.info['ch_names'][34][:2] == 'S8' assert_allclose( mni_locs[34], [0.0828, -0.046, 0.0285], atol=allowed_dist_error) assert len(raw.annotations) == 3 assert raw.annotations.description[0] == '1.0' assert raw.annotations.description[2] == '6.0' # Lose tolerance as I am eyeballing the time differences on screen assert_allclose( np.diff(raw.annotations.onset), [2.3, 3.1], atol=0.1) mon = raw.get_montage() assert len(mon.dig) == 43
def make_mne_anatomy(subject, anatomy_path, recordings_path=None, hcp_path=op.curdir, mode='minimal', outputs=( 'label', 'mri', 'surf')): """Extract relevant anatomy and create MNE friendly directory layout The function will create the following outputs by default: $anatomy_path/$subject/bem/inner_skull.surf $anatomy_path/$subject/label/* $anatomy_path/$subject/mri/* $anatomy_path/$subject/surf/* $recordings_path/$subject/$subject-head_mri-trans.fif These can then be set as $SUBJECTS_DIR and as MEG directory, consistent with MNE examples. Parameters ---------- subject : str The subject name. anatomy_path : str The path corresponding to MNE/freesurfer SUBJECTS_DIR (to be created) hcp_path : str The path where the HCP files can be found. mode : {'minimal', 'full'} If 'minimal', only the directory structure is created. If 'full' the freesurfer outputs shipped withm HCP (see `outputs`) are symbolically linked. outputs : {'label', 'mri', 'stats', 'surf', 'touch'} The outputs of the freesrufer pipeline shipped by HCP. Defaults to ('mri', 'surf'), the minimum needed to extract MNE-friendly anatomy files and data. """ if mode not in ('full', 'minimal'): raise ValueError('`mode` must either be "minimal" or "full"') if hcp_path == op.curdir: hcp_path = op.realpath(hcp_path) if not op.isabs(anatomy_path): anatomy_path = op.realpath(anatomy_path) this_anatomy_path = op.join(anatomy_path, subject) if not op.isabs(recordings_path): recordings_path = op.realpath(recordings_path) this_recordings_path = op.join(recordings_path, subject) if not op.exists(this_recordings_path): os.makedirs(this_recordings_path) for output in outputs: if not op.exists(op.join(this_anatomy_path, output)): os.makedirs(op.join(this_anatomy_path, output)) if output == 'mri': for suboutput in ['orig', 'transforms']: if not op.exists( op.join(this_anatomy_path, output, suboutput)): os.makedirs(op.join(this_anatomy_path, output, suboutput)) files = get_file_paths( subject=subject, data_type='freesurfer', output=output, mode=mode, processing='preprocessed', hcp_path=hcp_path) for source in files: match = [match for match in re.finditer(subject, source)][-1] split_path = source[:match.span()[1] + 1] target = op.join(this_anatomy_path, source.split(split_path)[-1]) if not op.isfile(target) and not op.islink(target): os.symlink(source, target) logger.info('reading extended structural processing ...') # Step 1 ################################################################# # transform head models to expected coordinate system # make hcp trans transforms_fname = get_file_paths( subject=subject, data_type='meg_anatomy', output='transforms', processing='preprocessed', hcp_path=hcp_path) transforms_fname = [k for k in transforms_fname if k.endswith('transform.txt')][0] hcp_trans = _read_trans_hcp(fname=transforms_fname, convert_to_meter=False) # get RAS freesurfer trans c_ras_trans_fname = get_file_paths( subject=subject, data_type='freesurfer', output='mri', processing='preprocessed', hcp_path=hcp_path) c_ras_trans_fname = [k for k in c_ras_trans_fname if k.endswith('c_ras.mat')][0] logger.info('reading RAS freesurfer transform') # ceci n'est pas un .mat file ... with open(op.join(anatomy_path, c_ras_trans_fname)) as fid: ras_trans = np.array([ r.split() for r in fid.read().split('\n') if r], dtype=np.float64) logger.info('Combining RAS transform and coregistration') ras_trans_m = linalg.inv(ras_trans) # and the inversion logger.info('extracting head model') head_model_fname = get_file_paths( subject=subject, data_type='meg_anatomy', output='head_model', processing='preprocessed', hcp_path=hcp_path)[0] pnts, faces = _get_head_model(head_model_fname=head_model_fname) logger.info('coregistring head model to MNE-HCP coordinates') pnts = apply_trans(ras_trans_m.dot(hcp_trans['bti2spm']), pnts) tri_fname = op.join(this_anatomy_path, 'bem', 'inner_skull.surf') if not op.exists(op.dirname(tri_fname)): os.makedirs(op.dirname(tri_fname)) write_surface(tri_fname, pnts, faces) # Step 2 ################################################################# # write corresponding device to MRI transform logger.info('extracting coregistration') # now convert to everything meter too here ras_trans_m[:3, 3] *= 1e-3 bti2spm = hcp_trans['bti2spm'] bti2spm[:3, 3] *= 1e-3 head_mri_t = Transform( # we're lying here for a good purpose 'head', 'mri', np.dot(ras_trans_m, bti2spm)) # it should be 'ctf_head' write_trans( op.join(this_recordings_path, '%s-head_mri-trans.fif') % subject, head_mri_t)
def test_scale_mri(tmpdir, few_surfaces, scale): """Test creating fsaverage and scaling it.""" # create fsaverage using the testing "fsaverage" instead of the FreeSurfer # one tempdir = str(tmpdir) fake_home = testing.data_path() create_default_subject(subjects_dir=tempdir, fs_home=fake_home, verbose=True) assert _is_mri_subject('fsaverage', tempdir), "Creating fsaverage failed" fid_path = op.join(tempdir, 'fsaverage', 'bem', 'fsaverage-fiducials.fif') os.remove(fid_path) create_default_subject(update=True, subjects_dir=tempdir, fs_home=fake_home) assert op.exists(fid_path), "Updating fsaverage" # copy MRI file from sample data (shouldn't matter that it's incorrect, # so here choose a small one) path_from = op.join(testing.data_path(), 'subjects', 'sample', 'mri', 'T1.mgz') path_to = op.join(tempdir, 'fsaverage', 'mri', 'orig.mgz') copyfile(path_from, path_to) # remove redundant label files label_temp = op.join(tempdir, 'fsaverage', 'label', '*.label') label_paths = glob(label_temp) for label_path in label_paths[1:]: os.remove(label_path) # create source space print('Creating surface source space') path = op.join(tempdir, 'fsaverage', 'bem', 'fsaverage-%s-src.fif') src = mne.setup_source_space('fsaverage', 'ico0', subjects_dir=tempdir, add_dist=False) mri = op.join(tempdir, 'fsaverage', 'mri', 'orig.mgz') print('Creating volume source space') vsrc = mne.setup_volume_source_space('fsaverage', pos=50, mri=mri, subjects_dir=tempdir, add_interpolator=False) write_source_spaces(path % 'vol-50', vsrc) # scale fsaverage write_source_spaces(path % 'ico-0', src, overwrite=True) with pytest.warns(None): # sometimes missing nibabel scale_mri('fsaverage', 'flachkopf', scale, True, subjects_dir=tempdir, verbose='debug') assert _is_mri_subject('flachkopf', tempdir), "Scaling failed" spath = op.join(tempdir, 'flachkopf', 'bem', 'flachkopf-%s-src.fif') assert op.exists(spath % 'ico-0'), "Source space ico-0 was not scaled" assert os.path.isfile( os.path.join(tempdir, 'flachkopf', 'surf', 'lh.sphere.reg')) vsrc_s = mne.read_source_spaces(spath % 'vol-50') for vox in ([0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 2, 3]): idx = np.ravel_multi_index(vox, vsrc[0]['shape'], order='F') err_msg = f'idx={idx} @ {vox}, scale={scale}' assert_allclose(apply_trans(vsrc[0]['src_mri_t'], vox), vsrc[0]['rr'][idx], err_msg=err_msg) assert_allclose(apply_trans(vsrc_s[0]['src_mri_t'], vox), vsrc_s[0]['rr'][idx], err_msg=err_msg) scale_labels('flachkopf', subjects_dir=tempdir) # add distances to source space after hacking the properties to make # it run *much* faster src_dist = src.copy() for s in src_dist: s.update(rr=s['rr'][s['vertno']], nn=s['nn'][s['vertno']], tris=s['use_tris']) s.update(np=len(s['rr']), ntri=len(s['tris']), vertno=np.arange(len(s['rr'])), inuse=np.ones(len(s['rr']), int)) mne.add_source_space_distances(src_dist) write_source_spaces(path % 'ico-0', src_dist, overwrite=True) # scale with distances os.remove(spath % 'ico-0') scale_source_space('flachkopf', 'ico-0', subjects_dir=tempdir) ssrc = mne.read_source_spaces(spath % 'ico-0') assert ssrc[0]['dist'] is not None assert ssrc[0]['nearest'] is not None # check patch info computation (only if SciPy is new enough to be fast) if check_version('scipy', '1.3'): for s in src_dist: for key in ('dist', 'dist_limit'): s[key] = None write_source_spaces(path % 'ico-0', src_dist, overwrite=True) # scale with distances os.remove(spath % 'ico-0') scale_source_space('flachkopf', 'ico-0', subjects_dir=tempdir) ssrc = mne.read_source_spaces(spath % 'ico-0') assert ssrc[0]['dist'] is None assert ssrc[0]['nearest'] is not None
def test_nirx_15_3_short(): """Test reading NIRX files.""" raw = read_raw_nirx(fname_nirx_15_3_short, preload=True) # Test data import assert raw._data.shape == (26, 220) assert raw.info['sfreq'] == 12.5 # Test channel naming assert raw.info['ch_names'][:4] == [ "S1_D2 760", "S1_D2 850", "S1_D9 760", "S1_D9 850" ] assert raw.info['ch_names'][24:26] == ["S5_D13 760", "S5_D13 850"] # Test frequency encoding assert raw.info['chs'][0]['loc'][9] == 760 assert raw.info['chs'][1]['loc'][9] == 850 # Test info import assert raw.info['subject_info'] == dict(birthday=(2020, 8, 18), sex=0, first_name="testMontage\\0A" "TestMontage") # Test distance between optodes matches values from # https://github.com/mne-tools/mne-testing-data/pull/72 allowed_distance_error = 0.001 distances = source_detector_distances(raw.info) assert_allclose(distances[::2], [ 0.0304, 0.0078, 0.0310, 0.0086, 0.0416, 0.0072, 0.0389, 0.0075, 0.0558, 0.0562, 0.0561, 0.0565, 0.0077 ], atol=allowed_distance_error) # Test which channels are short # These are the ones marked as red at # https://github.com/mne-tools/mne-testing-data/pull/72 is_short = short_channels(raw.info) assert_array_equal(is_short[:9:2], [False, True, False, True, False]) is_short = short_channels(raw.info, threshold=0.003) assert_array_equal(is_short[:3:2], [False, False]) is_short = short_channels(raw.info, threshold=50) assert_array_equal(is_short[:3:2], [True, True]) # Test trigger events assert_array_equal(raw.annotations.description, ['4.0', '2.0', '1.0']) # Test location of detectors # The locations of detectors can be seen in the first # figure on this page... # https://github.com/mne-tools/mne-testing-data/pull/72 # And have been manually copied below allowed_dist_error = 0.0002 locs = [ch['loc'][6:9] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][3:5] == 'D2' assert_allclose(mni_locs[0], [-0.0841, -0.0464, -0.0129], atol=allowed_dist_error) assert raw.info['ch_names'][4][3:5] == 'D1' assert_allclose(mni_locs[4], [0.0846, -0.0142, -0.0156], atol=allowed_dist_error) assert raw.info['ch_names'][8][3:5] == 'D3' assert_allclose(mni_locs[8], [0.0207, -0.1062, 0.0484], atol=allowed_dist_error) assert raw.info['ch_names'][12][3:5] == 'D4' assert_allclose(mni_locs[12], [-0.0196, 0.0821, 0.0275], atol=allowed_dist_error) assert raw.info['ch_names'][16][3:5] == 'D5' assert_allclose(mni_locs[16], [-0.0360, 0.0276, 0.0778], atol=allowed_dist_error) assert raw.info['ch_names'][19][3:5] == 'D6' assert_allclose(mni_locs[19], [0.0388, -0.0477, 0.0932], atol=allowed_dist_error) assert raw.info['ch_names'][21][3:5] == 'D7' assert_allclose(mni_locs[21], [-0.0394, -0.0483, 0.0928], atol=allowed_dist_error)
def plot_visualize_mft_sources(fwdmag, stcdata, tmin, tstep, subject, subjects_dir): ''' Plot the MFT sources at time point of peak. ''' print "##### Attempting to plot:" # cf. decoding/plot_decoding_spatio_temporal_source.py vertices = [s['vertno'] for s in fwdmag['src']] if len(vertices) == 1: vertices = [ fwdmag['src'][0]['vertno'] [fwdmag['src'][0]['rr'][fwdmag['src'][0]['vertno']][:, 0] <= -0.], fwdmag['src'][0]['vertno'][ fwdmag['src'][0]['rr'][fwdmag['src'][0]['vertno']][:, 0] > -0.] ] stc_feat = SourceEstimate(stcdata, vertices=vertices, tmin=-0.2, tstep=tstep, subject=subject) for hemi in ['lh', 'rh']: brain = stc_feat.plot(surface='white', hemi=hemi, subjects_dir=subjects_dir, transparent=True, clim='auto') brain.show_view('lateral') # use peak getter to move visualization to the time point of the peak tmin = 0.095 tmax = 0.10 print "Restricting peak search to [%fs, %fs]" % (tmin, tmax) if hemi == 'both': vertno_max, time_idx = stc_feat.get_peak(hemi='rh', time_as_index=True, tmin=tmin, tmax=tmax) else: vertno_max, time_idx = stc_feat.get_peak(hemi=hemi, time_as_index=True, tmin=tmin, tmax=tmax) if hemi == 'lh': comax = fwdmag['src'][0]['rr'][vertno_max] print "hemi=%s: vertno_max=%d, time_idx=%d fwdmag['src'][0]['rr'][vertno_max] = " %\ (hemi, vertno_max, time_idx), comax elif len(fwdmag['src']) > 1: comax = fwdmag['src'][1]['rr'][vertno_max] print "hemi=%s: vertno_max=%d, time_idx=%d fwdmag['src'][1]['rr'][vertno_max] = " %\ (hemi, vertno_max, time_idx), comax print "hemi=%s: setting time_idx=%d" % (hemi, time_idx) brain.set_data_time_index(time_idx) # draw marker at maximum peaking vertex brain.add_foci(vertno_max, coords_as_verts=True, hemi=hemi, color='blue', scale_factor=0.6) offsets = np.append([0], [s['nuse'] for s in fwdmag['src']]) if hemi == 'lh': ifoci = [ np.nonzero([ stcdata[0:offsets[1], time_idx] >= 0.25 * np.max(stcdata[:, time_idx]) ][0]) ] vfoci = fwdmag['src'][0]['vertno'][ifoci[0][0]] cfoci = fwdmag['src'][0]['rr'][vfoci] print "Coords of %d sel. vfoci: " % cfoci.shape[0] print cfoci print "vfoci: " print vfoci print "brain.geo['lh'].coords[vfoci] : " print brain.geo['lh'].coords[vfoci] elif len(fwdmag['src']) > 1: ifoci = [ np.nonzero([ stcdata[offsets[1]:, time_idx] >= 0.25 * np.max(stcdata[:, time_idx]) ][0]) ] vfoci = fwdmag['src'][1]['vertno'][ifoci[0][0]] cfoci = fwdmag['src'][1]['rr'][vfoci] print "Coords of %d sel. vfoci: " % cfoci.shape[0] print cfoci print "vfoci: " print vfoci print "brain.geo['rh'].coords[vfoci] : " print brain.geo['rh'].coords[vfoci] mrfoci = np.zeros(cfoci.shape) invmri_head_t = invert_transform(fwdmag['info']['mri_head_t']) mrfoci = apply_trans(invmri_head_t['trans'], cfoci, move=True) print "mrfoci: " print mrfoci # Just some blops: bloblist = np.zeros((300, 3)) for i in xrange(100): bloblist[i, 0] = float(i) bloblist[i + 100, 1] = float(i) bloblist[i + 200, 2] = float(i) mrblobs = apply_trans(invmri_head_t['trans'], bloblist, move=True) brain.save_image('testfig_map_%s.png' % hemi) brain.close()
def plot_visualize_mft_sources(fwdmag, stcdata, tmin, tstep, subject, subjects_dir): ''' Plot the MFT sources at time point of peak. ''' print "##### Attempting to plot:" # cf. decoding/plot_decoding_spatio_temporal_source.py vertices = [s['vertno'] for s in fwdmag['src']] if len(vertices) == 1: vertices = [fwdmag['src'][0]['vertno'][fwdmag['src'][0]['rr'][fwdmag['src'][0]['vertno']][:, 0] <= -0.], fwdmag['src'][0]['vertno'][fwdmag['src'][0]['rr'][fwdmag['src'][0]['vertno']][:, 0] > -0.]] stc_feat = SourceEstimate(stcdata, vertices=vertices, tmin=-0.2, tstep=tstep, subject=subject) for hemi in ['lh', 'rh']: brain = stc_feat.plot(surface='white', hemi=hemi, subjects_dir=subjects_dir, transparent=True, clim='auto') brain.show_view('lateral') # use peak getter to move visualization to the time point of the peak tmin = 0.095 tmax = 0.10 print "Restricting peak search to [%fs, %fs]" % (tmin, tmax) if hemi == 'both': vertno_max, time_idx = stc_feat.get_peak(hemi='rh', time_as_index=True, tmin=tmin, tmax=tmax) else: vertno_max, time_idx = stc_feat.get_peak(hemi=hemi, time_as_index=True, tmin=tmin, tmax=tmax) if hemi == 'lh': comax = fwdmag['src'][0]['rr'][vertno_max] print "hemi=%s: vertno_max=%d, time_idx=%d fwdmag['src'][0]['rr'][vertno_max] = " %\ (hemi, vertno_max, time_idx), comax elif len(fwdmag['src']) > 1: comax = fwdmag['src'][1]['rr'][vertno_max] print "hemi=%s: vertno_max=%d, time_idx=%d fwdmag['src'][1]['rr'][vertno_max] = " %\ (hemi, vertno_max, time_idx), comax print "hemi=%s: setting time_idx=%d" % (hemi, time_idx) brain.set_data_time_index(time_idx) # draw marker at maximum peaking vertex brain.add_foci(vertno_max, coords_as_verts=True, hemi=hemi, color='blue', scale_factor=0.6) offsets = np.append([0], [s['nuse'] for s in fwdmag['src']]) if hemi == 'lh': ifoci = [np.nonzero([stcdata[0:offsets[1],time_idx]>=0.25*np.max(stcdata[:,time_idx])][0])] vfoci = fwdmag['src'][0]['vertno'][ifoci[0][0]] cfoci = fwdmag['src'][0]['rr'][vfoci] print "Coords of %d sel. vfoci: " % cfoci.shape[0] print cfoci print "vfoci: " print vfoci print "brain.geo['lh'].coords[vfoci] : " print brain.geo['lh'].coords[vfoci] elif len(fwdmag['src']) > 1: ifoci = [np.nonzero([stcdata[offsets[1]:,time_idx]>=0.25*np.max(stcdata[:,time_idx])][0])] vfoci = fwdmag['src'][1]['vertno'][ifoci[0][0]] cfoci = fwdmag['src'][1]['rr'][vfoci] print "Coords of %d sel. vfoci: " % cfoci.shape[0] print cfoci print "vfoci: " print vfoci print "brain.geo['rh'].coords[vfoci] : " print brain.geo['rh'].coords[vfoci] mrfoci = np.zeros(cfoci.shape) invmri_head_t = invert_transform(fwdmag['info']['mri_head_t']) mrfoci = apply_trans(invmri_head_t['trans'],cfoci, move=True) print "mrfoci: " print mrfoci # Just some blops: bloblist = np.zeros((300,3)) for i in xrange(100): bloblist[i,0] = float(i) bloblist[i+100,1] = float(i) bloblist[i+200,2] = float(i) mrblobs = apply_trans(invmri_head_t['trans'], bloblist, move=True) brain.save_image('testfig_map_%s.png' % hemi) brain.close()
def select_vertices_in_sensor_range(inst, dist, info=None, picks=None, trans=None, indices=False, verbose=None): """Find vertices within given distance to a sensor. Parameters ---------- inst : instance of Forward | instance of SourceSpaces The object to select vertices from. dist : float The minimum distance between a vertex and the nearest sensor. All vertices for which the distance to the nearest sensor exceeds this limit are discarded. info : instance of Info | None The info structure that contains information about the channels. Only needs to be specified if the object to select vertices from does is an instance of SourceSpaces. picks : array-like of int | None Indices of sensors to include in the search for the nearest sensor. If ``None``, the default, only MEG channels are used. trans : str | instance of Transform | None Either the full path to the head<->MRI transform ``*-trans.fif`` file produced during coregistration, or the Transformation itself. If trans is None, an identity matrix is assumed. Only needed when ``inst`` is a source space in MRI coordinates. indices: False | True If ``True``, return vertex indices instead of vertex numbers. Defaults to ``False``. verbose : bool | str | int | None If not None, override default verbose level (see :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>` for more). Returns ------- vertices : pair of lists | list of int Either a list of vertex numbers for the left and right hemisphere (if ``indices==False``) or a single list with vertex indices. See Also -------- restrict_forward_to_vertices : restrict Forward to the given vertices restrict_src_to_vertices : restrict SourceSpaces to the given vertices """ if isinstance(inst, Forward): info = inst['info'] src = inst['src'] elif isinstance(inst, SourceSpaces): src = inst if info is None: raise ValueError('You need to specify an Info object with ' 'information about the channels.') # Load the head<->MRI transform if necessary if src[0]['coord_frame'] == FIFF.FIFFV_COORD_MRI: if trans is None: raise ValueError('Source space is in MRI coordinates, but no ' 'head<->MRI transform was given. Please specify ' 'the full path to the appropriate *-trans.fif ' 'file as the "trans" parameter.') if isinstance(trans, string_types): trans = read_trans(trans, return_all=True) for trans in trans: # we got at least 1 try: trans = _ensure_trans(trans, 'head', 'mri') except Exception as exp: pass else: break else: raise exp src_trans = invert_transform(_ensure_trans(trans, 'head', 'mri')) print('Transform!') else: src_trans = Transform('head', 'head') # Identity transform dev_to_head = _ensure_trans(info['dev_head_t'], 'meg', 'head') if picks is None: picks = pick_types(info, meg=True) if len(picks) > 0: logger.info('Using MEG channels') else: logger.info('Using EEG channels') picks = pick_types(info, eeg=True) src_pos = np.vstack([ apply_trans(src_trans, s['rr'][s['inuse'].astype(np.bool)]) for s in src ]) sensor_pos = [] for ch in picks: # MEG channels are in device coordinates, translate them to head if channel_type(info, ch) in ['mag', 'grad']: sensor_pos.append( apply_trans(dev_to_head, info['chs'][ch]['loc'][:3])) else: sensor_pos.append(info['chs'][ch]['loc'][:3]) sensor_pos = np.array(sensor_pos) # Find vertices that are within range of a sensor. We use a KD-tree for # speed. logger.info('Finding vertices within sensor range...') tree = cKDTree(sensor_pos) distances, _ = tree.query(src_pos, distance_upper_bound=dist) # Vertices out of range are flagged as np.inf src_sel = np.isfinite(distances) logger.info('[done]') if indices: return np.flatnonzero(src_sel) else: n_lh_verts = src[0]['nuse'] lh_sel, rh_sel = src_sel[:n_lh_verts], src_sel[n_lh_verts:] vert_lh = src[0]['vertno'][lh_sel] vert_rh = src[1]['vertno'][rh_sel] return [vert_lh, vert_rh]
def test_montage(): """Test making montages""" tempdir = _TempDir() # no pep8 input_str = [ 'FidNz 0.00000 10.56381 -2.05108\nFidT9 -7.82694 0.45386 -3.76056\n' 'very_very_very_long_name 7.82694 0.45386 -3.76056', '// MatLab Sphere coordinates [degrees] Cartesian coordinates\n' # noqa '// Label Theta Phi Radius X Y Z off sphere surface\n' # noqa 'E1 37.700 -14.000 1.000 0.7677 0.5934 -0.2419 -0.00000000000000011\n' # noqa 'E2 44.600 -0.880 1.000 0.7119 0.7021 -0.0154 0.00000000000000000\n' # noqa 'E3 51.700 11.000 1.000 0.6084 0.7704 0.1908 0.00000000000000000', # noqa '# ASA electrode file\nReferenceLabel avg\nUnitPosition mm\n' 'NumberPositions= 68\nPositions\n-86.0761 -19.9897 -47.9860\n' '85.7939 -20.0093 -48.0310\n0.0083 86.8110 -39.9830\n' 'Labels\nLPA\nRPA\nNz\n', 'Site Theta Phi\nFp1 -92 -72\nFp2 92 72\n' 'very_very_very_long_name -60 -51\n', '346\n' 'EEG F3 -62.027 -50.053 85\n' 'EEG Fz 45.608 90 85\n' 'EEG F4 62.01 50.103 85\n', 'eeg Fp1 -95.0 -31.0 -3.0\neeg AF7 -81 -59 -3\neeg AF3 -87 -41 28\n' ] kinds = ['test.sfp', 'test.csd', 'test.elc', 'test.txt', 'test.elp', 'test.hpts'] for kind, text in zip(kinds, input_str): fname = op.join(tempdir, kind) with open(fname, 'w') as fid: fid.write(text) montage = read_montage(fname) if ".sfp" in kind or ".txt" in kind: assert_true('very_very_very_long_name' in montage.ch_names) assert_equal(len(montage.ch_names), 3) assert_equal(len(montage.ch_names), len(montage.pos)) assert_equal(montage.pos.shape, (3, 3)) assert_equal(montage.kind, op.splitext(kind)[0]) if kind.endswith('csd'): dtype = [('label', 'S4'), ('theta', 'f8'), ('phi', 'f8'), ('radius', 'f8'), ('x', 'f8'), ('y', 'f8'), ('z', 'f8'), ('off_sph', 'f8')] try: table = np.loadtxt(fname, skip_header=2, dtype=dtype) except TypeError: table = np.loadtxt(fname, skiprows=2, dtype=dtype) pos2 = np.c_[table['x'], table['y'], table['z']] assert_array_almost_equal(pos2, montage.pos, 4) # test transform input_str = """ eeg Fp1 -95.0 -31.0 -3.0 eeg AF7 -81 -59 -3 eeg AF3 -87 -41 28 cardinal 2 -91 0 -42 cardinal 1 0 -91 -42 cardinal 3 0 91 -42 """ kind = 'test_fid.hpts' fname = op.join(tempdir, kind) with open(fname, 'w') as fid: fid.write(input_str) montage = read_montage(op.join(tempdir, 'test_fid.hpts'), transform=True) # check coordinate transformation pos = np.array([-95.0, -31.0, -3.0]) nasion = np.array([-91, 0, -42]) lpa = np.array([0, -91, -42]) rpa = np.array([0, 91, -42]) fids = np.vstack((nasion, lpa, rpa)) trans = get_ras_to_neuromag_trans(fids[0], fids[1], fids[2]) pos = apply_trans(trans, pos) assert_array_equal(montage.pos[0], pos) idx = montage.ch_names.index('2') assert_array_equal(montage.pos[idx, [0, 2]], [0, 0]) idx = montage.ch_names.index('1') assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) idx = montage.ch_names.index('3') assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) pos = np.array([-95.0, -31.0, -3.0]) montage_fname = op.join(tempdir, 'test_fid.hpts') montage = read_montage(montage_fname, unit='mm') assert_array_equal(montage.pos[0], pos * 1e-3) # test with last info = create_info(montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) _set_montage(info, montage) pos2 = np.array([c['loc'][:3] for c in info['chs']]) assert_array_equal(pos2, montage.pos) assert_equal(montage.ch_names, info['ch_names']) info = create_info( montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) evoked = EvokedArray( data=np.zeros((len(montage.ch_names), 1)), info=info, tmin=0) evoked.set_montage(montage) pos3 = np.array([c['loc'][:3] for c in evoked.info['chs']]) assert_array_equal(pos3, montage.pos) assert_equal(montage.ch_names, evoked.info['ch_names']) # Warning should be raised when some EEG are not specified in the montage with warnings.catch_warnings(record=True) as w: info = create_info(montage.ch_names + ['foo', 'bar'], 1e3, ['eeg'] * (len(montage.ch_names) + 2)) _set_montage(info, montage) assert_true(len(w) == 1)
def test_read_ctf(tmpdir): """Test CTF reader.""" temp_dir = str(tmpdir) out_fname = op.join(temp_dir, 'test_py_raw.fif') # Create a dummy .eeg file so we can test our reading/application of it os.mkdir(op.join(temp_dir, 'randpos')) ctf_eeg_fname = op.join(temp_dir, 'randpos', ctf_fname_catch) shutil.copytree(op.join(ctf_dir, ctf_fname_catch), ctf_eeg_fname) with pytest.warns(RuntimeWarning, match='RMSP .* changed to a MISC ch'): raw = _test_raw_reader(read_raw_ctf, directory=ctf_eeg_fname) picks = pick_types(raw.info, meg=False, eeg=True) pos = np.random.RandomState(42).randn(len(picks), 3) fake_eeg_fname = op.join(ctf_eeg_fname, 'catch-alp-good-f.eeg') # Create a bad file with open(fake_eeg_fname, 'wb') as fid: fid.write('foo\n'.encode('ascii')) pytest.raises(RuntimeError, read_raw_ctf, ctf_eeg_fname) # Create a good file with open(fake_eeg_fname, 'wb') as fid: for ii, ch_num in enumerate(picks): args = ( str(ch_num + 1), raw.ch_names[ch_num], ) + tuple('%0.5f' % x for x in 100 * pos[ii]) # convert to cm fid.write(('\t'.join(args) + '\n').encode('ascii')) pos_read_old = np.array([raw.info['chs'][p]['loc'][:3] for p in picks]) with pytest.warns(RuntimeWarning, match='RMSP .* changed to a MISC ch'): raw = read_raw_ctf(ctf_eeg_fname) # read modified data pos_read = np.array([raw.info['chs'][p]['loc'][:3] for p in picks]) assert_allclose(apply_trans(raw.info['ctf_head_t'], pos), pos_read, rtol=1e-5, atol=1e-5) assert (pos_read == pos_read_old).mean() < 0.1 shutil.copy(op.join(ctf_dir, 'catch-alp-good-f.ds_randpos_raw.fif'), op.join(temp_dir, 'randpos', 'catch-alp-good-f.ds_raw.fif')) # Create a version with no hc, starting out *with* EEG pos (error) os.mkdir(op.join(temp_dir, 'nohc')) ctf_no_hc_fname = op.join(temp_dir, 'no_hc', ctf_fname_catch) shutil.copytree(ctf_eeg_fname, ctf_no_hc_fname) remove_base = op.join(ctf_no_hc_fname, op.basename(ctf_fname_catch[:-3])) os.remove(remove_base + '.hc') with pytest.warns(RuntimeWarning, match='MISC channel'): pytest.raises(RuntimeError, read_raw_ctf, ctf_no_hc_fname) os.remove(remove_base + '.eeg') shutil.copy(op.join(ctf_dir, 'catch-alp-good-f.ds_nohc_raw.fif'), op.join(temp_dir, 'no_hc', 'catch-alp-good-f.ds_raw.fif')) # All our files use_fnames = [op.join(ctf_dir, c) for c in ctf_fnames] for fname in use_fnames: raw_c = read_raw_fif(fname + '_raw.fif', preload=True) with pytest.warns(None): # sometimes matches "MISC channel" raw = read_raw_ctf(fname) # check info match assert_array_equal(raw.ch_names, raw_c.ch_names) assert_allclose(raw.times, raw_c.times) assert_allclose(raw._cals, raw_c._cals) assert (raw.info['meas_id']['version'] == raw_c.info['meas_id']['version'] + 1) for t in ('dev_head_t', 'dev_ctf_t', 'ctf_head_t'): assert_allclose(raw.info[t]['trans'], raw_c.info[t]['trans'], rtol=1e-4, atol=1e-7) for key in ('acq_pars', 'acq_stim', 'bads', 'ch_names', 'custom_ref_applied', 'description', 'events', 'experimenter', 'highpass', 'line_freq', 'lowpass', 'nchan', 'proj_id', 'proj_name', 'projs', 'sfreq', 'subject_info'): assert raw.info[key] == raw_c.info[key], key if op.basename(fname) not in single_trials: # We don't force buffer size to be smaller like MNE-C assert raw.buffer_size_sec == raw_c.buffer_size_sec assert len(raw.info['comps']) == len(raw_c.info['comps']) for c1, c2 in zip(raw.info['comps'], raw_c.info['comps']): for key in ('colcals', 'rowcals'): assert_allclose(c1[key], c2[key]) assert c1['save_calibrated'] == c2['save_calibrated'] for key in ('row_names', 'col_names', 'nrow', 'ncol'): assert_array_equal(c1['data'][key], c2['data'][key]) assert_allclose(c1['data']['data'], c2['data']['data'], atol=1e-7, rtol=1e-5) assert_allclose(raw.info['hpi_results'][0]['coord_trans']['trans'], raw_c.info['hpi_results'][0]['coord_trans']['trans'], rtol=1e-5, atol=1e-7) assert len(raw.info['chs']) == len(raw_c.info['chs']) for ii, (c1, c2) in enumerate(zip(raw.info['chs'], raw_c.info['chs'])): for key in ('kind', 'scanno', 'unit', 'ch_name', 'unit_mul', 'range', 'coord_frame', 'coil_type', 'logno'): if c1['ch_name'] == 'RMSP' and \ 'catch-alp-good-f' in fname and \ key in ('kind', 'unit', 'coord_frame', 'coil_type', 'logno'): continue # XXX see below... if key == 'coil_type' and c1[key] == FIFF.FIFFV_COIL_EEG: # XXX MNE-C bug that this is not set assert c2[key] == FIFF.FIFFV_COIL_NONE continue assert c1[key] == c2[key], key for key in ('cal', ): assert_allclose(c1[key], c2[key], atol=1e-6, rtol=1e-4, err_msg='raw.info["chs"][%d][%s]' % (ii, key)) # XXX 2016/02/24: fixed bug with normal computation that used # to exist, once mne-C tools are updated we should update our FIF # conversion files, then the slices can go away (and the check # can be combined with that for "cal") for key in ('loc', ): if c1['ch_name'] == 'RMSP' and 'catch-alp-good-f' in fname: continue if (c2[key][:3] == 0.).all(): check = [np.nan] * 3 else: check = c2[key][:3] assert_allclose(c1[key][:3], check, atol=1e-6, rtol=1e-4, err_msg='raw.info["chs"][%d][%s]' % (ii, key)) if (c2[key][3:] == 0.).all(): check = [np.nan] * 3 else: check = c2[key][9:12] assert_allclose(c1[key][9:12], check, atol=1e-6, rtol=1e-4, err_msg='raw.info["chs"][%d][%s]' % (ii, key)) # Make sure all digitization points are in the MNE head coord frame for p in raw.info['dig']: assert p['coord_frame'] == FIFF.FIFFV_COORD_HEAD, \ 'dig points must be in FIFF.FIFFV_COORD_HEAD' if fname.endswith('catch-alp-good-f.ds'): # omit points from .pos file raw.info['dig'] = raw.info['dig'][:-10] # XXX: Next test would fail because c-tools assign the fiducials from # CTF data as HPI. Should eventually clarify/unify with Matti. # assert_dig_allclose(raw.info, raw_c.info) # check data match raw_c.save(out_fname, overwrite=True, buffer_size_sec=1.) raw_read = read_raw_fif(out_fname) # so let's check tricky cases based on sample boundaries rng = np.random.RandomState(0) pick_ch = rng.permutation(np.arange(len(raw.ch_names)))[:10] bnd = int(round(raw.info['sfreq'] * raw.buffer_size_sec)) assert bnd == raw._raw_extras[0]['block_size'] assert bnd == block_sizes[op.basename(fname)] slices = (slice(0, bnd), slice(bnd - 1, bnd), slice(3, bnd), slice(3, 300), slice(None)) if len(raw.times) >= 2 * bnd: # at least two complete blocks slices = slices + (slice(bnd, 2 * bnd), slice( bnd, bnd + 1), slice(0, bnd + 100)) for sl_time in slices: assert_allclose(raw[pick_ch, sl_time][0], raw_c[pick_ch, sl_time][0]) assert_allclose(raw_read[pick_ch, sl_time][0], raw_c[pick_ch, sl_time][0]) # all data / preload with pytest.warns(None): # sometimes MISC raw = read_raw_ctf(fname, preload=True) assert_allclose(raw[:][0], raw_c[:][0], atol=1e-15) # test bad segment annotations if 'testdata_ctf_short.ds' in fname: assert 'bad' in raw.annotations.description[0] assert_allclose(raw.annotations.onset, [2.15]) assert_allclose(raw.annotations.duration, [0.0225]) pytest.raises(TypeError, read_raw_ctf, 1) pytest.raises(ValueError, read_raw_ctf, ctf_fname_continuous + 'foo.ds') # test ignoring of system clock read_raw_ctf(op.join(ctf_dir, ctf_fname_continuous), 'ignore') pytest.raises(ValueError, read_raw_ctf, op.join(ctf_dir, ctf_fname_continuous), 'foo')
def plot_3d_montage(info, view_map, *, src_det_names='auto', ch_names='numbered', subject='fsaverage', trans='fsaverage', surface='pial', subjects_dir=None, verbose=None): """ Plot a 3D sensor montage. Parameters ---------- info : instance of Info Measurement info. view_map : dict Dict of view (key) to channel-pair-numbers (value) to use when plotting. Note that, because these get plotted as 1-based channel *numbers*, the values should be 1-based rather than 0-based. The keys are of the form: ``'{side}-{view}'`` For views like ``'left-lat'`` or ``'right-frontal'`` where the side matters. ``'{view}'`` For views like ``'caudal'`` that are along the midline. See :meth:`mne.viz.Brain.show_view` for ``view`` options, and the Examples section below for usage examples. src_det_names : None | dict | str Source and detector names to use. "auto" (default) will see if the channel locations correspond to standard 10-20 locations and will use those if they do (otherwise will act like None). None will use S1, S2, ..., D1, D2, ..., etc. Can also be an explicit dict mapping, for example:: src_det_names=dict(S1='Fz', D1='FCz', ...) ch_names : str | dict | None If ``'numbered'`` (default), use ``['1', '2', ...]`` for the channel names, or ``None`` to use ``['S1_D2', 'S2_D1', ...]``. Can also be a dict to provide a mapping from the ``'S1_D2'``-style names (keys) to other names, e.g., ``defaultdict(lambda: '')`` will prevent showing the names altogether. .. versionadded:: 0.3 subject : str The subject. trans : str | Transform The subjects head<->MRI transform. surface : str The FreeSurfer surface name (e.g., 'pial', 'white'). subjects_dir : str The subjects directory. %(verbose)s Returns ------- figure : matplotlib.figure.Figure The matplotlib figimage. Examples -------- For a Hitachi system with two sets of 12 source-detector arrangements, one on each side of the head, showing 1-12 on the left and 13-24 on the right can be accomplished using the following ``view_map``:: >>> view_map = { ... 'left-lat': np.arange(1, 13), ... 'right-lat': np.arange(13, 25), ... } NIRx typically involves more complicated arrangements. See :ref:`the 3D tutorial <tut-fnirs-vis-brain-plot-3d-montage>` for an advanced example that incorporates the ``'caudal'`` view as well. """ # noqa: E501 import matplotlib.pyplot as plt from scipy.spatial.distance import cdist _validate_type(info, Info, 'info') _validate_type(view_map, dict, 'views') _validate_type(src_det_names, (None, dict, str), 'src_det_names') _validate_type(ch_names, (dict, str, None), 'ch_names') info = pick_info(info, pick_types(info, fnirs=True, exclude=())[::2]) if isinstance(ch_names, str): _check_option('ch_names', ch_names, ('numbered', ), extra='when str') ch_names = { name.split()[0]: str(ni) for ni, name in enumerate(info['ch_names'], 1) } info['bads'] = [] if isinstance(src_det_names, str): _check_option('src_det_names', src_det_names, ('auto', ), extra='when str') # Decide if we can map to 10-20 locations names, pos = zip( *transform_to_head(make_standard_montage( 'standard_1020')).get_positions()['ch_pos'].items()) pos = np.array(pos, float) locs = dict() bad = False for ch in info['chs']: name = ch['ch_name'] s_name, d_name = name.split()[0].split('_') for name, loc in [(s_name, ch['loc'][3:6]), (d_name, ch['loc'][6:9])]: if name in locs: continue # see if it's close enough idx = np.where(cdist(loc[np.newaxis], pos)[0] < 1e-3)[0] if len(idx) < 1: bad = True break # Some are duplicated (e.g., T7+T3) but we can rely on the # first one being the canonical one locs[name] = names[idx[0]] if bad: break if bad: src_det_names = None logger.info('Could not automatically map source/detector names to ' '10-20 locations.') else: src_det_names = locs logger.info('Source-detector names automatically mapped to 10-20 ' 'locations') head_mri_t = _get_trans(trans, 'head', 'mri')[0] del trans views = list() for key, num in view_map.items(): _validate_type(key, str, f'view_map key {repr(key)}') _validate_type(num, np.ndarray, f'view_map[{repr(key)}]') if '-' in key: hemi, v = key.split('-', maxsplit=1) hemi = dict(left='lh', right='rh')[hemi] views.append((hemi, v, num)) else: views.append(('lh', key, num)) del view_map size = (400 * len(views), 400) brain = Brain(subject, 'both', surface, views=['lat'] * len(views), size=size, background='w', units='m', view_layout='horizontal', subjects_dir=subjects_dir) with _safe_brain_close(brain): brain.add_head(dense=False, alpha=0.1) brain.add_sensors(info, trans=head_mri_t, fnirs=['channels', 'pairs', 'sources', 'detectors']) add_text_kwargs = dict() if 'render' in _get_args(brain.plotter.add_text): add_text_kwargs['render'] = False for col, view in enumerate(views): plotted = set() brain.show_view(view[1], hemi=view[0], focalpoint=(0, -0.02, 0.02), distance=0.4, row=0, col=col) brain.plotter.subplot(0, col) vp = brain.plotter.renderer for ci in view[2]: # figure out what we need to plot this_ch = info['chs'][ci - 1] ch_name = this_ch['ch_name'].split()[0] s_name, d_name = ch_name.split('_') needed = [ (ch_names, 'ch_names', ch_name, this_ch['loc'][:3], 12, 'Centered'), (src_det_names, 'src_det_names', s_name, this_ch['loc'][3:6], 8, 'Bottom'), (src_det_names, 'src_det_names', d_name, this_ch['loc'][6:9], 8, 'Bottom'), ] for lookup, lname, name, ch_pos, font_size, va in needed: if name in plotted: continue plotted.add(name) orig_name = name if lookup is not None: name = lookup[name] _validate_type(name, str, f'{lname}[{repr(orig_name)}]') ch_pos = apply_trans(head_mri_t, ch_pos) vp.SetWorldPoint(np.r_[ch_pos, 1.]) vp.WorldToDisplay() ch_pos = (np.array(vp.GetDisplayPoint()[:2]) - np.array(vp.GetOrigin())) actor = brain.plotter.add_text(name, ch_pos, font_size=font_size, color=(0., 0., 0.), **add_text_kwargs) prop = actor.GetTextProperty() getattr(prop, f'SetVerticalJustificationTo{va}')() prop.SetJustificationToCentered() actor.SetTextProperty(prop) prop.SetBold(True) img = brain.screenshot() return plt.figimage(img, resize=True).figure
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 test_read_ctf(): """Test CTF reader""" temp_dir = _TempDir() out_fname = op.join(temp_dir, 'test_py_raw.fif') # Create a dummy .eeg file so we can test our reading/application of it os.mkdir(op.join(temp_dir, 'randpos')) ctf_eeg_fname = op.join(temp_dir, 'randpos', ctf_fname_catch) shutil.copytree(op.join(ctf_dir, ctf_fname_catch), ctf_eeg_fname) with warnings.catch_warnings(record=True) as w: # reclassified ch raw = _test_raw_reader(read_raw_ctf, directory=ctf_eeg_fname) assert_true(all('MISC channel' in str(ww.message) for ww in w)) picks = pick_types(raw.info, meg=False, eeg=True) pos = np.random.RandomState(42).randn(len(picks), 3) fake_eeg_fname = op.join(ctf_eeg_fname, 'catch-alp-good-f.eeg') # Create a bad file with open(fake_eeg_fname, 'wb') as fid: fid.write('foo\n'.encode('ascii')) assert_raises(RuntimeError, read_raw_ctf, ctf_eeg_fname) # Create a good file with open(fake_eeg_fname, 'wb') as fid: for ii, ch_num in enumerate(picks): args = (str(ch_num + 1), raw.ch_names[ch_num],) + tuple( '%0.5f' % x for x in 100 * pos[ii]) # convert to cm fid.write(('\t'.join(args) + '\n').encode('ascii')) pos_read_old = np.array([raw.info['chs'][p]['loc'][:3] for p in picks]) with warnings.catch_warnings(record=True) as w: # reclassified channel raw = read_raw_ctf(ctf_eeg_fname) # read modified data assert_true(all('MISC channel' in str(ww.message) for ww in w)) pos_read = np.array([raw.info['chs'][p]['loc'][:3] for p in picks]) assert_allclose(apply_trans(raw.info['ctf_head_t'], pos), pos_read, rtol=1e-5, atol=1e-5) assert_true((pos_read == pos_read_old).mean() < 0.1) shutil.copy(op.join(ctf_dir, 'catch-alp-good-f.ds_randpos_raw.fif'), op.join(temp_dir, 'randpos', 'catch-alp-good-f.ds_raw.fif')) # Create a version with no hc, starting out *with* EEG pos (error) os.mkdir(op.join(temp_dir, 'nohc')) ctf_no_hc_fname = op.join(temp_dir, 'no_hc', ctf_fname_catch) shutil.copytree(ctf_eeg_fname, ctf_no_hc_fname) remove_base = op.join(ctf_no_hc_fname, op.basename(ctf_fname_catch[:-3])) os.remove(remove_base + '.hc') with warnings.catch_warnings(record=True): # no coord tr assert_raises(RuntimeError, read_raw_ctf, ctf_no_hc_fname) os.remove(remove_base + '.eeg') shutil.copy(op.join(ctf_dir, 'catch-alp-good-f.ds_nohc_raw.fif'), op.join(temp_dir, 'no_hc', 'catch-alp-good-f.ds_raw.fif')) # All our files use_fnames = [op.join(ctf_dir, c) for c in ctf_fnames] for fname in use_fnames: raw_c = Raw(fname + '_raw.fif', add_eeg_ref=False, preload=True) with warnings.catch_warnings(record=True) as w: # reclassified ch raw = read_raw_ctf(fname) assert_true(all('MISC channel' in str(ww.message) for ww in w)) # check info match assert_array_equal(raw.ch_names, raw_c.ch_names) assert_allclose(raw.times, raw_c.times) assert_allclose(raw._cals, raw_c._cals) for key in ('version', 'usecs'): assert_equal(raw.info['meas_id'][key], raw_c.info['meas_id'][key]) py_time = raw.info['meas_id']['secs'] c_time = raw_c.info['meas_id']['secs'] max_offset = 24 * 60 * 60 # probably overkill but covers timezone assert_true(c_time - max_offset <= py_time <= c_time) for t in ('dev_head_t', 'dev_ctf_t', 'ctf_head_t'): assert_allclose(raw.info[t]['trans'], raw_c.info[t]['trans'], rtol=1e-4, atol=1e-7) for key in ('acq_pars', 'acq_stim', 'bads', 'ch_names', 'custom_ref_applied', 'description', 'events', 'experimenter', 'highpass', 'line_freq', 'lowpass', 'nchan', 'proj_id', 'proj_name', 'projs', 'sfreq', 'subject_info'): assert_equal(raw.info[key], raw_c.info[key], key) if op.basename(fname) not in single_trials: # We don't force buffer size to be smaller like MNE-C assert_equal(raw.info['buffer_size_sec'], raw_c.info['buffer_size_sec']) assert_equal(len(raw.info['comps']), len(raw_c.info['comps'])) for c1, c2 in zip(raw.info['comps'], raw_c.info['comps']): for key in ('colcals', 'rowcals'): assert_allclose(c1[key], c2[key]) assert_equal(c1['save_calibrated'], c2['save_calibrated']) for key in ('row_names', 'col_names', 'nrow', 'ncol'): assert_array_equal(c1['data'][key], c2['data'][key]) assert_allclose(c1['data']['data'], c2['data']['data'], atol=1e-7, rtol=1e-5) assert_allclose(raw.info['hpi_results'][0]['coord_trans']['trans'], raw_c.info['hpi_results'][0]['coord_trans']['trans'], rtol=1e-5, atol=1e-7) assert_equal(len(raw.info['chs']), len(raw_c.info['chs'])) for ii, (c1, c2) in enumerate(zip(raw.info['chs'], raw_c.info['chs'])): for key in ('kind', 'scanno', 'unit', 'ch_name', 'unit_mul', 'range', 'coord_frame', 'coil_type', 'logno'): if c1['ch_name'] == 'RMSP' and \ 'catch-alp-good-f' in fname and \ key in ('kind', 'unit', 'coord_frame', 'coil_type', 'logno'): continue # XXX see below... assert_equal(c1[key], c2[key], err_msg=key) for key in ('cal',): assert_allclose(c1[key], c2[key], atol=1e-6, rtol=1e-4, err_msg='raw.info["chs"][%d][%s]' % (ii, key)) # XXX 2016/02/24: fixed bug with normal computation that used # to exist, once mne-C tools are updated we should update our FIF # conversion files, then the slices can go away (and the check # can be combined with that for "cal") for key in ('loc',): if c1['ch_name'] == 'RMSP' and 'catch-alp-good-f' in fname: continue assert_allclose(c1[key][:3], c2[key][:3], atol=1e-6, rtol=1e-4, err_msg='raw.info["chs"][%d][%s]' % (ii, key)) assert_allclose(c1[key][9:12], c2[key][9:12], atol=1e-6, rtol=1e-4, err_msg='raw.info["chs"][%d][%s]' % (ii, key)) if fname.endswith('catch-alp-good-f.ds'): # omit points from .pos file raw.info['dig'] = raw.info['dig'][:-10] assert_dig_allclose(raw.info, raw_c.info) # check data match raw_c.save(out_fname, overwrite=True, buffer_size_sec=1.) raw_read = Raw(out_fname, add_eeg_ref=False) # so let's check tricky cases based on sample boundaries rng = np.random.RandomState(0) pick_ch = rng.permutation(np.arange(len(raw.ch_names)))[:10] bnd = int(round(raw.info['sfreq'] * raw.info['buffer_size_sec'])) assert_equal(bnd, raw._raw_extras[0]['block_size']) assert_equal(bnd, block_sizes[op.basename(fname)]) slices = (slice(0, bnd), slice(bnd - 1, bnd), slice(3, bnd), slice(3, 300), slice(None)) if len(raw.times) >= 2 * bnd: # at least two complete blocks slices = slices + (slice(bnd, 2 * bnd), slice(bnd, bnd + 1), slice(0, bnd + 100)) for sl_time in slices: assert_allclose(raw[pick_ch, sl_time][0], raw_c[pick_ch, sl_time][0]) assert_allclose(raw_read[pick_ch, sl_time][0], raw_c[pick_ch, sl_time][0]) # all data / preload with warnings.catch_warnings(record=True) as w: # reclassified ch raw = read_raw_ctf(fname, preload=True) assert_true(all('MISC channel' in str(ww.message) for ww in w)) assert_allclose(raw[:][0], raw_c[:][0]) assert_raises(TypeError, read_raw_ctf, 1) assert_raises(ValueError, read_raw_ctf, ctf_fname_continuous + 'foo.ds') # test ignoring of system clock read_raw_ctf(op.join(ctf_dir, ctf_fname_continuous), 'ignore') assert_raises(ValueError, read_raw_ctf, op.join(ctf_dir, ctf_fname_continuous), 'foo')
def get_head_mri_trans(bids_path, extra_params=None): """Produce transformation matrix from MEG and MRI landmark points. Will attempt to read the landmarks of Nasion, LPA, and RPA from the sidecar files of (i) the MEG and (ii) the T1 weighted MRI data. The two sets of points will then be used to calculate a transformation matrix from head coordinates to MRI coordinates. Parameters ---------- bids_path : mne_bids.BIDSPath The path of the recording for which to retrieve the transformation. The :class:`mne_bids.BIDSPath` instance passed here **must** have the ``.root`` attribute set. extra_params : None | dict Extra parameters to be passed to MNE read_raw_* functions when reading the lankmarks from the MEG file. If a dict, for example: ``extra_params=dict(allow_maxshield=True)``. Returns ------- trans : mne.transforms.Transform The data transformation matrix from head to MRI coordinates """ if not has_nibabel(): # pragma: no cover raise ImportError('This function requires nibabel.') import nibabel as nib if not isinstance(bids_path, BIDSPath): raise RuntimeError('"bids_path" must be a BIDSPath object. Please ' 'instantiate using mne_bids.BIDSPath().') # check root available bids_path = bids_path.copy() bids_root = bids_path.root if bids_root is None: raise ValueError('The root of the "bids_path" must be set. ' 'Please use `bids_path.update(root="<root>")` ' 'to set the root of the BIDS folder to read.') # only get this for MEG data bids_path.update(datatype='meg') # Get the sidecar file for MRI landmarks bids_fname = bids_path.update(suffix='meg', root=bids_root) t1w_json_path = _find_matching_sidecar(bids_fname, suffix='T1w', extension='.json') # Get MRI landmarks from the JSON sidecar with open(t1w_json_path, 'r', encoding='utf-8-sig') as f: t1w_json = json.load(f) mri_coords_dict = t1w_json.get('AnatomicalLandmarkCoordinates', dict()) mri_landmarks = np.asarray( (mri_coords_dict.get('LPA', np.nan), mri_coords_dict.get('NAS', np.nan), mri_coords_dict.get('RPA', np.nan))) if np.isnan(mri_landmarks).any(): raise RuntimeError( 'Could not parse T1w sidecar file: "{}"\n\n' 'The sidecar file MUST contain a key ' '"AnatomicalLandmarkCoordinates" pointing to a ' 'dict with keys "LPA", "NAS", "RPA". ' 'Yet, the following structure was found:\n\n"{}"'.format( t1w_json_path, t1w_json)) # The MRI landmarks are in "voxels". We need to convert the to the # neuromag RAS coordinate system in order to compare the with MEG landmarks # see also: `mne_bids.write.write_anat` t1w_path = t1w_json_path.replace('.json', '.nii') if not op.exists(t1w_path): t1w_path += '.gz' # perhaps it is .nii.gz? ... else raise an error if not op.exists(t1w_path): raise RuntimeError( 'Could not find the T1 weighted MRI associated ' 'with "{}". Tried: "{}" but it does not exist.'.format( t1w_json_path, t1w_path)) t1_nifti = nib.load(t1w_path) # Convert to MGH format to access vox2ras method t1_mgh = nib.MGHImage(t1_nifti.dataobj, t1_nifti.affine) # now extract transformation matrix and put back to RAS coordinates of MRI vox2ras_tkr = t1_mgh.header.get_vox2ras_tkr() mri_landmarks = apply_trans(vox2ras_tkr, mri_landmarks) mri_landmarks = mri_landmarks * 1e-3 # Get MEG landmarks from the raw file _, ext = _parse_ext(bids_fname) if extra_params is None: extra_params = dict() if ext == '.fif': extra_params = dict(allow_maxshield=True) raw = read_raw_bids(bids_path=bids_path, extra_params=extra_params) meg_coords_dict = _extract_landmarks(raw.info['dig']) meg_landmarks = np.asarray((meg_coords_dict['LPA'], meg_coords_dict['NAS'], meg_coords_dict['RPA'])) # Given the two sets of points, fit the transform trans_fitted = fit_matched_points(src_pts=meg_landmarks, tgt_pts=mri_landmarks) trans = mne.transforms.Transform(fro='head', to='mri', trans=trans_fitted) return trans
def test_montage(): """Test making montages.""" tempdir = _TempDir() inputs = dict( sfp="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", csd="// MatLab Sphere coordinates [degrees] Cartesian coordinates\n" # noqa: E501 "// Label Theta Phi Radius X Y Z off sphere surface\n" # noqa: E501 "E1 37.700 -14.000 1.000 0.7677 0.5934 -0.2419 -0.00000000000000011\n" # noqa: E501 "E3 51.700 11.000 1.000 0.6084 0.7704 0.1908 0.00000000000000000\n" # noqa: E501 "E31 90.000 -11.000 1.000 0.0000 0.9816 -0.1908 0.00000000000000000\n" # noqa: E501 "E61 158.000 -17.200 1.000 -0.8857 0.3579 -0.2957 -0.00000000000000022", # noqa: E501 mm_elc="# ASA electrode file\nReferenceLabel avg\nUnitPosition mm\n" # noqa:E501 "NumberPositions= 68\n" "Positions\n" "-86.0761 -19.9897 -47.9860\n" "85.7939 -20.0093 -48.0310\n" "0.0083 86.8110 -39.9830\n" "-86.0761 -24.9897 -67.9860\n" "Labels\nLPA\nRPA\nNz\nDummy\n", m_elc="# ASA electrode file\nReferenceLabel avg\nUnitPosition m\n" "NumberPositions= 68\nPositions\n-.0860761 -.0199897 -.0479860\n" # noqa:E501 ".0857939 -.0200093 -.0480310\n.0000083 .00868110 -.0399830\n" ".08 -.02 -.04\n" "Labels\nLPA\nRPA\nNz\nDummy\n", txt="Site Theta Phi\n" "Fp1 -92 -72\n" "Fp2 92 72\n" "very_very_very_long_name -92 72\n" "O2 92 -90\n", elp="346\n" "EEG\t F3\t -62.027\t -50.053\t 85\n" "EEG\t Fz\t 45.608\t 90\t 85\n" "EEG\t F4\t 62.01\t 50.103\t 85\n" "EEG\t FCz\t 68.01\t 58.103\t 85\n", hpts="eeg Fp1 -95.0 -3. -3.\n" "eeg AF7 -1 -1 -3\n" "eeg A3 -2 -2 2\n" "eeg A 0 0 0", ) # Get actual positions and save them for checking # csd comes from the string above, all others come from commit 2fa35d4 poss = dict( sfp=[[0.0, 9.07159, -2.35975], [-6.71176, 0.0404, -3.2516], [-5.83124, -4.49482, 4.95535], [0.0, 0.0, 8.89919]], mm_elc=[ [-0.08608, -0.01999, -0.04799], [0.08579, -0.02001, -0.04803], [1e-05, 0.08681, -0.03998], [-0.08608, -0.02499, -0.06799], ], m_elc=[ [-0.08608, -0.01999, -0.04799], [0.08579, -0.02001, -0.04803], [1e-05, 0.00868, -0.03998], [0.08, -0.02, -0.04], ], txt=[ [-26.25044, 80.79056, -2.96646], [26.25044, 80.79056, -2.96646], [-26.25044, -80.79056, -2.96646], [0.0, -84.94822, -2.96646], ], elp=[ [-48.20043, 57.55106, 39.86971], [0.0, 60.73848, 59.4629], [48.1426, 57.58403, 39.89198], [41.64599, 66.91489, 31.8278], ], hpts=[[-95, -3, -3], [-1, -1.0, -3.0], [-2, -2, 2.0], [0, 0, 0]], ) for key, text in inputs.items(): kind = key.split("_")[-1] fname = op.join(tempdir, "test." + kind) with open(fname, "w") as fid: fid.write(text) montage = read_montage(fname) if kind in ("sfp", "txt"): assert_true("very_very_very_long_name" in montage.ch_names) assert_equal(len(montage.ch_names), 4) assert_equal(len(montage.ch_names), len(montage.pos)) assert_equal(montage.pos.shape, (4, 3)) assert_equal(montage.kind, "test") if kind == "csd": dtype = [ ("label", "S4"), ("theta", "f8"), ("phi", "f8"), ("radius", "f8"), ("x", "f8"), ("y", "f8"), ("z", "f8"), ("off_sph", "f8"), ] try: table = np.loadtxt(fname, skip_header=2, dtype=dtype) except TypeError: table = np.loadtxt(fname, skiprows=2, dtype=dtype) poss["csd"] = np.c_[table["x"], table["y"], table["z"]] if kind == "elc": # Make sure points are reasonable distance from geometric centroid centroid = np.sum(montage.pos, axis=0) / montage.pos.shape[0] distance_from_centroid = np.apply_along_axis(np.linalg.norm, 1, montage.pos - centroid) assert_array_less(distance_from_centroid, 0.2) assert_array_less(0.01, distance_from_centroid) assert_array_almost_equal(poss[key], montage.pos, 4, err_msg=key) # Test reading in different letter case. ch_names = [ "F3", "FZ", "F4", "FC3", "FCz", "FC4", "C3", "CZ", "C4", "CP3", "CPZ", "CP4", "P3", "PZ", "P4", "O1", "OZ", "O2", ] montage = read_montage("standard_1020", ch_names=ch_names) assert_array_equal(ch_names, montage.ch_names) # test transform input_str = """ eeg Fp1 -95.0 -31.0 -3.0 eeg AF7 -81 -59 -3 eeg AF3 -87 -41 28 cardinal 2 -91 0 -42 cardinal 1 0 -91 -42 cardinal 3 0 91 -42 """ fname = op.join(tempdir, "test_fid.hpts") with open(fname, "w") as fid: fid.write(input_str) montage = read_montage(op.join(tempdir, "test_fid.hpts"), transform=True) # check coordinate transformation pos = np.array([-95.0, -31.0, -3.0]) nasion = np.array([-91, 0, -42]) lpa = np.array([0, -91, -42]) rpa = np.array([0, 91, -42]) fids = np.vstack((nasion, lpa, rpa)) trans = get_ras_to_neuromag_trans(fids[0], fids[1], fids[2]) pos = apply_trans(trans, pos) assert_array_equal(montage.pos[0], pos) idx = montage.ch_names.index("2") assert_array_equal(montage.pos[idx, [0, 2]], [0, 0]) idx = montage.ch_names.index("1") assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) idx = montage.ch_names.index("3") assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) pos = np.array([-95.0, -31.0, -3.0]) montage_fname = op.join(tempdir, "test_fid.hpts") montage = read_montage(montage_fname, unit="mm") assert_array_equal(montage.pos[0], pos * 1e-3) # test with last info = create_info(montage.ch_names, 1e3, ["eeg"] * len(montage.ch_names)) _set_montage(info, montage) pos2 = np.array([c["loc"][:3] for c in info["chs"]]) assert_array_equal(pos2, montage.pos) assert_equal(montage.ch_names, info["ch_names"]) info = create_info(montage.ch_names, 1e3, ["eeg"] * len(montage.ch_names)) evoked = EvokedArray(data=np.zeros((len(montage.ch_names), 1)), info=info, tmin=0) evoked.set_montage(montage) pos3 = np.array([c["loc"][:3] for c in evoked.info["chs"]]) assert_array_equal(pos3, montage.pos) assert_equal(montage.ch_names, evoked.info["ch_names"]) # Warning should be raised when some EEG are not specified in the montage with warnings.catch_warnings(record=True) as w: info = create_info(montage.ch_names + ["foo", "bar"], 1e3, ["eeg"] * (len(montage.ch_names) + 2)) _set_montage(info, montage) assert_true(len(w) == 1)
def get_head_mri_trans(bids_fname, bids_root): """Produce transformation matrix from MEG and MRI landmark points. Will attempt to read the landmarks of Nasion, LPA, and RPA from the sidecar files of (i) the MEG and (ii) the T1 weighted MRI data. The two sets of points will then be used to calculate a transformation matrix from head coordinates to MRI coordinates. Parameters ---------- bids_fname : str Full name of the MEG data file bids_root : str Path to root of the BIDS folder Returns ------- trans : instance of mne.transforms.Transform The data transformation matrix from head to MRI coordinates """ if not has_nibabel(): # pragma: no cover raise ImportError('This function requires nibabel.') import nibabel as nib # Get the sidecar file for MRI landmarks bids_fname = op.basename(bids_fname) t1w_json_path = _find_matching_sidecar(bids_fname, bids_root, 'T1w.json') # Get MRI landmarks from the JSON sidecar with open(t1w_json_path, 'r') as f: t1w_json = json.load(f) mri_coords_dict = t1w_json.get('AnatomicalLandmarkCoordinates', dict()) mri_landmarks = np.asarray( (mri_coords_dict.get('LPA', np.nan), mri_coords_dict.get('NAS', np.nan), mri_coords_dict.get('RPA', np.nan))) if np.isnan(mri_landmarks).any(): raise RuntimeError( 'Could not parse T1w sidecar file: "{}"\n\n' 'The sidecar file MUST contain a key ' '"AnatomicalLandmarkCoordinates" pointing to a ' 'dict with keys "LPA", "NAS", "RPA". ' 'Yet, the following structure was found:\n\n"{}"'.format( t1w_json_path, t1w_json)) # The MRI landmarks are in "voxels". We need to convert the to the # neuromag RAS coordinate system in order to compare the with MEG landmarks # see also: `mne_bids.write.write_anat` t1w_path = t1w_json_path.replace('.json', '.nii') if not op.exists(t1w_path): t1w_path += '.gz' # perhaps it is .nii.gz? ... else raise an error if not op.exists(t1w_path): raise RuntimeError( 'Could not find the T1 weighted MRI associated ' 'with "{}". Tried: "{}" but it does not exist.'.format( t1w_json_path, t1w_path)) t1_nifti = nib.load(t1w_path) # Convert to MGH format to access vox2ras method t1_mgh = nib.MGHImage(t1_nifti.dataobj, t1_nifti.affine) # now extract transformation matrix and put back to RAS coordinates of MRI vox2ras_tkr = t1_mgh.header.get_vox2ras_tkr() mri_landmarks = apply_trans(vox2ras_tkr, mri_landmarks) mri_landmarks = mri_landmarks * 1e-3 # Get MEG landmarks from the raw file raw = read_raw_bids(bids_fname, bids_root) meg_coords_dict = _extract_landmarks(raw.info['dig']) meg_landmarks = np.asarray((meg_coords_dict['LPA'], meg_coords_dict['NAS'], meg_coords_dict['RPA'])) # Given the two sets of points, fit the transform trans_fitted = fit_matched_points(src_pts=meg_landmarks, tgt_pts=mri_landmarks) trans = mne.transforms.Transform(fro='head', to='mri', trans=trans_fitted) return trans
def test_scale_mri(): """Test creating fsaverage and scaling it""" # create fsaverage tempdir = _TempDir() create_default_subject(subjects_dir=tempdir) assert_true(_is_mri_subject('fsaverage', tempdir), "Creating fsaverage failed") fid_path = os.path.join(tempdir, 'fsaverage', 'bem', 'fsaverage-fiducials.fif') os.remove(fid_path) create_default_subject(update=True, subjects_dir=tempdir) assert_true(os.path.exists(fid_path), "Updating fsaverage") # copy MRI file from sample data path = os.path.join('%s', 'fsaverage', 'mri', 'orig.mgz') sample_sdir = os.path.join(mne.datasets.sample.data_path(), 'subjects') copyfile(path % sample_sdir, path % tempdir) # remove redundant label files label_temp = os.path.join(tempdir, 'fsaverage', 'label', '*.label') label_paths = glob(label_temp) for label_path in label_paths[1:]: os.remove(label_path) # create source space path = os.path.join(tempdir, 'fsaverage', 'bem', 'fsaverage-%s-src.fif') src = mne.setup_source_space('fsaverage', 'ico0', subjects_dir=tempdir, add_dist=False) write_source_spaces(path % 'ico-0', src) mri = os.path.join(tempdir, 'fsaverage', 'mri', 'orig.mgz') vsrc = mne.setup_volume_source_space('fsaverage', pos=50, mri=mri, subjects_dir=tempdir, add_interpolator=False) write_source_spaces(path % 'vol-50', vsrc) # scale fsaverage os.environ['_MNE_FEW_SURFACES'] = 'true' scale = np.array([1, .2, .8]) scale_mri('fsaverage', 'flachkopf', scale, True, subjects_dir=tempdir) del os.environ['_MNE_FEW_SURFACES'] assert_true(_is_mri_subject('flachkopf', tempdir), "Scaling fsaverage failed") spath = os.path.join(tempdir, 'flachkopf', 'bem', 'flachkopf-%s-src.fif') assert_true(os.path.exists(spath % 'ico-0'), "Source space ico-0 was not scaled") vsrc_s = mne.read_source_spaces(spath % 'vol-50') pt = np.array([0.12, 0.41, -0.22]) assert_array_almost_equal(apply_trans(vsrc_s[0]['src_mri_t'], pt * scale), apply_trans(vsrc[0]['src_mri_t'], pt)) scale_labels('flachkopf', subjects_dir=tempdir) # add distances to source space mne.add_source_space_distances(src) src.save(path % 'ico-0', overwrite=True) # scale with distances os.remove(spath % 'ico-0') scale_source_space('flachkopf', 'ico-0', subjects_dir=tempdir) ssrc = mne.read_source_spaces(spath % 'ico-0') assert_is_not(ssrc[0]['dist'], None)
def test_read_ctf(): """Test CTF reader""" temp_dir = _TempDir() out_fname = op.join(temp_dir, 'test_py_raw.fif') # Create a dummy .eeg file so we can test our reading/application of it os.mkdir(op.join(temp_dir, 'randpos')) ctf_eeg_fname = op.join(temp_dir, 'randpos', ctf_fname_catch) shutil.copytree(op.join(ctf_dir, ctf_fname_catch), ctf_eeg_fname) with warnings.catch_warnings(record=True) as w: # reclassified ch raw = _test_raw_reader(read_raw_ctf, directory=ctf_eeg_fname) assert_true(all('MISC channel' in str(ww.message) for ww in w)) picks = pick_types(raw.info, meg=False, eeg=True) pos = np.random.RandomState(42).randn(len(picks), 3) fake_eeg_fname = op.join(ctf_eeg_fname, 'catch-alp-good-f.eeg') # Create a bad file with open(fake_eeg_fname, 'wb') as fid: fid.write('foo\n'.encode('ascii')) assert_raises(RuntimeError, read_raw_ctf, ctf_eeg_fname) # Create a good file with open(fake_eeg_fname, 'wb') as fid: for ii, ch_num in enumerate(picks): args = (str(ch_num + 1), raw.ch_names[ch_num],) + tuple( '%0.5f' % x for x in 100 * pos[ii]) # convert to cm fid.write(('\t'.join(args) + '\n').encode('ascii')) pos_read_old = np.array([raw.info['chs'][p]['loc'][:3] for p in picks]) with warnings.catch_warnings(record=True) as w: # reclassified channel raw = read_raw_ctf(ctf_eeg_fname) # read modified data assert_true(all('MISC channel' in str(ww.message) for ww in w)) pos_read = np.array([raw.info['chs'][p]['loc'][:3] for p in picks]) assert_allclose(apply_trans(raw.info['ctf_head_t'], pos), pos_read, rtol=1e-5, atol=1e-5) assert_true((pos_read == pos_read_old).mean() < 0.1) shutil.copy(op.join(ctf_dir, 'catch-alp-good-f.ds_randpos_raw.fif'), op.join(temp_dir, 'randpos', 'catch-alp-good-f.ds_raw.fif')) # Create a version with no hc, starting out *with* EEG pos (error) os.mkdir(op.join(temp_dir, 'nohc')) ctf_no_hc_fname = op.join(temp_dir, 'no_hc', ctf_fname_catch) shutil.copytree(ctf_eeg_fname, ctf_no_hc_fname) remove_base = op.join(ctf_no_hc_fname, op.basename(ctf_fname_catch[:-3])) os.remove(remove_base + '.hc') with warnings.catch_warnings(record=True): # no coord tr assert_raises(RuntimeError, read_raw_ctf, ctf_no_hc_fname) os.remove(remove_base + '.eeg') shutil.copy(op.join(ctf_dir, 'catch-alp-good-f.ds_nohc_raw.fif'), op.join(temp_dir, 'no_hc', 'catch-alp-good-f.ds_raw.fif')) # All our files use_fnames = [op.join(ctf_dir, c) for c in ctf_fnames] for fname in use_fnames: raw_c = read_raw_fif(fname + '_raw.fif', preload=True) with warnings.catch_warnings(record=True) as w: # reclassified ch raw = read_raw_ctf(fname) assert_true(all('MISC channel' in str(ww.message) for ww in w)) # check info match assert_array_equal(raw.ch_names, raw_c.ch_names) assert_allclose(raw.times, raw_c.times) assert_allclose(raw._cals, raw_c._cals) for key in ('version', 'usecs'): assert_equal(raw.info['meas_id'][key], raw_c.info['meas_id'][key]) py_time = raw.info['meas_id']['secs'] c_time = raw_c.info['meas_id']['secs'] max_offset = 24 * 60 * 60 # probably overkill but covers timezone assert_true(c_time - max_offset <= py_time <= c_time) for t in ('dev_head_t', 'dev_ctf_t', 'ctf_head_t'): assert_allclose(raw.info[t]['trans'], raw_c.info[t]['trans'], rtol=1e-4, atol=1e-7) for key in ('acq_pars', 'acq_stim', 'bads', 'ch_names', 'custom_ref_applied', 'description', 'events', 'experimenter', 'highpass', 'line_freq', 'lowpass', 'nchan', 'proj_id', 'proj_name', 'projs', 'sfreq', 'subject_info'): assert_equal(raw.info[key], raw_c.info[key], key) if op.basename(fname) not in single_trials: # We don't force buffer size to be smaller like MNE-C assert_equal(raw.info['buffer_size_sec'], raw_c.info['buffer_size_sec']) assert_equal(len(raw.info['comps']), len(raw_c.info['comps'])) for c1, c2 in zip(raw.info['comps'], raw_c.info['comps']): for key in ('colcals', 'rowcals'): assert_allclose(c1[key], c2[key]) assert_equal(c1['save_calibrated'], c2['save_calibrated']) for key in ('row_names', 'col_names', 'nrow', 'ncol'): assert_array_equal(c1['data'][key], c2['data'][key]) assert_allclose(c1['data']['data'], c2['data']['data'], atol=1e-7, rtol=1e-5) assert_allclose(raw.info['hpi_results'][0]['coord_trans']['trans'], raw_c.info['hpi_results'][0]['coord_trans']['trans'], rtol=1e-5, atol=1e-7) assert_equal(len(raw.info['chs']), len(raw_c.info['chs'])) for ii, (c1, c2) in enumerate(zip(raw.info['chs'], raw_c.info['chs'])): for key in ('kind', 'scanno', 'unit', 'ch_name', 'unit_mul', 'range', 'coord_frame', 'coil_type', 'logno'): if c1['ch_name'] == 'RMSP' and \ 'catch-alp-good-f' in fname and \ key in ('kind', 'unit', 'coord_frame', 'coil_type', 'logno'): continue # XXX see below... assert_equal(c1[key], c2[key], err_msg=key) for key in ('cal',): assert_allclose(c1[key], c2[key], atol=1e-6, rtol=1e-4, err_msg='raw.info["chs"][%d][%s]' % (ii, key)) # XXX 2016/02/24: fixed bug with normal computation that used # to exist, once mne-C tools are updated we should update our FIF # conversion files, then the slices can go away (and the check # can be combined with that for "cal") for key in ('loc',): if c1['ch_name'] == 'RMSP' and 'catch-alp-good-f' in fname: continue assert_allclose(c1[key][:3], c2[key][:3], atol=1e-6, rtol=1e-4, err_msg='raw.info["chs"][%d][%s]' % (ii, key)) assert_allclose(c1[key][9:12], c2[key][9:12], atol=1e-6, rtol=1e-4, err_msg='raw.info["chs"][%d][%s]' % (ii, key)) if fname.endswith('catch-alp-good-f.ds'): # omit points from .pos file raw.info['dig'] = raw.info['dig'][:-10] # XXX: Next test would fail because c-tools assign the fiducials from # CTF data as HPI. Should eventually clarify/unify with Matti. # assert_dig_allclose(raw.info, raw_c.info) # check data match raw_c.save(out_fname, overwrite=True, buffer_size_sec=1.) raw_read = read_raw_fif(out_fname) # so let's check tricky cases based on sample boundaries rng = np.random.RandomState(0) pick_ch = rng.permutation(np.arange(len(raw.ch_names)))[:10] bnd = int(round(raw.info['sfreq'] * raw.info['buffer_size_sec'])) assert_equal(bnd, raw._raw_extras[0]['block_size']) assert_equal(bnd, block_sizes[op.basename(fname)]) slices = (slice(0, bnd), slice(bnd - 1, bnd), slice(3, bnd), slice(3, 300), slice(None)) if len(raw.times) >= 2 * bnd: # at least two complete blocks slices = slices + (slice(bnd, 2 * bnd), slice(bnd, bnd + 1), slice(0, bnd + 100)) for sl_time in slices: assert_allclose(raw[pick_ch, sl_time][0], raw_c[pick_ch, sl_time][0]) assert_allclose(raw_read[pick_ch, sl_time][0], raw_c[pick_ch, sl_time][0]) # all data / preload with warnings.catch_warnings(record=True) as w: # reclassified ch raw = read_raw_ctf(fname, preload=True) assert_true(all('MISC channel' in str(ww.message) for ww in w)) assert_allclose(raw[:][0], raw_c[:][0]) assert_raises(TypeError, read_raw_ctf, 1) assert_raises(ValueError, read_raw_ctf, ctf_fname_continuous + 'foo.ds') # test ignoring of system clock read_raw_ctf(op.join(ctf_dir, ctf_fname_continuous), 'ignore') assert_raises(ValueError, read_raw_ctf, op.join(ctf_dir, ctf_fname_continuous), 'foo')
def test_montage(): """Test making montages.""" tempdir = _TempDir() inputs = dict( sfp='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', csd='// MatLab Sphere coordinates [degrees] Cartesian coordinates\n' # noqa: E501 '// Label Theta Phi Radius X Y Z off sphere surface\n' # noqa: E501 'E1 37.700 -14.000 1.000 0.7677 0.5934 -0.2419 -0.00000000000000011\n' # noqa: E501 'E3 51.700 11.000 1.000 0.6084 0.7704 0.1908 0.00000000000000000\n' # noqa: E501 'E31 90.000 -11.000 1.000 0.0000 0.9816 -0.1908 0.00000000000000000\n' # noqa: E501 'E61 158.000 -17.200 1.000 -0.8857 0.3579 -0.2957 -0.00000000000000022', # noqa: E501 mm_elc='# ASA electrode file\nReferenceLabel avg\nUnitPosition mm\n' # noqa:E501 'NumberPositions= 68\n' 'Positions\n' '-86.0761 -19.9897 -47.9860\n' '85.7939 -20.0093 -48.0310\n' '0.0083 86.8110 -39.9830\n' '-86.0761 -24.9897 -67.9860\n' 'Labels\nLPA\nRPA\nNz\nDummy\n', m_elc='# ASA electrode file\nReferenceLabel avg\nUnitPosition m\n' 'NumberPositions= 68\nPositions\n-.0860761 -.0199897 -.0479860\n' # noqa:E501 '.0857939 -.0200093 -.0480310\n.0000083 .00868110 -.0399830\n' '.08 -.02 -.04\n' 'Labels\nLPA\nRPA\nNz\nDummy\n', txt='Site Theta Phi\n' 'Fp1 -92 -72\n' 'Fp2 92 72\n' 'very_very_very_long_name -92 72\n' 'O2 92 -90\n', elp='346\n' 'EEG\t F3\t -62.027\t -50.053\t 85\n' 'EEG\t Fz\t 45.608\t 90\t 85\n' 'EEG\t F4\t 62.01\t 50.103\t 85\n' 'EEG\t FCz\t 68.01\t 58.103\t 85\n', hpts='eeg Fp1 -95.0 -3. -3.\n' 'eeg AF7 -1 -1 -3\n' 'eeg A3 -2 -2 2\n' 'eeg A 0 0 0', bvef='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' '<!-- Generated by EasyCap Configurator 19.05.2014 -->\n' '<Electrodes defaults="false">\n' ' <Electrode>\n' ' <Name>Fp1</Name>\n' ' <Theta>-90</Theta>\n' ' <Phi>-72</Phi>\n' ' <Radius>1</Radius>\n' ' <Number>1</Number>\n' ' </Electrode>\n' ' <Electrode>\n' ' <Name>Fz</Name>\n' ' <Theta>45</Theta>\n' ' <Phi>90</Phi>\n' ' <Radius>1</Radius>\n' ' <Number>2</Number>\n' ' </Electrode>\n' ' <Electrode>\n' ' <Name>F3</Name>\n' ' <Theta>-60</Theta>\n' ' <Phi>-51</Phi>\n' ' <Radius>1</Radius>\n' ' <Number>3</Number>\n' ' </Electrode>\n' ' <Electrode>\n' ' <Name>F7</Name>\n' ' <Theta>-90</Theta>\n' ' <Phi>-36</Phi>\n' ' <Radius>1</Radius>\n' ' <Number>4</Number>\n' ' </Electrode>\n' '</Electrodes>', ) # Get actual positions and save them for checking # csd comes from the string above, all others come from commit 2fa35d4 poss = dict( sfp=[[0.0, 9.07159, -2.35975], [-6.71176, 0.0404, -3.2516], [-5.83124, -4.49482, 4.95535], [0.0, 0.0, 8.89919]], mm_elc=[[-0.08608, -0.01999, -0.04799], [0.08579, -0.02001, -0.04803], [1e-05, 0.08681, -0.03998], [-0.08608, -0.02499, -0.06799]], m_elc=[[-0.08608, -0.01999, -0.04799], [0.08579, -0.02001, -0.04803], [1e-05, 0.00868, -0.03998], [0.08, -0.02, -0.04]], txt=[[-26.25044, 80.79056, -2.96646], [26.25044, 80.79056, -2.96646], [-26.25044, -80.79056, -2.96646], [0.0, -84.94822, -2.96646]], elp=[[-48.20043, 57.55106, 39.86971], [0.0, 60.73848, 59.4629], [48.1426, 57.58403, 39.89198], [41.64599, 66.91489, 31.8278]], hpts=[[-95, -3, -3], [-1, -1., -3.], [-2, -2, 2.], [0, 0, 0]], bvef=[[-26.266444, 80.839803, 5.204748e-15], [3.680313e-15, 60.104076, 60.104076], [-46.325632, 57.207392, 42.500000], [-68.766444, 49.961746, 5.204748e-15]], ) for key, text in inputs.items(): kind = key.split('_')[-1] fname = op.join(tempdir, 'test.' + kind) with open(fname, 'w') as fid: fid.write(text) montage = read_montage(fname) if kind in ('sfp', 'txt'): assert ('very_very_very_long_name' in montage.ch_names) assert_equal(len(montage.ch_names), 4) assert_equal(len(montage.ch_names), len(montage.pos)) assert_equal(montage.pos.shape, (4, 3)) assert_equal(montage.kind, 'test') if kind == 'csd': dtype = [('label', 'S4'), ('theta', 'f8'), ('phi', 'f8'), ('radius', 'f8'), ('x', 'f8'), ('y', 'f8'), ('z', 'f8'), ('off_sph', 'f8')] try: table = np.loadtxt(fname, skip_header=2, dtype=dtype) except TypeError: table = np.loadtxt(fname, skiprows=2, dtype=dtype) poss['csd'] = np.c_[table['x'], table['y'], table['z']] if kind == 'elc': # Make sure points are reasonable distance from geometric centroid centroid = np.sum(montage.pos, axis=0) / montage.pos.shape[0] distance_from_centroid = np.apply_along_axis( np.linalg.norm, 1, montage.pos - centroid) assert_array_less(distance_from_centroid, 0.2) assert_array_less(0.01, distance_from_centroid) assert_array_almost_equal(poss[key], montage.pos, 4, err_msg=key) # Test reading in different letter case. ch_names = ["F3", "FZ", "F4", "FC3", "FCz", "FC4", "C3", "CZ", "C4", "CP3", "CPZ", "CP4", "P3", "PZ", "P4", "O1", "OZ", "O2"] montage = read_montage('standard_1020', ch_names=ch_names) assert_array_equal(ch_names, montage.ch_names) # test transform input_strs = [""" eeg Fp1 -95.0 -31.0 -3.0 eeg AF7 -81 -59 -3 eeg AF3 -87 -41 28 cardinal 2 -91 0 -42 cardinal 1 0 -91 -42 cardinal 3 0 91 -42 """, """ Fp1 -95.0 -31.0 -3.0 AF7 -81 -59 -3 AF3 -87 -41 28 FidNz -91 0 -42 FidT9 0 -91 -42 FidT10 0 91 -42 """] # sfp files seem to have Nz, T9, and T10 as fiducials: # https://github.com/mne-tools/mne-python/pull/4482#issuecomment-321980611 kinds = ['test_fid.hpts', 'test_fid.sfp'] for kind, input_str in zip(kinds, input_strs): fname = op.join(tempdir, kind) with open(fname, 'w') as fid: fid.write(input_str) montage = read_montage(op.join(tempdir, kind), transform=True) # check coordinate transformation pos = np.array([-95.0, -31.0, -3.0]) nasion = np.array([-91, 0, -42]) lpa = np.array([0, -91, -42]) rpa = np.array([0, 91, -42]) fids = np.vstack((nasion, lpa, rpa)) trans = get_ras_to_neuromag_trans(fids[0], fids[1], fids[2]) pos = apply_trans(trans, pos) assert_array_equal(montage.pos[0], pos) assert_array_equal(montage.nasion[[0, 2]], [0, 0]) assert_array_equal(montage.lpa[[1, 2]], [0, 0]) assert_array_equal(montage.rpa[[1, 2]], [0, 0]) pos = np.array([-95.0, -31.0, -3.0]) montage_fname = op.join(tempdir, kind) montage = read_montage(montage_fname, unit='mm') assert_array_equal(montage.pos[0], pos * 1e-3) # test with last info = create_info(montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) _set_montage(info, montage) pos2 = np.array([c['loc'][:3] for c in info['chs']]) assert_array_equal(pos2, montage.pos) assert_equal(montage.ch_names, info['ch_names']) info = create_info( montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) evoked = EvokedArray( data=np.zeros((len(montage.ch_names), 1)), info=info, tmin=0) # test return type as well as set montage assert (isinstance(evoked.set_montage(montage), type(evoked))) pos3 = np.array([c['loc'][:3] for c in evoked.info['chs']]) assert_array_equal(pos3, montage.pos) assert_equal(montage.ch_names, evoked.info['ch_names']) # Warning should be raised when some EEG are not specified in montage info = create_info(montage.ch_names + ['foo', 'bar'], 1e3, ['eeg'] * (len(montage.ch_names) + 2)) with pytest.warns(RuntimeWarning, match='position specified'): _set_montage(info, montage) # Channel names can be treated case insensitive info = create_info(['FP1', 'af7', 'AF3'], 1e3, ['eeg'] * 3) _set_montage(info, montage) # Unless there is a collision in names info = create_info(['FP1', 'Fp1', 'AF3'], 1e3, ['eeg'] * 3) assert (info['dig'] is None) with pytest.warns(RuntimeWarning, match='position specified'): _set_montage(info, montage) assert len(info['dig']) == 5 # 2 EEG w/pos, 3 fiducials montage.ch_names = ['FP1', 'Fp1', 'AF3'] info = create_info(['fp1', 'AF3'], 1e3, ['eeg', 'eeg']) assert (info['dig'] is None) with pytest.warns(RuntimeWarning, match='position specified'): _set_montage(info, montage, set_dig=False) assert (info['dig'] is None) # test get_pos2d method montage = read_montage("standard_1020") c3 = montage.get_pos2d()[montage.ch_names.index("C3")] c4 = montage.get_pos2d()[montage.ch_names.index("C4")] fz = montage.get_pos2d()[montage.ch_names.index("Fz")] oz = montage.get_pos2d()[montage.ch_names.index("Oz")] f1 = montage.get_pos2d()[montage.ch_names.index("F1")] assert (c3[0] < 0) # left hemisphere assert (c4[0] > 0) # right hemisphere assert (fz[1] > 0) # frontal assert (oz[1] < 0) # occipital assert_allclose(fz[0], 0, atol=1e-2) # midline assert_allclose(oz[0], 0, atol=1e-2) # midline assert (f1[0] < 0 and f1[1] > 0) # left frontal # test get_builtin_montages function montages = get_builtin_montages() assert (len(montages) > 0) # MNE should always ship with montages assert ("standard_1020" in montages) # 10/20 montage assert ("standard_1005" in montages) # 10/05 montage
def test_montage(): """Test making montages.""" tempdir = _TempDir() inputs = dict( sfp='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', csd='// MatLab Sphere coordinates [degrees] Cartesian coordinates\n' # noqa: E501 '// Label Theta Phi Radius X Y Z off sphere surface\n' # noqa: E501 'E1 37.700 -14.000 1.000 0.7677 0.5934 -0.2419 -0.00000000000000011\n' # noqa: E501 'E3 51.700 11.000 1.000 0.6084 0.7704 0.1908 0.00000000000000000\n' # noqa: E501 'E31 90.000 -11.000 1.000 0.0000 0.9816 -0.1908 0.00000000000000000\n' # noqa: E501 'E61 158.000 -17.200 1.000 -0.8857 0.3579 -0.2957 -0.00000000000000022', # noqa: E501 mm_elc='# ASA electrode file\nReferenceLabel avg\nUnitPosition mm\n' # noqa:E501 'NumberPositions= 68\n' 'Positions\n' '-86.0761 -19.9897 -47.9860\n' '85.7939 -20.0093 -48.0310\n' '0.0083 86.8110 -39.9830\n' '-86.0761 -24.9897 -67.9860\n' 'Labels\nLPA\nRPA\nNz\nDummy\n', m_elc='# ASA electrode file\nReferenceLabel avg\nUnitPosition m\n' 'NumberPositions= 68\nPositions\n-.0860761 -.0199897 -.0479860\n' # noqa:E501 '.0857939 -.0200093 -.0480310\n.0000083 .00868110 -.0399830\n' '.08 -.02 -.04\n' 'Labels\nLPA\nRPA\nNz\nDummy\n', txt='Site Theta Phi\n' 'Fp1 -92 -72\n' 'Fp2 92 72\n' 'very_very_very_long_name -92 72\n' 'O2 92 -90\n', elp='346\n' 'EEG\t F3\t -62.027\t -50.053\t 85\n' 'EEG\t Fz\t 45.608\t 90\t 85\n' 'EEG\t F4\t 62.01\t 50.103\t 85\n' 'EEG\t FCz\t 68.01\t 58.103\t 85\n', hpts='eeg Fp1 -95.0 -3. -3.\n' 'eeg AF7 -1 -1 -3\n' 'eeg A3 -2 -2 2\n' 'eeg A 0 0 0', bvef='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' '<!-- Generated by EasyCap Configurator 19.05.2014 -->\n' '<Electrodes defaults="false">\n' ' <Electrode>\n' ' <Name>Fp1</Name>\n' ' <Theta>-90</Theta>\n' ' <Phi>-72</Phi>\n' ' <Radius>1</Radius>\n' ' <Number>1</Number>\n' ' </Electrode>\n' ' <Electrode>\n' ' <Name>Fz</Name>\n' ' <Theta>45</Theta>\n' ' <Phi>90</Phi>\n' ' <Radius>1</Radius>\n' ' <Number>2</Number>\n' ' </Electrode>\n' ' <Electrode>\n' ' <Name>F3</Name>\n' ' <Theta>-60</Theta>\n' ' <Phi>-51</Phi>\n' ' <Radius>1</Radius>\n' ' <Number>3</Number>\n' ' </Electrode>\n' ' <Electrode>\n' ' <Name>F7</Name>\n' ' <Theta>-90</Theta>\n' ' <Phi>-36</Phi>\n' ' <Radius>1</Radius>\n' ' <Number>4</Number>\n' ' </Electrode>\n' '</Electrodes>', ) # Get actual positions and save them for checking # csd comes from the string above, all others come from commit 2fa35d4 poss = dict( sfp=[[0.0, 9.07159, -2.35975], [-6.71176, 0.0404, -3.2516], [-5.83124, -4.49482, 4.95535], [0.0, 0.0, 8.89919]], mm_elc=[[-0.08608, -0.01999, -0.04799], [0.08579, -0.02001, -0.04803], [1e-05, 0.08681, -0.03998], [-0.08608, -0.02499, -0.06799]], m_elc=[[-0.08608, -0.01999, -0.04799], [0.08579, -0.02001, -0.04803], [1e-05, 0.00868, -0.03998], [0.08, -0.02, -0.04]], txt=[[-26.25044, 80.79056, -2.96646], [26.25044, 80.79056, -2.96646], [-26.25044, -80.79056, -2.96646], [0.0, -84.94822, -2.96646]], elp=[[-48.20043, 57.55106, 39.86971], [0.0, 60.73848, 59.4629], [48.1426, 57.58403, 39.89198], [41.64599, 66.91489, 31.8278]], hpts=[[-95, -3, -3], [-1, -1., -3.], [-2, -2, 2.], [0, 0, 0]], bvef=[[-2.62664445e-02, 8.08398039e-02, 5.20474890e-18], [3.68031324e-18, 6.01040764e-02, 6.01040764e-02], [-4.63256329e-02, 5.72073923e-02, 4.25000000e-02], [-6.87664445e-02, 4.99617464e-02, 5.20474890e-18]], ) for key, text in inputs.items(): kind = key.split('_')[-1] fname = op.join(tempdir, 'test.' + kind) with open(fname, 'w') as fid: fid.write(text) unit = 'mm' if kind == 'bvef' else 'm' montage = read_montage(fname, unit=unit) if kind in ('sfp', 'txt'): assert ('very_very_very_long_name' in montage.ch_names) assert_equal(len(montage.ch_names), 4) assert_equal(len(montage.ch_names), len(montage.pos)) assert_equal(montage.pos.shape, (4, 3)) assert_equal(montage.kind, 'test') if kind == 'csd': dtype = [('label', 'S4'), ('theta', 'f8'), ('phi', 'f8'), ('radius', 'f8'), ('x', 'f8'), ('y', 'f8'), ('z', 'f8'), ('off_sph', 'f8')] try: table = np.loadtxt(fname, skip_header=2, dtype=dtype) except TypeError: table = np.loadtxt(fname, skiprows=2, dtype=dtype) poss['csd'] = np.c_[table['x'], table['y'], table['z']] if kind == 'elc': # Make sure points are reasonable distance from geometric centroid centroid = np.sum(montage.pos, axis=0) / montage.pos.shape[0] distance_from_centroid = np.apply_along_axis( np.linalg.norm, 1, montage.pos - centroid) assert_array_less(distance_from_centroid, 0.2) assert_array_less(0.01, distance_from_centroid) assert_array_almost_equal(poss[key], montage.pos, 4, err_msg=key) # Bvef is either auto or mm in terms of "units" with pytest.raises(ValueError, match='be "auto" or "mm" for .bvef files.'): bvef_file = op.join(tempdir, 'test.' + 'bvef') read_montage(bvef_file, unit='m') # Test reading in different letter case. ch_names = ["F3", "FZ", "F4", "FC3", "FCz", "FC4", "C3", "CZ", "C4", "CP3", "CPZ", "CP4", "P3", "PZ", "P4", "O1", "OZ", "O2"] montage = read_montage('standard_1020', ch_names=ch_names) assert_array_equal(ch_names, montage.ch_names) # test transform input_strs = [""" eeg Fp1 -95.0 -31.0 -3.0 eeg AF7 -81 -59 -3 eeg AF3 -87 -41 28 cardinal 2 -91 0 -42 cardinal 1 0 -91 -42 cardinal 3 0 91 -42 """, """ Fp1 -95.0 -31.0 -3.0 AF7 -81 -59 -3 AF3 -87 -41 28 FidNz -91 0 -42 FidT9 0 -91 -42 FidT10 0 91 -42 """] # sfp files seem to have Nz, T9, and T10 as fiducials: # https://github.com/mne-tools/mne-python/pull/4482#issuecomment-321980611 kinds = ['test_fid.hpts', 'test_fid.sfp'] for kind, input_str in zip(kinds, input_strs): fname = op.join(tempdir, kind) with open(fname, 'w') as fid: fid.write(input_str) montage = read_montage(op.join(tempdir, kind), transform=True) # check coordinate transformation pos = np.array([-95.0, -31.0, -3.0]) nasion = np.array([-91, 0, -42]) lpa = np.array([0, -91, -42]) rpa = np.array([0, 91, -42]) fids = np.vstack((nasion, lpa, rpa)) trans = get_ras_to_neuromag_trans(fids[0], fids[1], fids[2]) pos = apply_trans(trans, pos) assert_array_equal(montage.pos[0], pos) assert_array_equal(montage.nasion[[0, 2]], [0, 0]) assert_array_equal(montage.lpa[[1, 2]], [0, 0]) assert_array_equal(montage.rpa[[1, 2]], [0, 0]) pos = np.array([-95.0, -31.0, -3.0]) montage_fname = op.join(tempdir, kind) montage = read_montage(montage_fname, unit='mm') assert_array_equal(montage.pos[0], pos * 1e-3) # test with last info = create_info(montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) _set_montage(info, montage) pos2 = np.array([c['loc'][:3] for c in info['chs']]) assert_array_equal(pos2, montage.pos) assert_equal(montage.ch_names, info['ch_names']) info = create_info( montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) evoked = EvokedArray( data=np.zeros((len(montage.ch_names), 1)), info=info, tmin=0) # test return type as well as set montage assert (isinstance(evoked.set_montage(montage), type(evoked))) pos3 = np.array([c['loc'][:3] for c in evoked.info['chs']]) assert_array_equal(pos3, montage.pos) assert_equal(montage.ch_names, evoked.info['ch_names']) # Warning should be raised when some EEG are not specified in montage info = create_info(montage.ch_names + ['foo', 'bar'], 1e3, ['eeg'] * (len(montage.ch_names) + 2)) with pytest.warns(RuntimeWarning, match='position specified'): _set_montage(info, montage) # Channel names can be treated case insensitive info = create_info(['FP1', 'af7', 'AF3'], 1e3, ['eeg'] * 3) _set_montage(info, montage) # Unless there is a collision in names info = create_info(['FP1', 'Fp1', 'AF3'], 1e3, ['eeg'] * 3) assert (info['dig'] is None) with pytest.warns(RuntimeWarning, match='position specified'): _set_montage(info, montage) assert len(info['dig']) == 5 # 2 EEG w/pos, 3 fiducials montage.ch_names = ['FP1', 'Fp1', 'AF3'] info = create_info(['fp1', 'AF3'], 1e3, ['eeg', 'eeg']) assert (info['dig'] is None) with pytest.warns(RuntimeWarning, match='position specified'): _set_montage(info, montage, set_dig=False) assert (info['dig'] is None) # test get_pos2d method montage = read_montage("standard_1020") c3 = montage.get_pos2d()[montage.ch_names.index("C3")] c4 = montage.get_pos2d()[montage.ch_names.index("C4")] fz = montage.get_pos2d()[montage.ch_names.index("Fz")] oz = montage.get_pos2d()[montage.ch_names.index("Oz")] f1 = montage.get_pos2d()[montage.ch_names.index("F1")] assert (c3[0] < 0) # left hemisphere assert (c4[0] > 0) # right hemisphere assert (fz[1] > 0) # frontal assert (oz[1] < 0) # occipital assert_allclose(fz[0], 0, atol=1e-2) # midline assert_allclose(oz[0], 0, atol=1e-2) # midline assert (f1[0] < 0 and f1[1] > 0) # left frontal # test get_builtin_montages function montages = get_builtin_montages() assert (len(montages) > 0) # MNE should always ship with montages assert ("standard_1020" in montages) # 10/20 montage assert ("standard_1005" in montages) # 10/05 montage
def test_fit_point_cloud(): """Test fit_point_cloud: fitting a set of points to a point cloud""" # evenly spaced target points on a sphere u = np.linspace(0, np.pi, 150) v = np.linspace(0, np.pi, 150) x = np.outer(np.cos(u), np.sin(v)).reshape((-1, 1)) y = np.outer(np.sin(u), np.sin(v)).reshape((-1, 1)) z = np.outer(np.ones(np.size(u)), np.cos(v)).reshape((-1, 1)) * 3 tgt_pts = np.hstack((x, y, z)) tgt_pts = _decimate_points(tgt_pts, .05) # pick some points to fit some_tgt_pts = tgt_pts[::362] # rotation only trans = rotation(1.5, .3, -0.4) src_pts = apply_trans(trans, some_tgt_pts) trans_est = fit_point_cloud(src_pts, tgt_pts, rotate=True, translate=False, scale=0, out='trans') est_pts = apply_trans(trans_est, src_pts) err = _point_cloud_error(est_pts, tgt_pts) assert_array_less(err, .1, "fit_point_cloud with rotation.") # rotation and translation trans = np.dot(rotation(0.5, .3, -0.4), translation(.3, .2, -.2)) src_pts = apply_trans(trans, some_tgt_pts) trans_est = fit_point_cloud(src_pts, tgt_pts, rotate=True, translate=True, scale=0, out='trans') est_pts = apply_trans(trans_est, src_pts) err = _point_cloud_error(est_pts, tgt_pts) assert_array_less(err, .1, "fit_point_cloud with rotation and " "translation.") # rotation and 1 scale parameter trans = np.dot(rotation(0.5, .3, -0.4), scaling(1.5, 1.5, 1.5)) src_pts = apply_trans(trans, some_tgt_pts) trans_est = fit_point_cloud(src_pts, tgt_pts, rotate=True, translate=False, scale=1, out='trans') est_pts = apply_trans(trans_est, src_pts) err = _point_cloud_error(est_pts, tgt_pts) assert_array_less( err, .1, "fit_point_cloud with rotation and 1 scaling " "parameter.") # rotation and 3 scale parameter trans = np.dot(rotation(0.5, .3, -0.4), scaling(1.5, 1.7, 1.1)) src_pts = apply_trans(trans, some_tgt_pts) trans_est = fit_point_cloud(src_pts, tgt_pts, rotate=True, translate=False, scale=3, out='trans') est_pts = apply_trans(trans_est, src_pts) err = _point_cloud_error(est_pts, tgt_pts) assert_array_less( err, .1, "fit_point_cloud with rotation and 3 scaling " "parameters.")
def get_sensor_pos_from_fwd(inst, info=None, picks=None, trans=None): from mne import SourceSpaces, Forward from mne.io.constants import FIFF from six import string_types from mne.transforms import read_trans, _ensure_trans, invert_transform, Transform, apply_trans from mne.io.pick import channel_type, pick_types if isinstance(inst, Forward): info = inst['info'] src = inst['src'] elif isinstance(inst, SourceSpaces): src = inst if info is None: raise ValueError('You need to specify an Info object with ' 'information about the channels.') # Load the head<->MRI transform if necessary if src[0]['coord_frame'] == FIFF.FIFFV_COORD_MRI: if trans is None: raise ValueError('Source space is in MRI coordinates, but no ' 'head<->MRI transform was given. Please specify ' 'the full path to the appropriate *-trans.fif ' 'file as the "trans" parameter.') if isinstance(trans, string_types): trans = read_trans(trans, return_all=True) for trans in trans: # we got at least 1 try: trans = _ensure_trans(trans, 'head', 'mri') except Exception as exp: pass else: break else: raise exp src_trans = invert_transform(_ensure_trans(trans, 'head', 'mri')) print('Transform!') else: src_trans = Transform('head', 'head') # Identity transform dev_to_head = _ensure_trans(info['dev_head_t'], 'meg', 'head') if picks is None: picks = pick_types(info, meg=True) if len(picks) > 0: print('Using MEG channels') else: print('Using EEG channels') picks = pick_types(info, eeg=True) sensor_pos = [] for ch in picks: # MEG channels are in device coordinates, translate them to head if channel_type(info, ch) in ['mag', 'grad']: sensor_pos.append( apply_trans(dev_to_head, info['chs'][ch]['loc'][:3])) else: sensor_pos.append(info['chs'][ch]['loc'][:3]) sensor_pos = np.array(sensor_pos) return sensor_pos
def test_read_ctf(): """Test CTF reader.""" temp_dir = _TempDir() out_fname = op.join(temp_dir, 'test_py_raw.fif') # Create a dummy .eeg file so we can test our reading/application of it os.mkdir(op.join(temp_dir, 'randpos')) ctf_eeg_fname = op.join(temp_dir, 'randpos', ctf_fname_catch) shutil.copytree(op.join(ctf_dir, ctf_fname_catch), ctf_eeg_fname) with pytest.warns(RuntimeWarning, match='RMSP .* changed to a MISC ch'): raw = _test_raw_reader(read_raw_ctf, directory=ctf_eeg_fname) picks = pick_types(raw.info, meg=False, eeg=True) pos = np.random.RandomState(42).randn(len(picks), 3) fake_eeg_fname = op.join(ctf_eeg_fname, 'catch-alp-good-f.eeg') # Create a bad file with open(fake_eeg_fname, 'wb') as fid: fid.write('foo\n'.encode('ascii')) pytest.raises(RuntimeError, read_raw_ctf, ctf_eeg_fname) # Create a good file with open(fake_eeg_fname, 'wb') as fid: for ii, ch_num in enumerate(picks): args = (str(ch_num + 1), raw.ch_names[ch_num],) + tuple( '%0.5f' % x for x in 100 * pos[ii]) # convert to cm fid.write(('\t'.join(args) + '\n').encode('ascii')) pos_read_old = np.array([raw.info['chs'][p]['loc'][:3] for p in picks]) with pytest.warns(RuntimeWarning, match='RMSP .* changed to a MISC ch'): raw = read_raw_ctf(ctf_eeg_fname) # read modified data pos_read = np.array([raw.info['chs'][p]['loc'][:3] for p in picks]) assert_allclose(apply_trans(raw.info['ctf_head_t'], pos), pos_read, rtol=1e-5, atol=1e-5) assert (pos_read == pos_read_old).mean() < 0.1 shutil.copy(op.join(ctf_dir, 'catch-alp-good-f.ds_randpos_raw.fif'), op.join(temp_dir, 'randpos', 'catch-alp-good-f.ds_raw.fif')) # Create a version with no hc, starting out *with* EEG pos (error) os.mkdir(op.join(temp_dir, 'nohc')) ctf_no_hc_fname = op.join(temp_dir, 'no_hc', ctf_fname_catch) shutil.copytree(ctf_eeg_fname, ctf_no_hc_fname) remove_base = op.join(ctf_no_hc_fname, op.basename(ctf_fname_catch[:-3])) os.remove(remove_base + '.hc') with pytest.warns(RuntimeWarning, match='MISC channel'): pytest.raises(RuntimeError, read_raw_ctf, ctf_no_hc_fname) os.remove(remove_base + '.eeg') shutil.copy(op.join(ctf_dir, 'catch-alp-good-f.ds_nohc_raw.fif'), op.join(temp_dir, 'no_hc', 'catch-alp-good-f.ds_raw.fif')) # All our files use_fnames = [op.join(ctf_dir, c) for c in ctf_fnames] for fname in use_fnames: raw_c = read_raw_fif(fname + '_raw.fif', preload=True) with pytest.warns(None): # sometimes matches "MISC channel" raw = read_raw_ctf(fname) # check info match assert_array_equal(raw.ch_names, raw_c.ch_names) assert_allclose(raw.times, raw_c.times) assert_allclose(raw._cals, raw_c._cals) assert_equal(raw.info['meas_id']['version'], raw_c.info['meas_id']['version'] + 1) for t in ('dev_head_t', 'dev_ctf_t', 'ctf_head_t'): assert_allclose(raw.info[t]['trans'], raw_c.info[t]['trans'], rtol=1e-4, atol=1e-7) for key in ('acq_pars', 'acq_stim', 'bads', 'ch_names', 'custom_ref_applied', 'description', 'events', 'experimenter', 'highpass', 'line_freq', 'lowpass', 'nchan', 'proj_id', 'proj_name', 'projs', 'sfreq', 'subject_info'): assert_equal(raw.info[key], raw_c.info[key], key) if op.basename(fname) not in single_trials: # We don't force buffer size to be smaller like MNE-C assert raw.buffer_size_sec == raw_c.buffer_size_sec assert_equal(len(raw.info['comps']), len(raw_c.info['comps'])) for c1, c2 in zip(raw.info['comps'], raw_c.info['comps']): for key in ('colcals', 'rowcals'): assert_allclose(c1[key], c2[key]) assert_equal(c1['save_calibrated'], c2['save_calibrated']) for key in ('row_names', 'col_names', 'nrow', 'ncol'): assert_array_equal(c1['data'][key], c2['data'][key]) assert_allclose(c1['data']['data'], c2['data']['data'], atol=1e-7, rtol=1e-5) assert_allclose(raw.info['hpi_results'][0]['coord_trans']['trans'], raw_c.info['hpi_results'][0]['coord_trans']['trans'], rtol=1e-5, atol=1e-7) assert_equal(len(raw.info['chs']), len(raw_c.info['chs'])) for ii, (c1, c2) in enumerate(zip(raw.info['chs'], raw_c.info['chs'])): for key in ('kind', 'scanno', 'unit', 'ch_name', 'unit_mul', 'range', 'coord_frame', 'coil_type', 'logno'): if c1['ch_name'] == 'RMSP' and \ 'catch-alp-good-f' in fname and \ key in ('kind', 'unit', 'coord_frame', 'coil_type', 'logno'): continue # XXX see below... assert_equal(c1[key], c2[key], err_msg=key) for key in ('cal',): assert_allclose(c1[key], c2[key], atol=1e-6, rtol=1e-4, err_msg='raw.info["chs"][%d][%s]' % (ii, key)) # XXX 2016/02/24: fixed bug with normal computation that used # to exist, once mne-C tools are updated we should update our FIF # conversion files, then the slices can go away (and the check # can be combined with that for "cal") for key in ('loc',): if c1['ch_name'] == 'RMSP' and 'catch-alp-good-f' in fname: continue if (c2[key][:3] == 0.).all(): check = [np.nan] * 3 else: check = c2[key][:3] assert_allclose(c1[key][:3], check, atol=1e-6, rtol=1e-4, err_msg='raw.info["chs"][%d][%s]' % (ii, key)) if (c2[key][3:] == 0.).all(): check = [np.nan] * 3 else: check = c2[key][9:12] assert_allclose(c1[key][9:12], check, atol=1e-6, rtol=1e-4, err_msg='raw.info["chs"][%d][%s]' % (ii, key)) # Make sure all digitization points are in the MNE head coord frame for p in raw.info['dig']: assert_equal(p['coord_frame'], FIFF.FIFFV_COORD_HEAD, err_msg='dig points must be in FIFF.FIFFV_COORD_HEAD') if fname.endswith('catch-alp-good-f.ds'): # omit points from .pos file raw.info['dig'] = raw.info['dig'][:-10] # XXX: Next test would fail because c-tools assign the fiducials from # CTF data as HPI. Should eventually clarify/unify with Matti. # assert_dig_allclose(raw.info, raw_c.info) # check data match raw_c.save(out_fname, overwrite=True, buffer_size_sec=1.) raw_read = read_raw_fif(out_fname) # so let's check tricky cases based on sample boundaries rng = np.random.RandomState(0) pick_ch = rng.permutation(np.arange(len(raw.ch_names)))[:10] bnd = int(round(raw.info['sfreq'] * raw.buffer_size_sec)) assert_equal(bnd, raw._raw_extras[0]['block_size']) assert_equal(bnd, block_sizes[op.basename(fname)]) slices = (slice(0, bnd), slice(bnd - 1, bnd), slice(3, bnd), slice(3, 300), slice(None)) if len(raw.times) >= 2 * bnd: # at least two complete blocks slices = slices + (slice(bnd, 2 * bnd), slice(bnd, bnd + 1), slice(0, bnd + 100)) for sl_time in slices: assert_allclose(raw[pick_ch, sl_time][0], raw_c[pick_ch, sl_time][0]) assert_allclose(raw_read[pick_ch, sl_time][0], raw_c[pick_ch, sl_time][0]) # all data / preload with pytest.warns(None): # sometimes MISC raw = read_raw_ctf(fname, preload=True) assert_allclose(raw[:][0], raw_c[:][0], atol=1e-15) # test bad segment annotations if 'testdata_ctf_short.ds' in fname: assert 'bad' in raw.annotations.description[0] assert_allclose(raw.annotations.onset, [2.15]) assert_allclose(raw.annotations.duration, [0.0225]) pytest.raises(TypeError, read_raw_ctf, 1) pytest.raises(ValueError, read_raw_ctf, ctf_fname_continuous + 'foo.ds') # test ignoring of system clock read_raw_ctf(op.join(ctf_dir, ctf_fname_continuous), 'ignore') pytest.raises(ValueError, read_raw_ctf, op.join(ctf_dir, ctf_fname_continuous), 'foo')
def test_montage(): """Test making montages.""" tempdir = _TempDir() # no pep8 input_str = [ 'FidNz 0.00000 10.56381 -2.05108\nFidT9 -7.82694 0.45386 -3.76056\n' 'very_very_very_long_name 7.82694 0.45386 -3.76056\nDmy 7.0 0.0 1.0', '// MatLab Sphere coordinates [degrees] Cartesian coordinates\n' # noqa: E501 '// Label Theta Phi Radius X Y Z off sphere surface\n' # noqa: E501 'E1 37.700 -14.000 1.000 0.7677 0.5934 -0.2419 -0.00000000000000011\n' # noqa: E501 'E2 44.600 -0.880 1.000 0.7119 0.7021 -0.0154 0.00000000000000000\n' # noqa: E501 'E3 51.700 11.000 1.000 0.6084 0.7704 0.1908 0.00000000000000000\n' # noqa: E501 'E4 52.700 12.000 1.000 0.7084 0.7504 0.1508 0.00000000000000000', # noqa: E501; dummy line '# ASA electrode file\nReferenceLabel avg\nUnitPosition mm\n' 'NumberPositions= 68\nPositions\n-86.0761 -19.9897 -47.9860\n' '85.7939 -20.0093 -48.0310\n0.0083 86.8110 -39.9830\n' '85 -20 -48\n' 'Labels\nLPA\nRPA\nNz\nDummy\n', '# ASA electrode file\nReferenceLabel avg\nUnitPosition m\n' 'NumberPositions= 68\nPositions\n-.0860761 -.0199897 -.0479860\n' '.0857939 -.0200093 -.0480310\n.0000083 .00868110 -.0399830\n' '.08 -.02 -.04\n' 'Labels\nLPA\nRPA\nNz\nDummy\n', 'Site Theta Phi\nFp1 -92 -72\nFp2 92 72\n' 'very_very_very_long_name -60 -51\n' 'dummy -60 -52\n', '346\n' 'EEG\t F3\t -62.027\t -50.053\t 85\n' 'EEG\t Fz\t 45.608\t 90\t 85\n' 'EEG\t F4\t 62.01\t 50.103\t 85\n' 'EEG\t FCz\t 68.01\t 58.103\t 85\n', 'eeg Fp1 -95.0 -3. -3.\neeg AF7 -1 -1 -3\neeg A3 -2 -2 2\neeg A 0 0 0' ] kinds = [ 'test.sfp', 'test.csd', 'test_mm.elc', 'test_m.elc', 'test.txt', 'test.elp', 'test.hpts' ] for kind, text in zip(kinds, input_str): fname = op.join(tempdir, kind) with open(fname, 'w') as fid: fid.write(text) montage = read_montage(fname) if ".sfp" in kind or ".txt" in kind: assert_true('very_very_very_long_name' in montage.ch_names) assert_equal(len(montage.ch_names), 4) assert_equal(len(montage.ch_names), len(montage.pos)) assert_equal(montage.pos.shape, (4, 3)) assert_equal(montage.kind, op.splitext(kind)[0]) if kind.endswith('csd'): dtype = [('label', 'S4'), ('theta', 'f8'), ('phi', 'f8'), ('radius', 'f8'), ('x', 'f8'), ('y', 'f8'), ('z', 'f8'), ('off_sph', 'f8')] try: table = np.loadtxt(fname, skip_header=2, dtype=dtype) except TypeError: table = np.loadtxt(fname, skiprows=2, dtype=dtype) pos2 = np.c_[table['x'], table['y'], table['z']] assert_array_almost_equal(pos2[:-1, :], montage.pos[:-1, :], 4) if kind.endswith('elc'): # Make sure points are reasonable distance from geometric centroid centroid = np.sum(montage.pos, axis=0) / montage.pos.shape[0] distance_from_centroid = np.apply_along_axis( np.linalg.norm, 1, montage.pos - centroid) assert_array_less(distance_from_centroid, 0.2) assert_array_less(0.01, distance_from_centroid) # Test reading in different letter case. ch_names = [ "F3", "FZ", "F4", "FC3", "FCz", "FC4", "C3", "CZ", "C4", "CP3", "CPZ", "CP4", "P3", "PZ", "P4", "O1", "OZ", "O2" ] montage = read_montage('standard_1020', ch_names=ch_names) assert_array_equal(ch_names, montage.ch_names) # test transform input_str = """ eeg Fp1 -95.0 -31.0 -3.0 eeg AF7 -81 -59 -3 eeg AF3 -87 -41 28 cardinal 2 -91 0 -42 cardinal 1 0 -91 -42 cardinal 3 0 91 -42 """ kind = 'test_fid.hpts' fname = op.join(tempdir, kind) with open(fname, 'w') as fid: fid.write(input_str) montage = read_montage(op.join(tempdir, 'test_fid.hpts'), transform=True) # check coordinate transformation pos = np.array([-95.0, -31.0, -3.0]) nasion = np.array([-91, 0, -42]) lpa = np.array([0, -91, -42]) rpa = np.array([0, 91, -42]) fids = np.vstack((nasion, lpa, rpa)) trans = get_ras_to_neuromag_trans(fids[0], fids[1], fids[2]) pos = apply_trans(trans, pos) assert_array_equal(montage.pos[0], pos) idx = montage.ch_names.index('2') assert_array_equal(montage.pos[idx, [0, 2]], [0, 0]) idx = montage.ch_names.index('1') assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) idx = montage.ch_names.index('3') assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) pos = np.array([-95.0, -31.0, -3.0]) montage_fname = op.join(tempdir, 'test_fid.hpts') montage = read_montage(montage_fname, unit='mm') assert_array_equal(montage.pos[0], pos * 1e-3) # test with last info = create_info(montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) _set_montage(info, montage) pos2 = np.array([c['loc'][:3] for c in info['chs']]) assert_array_equal(pos2, montage.pos) assert_equal(montage.ch_names, info['ch_names']) info = create_info(montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) evoked = EvokedArray(data=np.zeros((len(montage.ch_names), 1)), info=info, tmin=0) evoked.set_montage(montage) pos3 = np.array([c['loc'][:3] for c in evoked.info['chs']]) assert_array_equal(pos3, montage.pos) assert_equal(montage.ch_names, evoked.info['ch_names']) # Warning should be raised when some EEG are not specified in the montage with warnings.catch_warnings(record=True) as w: info = create_info(montage.ch_names + ['foo', 'bar'], 1e3, ['eeg'] * (len(montage.ch_names) + 2)) _set_montage(info, montage) assert_true(len(w) == 1)
def test_scale_mri(): """Test creating fsaverage and scaling it.""" # create fsaverage using the testing "fsaverage" instead of the FreeSurfer # one tempdir = _TempDir() fake_home = testing.data_path() create_default_subject(subjects_dir=tempdir, fs_home=fake_home, verbose=True) assert _is_mri_subject('fsaverage', tempdir), "Creating fsaverage failed" fid_path = op.join(tempdir, 'fsaverage', 'bem', 'fsaverage-fiducials.fif') os.remove(fid_path) create_default_subject(update=True, subjects_dir=tempdir, fs_home=fake_home) assert op.exists(fid_path), "Updating fsaverage" # copy MRI file from sample data (shouldn't matter that it's incorrect, # so here choose a small one) path_from = op.join(testing.data_path(), 'subjects', 'sample', 'mri', 'T1.mgz') path_to = op.join(tempdir, 'fsaverage', 'mri', 'orig.mgz') copyfile(path_from, path_to) # remove redundant label files label_temp = op.join(tempdir, 'fsaverage', 'label', '*.label') label_paths = glob(label_temp) for label_path in label_paths[1:]: os.remove(label_path) # create source space print('Creating surface source space') path = op.join(tempdir, 'fsaverage', 'bem', 'fsaverage-%s-src.fif') src = mne.setup_source_space('fsaverage', 'ico0', subjects_dir=tempdir, add_dist=False) mri = op.join(tempdir, 'fsaverage', 'mri', 'orig.mgz') print('Creating volume source space') vsrc = mne.setup_volume_source_space( 'fsaverage', pos=50, mri=mri, subjects_dir=tempdir, add_interpolator=False) write_source_spaces(path % 'vol-50', vsrc) # scale fsaverage for scale in (.9, [1, .2, .8]): write_source_spaces(path % 'ico-0', src, overwrite=True) os.environ['_MNE_FEW_SURFACES'] = 'true' with pytest.warns(None): # sometimes missing nibabel scale_mri('fsaverage', 'flachkopf', scale, True, subjects_dir=tempdir, verbose='debug') del os.environ['_MNE_FEW_SURFACES'] assert _is_mri_subject('flachkopf', tempdir), "Scaling failed" spath = op.join(tempdir, 'flachkopf', 'bem', 'flachkopf-%s-src.fif') assert op.exists(spath % 'ico-0'), "Source space ico-0 was not scaled" assert os.path.isfile(os.path.join(tempdir, 'flachkopf', 'surf', 'lh.sphere.reg')) vsrc_s = mne.read_source_spaces(spath % 'vol-50') pt = np.array([0.12, 0.41, -0.22]) assert_array_almost_equal( apply_trans(vsrc_s[0]['src_mri_t'], pt * np.array(scale)), apply_trans(vsrc[0]['src_mri_t'], pt)) scale_labels('flachkopf', subjects_dir=tempdir) # add distances to source space after hacking the properties to make # it run *much* faster src_dist = src.copy() for s in src_dist: s.update(rr=s['rr'][s['vertno']], nn=s['nn'][s['vertno']], tris=s['use_tris']) s.update(np=len(s['rr']), ntri=len(s['tris']), vertno=np.arange(len(s['rr'])), inuse=np.ones(len(s['rr']), int)) mne.add_source_space_distances(src_dist) write_source_spaces(path % 'ico-0', src_dist, overwrite=True) # scale with distances os.remove(spath % 'ico-0') scale_source_space('flachkopf', 'ico-0', subjects_dir=tempdir) ssrc = mne.read_source_spaces(spath % 'ico-0') assert ssrc[0]['dist'] is not None
def test_montage(): """Test making montages""" tempdir = _TempDir() # no pep8 input_str = ["""FidNz 0.00000 10.56381 -2.05108 FidT9 -7.82694 0.45386 -3.76056 FidT10 7.82694 0.45386 -3.76056""", """// MatLab Sphere coordinates [degrees] Cartesian coordinates // Label Theta Phi Radius X Y Z off sphere surface E1 37.700 -14.000 1.000 0.7677 0.5934 -0.2419 -0.00000000000000011 E2 44.600 -0.880 1.000 0.7119 0.7021 -0.0154 0.00000000000000000 E3 51.700 11.000 1.000 0.6084 0.7704 0.1908 0.00000000000000000""", # noqa """# ASA electrode file ReferenceLabel avg UnitPosition mm NumberPositions= 68 Positions -86.0761 -19.9897 -47.9860 85.7939 -20.0093 -48.0310 0.0083 86.8110 -39.9830 Labels LPA RPA Nz """, """Site Theta Phi Fp1 -92 -72 Fp2 92 72 F3 -60 -51 """, """346 EEG F3 -62.027 -50.053 85 EEG Fz 45.608 90 85 EEG F4 62.01 50.103 85 """, """ eeg Fp1 -95.0 -31.0 -3.0 eeg AF7 -81 -59 -3 eeg AF3 -87 -41 28 """] kinds = ['test.sfp', 'test.csd', 'test.elc', 'test.txt', 'test.elp', 'test.hpts'] for kind, text in zip(kinds, input_str): fname = op.join(tempdir, kind) with open(fname, 'w') as fid: fid.write(text) montage = read_montage(fname) assert_equal(len(montage.ch_names), 3) assert_equal(len(montage.ch_names), len(montage.pos)) assert_equal(montage.pos.shape, (3, 3)) assert_equal(montage.kind, op.splitext(kind)[0]) if kind.endswith('csd'): dtype = [('label', 'S4'), ('theta', 'f8'), ('phi', 'f8'), ('radius', 'f8'), ('x', 'f8'), ('y', 'f8'), ('z', 'f8'), ('off_sph', 'f8')] try: table = np.loadtxt(fname, skip_header=2, dtype=dtype) except TypeError: table = np.loadtxt(fname, skiprows=2, dtype=dtype) pos2 = np.c_[table['x'], table['y'], table['z']] assert_array_almost_equal(pos2, montage.pos, 4) # test transform input_str = """ eeg Fp1 -95.0 -31.0 -3.0 eeg AF7 -81 -59 -3 eeg AF3 -87 -41 28 cardinal 2 -91 0 -42 cardinal 1 0 -91 -42 cardinal 3 0 91 -42 """ kind = 'test_fid.hpts' fname = op.join(tempdir, kind) with open(fname, 'w') as fid: fid.write(input_str) montage = read_montage(op.join(tempdir, 'test_fid.hpts'), transform=True) # check coordinate transformation pos = np.array([-95.0, -31.0, -3.0]) nasion = np.array([-91, 0, -42]) lpa = np.array([0, -91, -42]) rpa = np.array([0, 91, -42]) fids = np.vstack((nasion, lpa, rpa)) trans = get_ras_to_neuromag_trans(fids[0], fids[1], fids[2]) pos = apply_trans(trans, pos) assert_array_equal(montage.pos[0], pos) idx = montage.ch_names.index('2') assert_array_equal(montage.pos[idx, [0, 2]], [0, 0]) idx = montage.ch_names.index('1') assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) idx = montage.ch_names.index('3') assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) pos = np.array([-95.0, -31.0, -3.0]) montage_fname = op.join(tempdir, 'test_fid.hpts') montage = read_montage(montage_fname, unit='mm') assert_array_equal(montage.pos[0], pos * 1e-3) # test with last info = create_info(montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) _set_montage(info, montage) pos2 = np.array([c['loc'][:3] for c in info['chs']]) assert_array_equal(pos2, montage.pos) assert_equal(montage.ch_names, info['ch_names']) info = create_info( montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) evoked = EvokedArray( data=np.zeros((len(montage.ch_names), 1)), info=info, tmin=0) evoked.set_montage(montage) pos3 = np.array([c['loc'][:3] for c in evoked.info['chs']]) assert_array_equal(pos3, montage.pos) assert_equal(montage.ch_names, evoked.info['ch_names']) # Warning should be raised when some EEG are not specified in the montage with warnings.catch_warnings(record=True) as w: info = create_info(montage.ch_names + ['foo', 'bar'], 1e3, ['eeg'] * (len(montage.ch_names) + 2)) _set_montage(info, montage) assert_true(len(w) == 1)
def get_head_mri_trans(bids_path, extra_params=None, t1_bids_path=None, fs_subject=None, fs_subjects_dir=None, *, kind=None, verbose=None): """Produce transformation matrix from MEG and MRI landmark points. Will attempt to read the landmarks of Nasion, LPA, and RPA from the sidecar files of (i) the MEG and (ii) the T1-weighted MRI data. The two sets of points will then be used to calculate a transformation matrix from head coordinates to MRI coordinates. .. note:: The MEG and MRI data need **not** necessarily be stored in the same session or even in the same BIDS dataset. See the ``t1_bids_path`` parameter for details. Parameters ---------- bids_path : BIDSPath The path of the electrophysiology recording. If ``datatype`` and ``suffix`` are not present, they will be set to ``'meg'``, and a warning will be raised. .. versionchanged:: 0.10 A warning is raised it ``datatype`` or ``suffix`` are not set. extra_params : None | dict Extra parameters to be passed to :func:`mne.io.read_raw` when reading the MEG file. t1_bids_path : BIDSPath | None If ``None`` (default), will try to discover the T1-weighted MRI file based on the name and location of the MEG recording specified via the ``bids_path`` parameter. Alternatively, you explicitly specify which T1-weighted MRI scan to use for extraction of MRI landmarks. To do that, pass a :class:`mne_bids.BIDSPath` pointing to the scan. Use this parameter e.g. if the T1 scan was recorded during a different session than the MEG. It is even possible to point to a T1 image stored in an entirely different BIDS dataset than the MEG data. fs_subject : str The subject identifier used for FreeSurfer. .. versionchanged:: 0.10 Does not default anymore to ``bids_path.subject`` if ``None``. fs_subjects_dir : path-like | None The FreeSurfer subjects directory. If ``None``, defaults to the ``SUBJECTS_DIR`` environment variable. .. versionadded:: 0.8 kind : str | None The suffix of the anatomical landmark names in the JSON sidecar. A suffix might be present e.g. to distinguish landmarks between sessions. If provided, should not include a leading underscore ``_``. For example, if the landmark names in the JSON sidecar file are ``LPA_ses-1``, ``RPA_ses-1``, ``NAS_ses-1``, you should pass ``'ses-1'`` here. If ``None``, no suffix is appended, the landmarks named ``Nasion`` (or ``NAS``), ``LPA``, and ``RPA`` will be used. .. versionadded:: 0.10 %(verbose)s Returns ------- trans : mne.transforms.Transform The data transformation matrix from head to MRI coordinates. """ if not has_nibabel(): # pragma: no cover raise ImportError('This function requires nibabel.') import nibabel as nib if not isinstance(bids_path, BIDSPath): raise RuntimeError('"bids_path" must be a BIDSPath object. Please ' 'instantiate using mne_bids.BIDSPath().') # check root available meg_bids_path = bids_path.copy() del bids_path if meg_bids_path.root is None: raise ValueError('The root of the "bids_path" must be set. ' 'Please use `bids_path.update(root="<root>")` ' 'to set the root of the BIDS folder to read.') # if the bids_path is underspecified, only get info for MEG data if meg_bids_path.datatype is None: meg_bids_path.datatype = 'meg' warn( 'bids_path did not have a datatype set. Assuming "meg". This ' 'will raise an exception in the future.', module='mne_bids', category=DeprecationWarning) if meg_bids_path.suffix is None: meg_bids_path.suffix = 'meg' warn( 'bids_path did not have a suffix set. Assuming "meg". This ' 'will raise an exception in the future.', module='mne_bids', category=DeprecationWarning) # Get the sidecar file for MRI landmarks t1w_bids_path = ((meg_bids_path if t1_bids_path is None else t1_bids_path).copy().update(datatype='anat', suffix='T1w', task=None)) t1w_json_path = _find_matching_sidecar(bids_path=t1w_bids_path, extension='.json', on_error='ignore') del t1_bids_path if t1w_json_path is not None: t1w_json_path = Path(t1w_json_path) if t1w_json_path is None or not t1w_json_path.exists(): raise FileNotFoundError( f'Did not find T1w JSON sidecar file, tried location: ' f'{t1w_json_path}') for extension in ('.nii', '.nii.gz'): t1w_path_candidate = t1w_json_path.with_suffix(extension) if t1w_path_candidate.exists(): t1w_bids_path = get_bids_path_from_fname(fname=t1w_path_candidate) break if not t1w_bids_path.fpath.exists(): raise FileNotFoundError( f'Did not find T1w recording file, tried location: ' f'{t1w_path_candidate.name.replace(".nii.gz", "")}[.nii, .nii.gz]') # Get MRI landmarks from the JSON sidecar t1w_json = json.loads(t1w_json_path.read_text(encoding='utf-8')) mri_coords_dict = t1w_json.get('AnatomicalLandmarkCoordinates', dict()) # landmarks array: rows: [LPA, NAS, RPA]; columns: [x, y, z] suffix = f"_{kind}" if kind is not None else "" mri_landmarks = np.full((3, 3), np.nan) for landmark_name, coords in mri_coords_dict.items(): if landmark_name.upper() == ('LPA' + suffix).upper(): mri_landmarks[0, :] = coords elif landmark_name.upper() == ('RPA' + suffix).upper(): mri_landmarks[2, :] = coords elif (landmark_name.upper() == ('NAS' + suffix).upper() or landmark_name.lower() == ('nasion' + suffix).lower()): mri_landmarks[1, :] = coords else: continue if np.isnan(mri_landmarks).any(): raise RuntimeError( f'Could not extract fiducial points from T1w sidecar file: ' f'{t1w_json_path}\n\n' f'The sidecar file SHOULD contain a key ' f'"AnatomicalLandmarkCoordinates" pointing to an ' f'object with the keys "LPA", "NAS", and "RPA". ' f'Yet, the following structure was found:\n\n' f'{mri_coords_dict}') # The MRI landmarks are in "voxels". We need to convert them to the # Neuromag RAS coordinate system in order to compare them with MEG # landmarks. See also: `mne_bids.write.write_anat` if fs_subject is None: warn( 'Passing "fs_subject=None" has been deprecated and will raise ' 'an error in future versions. Please explicitly specify the ' 'FreeSurfer subject name.', DeprecationWarning) fs_subject = f'sub-{meg_bids_path.subject}' fs_subjects_dir = get_subjects_dir(fs_subjects_dir, raise_error=False) fs_t1_path = Path(fs_subjects_dir) / fs_subject / 'mri' / 'T1.mgz' if not fs_t1_path.exists(): raise ValueError( f"Could not find {fs_t1_path}. Consider running FreeSurfer's " f"'recon-all` for subject {fs_subject}.") fs_t1_mgh = nib.load(str(fs_t1_path)) t1_nifti = nib.load(str(t1w_bids_path.fpath)) # Convert to MGH format to access vox2ras method t1_mgh = nib.MGHImage(t1_nifti.dataobj, t1_nifti.affine) # convert to scanner RAS mri_landmarks = apply_trans(t1_mgh.header.get_vox2ras(), mri_landmarks) # convert to FreeSurfer T1 voxels (same scanner RAS as T1) mri_landmarks = apply_trans(fs_t1_mgh.header.get_ras2vox(), mri_landmarks) # now extract transformation matrix and put back to RAS coordinates of MRI vox2ras_tkr = fs_t1_mgh.header.get_vox2ras_tkr() mri_landmarks = apply_trans(vox2ras_tkr, mri_landmarks) mri_landmarks = mri_landmarks * 1e-3 # Get MEG landmarks from the raw file _, ext = _parse_ext(meg_bids_path) if extra_params is None: extra_params = dict() if ext == '.fif': extra_params['allow_maxshield'] = True raw = read_raw_bids(bids_path=meg_bids_path, extra_params=extra_params) if (raw.get_montage() is None or raw.get_montage().get_positions() is None or any([ raw.get_montage().get_positions()[fid_key] is None for fid_key in ('nasion', 'lpa', 'rpa') ])): raise RuntimeError( f'Could not extract fiducial points from ``raw`` file: ' f'{meg_bids_path}\n\n' f'The ``raw`` file SHOULD contain digitization points ' 'for the nasion and left and right pre-auricular points ' 'but none were found') pos = raw.get_montage().get_positions() meg_landmarks = np.asarray((pos['lpa'], pos['nasion'], pos['rpa'])) # Given the two sets of points, fit the transform trans_fitted = fit_matched_points(src_pts=meg_landmarks, tgt_pts=mri_landmarks) trans = mne.transforms.Transform(fro='head', to='mri', trans=trans_fitted) return trans
def test_montage(): """Test making montages.""" tempdir = _TempDir() # no pep8 input_str = [ 'FidNz 0.00000 10.56381 -2.05108\nFidT9 -7.82694 0.45386 -3.76056\n' 'very_very_very_long_name 7.82694 0.45386 -3.76056\nDmy 7.0 0.0 1.0', '// MatLab Sphere coordinates [degrees] Cartesian coordinates\n' # noqa: E501 '// Label Theta Phi Radius X Y Z off sphere surface\n' # noqa: E501 'E1 37.700 -14.000 1.000 0.7677 0.5934 -0.2419 -0.00000000000000011\n' # noqa: E501 'E2 44.600 -0.880 1.000 0.7119 0.7021 -0.0154 0.00000000000000000\n' # noqa: E501 'E3 51.700 11.000 1.000 0.6084 0.7704 0.1908 0.00000000000000000\n' # noqa: E501 'E4 52.700 12.000 1.000 0.7084 0.7504 0.1508 0.00000000000000000', # noqa: E501; dummy line '# ASA electrode file\nReferenceLabel avg\nUnitPosition mm\n' 'NumberPositions= 68\nPositions\n-86.0761 -19.9897 -47.9860\n' '85.7939 -20.0093 -48.0310\n0.0083 86.8110 -39.9830\n' '85 -20 -48\n' 'Labels\nLPA\nRPA\nNz\nDummy\n', '# ASA electrode file\nReferenceLabel avg\nUnitPosition m\n' 'NumberPositions= 68\nPositions\n-.0860761 -.0199897 -.0479860\n' '.0857939 -.0200093 -.0480310\n.0000083 .00868110 -.0399830\n' '.08 -.02 -.04\n' 'Labels\nLPA\nRPA\nNz\nDummy\n', 'Site Theta Phi\nFp1 -92 -72\nFp2 92 72\n' 'very_very_very_long_name -60 -51\n' 'dummy -60 -52\n', '346\n' 'EEG\t F3\t -62.027\t -50.053\t 85\n' 'EEG\t Fz\t 45.608\t 90\t 85\n' 'EEG\t F4\t 62.01\t 50.103\t 85\n' 'EEG\t FCz\t 68.01\t 58.103\t 85\n', 'eeg Fp1 -95.0 -3. -3.\neeg AF7 -1 -1 -3\neeg A3 -2 -2 2\neeg A 0 0 0' ] kinds = ['test.sfp', 'test.csd', 'test_mm.elc', 'test_m.elc', 'test.txt', 'test.elp', 'test.hpts'] for kind, text in zip(kinds, input_str): fname = op.join(tempdir, kind) with open(fname, 'w') as fid: fid.write(text) montage = read_montage(fname) if ".sfp" in kind or ".txt" in kind: assert_true('very_very_very_long_name' in montage.ch_names) assert_equal(len(montage.ch_names), 4) assert_equal(len(montage.ch_names), len(montage.pos)) assert_equal(montage.pos.shape, (4, 3)) assert_equal(montage.kind, op.splitext(kind)[0]) if kind.endswith('csd'): dtype = [('label', 'S4'), ('theta', 'f8'), ('phi', 'f8'), ('radius', 'f8'), ('x', 'f8'), ('y', 'f8'), ('z', 'f8'), ('off_sph', 'f8')] try: table = np.loadtxt(fname, skip_header=2, dtype=dtype) except TypeError: table = np.loadtxt(fname, skiprows=2, dtype=dtype) pos2 = np.c_[table['x'], table['y'], table['z']] assert_array_almost_equal(pos2[:-1, :], montage.pos[:-1, :], 4) if kind.endswith('elc'): # Make sure points are reasonable distance from geometric centroid centroid = np.sum(montage.pos, axis=0) / montage.pos.shape[0] distance_from_centroid = np.apply_along_axis( np.linalg.norm, 1, montage.pos - centroid) assert_array_less(distance_from_centroid, 0.2) assert_array_less(0.01, distance_from_centroid) # Test reading in different letter case. ch_names = ["F3", "FZ", "F4", "FC3", "FCz", "FC4", "C3", "CZ", "C4", "CP3", "CPZ", "CP4", "P3", "PZ", "P4", "O1", "OZ", "O2"] montage = read_montage('standard_1020', ch_names=ch_names) assert_array_equal(ch_names, montage.ch_names) # test transform input_str = """ eeg Fp1 -95.0 -31.0 -3.0 eeg AF7 -81 -59 -3 eeg AF3 -87 -41 28 cardinal 2 -91 0 -42 cardinal 1 0 -91 -42 cardinal 3 0 91 -42 """ kind = 'test_fid.hpts' fname = op.join(tempdir, kind) with open(fname, 'w') as fid: fid.write(input_str) montage = read_montage(op.join(tempdir, 'test_fid.hpts'), transform=True) # check coordinate transformation pos = np.array([-95.0, -31.0, -3.0]) nasion = np.array([-91, 0, -42]) lpa = np.array([0, -91, -42]) rpa = np.array([0, 91, -42]) fids = np.vstack((nasion, lpa, rpa)) trans = get_ras_to_neuromag_trans(fids[0], fids[1], fids[2]) pos = apply_trans(trans, pos) assert_array_equal(montage.pos[0], pos) idx = montage.ch_names.index('2') assert_array_equal(montage.pos[idx, [0, 2]], [0, 0]) idx = montage.ch_names.index('1') assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) idx = montage.ch_names.index('3') assert_array_equal(montage.pos[idx, [1, 2]], [0, 0]) pos = np.array([-95.0, -31.0, -3.0]) montage_fname = op.join(tempdir, 'test_fid.hpts') montage = read_montage(montage_fname, unit='mm') assert_array_equal(montage.pos[0], pos * 1e-3) # test with last info = create_info(montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) _set_montage(info, montage) pos2 = np.array([c['loc'][:3] for c in info['chs']]) assert_array_equal(pos2, montage.pos) assert_equal(montage.ch_names, info['ch_names']) info = create_info( montage.ch_names, 1e3, ['eeg'] * len(montage.ch_names)) evoked = EvokedArray( data=np.zeros((len(montage.ch_names), 1)), info=info, tmin=0) evoked.set_montage(montage) pos3 = np.array([c['loc'][:3] for c in evoked.info['chs']]) assert_array_equal(pos3, montage.pos) assert_equal(montage.ch_names, evoked.info['ch_names']) # Warning should be raised when some EEG are not specified in the montage with warnings.catch_warnings(record=True) as w: info = create_info(montage.ch_names + ['foo', 'bar'], 1e3, ['eeg'] * (len(montage.ch_names) + 2)) _set_montage(info, montage) assert_true(len(w) == 1)