32
32
from __future__ import annotations
33
33
34
34
from itertools import product
35
+ from typing import Tuple
35
36
36
37
import nibabel as nb
37
38
import nitransforms as nt
38
39
import numpy as np
39
40
41
+ RADIUS = 50.0
42
+ """Typical radius (in mm) of a sphere mimicking the size of a typical human brain."""
43
+
40
44
41
45
def displacements_within_mask (
42
46
mask_img : nb .spatialimages .SpatialImage ,
@@ -79,11 +83,11 @@ def displacements_within_mask(
79
83
return np .linalg .norm (diffs , axis = - 1 )
80
84
81
85
82
- def displacement_framewise (
86
+ def displacement_framewise_transform (
83
87
img : nb .spatialimages .SpatialImage ,
84
88
test_xfm : nt .base .BaseTransform ,
85
- radius : float = 50.0 ,
86
- ):
89
+ radius : float = RADIUS ,
90
+ ) -> float :
87
91
"""
88
92
Compute the framewise displacement (FD) for a given transformation.
89
93
@@ -95,7 +99,6 @@ def displacement_framewise(
95
99
The transformation to test. Applied to coordinates around the image center.
96
100
radius : :obj:`float`, optional
97
101
The radius (in mm) of the spherical neighborhood around the center of the image.
98
- Default is 50.0 mm.
99
102
100
103
Returns
101
104
-------
@@ -112,3 +115,63 @@ def displacement_framewise(
112
115
fd_coords = np .array (list (product (* ((radius , - radius ),) * 3 ))) + center_xyz
113
116
# Compute the average displacement from the test transformation
114
117
return np .mean (np .linalg .norm (test_xfm .map (fd_coords ) - fd_coords , axis = - 1 ))
118
+
119
+
120
+ def displacement_framewise_motion (
121
+ motion_parameters : np .ndarray , radius : float = RADIUS
122
+ ) -> np .ndarray :
123
+ """Compute framewise displacement (FD) from motion parameters.
124
+
125
+ Each row in the motion parameters represents one frame, and columns
126
+ represent each coordinate axis ``x``, `y``, and ``z``. Translation
127
+ parameters are followed by rotation parameters column-wise.
128
+
129
+ Parameters
130
+ ----------
131
+ motion_parameters : :obj:`numpy.ndarray`
132
+ Motion parameters.
133
+ radius : :obj:`float`, optional
134
+ Radius (in mm) of a sphere mimicking the size of a typical human brain.
135
+
136
+ Returns
137
+ -------
138
+ :obj:`numpy.ndarray`
139
+ The framewise displacement (FD) as the sum of absolute differences
140
+ between consecutive frames.
141
+ """
142
+
143
+ translations = motion_parameters [:, :3 ]
144
+ rotations_deg = motion_parameters [:, 3 :]
145
+ rotations_rad = np .deg2rad (rotations_deg )
146
+
147
+ # Compute differences between consecutive frames
148
+ d_translations = np .vstack ([np .zeros ((1 , 3 )), np .diff (translations , axis = 0 )])
149
+ d_rotations = np .vstack ([np .zeros ((1 , 3 )), np .diff (rotations_rad , axis = 0 )])
150
+
151
+ # Convert rotations from radians to displacement on a sphere
152
+ rotation_displacement = d_rotations * radius
153
+
154
+ # Compute FD as sum of absolute differences
155
+ return np .sum (np .abs (d_translations ) + np .abs (rotation_displacement ), axis = 1 )
156
+
157
+
158
+ def extract_motion_parameters (affine : np .ndarray ) -> Tuple [np .ndarray , np .ndarray ]:
159
+ """Extract translation (mm) and rotation (degrees) parameters from an affine matrix.
160
+
161
+ Parameters
162
+ ----------
163
+ affine : :obj:`~numpy.ndarray`
164
+ The affine transformation matrix.
165
+
166
+ Returns
167
+ -------
168
+ :obj:`tuple`
169
+ Extracted translation and rotation parameters.
170
+ """
171
+
172
+ translation = affine [:3 , 3 ]
173
+ rotation_rad = np .arctan2 (
174
+ [affine [2 , 1 ], affine [0 , 2 ], affine [1 , 0 ]], [affine [2 , 2 ], affine [0 , 0 ], affine [1 , 1 ]]
175
+ )
176
+ rotation_deg = np .rad2deg (rotation_rad )
177
+ return * translation , * rotation_deg
0 commit comments