248 lines
8.8 KiB
Python
248 lines
8.8 KiB
Python
# 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
|
|
|
|
|
|
@verbose
|
|
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
|