Skip to content

Commit 782224b

Browse files
authored
Merge pull request #266 from j-obriot/affine
Nifti affine matrix
2 parents 0b5edd9 + 5625c70 commit 782224b

File tree

2 files changed

+76
-3
lines changed

2 files changed

+76
-3
lines changed

src/mrinufft/io/siemens.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
import numpy as np
6-
from .utils import siemens_quat_to_rot_mat
6+
from .utils import siemens_quat_to_rot_mat, nifti_affine
77

88

99
def read_siemens_rawdat(
@@ -76,6 +76,7 @@ def read_siemens_rawdat(
7676
"n_slices": int(twixObj.image.NSli),
7777
"n_average": int(twixObj.image.NAve),
7878
"orientation": siemens_quat_to_rot_mat(twixObj.image.slicePos[0][-4:]),
79+
"affine": nifti_affine(twixObj),
7980
"acs": None,
8081
}
8182
if "refscan" in twixObj.keys():

src/mrinufft/io/utils.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,16 @@ def add_phase_to_kspace_with_shifts(kspace_data, kspace_loc, normalized_shifts):
3838
return kspace_data * phase
3939

4040

41-
def siemens_quat_to_rot_mat(quat):
41+
def siemens_quat_to_rot_mat(quat, return_det=False):
4242
"""
4343
Calculate the rotation matrix from Siemens Twix quaternion.
4444
4545
Parameters
4646
----------
4747
quat : np.ndarray
4848
The quaternion from the Siemens Twix file.
49+
return_det : bool
50+
Whether to return the determinent of the rotation before norm
4951
5052
Returns
5153
-------
@@ -56,12 +58,82 @@ def siemens_quat_to_rot_mat(quat):
5658
R = np.zeros((4, 4))
5759
R[:3, :3] = Rotation.from_quat([quat[1], quat[2], quat[3], quat[0]]).as_matrix()
5860
R[:, (0, 1)] = R[:, (1, 0)]
59-
if np.linalg.det(R[:3, :3]) < 0:
61+
det = np.linalg.det(R[:3, :3])
62+
if det < 0:
6063
R[2] = -R[2]
6164
R[-1, -1] = 1
65+
if return_det:
66+
return R, det
6267
return R
6368

6469

70+
def nifti_affine(twixObj):
71+
"""
72+
Calculate the affine transformation matrix from Siemens Twix object.
73+
74+
Parameters
75+
----------
76+
twixObj : twixObj
77+
The twix object returned by mapVBVD.
78+
79+
Returns
80+
-------
81+
np.ndarray
82+
The affine transformation matrix which is a 4x4 matrix.
83+
This can be passed as input to `affine` parameter in `nibabel`.
84+
"""
85+
# required keys
86+
keys = {
87+
"dthick": ("sSliceArray", "asSlice", "0", "dThickness"),
88+
"dread": ("sSliceArray", "asSlice", "0", "dReadoutFOV"),
89+
"dphase": ("sSliceArray", "asSlice", "0", "dPhaseFOV"),
90+
"lbase": ("sKSpace", "lBaseResolution"),
91+
"lphase": ("sKSpace", "lPhaseEncodingLines"),
92+
"ucdim": ("sKSpace", "ucDimension"),
93+
}
94+
sos = ("sKSpace", "dSliceOversamplingForDialog")
95+
rot, det = siemens_quat_to_rot_mat(twixObj.image.slicePos[0][-4:], True)
96+
my = twixObj.hdr.MeasYaps
97+
98+
for k in keys.keys():
99+
if keys[k] not in my:
100+
return rot
101+
102+
dthick = my[keys["dthick"]]
103+
fov = np.array(
104+
[
105+
my[keys["dread"]],
106+
my[keys["dphase"]],
107+
dthick * (1 + my[sos] if sos in my else 1),
108+
]
109+
)
110+
111+
lpart = ("sKSpace", "lPartitions")
112+
res = np.array(
113+
[
114+
my[keys["lbase"]],
115+
my[keys["lphase"]],
116+
my[lpart] if my[keys["ucdim"]] == 4 and lpart in my else 1,
117+
]
118+
)
119+
120+
scale = np.diag([*(fov / res), 1])
121+
122+
offset = twixObj.image.slicePos[0][:3]
123+
124+
fovz = fov[2] - (my[sos] * dthick if sos in my else 0)
125+
center = [-fov[0] / 2, -fov[1] / 2, -fovz / 2, 1]
126+
127+
t = (rot @ center)[:3] - offset
128+
if det < 0:
129+
t[2] = (rot @ center)[2] * 2 - t[2]
130+
131+
full_mat = rot @ scale
132+
full_mat[:3, 3] = t
133+
134+
return full_mat
135+
136+
65137
def remove_extra_kspace_samples(kspace_data, num_samples_per_shot):
66138
"""Remove extra samples from k-space data.
67139

0 commit comments

Comments
 (0)