Aluode/PerceptionLabPortable
0
1"""Freesurfer handling functions."""2 3# Authors: The MNE-Python contributors.4# License: BSD-3-Clause5# Copyright the MNE-Python contributors.6 7import os.path as op8from gzip import GzipFile9from pathlib import Path10 11import numpy as np12 13from ._fiff.constants import FIFF14from ._fiff.meas_info import read_fiducials15from .surface import _read_mri_surface, read_surface16from .transforms import (17 Transform,18 _ensure_trans,19 apply_trans,20 combine_transforms,21 invert_transform,22 read_ras_mni_t,23)24from .utils import (25 _check_fname,26 _check_option,27 _import_nibabel,28 _validate_type,29 get_subjects_dir,30 logger,31 verbose,32)33 34 35def _check_subject_dir(subject, subjects_dir):36 """Check that the Freesurfer subject directory is as expected."""37 subjects_dir = Path(get_subjects_dir(subjects_dir, raise_error=True))38 for img_name in ("T1", "brain", "aseg"):39 if not (subjects_dir / subject / "mri" / f"{img_name}.mgz").is_file():40 raise ValueError(41 "Freesurfer recon-all subject folder "42 "is incorrect or improperly formatted, "43 f"got {subjects_dir / subject}"44 )45 return subjects_dir / subject46 47 48def _get_aseg(aseg, subject, subjects_dir):49 """Check that the anatomical segmentation file exists and load it."""50 nib = _import_nibabel("load aseg")51 subjects_dir = Path(get_subjects_dir(subjects_dir, raise_error=True))52 if aseg == "auto": # use aparc+aseg if auto53 aseg = _check_fname(54 subjects_dir / subject / "mri" / "aparc+aseg.mgz",55 overwrite="read",56 must_exist=False,57 )58 if not aseg: # if doesn't exist use wmparc59 aseg = subjects_dir / subject / "mri" / "wmparc.mgz"60 else:61 aseg = subjects_dir / subject / "mri" / f"{aseg}.mgz"62 _check_fname(aseg, overwrite="read", must_exist=True)63 aseg = nib.load(aseg)64 aseg_data = np.array(aseg.dataobj)65 return aseg, aseg_data66 67 68def _reorient_image(img, axcodes="RAS"):69 """Reorient an image to a given orientation.70 71 Parameters72 ----------73 img : instance of SpatialImage74 The MRI image.75 axcodes : tuple | str76 The axis codes specifying the orientation, e.g. "RAS".77 See :func:`nibabel.orientations.aff2axcodes`.78 79 Returns80 -------81 img_data : ndarray82 The reoriented image data.83 vox_ras_t : ndarray84 The new transform from the new voxels to surface RAS.85 86 Notes87 -----88 .. versionadded:: 0.2489 """90 nib = _import_nibabel("reorient MRI image")91 orig_data = np.array(img.dataobj).astype(np.float32)92 # reorient data to RAS93 ornt = nib.orientations.axcodes2ornt(94 nib.orientations.aff2axcodes(img.affine)95 ).astype(int)96 ras_ornt = nib.orientations.axcodes2ornt(axcodes)97 ornt_trans = nib.orientations.ornt_transform(ornt, ras_ornt)98 img_data = nib.orientations.apply_orientation(orig_data, ornt_trans)99 orig_mgh = nib.MGHImage(orig_data, img.affine)100 aff_trans = nib.orientations.inv_ornt_aff(ornt_trans, img.shape)101 vox_ras_t = np.dot(orig_mgh.header.get_vox2ras_tkr(), aff_trans)102 return img_data, vox_ras_t103 104 105def _mri_orientation(orientation):106 """Get MRI orientation information from an image.107 108 Parameters109 ----------110 orientation : str111 Orientation that you want. Can be "axial", "sagittal", or "coronal".112 113 Returns114 -------115 axis : int116 The dimension of the axis to take slices over when plotting.117 x : int118 The dimension of the x axis.119 y : int120 The dimension of the y axis.121 122 Notes123 -----124 .. versionadded:: 0.21125 .. versionchanged:: 0.24126 """127 _check_option("orientation", orientation, ("coronal", "axial", "sagittal"))128 axis = dict(coronal=1, axial=2, sagittal=0)[orientation]129 x, y = sorted(set([0, 1, 2]).difference(set([axis])))130 return axis, x, y131 132 133def _get_mri_info_data(mri, data):134 # Read the segmentation data using nibabel135 if data:136 _import_nibabel("load MRI atlas data")137 out = dict()138 _, out["vox_mri_t"], out["mri_ras_t"], dims, _, mgz = _read_mri_info(139 mri, return_img=True140 )141 out.update(142 mri_width=dims[0], mri_height=dims[1], mri_depth=dims[1], mri_volume_name=mri143 )144 if data:145 assert mgz is not None146 out["mri_vox_t"] = invert_transform(out["vox_mri_t"])147 out["data"] = np.asarray(mgz.dataobj)148 return out149 150 151def _get_mgz_header(fname):152 """Adapted from nibabel to quickly extract header info."""153 fname = _check_fname(fname, overwrite="read", must_exist=True, name="MRI image")154 if fname.suffix != ".mgz":155 raise OSError("Filename must end with .mgz")156 header_dtd = [157 ("version", ">i4"),158 ("dims", ">i4", (4,)),159 ("type", ">i4"),160 ("dof", ">i4"),161 ("goodRASFlag", ">i2"),162 ("delta", ">f4", (3,)),163 ("Mdc", ">f4", (3, 3)),164 ("Pxyz_c", ">f4", (3,)),165 ]166 header_dtype = np.dtype(header_dtd)167 with GzipFile(fname, "rb") as fid:168 hdr_str = fid.read(header_dtype.itemsize)169 header = np.ndarray(shape=(), dtype=header_dtype, buffer=hdr_str)170 # dims171 dims = header["dims"].astype(int)172 dims = dims[:3] if len(dims) == 4 else dims173 # vox2ras_tkr174 delta = header["delta"]175 ds = np.array(delta, float)176 ns = np.array(dims * ds) / 2.0177 v2rtkr = np.array(178 [179 [-ds[0], 0, 0, ns[0]],180 [0, 0, ds[2], -ns[2]],181 [0, -ds[1], 0, ns[1]],182 [0, 0, 0, 1],183 ],184 dtype=np.float32,185 )186 # ras2vox187 d = np.diag(delta)188 pcrs_c = dims / 2.0189 Mdc = header["Mdc"].T190 pxyz_0 = header["Pxyz_c"] - np.dot(Mdc, np.dot(d, pcrs_c))191 M = np.eye(4, 4)192 M[0:3, 0:3] = np.dot(Mdc, d)193 M[0:3, 3] = pxyz_0.T194 header = dict(dims=dims, vox2ras_tkr=v2rtkr, vox2ras=M, zooms=header["delta"])195 return header196 197 198def _get_atlas_values(vol_info, rr):199 # Transform MRI coordinates (where our surfaces live) to voxels200 rr_vox = apply_trans(vol_info["mri_vox_t"], rr)201 good = (202 (rr_vox >= -0.5) & (rr_vox < np.array(vol_info["data"].shape, int) - 0.5)203 ).all(-1)204 idx = np.round(rr_vox[good].T).astype(np.int64)205 values = np.full(rr.shape[0], np.nan)206 values[good] = vol_info["data"][tuple(idx)]207 return values208 209 210def get_volume_labels_from_aseg(mgz_fname, return_colors=False, atlas_ids=None):211 """Return a list of names and colors of segmented volumes.212 213 Parameters214 ----------215 mgz_fname : path-like216 Filename to read. Typically ``aseg.mgz`` or some variant in the217 freesurfer pipeline.218 return_colors : bool219 If True returns also the labels colors.220 atlas_ids : dict | None221 A lookup table providing a mapping from region names (str) to ID values222 (int). Can be None to use the standard Freesurfer LUT.223 224 .. versionadded:: 0.21.0225 226 Returns227 -------228 label_names : list of str229 The names of segmented volumes included in this mgz file.230 label_colors : list of str231 The RGB colors of the labels included in this mgz file.232 233 See Also234 --------235 read_freesurfer_lut236 237 Notes238 -----239 .. versionchanged:: 0.21.0240 The label names are now sorted in the same order as their corresponding241 values in the MRI file.242 243 .. versionadded:: 0.9.0244 """245 nib = _import_nibabel("load MRI atlas data")246 mgz_fname = _check_fname(247 mgz_fname, overwrite="read", must_exist=True, name="mgz_fname"248 )249 atlas = nib.load(mgz_fname)250 data = np.asarray(atlas.dataobj) # don't need float here251 want = np.unique(data)252 if atlas_ids is None:253 atlas_ids, colors = read_freesurfer_lut()254 elif return_colors:255 raise ValueError("return_colors must be False if atlas_ids are provided")256 # restrict to the ones in the MRI, sorted by label name257 keep = np.isin(list(atlas_ids.values()), want)258 keys = sorted(259 (key for ki, key in enumerate(atlas_ids.keys()) if keep[ki]),260 key=lambda x: atlas_ids[x],261 )262 if return_colors:263 colors = [colors[k] for k in keys]264 out = keys, colors265 else:266 out = keys267 return out268 269 270##############################################################################271# Head to MRI volume conversion272 273 274@verbose275def head_to_mri(276 pos,277 subject,278 mri_head_t,279 subjects_dir=None,280 *,281 kind="mri",282 unscale=False,283 verbose=None,284):285 """Convert pos from head coordinate system to MRI ones.286 287 Parameters288 ----------289 pos : array, shape (n_pos, 3)290 The coordinates (in m) in head coordinate system.291 %(subject)s292 mri_head_t : instance of Transform293 MRI<->Head coordinate transformation.294 %(subjects_dir)s295 kind : str296 The MRI coordinate frame kind, can be ``'mri'`` (default) for297 FreeSurfer surface RAS or ``'ras'`` (default in 1.2) to use MRI RAS298 (scanner RAS).299 300 .. versionadded:: 1.2301 unscale : bool302 For surrogate MRIs (e.g., scaled using ``mne coreg``), if True303 (default False), use the MRI scaling parameters to obtain points in304 the original/surrogate subject's MRI space.305 306 .. versionadded:: 1.2307 %(verbose)s308 309 Returns310 -------311 coordinates : array, shape (n_pos, 3)312 The MRI RAS coordinates (in mm) of pos.313 314 Notes315 -----316 This function requires nibabel.317 """318 from .coreg import read_mri_cfg319 320 _validate_type(kind, str, "kind")321 _check_option("kind", kind, ("ras", "mri"))322 subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)323 t1_fname = subjects_dir / subject / "mri" / "T1.mgz"324 head_mri_t = _ensure_trans(mri_head_t, "head", "mri")325 if kind == "ras":326 _, _, mri_ras_t, _, _ = _read_mri_info(t1_fname)327 head_ras_t = combine_transforms(head_mri_t, mri_ras_t, "head", "ras")328 head_dest_t = head_ras_t329 else:330 assert kind == "mri"331 head_dest_t = head_mri_t332 pos_dest = apply_trans(head_dest_t, pos)333 # unscale if requested334 if unscale:335 params = read_mri_cfg(subject, subjects_dir)336 pos_dest /= params["scale"]337 pos_dest *= 1e3 # mm338 return pos_dest339 340 341##############################################################################342# Surface to MNI conversion343 344 345@verbose346def vertex_to_mni(vertices, hemis, subject, subjects_dir=None, verbose=None):347 """Convert the array of vertices for a hemisphere to MNI coordinates.348 349 Parameters350 ----------351 vertices : int, or list of int352 Vertex number(s) to convert.353 hemis : int, or list of int354 Hemisphere(s) the vertices belong to.355 %(subject)s356 subjects_dir : str, or None357 Path to ``SUBJECTS_DIR`` if it is not set in the environment.358 %(verbose)s359 360 Returns361 -------362 coordinates : array, shape (n_vertices, 3)363 The MNI coordinates (in mm) of the vertices.364 """365 singleton = False366 if not isinstance(vertices, list) and not isinstance(vertices, np.ndarray):367 singleton = True368 vertices = [vertices]369 370 if not isinstance(hemis, list) and not isinstance(hemis, np.ndarray):371 hemis = [hemis] * len(vertices)372 373 if not len(hemis) == len(vertices):374 raise ValueError("hemi and vertices must match in length")375 376 subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)377 378 surfs = [subjects_dir / subject / "surf" / f"{h}.white" for h in ["lh", "rh"]]379 380 # read surface locations in MRI space381 rr = [read_surface(s)[0] for s in surfs]382 383 # take point locations in MRI space and convert to MNI coordinates384 xfm = read_talxfm(subject, subjects_dir)385 xfm["trans"][:3, 3] *= 1000.0 # m->mm386 data = np.array([rr[h][v, :] for h, v in zip(hemis, vertices)])387 if singleton:388 data = data[0]389 return apply_trans(xfm["trans"], data)390 391 392##############################################################################393# Volume to MNI conversion394 395 396@verbose397def head_to_mni(pos, subject, mri_head_t, subjects_dir=None, verbose=None):398 """Convert pos from head coordinate system to MNI ones.399 400 Parameters401 ----------402 pos : array, shape (n_pos, 3)403 The coordinates (in m) in head coordinate system.404 %(subject)s405 mri_head_t : instance of Transform406 MRI<->Head coordinate transformation.407 %(subjects_dir)s408 %(verbose)s409 410 Returns411 -------412 coordinates : array, shape (n_pos, 3)413 The MNI coordinates (in mm) of pos.414 415 Notes416 -----417 This function requires either nibabel.418 """419 subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)420 421 # before we go from head to MRI (surface RAS)422 head_mni_t = combine_transforms(423 _ensure_trans(mri_head_t, "head", "mri"),424 read_talxfm(subject, subjects_dir),425 "head",426 "mni_tal",427 )428 return apply_trans(head_mni_t, pos) * 1000.0429 430 431@verbose432def get_mni_fiducials(subject, subjects_dir=None, verbose=None):433 """Estimate fiducials for a subject.434 435 Parameters436 ----------437 %(subject)s438 %(subjects_dir)s439 %(verbose)s440 441 Returns442 -------443 fids_mri : list444 List of estimated fiducials (each point in a dict), in the order445 LPA, nasion, RPA.446 447 Notes448 -----449 This takes the ``fsaverage-fiducials.fif`` file included with MNE—which450 contain the LPA, nasion, and RPA for the ``fsaverage`` subject—and451 transforms them to the given FreeSurfer subject's MRI space.452 The MRI of ``fsaverage`` is already in MNI Talairach space, so applying453 the inverse of the given subject's MNI Talairach affine transformation454 (``$SUBJECTS_DIR/$SUBJECT/mri/transforms/talairach.xfm``) is used455 to estimate the subject's fiducial locations.456 457 For more details about the coordinate systems and transformations involved,458 see https://surfer.nmr.mgh.harvard.edu/fswiki/CoordinateSystems and459 :ref:`tut-source-alignment`.460 """461 # Eventually we might want to allow using the MNI Talairach with-skull462 # transformation rather than the standard brain-based MNI Talaranch463 # transformation, and/or project the points onto the head surface464 # (if available).465 fname_fids_fs = (466 Path(__file__).parent / "data" / "fsaverage" / "fsaverage-fiducials.fif"467 )468 469 # Read fsaverage fiducials file and subject Talairach.470 fids, coord_frame = read_fiducials(fname_fids_fs)471 assert coord_frame == FIFF.FIFFV_COORD_MRI472 if subject == "fsaverage":473 return fids # special short-circuit for fsaverage474 mni_mri_t = invert_transform(read_talxfm(subject, subjects_dir))475 for f in fids:476 f["r"] = apply_trans(mni_mri_t, f["r"])477 return fids478 479 480@verbose481def estimate_head_mri_t(subject, subjects_dir=None, verbose=None):482 """Estimate the head->mri transform from fsaverage fiducials.483 484 A subject's fiducials can be estimated given a Freesurfer ``recon-all``485 by transforming ``fsaverage`` fiducials using the inverse Talairach486 transform, see :func:`mne.coreg.get_mni_fiducials`.487 488 Parameters489 ----------490 %(subject)s491 %(subjects_dir)s492 %(verbose)s493 494 Returns495 -------496 %(trans_not_none)s497 """498 from .channels.montage import compute_native_head_t, make_dig_montage499 500 subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)501 lpa, nasion, rpa = get_mni_fiducials(subject, subjects_dir)502 montage = make_dig_montage(503 lpa=lpa["r"], nasion=nasion["r"], rpa=rpa["r"], coord_frame="mri"504 )505 return invert_transform(compute_native_head_t(montage))506 507 508def _get_affine_from_lta_info(lines):509 """Get the vox2ras affine from lta file info."""510 volume_data = np.loadtxt([line.split("=")[1] for line in lines])511 # get the size of the volume (number of voxels), slice resolution.512 # the matrix of directional cosines and the ras at the center of the bore513 dims, deltas, dir_cos, center_ras = (514 volume_data[0],515 volume_data[1],516 volume_data[2:5],517 volume_data[5],518 )519 dir_cos_delta = dir_cos.T * deltas520 vol_center = (dir_cos_delta @ dims[:3]) / 2521 affine = np.eye(4)522 affine[:3, :3] = dir_cos_delta523 affine[:3, 3] = center_ras - vol_center524 return affine525 526 527@verbose528def read_lta(fname, verbose=None):529 """Read a Freesurfer linear transform array file.530 531 Parameters532 ----------533 fname : path-like534 The transform filename.535 %(verbose)s536 537 Returns538 -------539 affine : ndarray540 The affine transformation described by the lta file.541 """542 _check_fname(fname, "read", must_exist=True)543 with open(fname) as fid:544 lines = fid.readlines()545 # 0 is linear vox2vox, 1 is linear ras2ras546 trans_type = int(lines[0].split("=")[1].strip()[0])547 assert trans_type in (0, 1)548 affine = np.loadtxt(lines[5:9])549 if trans_type == 1:550 return affine551 552 src_affine = _get_affine_from_lta_info(lines[12:18])553 dst_affine = _get_affine_from_lta_info(lines[21:27])554 555 # don't compute if src and dst are already identical556 if np.allclose(src_affine, dst_affine):557 return affine558 559 ras2ras = src_affine @ np.linalg.inv(affine) @ np.linalg.inv(dst_affine)560 affine = np.linalg.inv(np.linalg.inv(src_affine) @ ras2ras @ src_affine)561 return affine562 563 564@verbose565def read_talxfm(subject, subjects_dir=None, verbose=None):566 """Compute MRI-to-MNI transform from FreeSurfer talairach.xfm file.567 568 Parameters569 ----------570 %(subject)s571 %(subjects_dir)s572 %(verbose)s573 574 Returns575 -------576 mri_mni_t : instance of Transform577 The affine transformation from MRI to MNI space for the subject.578 """579 # Adapted from freesurfer m-files. Altered to deal with Norig580 # and Torig correctly581 subjects_dir = get_subjects_dir(subjects_dir)582 # Setup the RAS to MNI transform583 ras_mni_t = read_ras_mni_t(subject, subjects_dir)584 ras_mni_t["trans"][:3, 3] /= 1000.0 # mm->m585 586 # We want to get from Freesurfer surface RAS ('mri') to MNI ('mni_tal').587 # This file only gives us RAS (non-zero origin) ('ras') to MNI ('mni_tal').588 # Se we need to get the ras->mri transform from the MRI headers.589 590 # To do this, we get Norig and Torig591 # (i.e. vox_ras_t and vox_mri_t, respectively)592 path = subjects_dir / subject / "mri" / "orig.mgz"593 if not path.is_file():594 path = subjects_dir / subject / "mri" / "T1.mgz"595 if not path.is_file():596 raise OSError(f"mri not found: {path}")597 _, _, mri_ras_t, _, _ = _read_mri_info(path)598 mri_mni_t = combine_transforms(mri_ras_t, ras_mni_t, "mri", "mni_tal")599 return mri_mni_t600 601 602def _check_mri(mri, subject, subjects_dir) -> str:603 """Check whether an mri exists in the Freesurfer subject directory."""604 _validate_type(mri, "path-like", mri)605 mri = Path(mri)606 if mri.is_file() and mri.name != mri:607 return str(mri)608 elif not mri.is_file():609 if subject is None:610 raise FileNotFoundError(611 f"MRI file {mri!r} not found and no subject provided."612 )613 subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)614 mri = subjects_dir / subject / "mri" / mri615 if not mri.is_file():616 raise FileNotFoundError(617 f"MRI file {mri!r} not found in the subjects directory "618 f"{subjects_dir!r} for subject {subject}."619 )620 if mri.name == mri:621 raise OSError(622 f"Ambiguous filename - found {mri!r} in current folder. "623 "If this is correct prefix name with relative or absolute path."624 )625 return str(mri)626 627 628def _read_mri_info(path, units="m", return_img=False, use_nibabel=False):629 # This is equivalent but 100x slower, so only use nibabel if we need to630 # (later):631 if use_nibabel:632 nib = _import_nibabel()633 hdr = nib.load(path).header634 n_orig = hdr.get_vox2ras()635 t_orig = hdr.get_vox2ras_tkr()636 dims = hdr.get_data_shape()637 zooms = hdr.get_zooms()[:3]638 else:639 hdr = _get_mgz_header(path)640 n_orig = hdr["vox2ras"]641 t_orig = hdr["vox2ras_tkr"]642 dims = hdr["dims"]643 zooms = hdr["zooms"]644 645 # extract the MRI_VOXEL to RAS (non-zero origin) transform646 vox_ras_t = Transform("mri_voxel", "ras", n_orig)647 648 # extract the MRI_VOXEL to MRI transform649 vox_mri_t = Transform("mri_voxel", "mri", t_orig)650 651 # construct the MRI to RAS (non-zero origin) transform652 mri_ras_t = combine_transforms(invert_transform(vox_mri_t), vox_ras_t, "mri", "ras")653 654 assert units in ("m", "mm")655 if units == "m":656 conv = np.array([[1e-3, 1e-3, 1e-3, 1]]).T657 # scaling and translation terms658 vox_ras_t["trans"] *= conv659 vox_mri_t["trans"] *= conv660 # just the translation term661 mri_ras_t["trans"][:, 3:4] *= conv662 663 out = (vox_ras_t, vox_mri_t, mri_ras_t, dims, zooms)664 if return_img:665 nibabel = _import_nibabel()666 out += (nibabel.load(path),)667 return out668 669 670def read_freesurfer_lut(fname=None):671 """Read a Freesurfer-formatted LUT.672 673 Parameters674 ----------675 fname : path-like | None676 The filename. Can be None to read the standard Freesurfer LUT.677 678 Returns679 -------680 atlas_ids : dict681 Mapping from label names to IDs.682 colors : dict683 Mapping from label names to colors.684 """685 lut = _get_lut(fname)686 names, ids = lut["name"], lut["id"]687 colors = np.array([lut["R"], lut["G"], lut["B"], lut["A"]], float).T688 atlas_ids = dict(zip(names, ids))689 colors = dict(zip(names, colors))690 return atlas_ids, colors691 692 693def _get_lut(fname=None):694 """Get a FreeSurfer LUT."""695 if fname is None:696 fname = Path(__file__).parent / "data" / "FreeSurferColorLUT.txt"697 _check_fname(fname, "read", must_exist=True)698 dtype = [699 ("id", "<i8"),700 ("name", "U"),701 ("R", "<i8"),702 ("G", "<i8"),703 ("B", "<i8"),704 ("A", "<i8"),705 ]706 lut = {d[0]: list() for d in dtype}707 with open(fname) as fid:708 for line in fid:709 line = line.strip()710 if line.startswith("#") or not line:711 continue712 line = line.split()713 if len(line) != len(dtype):714 raise RuntimeError(f"LUT is improperly formatted: {fname}")715 for d, part in zip(dtype, line):716 lut[d[0]].append(part)717 lut = {d[0]: np.array(lut[d[0]], dtype=d[1]) for d in dtype}718 assert len(lut["name"]) > 0719 lut["name"] = [str(name) for name in lut["name"]]720 return lut721 722 723@verbose724def _get_head_surface(surf, subject, subjects_dir, bem=None, verbose=None):725 """Get a head surface from the Freesurfer subject directory.726 727 Parameters728 ----------729 surf : str730 The name of the surface 'auto', 'head', 'outer_skin', 'head-dense'731 or 'seghead'.732 %(subject)s733 %(subjects_dir)s734 bem : mne.bem.ConductorModel | None735 The conductor model that stores information about the head surface.736 %(verbose)s737 738 Returns739 -------740 head_surf : dict | None741 A dictionary with keys 'rr', 'tris', 'ntri', 'use_tris', 'np'742 and 'coord_frame' that store information for mesh plotting and other743 useful information about the head surface.744 745 Notes746 -----747 .. versionadded: 0.24748 """749 from .bem import _bem_find_surface, read_bem_surfaces750 751 _check_option("surf", surf, ("auto", "head", "outer_skin", "head-dense", "seghead"))752 if surf in ("auto", "head", "outer_skin"):753 if bem is not None:754 try:755 return _bem_find_surface(bem, "head")756 except RuntimeError:757 logger.info(758 "Could not find the surface for "759 "head in the provided BEM model, "760 "looking in the subject directory."761 )762 if subject is None:763 if surf == "auto":764 return765 raise ValueError(766 "To plot the head surface, the BEM/sphere"767 " model must contain a head surface "768 'or "subject" must be provided (got '769 "None)"770 )771 subject_dir = op.join(get_subjects_dir(subjects_dir, raise_error=True), subject)772 if surf in ("head-dense", "seghead"):773 try_fnames = [774 op.join(subject_dir, "bem", f"{subject}-head-dense.fif"),775 op.join(subject_dir, "surf", "lh.seghead"),776 ]777 else:778 try_fnames = [779 op.join(subject_dir, "bem", "outer_skin.surf"),780 op.join(subject_dir, "bem", "flash", "outer_skin.surf"),781 op.join(subject_dir, "bem", f"{subject}-head-sparse.fif"),782 op.join(subject_dir, "bem", f"{subject}-head.fif"),783 ]784 for fname in try_fnames:785 if op.exists(fname):786 logger.info(f"Using {op.basename(fname)} for head surface.")787 if op.splitext(fname)[-1] == ".fif":788 return read_bem_surfaces(fname, on_defects="warn")[0]789 else:790 return _read_mri_surface(fname)791 raise OSError(792 "No head surface found for subject "793 f"{subject} after trying:\n" + "\n".join(try_fnames)794 )795 796 797@verbose798def _get_skull_surface(surf, subject, subjects_dir, bem=None, verbose=None):799 """Get a skull surface from the Freesurfer subject directory.800 801 Parameters802 ----------803 surf : str804 The name of the surface 'outer' or 'inner'.805 %(subject)s806 %(subjects_dir)s807 bem : mne.bem.ConductorModel | None808 The conductor model that stores information about the skull surface.809 %(verbose)s810 811 Returns812 -------813 skull_surf : dict | None814 A dictionary with keys 'rr', 'tris', 'ntri', 'use_tris', 'np'815 and 'coord_frame' that store information for mesh plotting and other816 useful information about the head surface.817 818 Notes819 -----820 .. versionadded: 0.24821 """822 from .bem import _bem_find_surface823 824 if bem is not None:825 try:826 return _bem_find_surface(bem, surf + "_skull")827 except RuntimeError:828 logger.info(829 "Could not find the surface for "830 "skull in the provided BEM model, "831 "looking in the subject directory."832 )833 subjects_dir = Path(get_subjects_dir(subjects_dir, raise_error=True))834 fname = _check_fname(835 subjects_dir / subject / "bem" / (surf + "_skull.surf"),836 overwrite="read",837 must_exist=True,838 name=f"{surf} skull surface",839 )840 return _read_mri_surface(fname)841 842 843def _estimate_talxfm_rigid(subject, subjects_dir):844 from .coreg import _trans_from_params, fit_matched_points845 846 xfm = read_talxfm(subject, subjects_dir)847 # XYZ+origin + halfway848 pts_tal = np.concatenate([np.eye(4)[:, :3], np.eye(3) * 0.5])849 pts_subj = apply_trans(invert_transform(xfm), pts_tal)850 # we fit with scaling enabled, but then discard it (we just need851 # the rigid-body components)852 params = fit_matched_points(pts_subj, pts_tal, scale=3, out="params")853 rigid = _trans_from_params((True, True, False), params[:6])854 return rigid855 