1
+ import os
1
2
import numpy as np
2
3
import nibabel as nb
3
4
from nipype .utils .filemanip import fname_presuffix
4
5
5
6
6
- def extract_b0 (in_file , b0_ixs , newpath = None ):
7
- """Extract the *b0* volumes from a DWI dataset."""
8
- out_file = fname_presuffix (in_file , suffix = "_b0" , newpath = newpath )
7
+ def extract_b0 (in_file , b0_ixs , out_path = None ):
8
+ """
9
+ Extract the *b0* volumes from a DWI dataset.
10
+
11
+ Parameters
12
+ ----------
13
+ in_file : str
14
+ DWI NIfTI file.
15
+ b0_ixs : list
16
+ List of B0 indices in `in_file`.
17
+ out_path : str
18
+ Optionally specify an output path.
19
+
20
+ Returns
21
+ -------
22
+ out_path : str
23
+ 4D NIfTI file consisting of B0's.
24
+
25
+ Examples
26
+ --------
27
+ >>> os.chdir(tmpdir)
28
+ >>> b0_ixs = np.where(np.loadtxt(str(data_dir / 'bval')) <= 50)[0].tolist()[:2]
29
+ >>> in_file = str(data_dir / 'dwi.nii.gz')
30
+ >>> out_path = extract_b0(in_file, b0_ixs)
31
+ >>> assert os.path.isfile(out_path)
32
+ """
33
+ if out_path is None :
34
+ out_path = fname_presuffix (
35
+ in_file , suffix = "_b0"
36
+ )
37
+ else :
38
+ out_path = fname_presuffix (in_file , suffix = "_b0" ,
39
+ newpath = out_path )
9
40
10
41
img = nb .load (in_file )
11
42
data = img .get_fdata (dtype = "float32" )
@@ -16,13 +47,43 @@ def extract_b0(in_file, b0_ixs, newpath=None):
16
47
hdr .set_data_shape (b0 .shape )
17
48
hdr .set_xyzt_units ("mm" )
18
49
hdr .set_data_dtype (np .float32 )
19
- nb .Nifti1Image (b0 , img .affine , hdr ).to_filename (out_file )
20
- return out_file
50
+ nb .Nifti1Image (b0 , img .affine , hdr ).to_filename (out_path )
51
+ return out_path
21
52
22
53
23
- def rescale_b0 (in_file , mask_file , newpath = None ):
24
- """Rescale the input volumes using the median signal intensity."""
25
- out_file = fname_presuffix (in_file , suffix = "_rescaled_b0" , newpath = newpath )
54
+ def rescale_b0 (in_file , mask_file , out_path = None ):
55
+ """
56
+ Rescale the input volumes using the median signal intensity.
57
+
58
+ Parameters
59
+ ----------
60
+ in_file : str
61
+ A NIfTI file consisting of one or more B0's.
62
+ mask_file : str
63
+ A B0 mask NIFTI file.
64
+ out_path : str
65
+ Optionally specify an output path.
66
+
67
+ Returns
68
+ -------
69
+ out_path : str
70
+ A rescaled B0 NIFTI file.
71
+
72
+ Examples
73
+ --------
74
+ >>> os.chdir(tmpdir)
75
+ >>> mask_file = str(data_dir / 'dwi_mask.nii.gz')
76
+ >>> in_file = str(data_dir / 'dwi_b0.nii.gz')
77
+ >>> out_path = rescale_b0(in_file, mask_file)
78
+ >>> assert os.path.isfile(out_path)
79
+ """
80
+ if out_path is None :
81
+ out_path = fname_presuffix (
82
+ in_file , suffix = "_rescaled_b0"
83
+ )
84
+ else :
85
+ out_path = fname_presuffix (in_file , suffix = "_rescaled_b0" ,
86
+ newpath = out_path )
26
87
27
88
img = nb .load (in_file )
28
89
if img .dataobj .ndim == 3 :
@@ -35,31 +96,79 @@ def rescale_b0(in_file, mask_file, newpath=None):
35
96
median_signal = np .median (data [mask_data > 0 , ...], axis = 0 )
36
97
rescaled_data = 1000 * data / median_signal
37
98
hdr = img .header .copy ()
38
- nb .Nifti1Image (rescaled_data , img .affine , hdr ).to_filename (out_file )
39
- return out_file
99
+ nb .Nifti1Image (rescaled_data , img .affine , hdr ).to_filename (out_path )
100
+ return out_path
40
101
41
102
42
- def median (in_file , newpath = None ):
43
- """Average a 4D dataset across the last dimension using median."""
44
- out_file = fname_presuffix (in_file , suffix = "_b0ref" , newpath = newpath )
103
+ def median (in_file , out_path = None ):
104
+ """
105
+ Summarize a 4D dataset across the last dimension using median.
106
+
107
+ Parameters
108
+ ----------
109
+ in_file : str
110
+ A NIfTI file consisting of one or more B0's.
111
+ out_path : str
112
+ Optionally specify an output path for `out_path`.
113
+
114
+ Returns
115
+ -------
116
+ out_path : str
117
+ A 3D B0 NIFTI file.
118
+
119
+ Examples
120
+ --------
121
+ >>> os.chdir(tmpdir)
122
+ >>> in_file = str(data_dir / 'dwi_b0.nii.gz')
123
+ >>> out_path = median(in_file)
124
+ >>> assert os.path.isfile(out_path)
125
+ """
126
+ if out_path is None :
127
+ out_path = fname_presuffix (
128
+ in_file , suffix = "_b0ref"
129
+ )
130
+ else :
131
+ out_path = fname_presuffix (in_file , suffix = "_b0ref" ,
132
+ newpath = out_path )
45
133
46
134
img = nb .load (in_file )
47
135
if img .dataobj .ndim == 3 :
48
136
return in_file
49
137
if img .shape [- 1 ] == 1 :
50
- nb .squeeze_image (img ).to_filename (out_file )
51
- return out_file
138
+ nb .squeeze_image (img ).to_filename (out_path )
139
+ return out_path
52
140
53
141
median_data = np .median (img .get_fdata (), axis = - 1 )
54
142
55
143
hdr = img .header .copy ()
56
144
hdr .set_xyzt_units ("mm" )
57
- nb .Nifti1Image (median_data , img .affine , hdr ).to_filename (out_file )
58
- return out_file
145
+ nb .Nifti1Image (median_data , img .affine , hdr ).to_filename (out_path )
146
+ return out_path
59
147
60
148
61
149
def average_images (images , out_path = None ):
62
- """Average a 4D dataset across the last dimension using mean."""
150
+ """
151
+ Average a 4D dataset across the last dimension using mean.
152
+
153
+ Parameters
154
+ ----------
155
+ images : str
156
+ A list of NIFTI file paths.
157
+ out_path : str
158
+ Optionally specify an output path for `out_path`.
159
+
160
+ Returns
161
+ -------
162
+ out_path : str
163
+ A 3D NIFTI file averaged along the 4th dimension.
164
+
165
+ Examples
166
+ --------
167
+ >>> os.chdir(tmpdir)
168
+ >>> in_file = str(data_dir / 'dwi_b0.nii.gz')
169
+ >>> out_path = average_images(in_file)
170
+ >>> assert os.path.isfile(out_path)
171
+ """
63
172
from nilearn .image import mean_img
64
173
65
174
average_img = mean_img ([nb .load (img ) for img in images ])
@@ -71,19 +180,39 @@ def average_images(images, out_path=None):
71
180
return out_path
72
181
73
182
74
- def quick_load_images (image_list , dtype = np .float32 ):
75
- """Load 3D volumes from a list of file paths into a 4D array."""
76
- return nb .concat_images ([nb .load (fname ) for fname in image_list ]).get_fdata (dtype = dtype )
183
+ def get_list_data (file_list , dtype = np .float32 ):
184
+ """
185
+ Load 3D volumes from a list of file paths into a 4D array.
186
+
187
+ Parameters
188
+ ----------
189
+ file_list : str
190
+ A list of file paths to 3D NIFTI images.
191
+ """
192
+ return nb .concat_images ([nb .load (fname ) for fname in file_list ]).get_fdata (dtype = dtype )
77
193
78
194
79
- def match_transforms (dwi_files , transforms , b0_indices ):
195
+ def match_transforms (dwi_files , transforms , b0_ixs ):
80
196
"""
81
197
Arrange the order of a list of transforms.
82
-
198
+
83
199
This is a helper function for :abbr:`EMC (Eddy-currents and Motion Correction)`.
84
200
Sorts the input list of affine transforms to correspond with that of
85
201
each individual dwi volume file, accounting for the indices of :math:`b = 0` volumes.
86
-
202
+
203
+ Parameters
204
+ ----------
205
+ dwi_files : list
206
+ A list of file paths to 3D diffusion-weighted NIFTI volumes.
207
+ transforms : list
208
+ A list of ndarrays.
209
+ b0_ixs : list
210
+ List of B0 indices.
211
+
212
+ Returns
213
+ -------
214
+ out_path : str
215
+ A 3D NIFTI file averaged along the 4th dimension.
87
216
"""
88
217
num_dwis = len (dwi_files )
89
218
num_transforms = len (transforms )
@@ -92,21 +221,33 @@ def match_transforms(dwi_files, transforms, b0_indices):
92
221
return transforms
93
222
94
223
# Do sanity checks
95
- if not len (transforms ) == len (b0_indices ):
224
+ if not len (transforms ) == len (b0_ixs ):
96
225
raise Exception ("number of transforms does not match number of b0 images" )
97
226
98
227
# Create a list of which emc affines go with each of the split images
99
228
nearest_affines = []
100
229
for index in range (num_dwis ):
101
- nearest_b0_num = np .argmin (np .abs (index - np .array (b0_indices )))
230
+ nearest_b0_num = np .argmin (np .abs (index - np .array (b0_ixs )))
102
231
this_transform = transforms [nearest_b0_num ]
103
232
nearest_affines .append (this_transform )
104
233
105
234
return nearest_affines
106
235
107
236
108
237
def save_4d_to_3d (in_file ):
109
- """Split a 4D dataset along the last dimension into multiple 3D volumes."""
238
+ """
239
+ Split a 4D dataset along the last dimension into multiple 3D volumes.
240
+
241
+ Parameters
242
+ ----------
243
+ in_file : str
244
+ DWI NIfTI file.
245
+
246
+ Returns
247
+ -------
248
+ out_files : list
249
+ A list of file paths to 3d NIFTI images.
250
+ """
110
251
files_3d = nb .four_to_three (nb .load (in_file ))
111
252
out_files = []
112
253
for i , file_3d in enumerate (files_3d ):
@@ -118,7 +259,22 @@ def save_4d_to_3d(in_file):
118
259
119
260
120
261
def prune_b0s_from_dwis (in_files , b0_ixs ):
121
- """Remove *b0* volume files from a complete list of DWI volume files."""
262
+ """
263
+ Remove *b0* volume files from a complete list of DWI volume files.
264
+
265
+ Parameters
266
+ ----------
267
+ in_files : list
268
+ A list of NIfTI file paths corresponding to each 3D volume of a
269
+ DWI image (i.e. including B0's).
270
+ b0_ixs : list
271
+ List of B0 indices.
272
+
273
+ Returns
274
+ -------
275
+ out_files : list
276
+ A list of file paths to 3d NIFTI images.
277
+ """
122
278
if in_files [0 ].endswith ("_warped.nii.gz" ):
123
279
out_files = [
124
280
i
@@ -143,34 +299,21 @@ def prune_b0s_from_dwis(in_files, b0_ixs):
143
299
144
300
145
301
def save_3d_to_4d (in_files ):
146
- """Concatenate a list of 3D volumes into a 4D output."""
302
+ """
303
+ Concatenate a list of 3D volumes into a 4D output.
304
+
305
+ Parameters
306
+ ----------
307
+ in_files : list
308
+ A list of file paths to 3D NIFTI images.
309
+
310
+ Returns
311
+ -------
312
+ out_file : str
313
+ A file path to a 4d NIFTI image of concatenated 3D volumes.
314
+ """
147
315
img_4d = nb .funcs .concat_images ([nb .load (img_3d ) for img_3d in in_files ])
148
316
out_file = fname_presuffix (in_files [0 ], suffix = "_merged" )
149
317
img_4d .to_filename (out_file )
150
318
return out_file
151
319
152
-
153
- def decompose_affine (affine ):
154
- """Takes an transformation affine matrix A and determines
155
- rotations and translations."""
156
-
157
- def rang (b ):
158
- a = min (max (b , - 1 ), 1 )
159
- return a
160
-
161
- Ry = np .arcsin (A [0 , 2 ])
162
- # Rx = np.arcsin(A[1, 2] / np.cos(Ry))
163
- # Rz = np.arccos(A[0, 1] / np.sin(Ry))
164
-
165
- if (abs (Ry ) - np .pi / 2 ) ** 2 < 1e-9 :
166
- Rx = 0
167
- Rz = np .arctan2 (- rang (A [1 , 0 ]), rang (- A [2 , 0 ] / A [0 , 2 ]))
168
- else :
169
- c = np .cos (Ry )
170
- Rx = np .arctan2 (rang (A [1 , 2 ] / c ), rang (A [2 , 2 ] / c ))
171
- Rz = np .arctan2 (rang (A [0 , 1 ] / c ), rang (A [0 , 0 ] / c ))
172
-
173
- rotations = [Rx , Ry , Rz ]
174
- translations = [A [0 , 3 ], A [1 , 3 ], A [2 , 3 ]]
175
-
176
- return rotations , translations
0 commit comments