[074d3d]: / mne / io / ctf / trans.py

Download this file

133 lines (119 with data), 4.8 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
"""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