133 lines
4.7 KiB
Python
133 lines
4.7 KiB
Python
"""Create coordinate transforms."""
|
|
|
|
# Authors: The MNE-Python contributors.
|
|
# License: BSD-3-Clause
|
|
# Copyright the MNE-Python contributors.
|
|
|
|
import numpy as np
|
|
|
|
from ..._fiff.constants import FIFF
|
|
from ...transforms import (
|
|
Transform,
|
|
_fit_matched_points,
|
|
_quat_to_affine,
|
|
apply_trans,
|
|
combine_transforms,
|
|
get_ras_to_neuromag_trans,
|
|
invert_transform,
|
|
)
|
|
from ...utils import logger
|
|
from .constants import CTF
|
|
|
|
|
|
def _make_transform_card(fro, to, r_lpa, r_nasion, r_rpa):
|
|
"""Make a transform from cardinal landmarks."""
|
|
return invert_transform(
|
|
Transform(to, fro, get_ras_to_neuromag_trans(r_nasion, r_lpa, r_rpa))
|
|
)
|
|
|
|
|
|
def _quaternion_align(from_frame, to_frame, from_pts, to_pts, diff_tol=1e-4):
|
|
"""Perform an alignment using the unit quaternions (modifies points)."""
|
|
assert from_pts.shape[1] == to_pts.shape[1] == 3
|
|
trans = _quat_to_affine(_fit_matched_points(from_pts, to_pts)[0])
|
|
|
|
# Test the transformation and print the results
|
|
logger.info(" Quaternion matching (desired vs. transformed):")
|
|
for fro, to in zip(from_pts, to_pts):
|
|
rr = apply_trans(trans, fro)
|
|
diff = np.linalg.norm(to - rr)
|
|
logger.info(
|
|
" %7.2f %7.2f %7.2f mm <-> %7.2f %7.2f %7.2f mm "
|
|
"(orig : %7.2f %7.2f %7.2f mm) diff = %8.3f mm"
|
|
% (tuple(1000 * to) + tuple(1000 * rr) + tuple(1000 * fro) + (1000 * diff,))
|
|
)
|
|
if diff > diff_tol:
|
|
raise RuntimeError(
|
|
"Something is wrong: quaternion matching did not work (see above)"
|
|
)
|
|
return Transform(from_frame, to_frame, trans)
|
|
|
|
|
|
def _make_ctf_coord_trans_set(res4, coils):
|
|
"""Figure out the necessary coordinate transforms."""
|
|
# CTF head > Neuromag head
|
|
lpa = rpa = nas = T1 = T2 = T3 = T5 = None
|
|
if coils is not None:
|
|
for p in coils:
|
|
if p["valid"] and (p["coord_frame"] == FIFF.FIFFV_MNE_COORD_CTF_HEAD):
|
|
if lpa is None and p["kind"] == CTF.CTFV_COIL_LPA:
|
|
lpa = p
|
|
elif rpa is None and p["kind"] == CTF.CTFV_COIL_RPA:
|
|
rpa = p
|
|
elif nas is None and p["kind"] == CTF.CTFV_COIL_NAS:
|
|
nas = p
|
|
if lpa is None or rpa is None or nas is None:
|
|
raise RuntimeError(
|
|
"Some of the mandatory HPI device-coordinate info was not there."
|
|
)
|
|
t = _make_transform_card("head", "ctf_head", lpa["r"], nas["r"], rpa["r"])
|
|
T3 = invert_transform(t)
|
|
|
|
# CTF device -> Neuromag device
|
|
#
|
|
# Rotate the CTF coordinate frame by 45 degrees and shift by 190 mm
|
|
# in z direction to get a coordinate system comparable to the Neuromag one
|
|
#
|
|
R = np.eye(4)
|
|
R[:3, 3] = [0.0, 0.0, 0.19]
|
|
val = 0.5 * np.sqrt(2.0)
|
|
R[0, 0] = val
|
|
R[0, 1] = -val
|
|
R[1, 0] = val
|
|
R[1, 1] = val
|
|
T4 = Transform("ctf_meg", "meg", R)
|
|
|
|
# CTF device -> CTF head
|
|
# We need to make the implicit transform explicit!
|
|
h_pts = dict()
|
|
d_pts = dict()
|
|
kinds = (
|
|
CTF.CTFV_COIL_LPA,
|
|
CTF.CTFV_COIL_RPA,
|
|
CTF.CTFV_COIL_NAS,
|
|
CTF.CTFV_COIL_SPARE,
|
|
)
|
|
if coils is not None:
|
|
for p in coils:
|
|
if p["valid"]:
|
|
if p["coord_frame"] == FIFF.FIFFV_MNE_COORD_CTF_HEAD:
|
|
for kind in kinds:
|
|
if kind not in h_pts and p["kind"] == kind:
|
|
h_pts[kind] = p["r"]
|
|
elif p["coord_frame"] == FIFF.FIFFV_MNE_COORD_CTF_DEVICE:
|
|
for kind in kinds:
|
|
if kind not in d_pts and p["kind"] == kind:
|
|
d_pts[kind] = p["r"]
|
|
if any(kind not in h_pts for kind in kinds[:-1]):
|
|
raise RuntimeError(
|
|
"Some of the mandatory HPI device-coordinate info was not there."
|
|
)
|
|
if any(kind not in d_pts for kind in kinds[:-1]):
|
|
raise RuntimeError(
|
|
"Some of the mandatory HPI head-coordinate info was not there."
|
|
)
|
|
use_kinds = [kind for kind in kinds if (kind in h_pts and kind in d_pts)]
|
|
r_head = np.array([h_pts[kind] for kind in use_kinds])
|
|
r_dev = np.array([d_pts[kind] for kind in use_kinds])
|
|
T2 = _quaternion_align("ctf_meg", "ctf_head", r_dev, r_head)
|
|
|
|
# The final missing transform
|
|
if T3 is not None and T2 is not None:
|
|
T5 = combine_transforms(T2, T3, "ctf_meg", "head")
|
|
T1 = combine_transforms(invert_transform(T4), T5, "meg", "head")
|
|
s = dict(
|
|
t_dev_head=T1,
|
|
t_ctf_dev_ctf_head=T2,
|
|
t_ctf_head_head=T3,
|
|
t_ctf_dev_dev=T4,
|
|
t_ctf_dev_head=T5,
|
|
)
|
|
logger.info(" Coordinate transformations established.")
|
|
return s
|