Spaces:
Running
Running
| # Authors: The MNE-Python contributors. | |
| # License: BSD-3-Clause | |
| # Copyright the MNE-Python contributors. | |
| # Many of the computations in this code were derived from Matti Hämäläinen's | |
| # C code. | |
| import os | |
| import numpy as np | |
| from scipy.sparse import csr_array | |
| from ._fiff.constants import FIFF | |
| from ._fiff.open import fiff_open | |
| from ._fiff.tag import find_tag | |
| from ._fiff.tree import dir_tree_find | |
| from ._fiff.write import ( | |
| end_block, | |
| start_and_end_file, | |
| start_block, | |
| write_float_sparse_rcs, | |
| write_int, | |
| write_string, | |
| ) | |
| from .fixes import _eye_array | |
| from .surface import ( | |
| _compute_nearest, | |
| _find_nearest_tri_pts, | |
| _get_tri_supp_geom, | |
| _normalize_vectors, | |
| _triangle_neighbors, | |
| read_surface, | |
| ) | |
| from .utils import get_subjects_dir, logger, verbose, warn | |
| def read_morph_map( | |
| subject_from, subject_to, subjects_dir=None, xhemi=False, verbose=None | |
| ): | |
| """Read morph map. | |
| Morph maps can be generated with mne_make_morph_maps. If one isn't | |
| available, it will be generated automatically and saved to the | |
| ``subjects_dir/morph_maps`` directory. | |
| Parameters | |
| ---------- | |
| subject_from : str | |
| Name of the original subject as named in the ``SUBJECTS_DIR``. | |
| subject_to : str | |
| Name of the subject on which to morph as named in the ``SUBJECTS_DIR``. | |
| subjects_dir : path-like | |
| Path to ``SUBJECTS_DIR`` is not set in the environment. | |
| xhemi : bool | |
| Morph across hemisphere. Currently only implemented for | |
| ``subject_to == subject_from``. See notes of | |
| :func:`mne.compute_source_morph`. | |
| %(verbose)s | |
| Returns | |
| ------- | |
| left_map, right_map : ~scipy.sparse.csr_array | |
| The morph maps for the 2 hemispheres. | |
| """ | |
| subjects_dir = get_subjects_dir(subjects_dir, raise_error=True) | |
| # First check for morph-map dir existence | |
| mmap_dir = subjects_dir / "morph-maps" | |
| if not mmap_dir.is_dir(): | |
| try: | |
| os.mkdir(mmap_dir) | |
| except Exception: | |
| warn(f'Could not find or make morph map directory "{mmap_dir}"') | |
| # filename components | |
| if xhemi: | |
| if subject_to != subject_from: | |
| raise NotImplementedError( | |
| "Morph-maps between hemispheres are currently only " | |
| "implemented for subject_to == subject_from" | |
| ) | |
| map_name_temp = "%s-%s-xhemi" | |
| log_msg = "Creating morph map %s -> %s xhemi" | |
| else: | |
| map_name_temp = "%s-%s" | |
| log_msg = "Creating morph map %s -> %s" | |
| map_names = [ | |
| map_name_temp % (subject_from, subject_to), | |
| map_name_temp % (subject_to, subject_from), | |
| ] | |
| # find existing file | |
| fname = None | |
| for map_name in map_names: | |
| fname = mmap_dir / f"{map_name}-morph.fif" | |
| if fname.exists(): | |
| return _read_morph_map(fname, subject_from, subject_to) | |
| # if file does not exist, make it | |
| logger.info( | |
| f'Morph map "{fname}" does not exist, creating it and saving it to disk' | |
| ) | |
| logger.info(log_msg % (subject_from, subject_to)) | |
| mmap_1 = _make_morph_map(subject_from, subject_to, subjects_dir, xhemi) | |
| if subject_to == subject_from: | |
| mmap_2 = None | |
| else: | |
| logger.info(log_msg % (subject_to, subject_from)) | |
| mmap_2 = _make_morph_map(subject_to, subject_from, subjects_dir, xhemi) | |
| _write_morph_map(fname, subject_from, subject_to, mmap_1, mmap_2) | |
| return mmap_1 | |
| def _read_morph_map(fname, subject_from, subject_to): | |
| """Read a morph map from disk.""" | |
| f, tree, _ = fiff_open(fname) | |
| with f as fid: | |
| # Locate all maps | |
| maps = dir_tree_find(tree, FIFF.FIFFB_MNE_MORPH_MAP) | |
| if len(maps) == 0: | |
| raise ValueError("Morphing map data not found") | |
| # Find the correct ones | |
| left_map = None | |
| right_map = None | |
| for m in maps: | |
| tag = find_tag(fid, m, FIFF.FIFF_MNE_MORPH_MAP_FROM) | |
| if tag.data == subject_from: | |
| tag = find_tag(fid, m, FIFF.FIFF_MNE_MORPH_MAP_TO) | |
| if tag.data == subject_to: | |
| # Names match: which hemishere is this? | |
| tag = find_tag(fid, m, FIFF.FIFF_MNE_HEMI) | |
| if tag.data == FIFF.FIFFV_MNE_SURF_LEFT_HEMI: | |
| tag = find_tag(fid, m, FIFF.FIFF_MNE_MORPH_MAP) | |
| left_map = tag.data | |
| logger.info(" Left-hemisphere map read.") | |
| elif tag.data == FIFF.FIFFV_MNE_SURF_RIGHT_HEMI: | |
| tag = find_tag(fid, m, FIFF.FIFF_MNE_MORPH_MAP) | |
| right_map = tag.data | |
| logger.info(" Right-hemisphere map read.") | |
| if left_map is None or right_map is None: | |
| raise ValueError(f"Could not find both hemispheres in {fname}") | |
| return left_map, right_map | |
| def _write_morph_map(fname, subject_from, subject_to, mmap_1, mmap_2): | |
| """Write a morph map to disk.""" | |
| try: | |
| with start_and_end_file(fname) as fid: | |
| _write_morph_map_(fid, subject_from, subject_to, mmap_1, mmap_2) | |
| except Exception as exp: | |
| warn(f'Could not write morph-map file "{fname}" (error: {exp})') | |
| def _write_morph_map_(fid, subject_from, subject_to, mmap_1, mmap_2): | |
| assert len(mmap_1) == 2 | |
| hemis = [FIFF.FIFFV_MNE_SURF_LEFT_HEMI, FIFF.FIFFV_MNE_SURF_RIGHT_HEMI] | |
| for m, hemi in zip(mmap_1, hemis): | |
| start_block(fid, FIFF.FIFFB_MNE_MORPH_MAP) | |
| write_string(fid, FIFF.FIFF_MNE_MORPH_MAP_FROM, subject_from) | |
| write_string(fid, FIFF.FIFF_MNE_MORPH_MAP_TO, subject_to) | |
| write_int(fid, FIFF.FIFF_MNE_HEMI, hemi) | |
| write_float_sparse_rcs(fid, FIFF.FIFF_MNE_MORPH_MAP, m) | |
| end_block(fid, FIFF.FIFFB_MNE_MORPH_MAP) | |
| # don't write mmap_2 if it is identical (subject_to == subject_from) | |
| if mmap_2 is not None: | |
| assert len(mmap_2) == 2 | |
| for m, hemi in zip(mmap_2, hemis): | |
| start_block(fid, FIFF.FIFFB_MNE_MORPH_MAP) | |
| write_string(fid, FIFF.FIFF_MNE_MORPH_MAP_FROM, subject_to) | |
| write_string(fid, FIFF.FIFF_MNE_MORPH_MAP_TO, subject_from) | |
| write_int(fid, FIFF.FIFF_MNE_HEMI, hemi) | |
| write_float_sparse_rcs(fid, FIFF.FIFF_MNE_MORPH_MAP, m) | |
| end_block(fid, FIFF.FIFFB_MNE_MORPH_MAP) | |
| def _make_morph_map(subject_from, subject_to, subjects_dir, xhemi): | |
| """Construct morph map from one subject to another. | |
| Note that this is close, but not exactly like the C version. | |
| For example, parts are more accurate due to double precision, | |
| so expect some small morph-map differences! | |
| Note: This seems easily parallelizable, but the overhead | |
| of pickling all the data structures makes it less efficient | |
| than just running on a single core :( | |
| """ | |
| subjects_dir = get_subjects_dir(subjects_dir) | |
| if xhemi: | |
| reg = "%s.sphere.left_right" | |
| hemis = (("lh", "rh"), ("rh", "lh")) | |
| else: | |
| reg = "%s.sphere.reg" | |
| hemis = (("lh", "lh"), ("rh", "rh")) | |
| return [ | |
| _make_morph_map_hemi( | |
| subject_from, subject_to, subjects_dir, reg % hemi_from, reg % hemi_to | |
| ) | |
| for hemi_from, hemi_to in hemis | |
| ] | |
| def _make_morph_map_hemi(subject_from, subject_to, subjects_dir, reg_from, reg_to): | |
| """Construct morph map for one hemisphere.""" | |
| # add speedy short-circuit for self-maps | |
| if subject_from == subject_to and reg_from == reg_to: | |
| fname = subjects_dir / subject_from / "surf" / reg_from | |
| n_pts = len(read_surface(fname, verbose=False)[0]) | |
| return _eye_array(n_pts, format="csr") | |
| # load surfaces and normalize points to be on unit sphere | |
| fname = subjects_dir / subject_from / "surf" / reg_from | |
| from_rr, from_tri = read_surface(fname, verbose=False) | |
| fname = subjects_dir / subject_to / "surf" / reg_to | |
| to_rr = read_surface(fname, verbose=False)[0] | |
| _normalize_vectors(from_rr) | |
| _normalize_vectors(to_rr) | |
| # from surface: get nearest neighbors, find triangles for each vertex | |
| nn_pts_idx = _compute_nearest(from_rr, to_rr, method="KDTree") | |
| from_pt_tris = _triangle_neighbors(from_tri, len(from_rr)) | |
| from_pt_tris = [from_pt_tris[pt_idx].astype(int) for pt_idx in nn_pts_idx] | |
| from_pt_lens = np.cumsum([0] + [len(x) for x in from_pt_tris]) | |
| from_pt_tris = np.concatenate(from_pt_tris) | |
| assert from_pt_tris.ndim == 1 | |
| assert from_pt_lens[-1] == len(from_pt_tris) | |
| # find triangle in which point lies and assoc. weights | |
| tri_inds = [] | |
| weights = [] | |
| tri_geom = _get_tri_supp_geom(dict(rr=from_rr, tris=from_tri)) | |
| weights, tri_inds = _find_nearest_tri_pts( | |
| to_rr, from_pt_tris, from_pt_lens, run_all=False, reproject=False, **tri_geom | |
| ) | |
| nn_idx = from_tri[tri_inds] | |
| weights = np.array(weights) | |
| row_ind = np.repeat(np.arange(len(to_rr)), 3) | |
| this_map = csr_array( | |
| (weights.ravel(), (row_ind, nn_idx.ravel())), shape=(len(to_rr), len(from_rr)) | |
| ) | |
| return this_map | |