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]]) / 1000. xfm = read_talxfm('sample', subjects_dir) coords_MNI = apply_trans(xfm['trans'], coords) * 1000. mri_head_t, _ = _get_trans(trans_fname, 'mri', 'head', allow_none=False) # 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)
# %% # Let use the Talairach transform computed in the Freesurfer recon-all # to apply the Freesurfer surface RAS ('mri') to MNI ('mni_tal') transform. montage = epochs.get_montage() # first we need a head to mri transform since the data is stored in "head" # coordinates, let's load the mri to head transform and invert it this_subject_dir = op.join(misc_path, 'seeg') head_mri_t = mne.coreg.estimate_head_mri_t('sample_seeg', this_subject_dir) # apply the transform to our montage montage.apply_trans(head_mri_t) # now let's load our Talairach transform and apply it mri_mni_t = mne.read_talxfm('sample_seeg', op.join(misc_path, 'seeg')) montage.apply_trans(mri_mni_t) # mri to mni_tal (MNI Taliarach) # for fsaverage, "mri" and "mni_tal" are equivalent and, since # we want to plot in fsaverage "mri" space, we need use an identity # transform to equate these coordinate frames montage.apply_trans( mne.transforms.Transform(fro='mni_tal', to='mri', trans=np.eye(4))) epochs.set_montage(montage) # %% # Let's check to make sure everything is aligned. # # .. note:: # The most rostral electrode in the temporal lobe is outside the
# # FreeSurfer's MNI affine transformation # -------------------------------------- # In addition to surface-based approaches, FreeSurfer also provides a simple # affine coregistration of each subject's data to the ``fsaverage`` subject. # Let's pick a point for ``sample`` and plot it on the brain: brain = mne.viz.Brain('sample', 'lh', 'white', subjects_dir=subjects_dir, background='w') xyz = np.array([[-55, -10, 35]]) brain.add_foci(xyz, hemi='lh', color='k') brain.show_view('lat') # %% # We can take this point and transform it to MNI space: mri_mni_trans = mne.read_talxfm(subject, subjects_dir) print(mri_mni_trans) xyz_mni = apply_trans(mri_mni_trans, xyz / 1000.) * 1000. print(np.round(xyz_mni, 1)) # %% # And because ``fsaverage`` is special in that it's already in MNI space # (its MRI-to-MNI transform is identity), it should land in the equivalent # anatomical location: brain = mne.viz.Brain('fsaverage', 'lh', 'white', subjects_dir=subjects_dir, background='w') brain.add_foci(xyz_mni, hemi='lh', color='k') brain.show_view('lat')
def test_convert_coordunits(_temp_bids_root, to_frame, to_unit): """Test conversion of coordinate units between voxel and xyz.""" bids_path = _bids_path.copy().update(root=_temp_bids_root) subject = bids_path.subject coordsystem_fpath = bids_path.copy().update(suffix='coordsystem', extension='.json') # read dig bids sensors_mm = read_dig_bids(bids_path, root=_temp_bids_root) img_fpath = sensors_mm.intended_for # check error on unexpected kwarg if to_unit == 'm': sensors_m = convert_coord_units(sensors=sensors_mm, to_unit=to_unit, round=False) np.testing.assert_array_almost_equal(1000. * sensors_m.get_coords(), sensors_mm.get_coords()) with pytest.raises(ValueError, match='Converting coordinates ' 'to km is not accepted.'): convert_coord_units(sensors=sensors_mm, to_unit='km') return elif to_frame in ['tkras', 'mni']: # conversion should not work if starting from xyz coords with pytest.raises(ValueError, match='Converting coordinates ' 'to .* is not accepted.'): convert_coord_units(sensors=sensors_mm, to_unit=to_frame) # conversion should not work if trying to go to mm with pytest.raises(ValueError, match='Converting coordinates ' 'to mm is not accepted.'): convert_coord_space(sensors=sensors_mm, to_frame='mm') # conversion should not work if trying to start # from mm xyz coords with pytest.raises(ValueError, match='Converting coordinates ' 'requires sensor'): convert_coord_space(sensors=sensors_mm, to_frame='tkras') # default settings for the read in coordinates file assert sensors_mm.coord_unit == 'mm' assert sensors_mm.coord_system == 'mri' # test units if to_unit == 'mm': # convert sensors sensors_conv = convert_coord_units(sensors=sensors_mm, to_unit=to_unit, round=False) np.testing.assert_array_almost_equal(sensors_conv.get_coords(), sensors_mm.get_coords()) elif to_unit == 'voxel': pass # convert to voxel should just require convert elec coords if to_frame == 'mri': # convert sensors first to voxels sensors_conv = convert_coord_units(sensors=sensors_mm, to_unit='voxel', round=False) # returning nothing should be the same sensors_mm_mri = convert_coord_space(sensors_mm, to_frame=to_frame) # apply affine yourself to go from mm -> voxels img = nb.load(img_fpath) inv_affine = np.linalg.inv(img.affine) coords = apply_affine(inv_affine, sensors_mm.get_coords().copy()) # round trip should be the same sensors_mm_new = convert_coord_units(sensors=sensors_conv, to_unit='mm', round=False) # the coordinates should match np.testing.assert_array_almost_equal(sensors_mm_new.get_coords(), sensors_mm.get_coords()) elif to_frame == 'fsaverage': # convert to voxels sensors_vox = convert_coord_units(sensors=sensors_mm, to_unit='voxel', round=False) # convert voxels to mni sensors_conv = convert_coord_space(sensors_vox, to_frame=to_frame, subjects_dir=subjects_dir) # load FreeSurfer -> MNI transform (i.e. fsaverage) mni_mri_t = read_talxfm(subject=f'sub-{subject}', subjects_dir=subjects_dir) # go voxels -> tkras coords = apply_affine(mni_mri_t['trans'] * 1000., sensors_vox.get_coords()) # the coordinates should match np.testing.assert_array_almost_equal(sensors_conv.get_coords(), coords) elif to_frame == 'tkras': # convert to voxels sensors_vox = convert_coord_units(sensors=sensors_mm, to_unit='voxel', round=False) # convert voxels to tkras sensors_conv = convert_coord_space(sensors_vox, to_frame=to_frame) # load FreeSurfer MGH file img = nb.load(T1mgz) # go voxels -> tkras coords = apply_affine(img.header.get_vox2ras_tkr(), sensors_vox.get_coords()) # the coordinates should match np.testing.assert_array_almost_equal(sensors_conv.get_coords(), coords) # intended for image path should match assert img_fpath == sensors_conv.intended_for # new coordinate unit should be set if to_frame == 'tkras': sensors_conv.coord_unit == 'mm' else: assert sensors_conv.coord_unit == 'voxel' assert sensors_conv.coord_system == to_frame # the coordinates should match np.testing.assert_array_equal(coords, sensors_conv.get_coords()) assert all([ sensors_conv.__dict__[key] == sensors_mm.__dict__[key] for key in sensors_conv.__dict__.keys() if key not in ['x', 'y', 'z', 'coord_unit', 'coord_system'] ])
# so that ``scanner RAS`` is equivalent to ``surface RAS``. # BIDS requires that template data be in ``scanner RAS`` so for # coordinate frames where this is not the case, the coordinates # must be converted (see below). # ensure the output path doesn't contain any leftover files from previous # tests and example runs if op.exists(bids_root): shutil.rmtree(bids_root) # load our raw data again raw = mne.io.read_raw_fif(op.join(misc_path, 'seeg', 'sample_seeg_ieeg.fif')) raw.info['line_freq'] = 60 # specify power line frequency as required by BIDS # get Talairach transform mri_mni_t = mne.read_talxfm('sample_seeg', subjects_dir) # %% # Now let's convert the montage to MNI Talairach ("mni_tal"). montage = raw.get_montage() montage.apply_trans(trans) # head->mri montage.apply_trans(mri_mni_t) # write to BIDS, this time with a template coordinate system write_raw_bids(raw, bids_path, anonymize=dict(daysback=40000), montage=montage, overwrite=True) # read in the BIDS dataset
def _handle_mni_trans( elec_coords, img_fpath: Union[str, Path], subjects_dir: Union[str, Path, None], revert_mni: bool, verbose: bool = True, ): """Handle FreeSurfer MRI <-> MNI voxels.""" entities = get_entities_from_fname(img_fpath) subject = entities.get("subject") if subject is None: raise RuntimeError( f"Could not interpret the subject from " f"IntendedFor Image filepath {img_fpath}. " f"This file path is possibly not named " f"according to BIDS." ) # Try to get Norig and Torig # (i.e. vox_ras_t and vox_mri_t, respectively) subj_fs_dir = Path(subjects_dir) / subject # type: ignore if not op.exists(subj_fs_dir): subject = f"sub-{subject}" subj_fs_dir = Path(subjects_dir) / subject # type: ignore path = op.join(subj_fs_dir, "mri", "orig.mgz") # type: ignore if not op.isfile(path): path = op.join(subj_fs_dir, "mri", "T1.mgz") # type: ignore if not op.isfile(path): raise IOError("mri not found: %s" % path) _, _, mri_ras_t, _, _ = _read_mri_info(path, units="mm") # get the intended affine transform from vox -> RAS img = nb.load(img_fpath) # voxel -> xyz intended_affine = img.affine # check that vox2ras is the same as our affine # if not np.all(np.isclose(intended_affine, mri_ras_t['trans'], # atol=1e-6)): # print(np.isclose(intended_affine, mri_ras_t['trans'], # atol=1e-6)) # print(intended_affine) # print(mri_ras_t['trans']) # raise RuntimeError( # f"You are trying to convert data " # f"to MNI coordinates for {img_fpath}, " # f"but this does not correspond to the " # f"original T1.mgz file of FreeSurfer. " # f"This is a limitation..." # ) # read mri voxel of T1.mgz -> MNI tal xfm mri_mni_t = read_talxfm(subject=subject, subjects_dir=subjects_dir, verbose=verbose) # make sure these are in mm mri_to_mni_aff = mri_mni_t["trans"] * 1000.0 # if reversing MNI, invert affine transform # else keep the same as read in if revert_mni: affine = np.linalg.inv(mri_to_mni_aff) else: affine = mri_to_mni_aff # first convert to voxels elec_coords = apply_affine(affine, elec_coords) return elec_coords
# %% # Let use the Talairach transform computed in the Freesurfer recon-all # to apply the Freesurfer surface RAS ('mri') to MNI ('mni_tal') transform. montage = epochs.get_montage() # first we need a head to mri transform since the data is stored in "head" # coordinates, let's load the mri to head transform and invert it this_subject_dir = misc_path / 'seeg' head_mri_t = mne.coreg.estimate_head_mri_t('sample_seeg', this_subject_dir) # apply the transform to our montage montage.apply_trans(head_mri_t) # now let's load our Talairach transform and apply it mri_mni_t = mne.read_talxfm('sample_seeg', misc_path / 'seeg') montage.apply_trans(mri_mni_t) # mri to mni_tal (MNI Taliarach) # for fsaverage, "mri" and "mni_tal" are equivalent and, since # we want to plot in fsaverage "mri" space, we need use an identity # transform to equate these coordinate frames montage.apply_trans( mne.transforms.Transform(fro='mni_tal', to='mri', trans=np.eye(4))) epochs.set_montage(montage) # %% # Let's check to make sure everything is aligned. # # .. note:: # The most rostral electrode in the temporal lobe is outside the