@@ -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+
65137def remove_extra_kspace_samples (kspace_data , num_samples_per_shot ):
66138 """Remove extra samples from k-space data.
67139
0 commit comments