22
22
#
23
23
"""The :math:`B_0` unwarping transform formalism."""
24
24
from pathlib import Path
25
+ from typing import Sequence , Union
25
26
26
27
import attr
27
28
import numpy as np
@@ -50,7 +51,12 @@ class B0FieldTransform:
50
51
target image we want to correct).
51
52
"""
52
53
53
- def fit (self , target_reference , affine = None , approx = True ):
54
+ def fit (
55
+ self ,
56
+ target_reference : nb .spatialimages .SpatialImage ,
57
+ affine : np .array = None ,
58
+ approx : bool = True ,
59
+ ) -> bool :
54
60
r"""
55
61
Generate the interpolation matrix (and the VSM with it).
56
62
@@ -59,7 +65,7 @@ def fit(self, target_reference, affine=None, approx=True):
59
65
60
66
Parameters
61
67
----------
62
- target_reference : `spatialimage `
68
+ target_reference : :obj:`~nibabel.spatialimages.SpatialImage `
63
69
The image object containing a reference grid (same as that of the data
64
70
to be resampled). If a 4D dataset is provided, then the fourth dimension
65
71
will be dropped.
@@ -83,7 +89,9 @@ def fit(self, target_reference, affine=None, approx=True):
83
89
if isinstance (target_reference , (str , bytes , Path )):
84
90
target_reference = nb .load (target_reference )
85
91
86
- approx = approx if affine is not None else False # Approximate iff affine is defined
92
+ approx = (
93
+ approx if affine is not None else False
94
+ ) # Approximate iff affine is defined
87
95
affine = affine if affine is not None else np .eye (4 )
88
96
target_affine = target_reference .affine .copy ()
89
97
@@ -141,22 +149,22 @@ def fit(self, target_reference, affine=None, approx=True):
141
149
hdr .set_intent ("estimate" , name = "fieldmap Hz" )
142
150
hdr .set_data_dtype ("float32" )
143
151
hdr ["cal_max" ] = max ((abs (fmap .min ()), fmap .max ()))
144
- hdr ["cal_min" ] = - hdr ["cal_max" ]
152
+ hdr ["cal_min" ] = - hdr ["cal_max" ]
145
153
self .mapped = nb .Nifti1Image (fmap , target_affine , hdr )
146
154
return True
147
155
148
156
def apply (
149
157
self ,
150
- moving ,
151
- pe_dir ,
152
- ro_time ,
153
- xfms = None ,
154
- order = 3 ,
155
- mode = "constant" ,
156
- cval = 0.0 ,
157
- prefilter = True ,
158
- output_dtype = None ,
159
- num_threads = None ,
158
+ moving : nb . spatialimages . SpatialImage ,
159
+ pe_dir : str ,
160
+ ro_time : float ,
161
+ xfms : Sequence [ np . array ] = None ,
162
+ order : int = 3 ,
163
+ mode : str = "constant" ,
164
+ cval : float = 0.0 ,
165
+ prefilter : bool = True ,
166
+ output_dtype : Union [ str , np . dtype ] = None ,
167
+ num_threads : int = None ,
160
168
):
161
169
"""
162
170
Apply a transformation to an image, resampling on the reference spatial object.
@@ -165,32 +173,41 @@ def apply(
165
173
166
174
Parameters
167
175
----------
168
- moving : `spatialimage `
176
+ moving : :obj:`~nibabel.spatialimages.SpatialImage `
169
177
The image object containing the data to be resampled in reference
170
178
space
171
- xfms : `None` or :obj:`list`
179
+ pe_dir : :obj:`str`
180
+ A valid ``PhaseEncodingDirection`` metadata value.
181
+ ro_time : :obj:`float`
182
+ The total readout time in seconds.
183
+ xfms : :obj:`None` or :obj:`list`
172
184
A list of rigid-body transformations previously estimated that will
173
185
realign the dataset (that is, compensate for head motion) after resampling.
174
- order : int, optional
186
+ order : :obj:` int` , optional
175
187
The order of the spline interpolation, default is 3.
176
188
The order has to be in the range 0-5.
177
189
mode : {'constant', 'reflect', 'nearest', 'mirror', 'wrap'}, optional
178
190
Determines how the input image is extended when the resamplings overflows
179
191
a border. Default is 'constant'.
180
192
cval : float, optional
181
193
Constant value for ``mode='constant'``. Default is 0.0.
182
- prefilter: bool, optional
194
+ prefilter : :obj:` bool` , optional
183
195
Determines if the image's data array is prefiltered with
184
196
a spline filter before interpolation. The default is ``True``,
185
197
which will create a temporary *float64* array of filtered values
186
198
if *order > 1*. If setting this to ``False``, the output will be
187
199
slightly blurred if *order > 1*, unless the input is prefiltered,
188
200
i.e. it is the result of calling the spline filter on the original
189
201
input.
202
+ output_dtype : :obj:`str` or :obj:`~numpy.dtype`
203
+ Override the output data type, instead of propagating it from the
204
+ moving image.
205
+ num_threads : :obj:`int`
206
+ Number of CPUs resampling can be parallelized on.
190
207
191
208
Returns
192
209
-------
193
- resampled : `spatialimage` or ndarray
210
+ resampled : :obj:`~nibabel.spatialimages.SpatialImage`
194
211
The data imaged after resampling to reference space.
195
212
196
213
"""
@@ -249,9 +266,7 @@ def apply(
249
266
prefilter = prefilter ,
250
267
).reshape (moving .shape )
251
268
252
- moved = moving .__class__ (
253
- resampled , moving .affine , moving .header
254
- )
269
+ moved = moving .__class__ (resampled , moving .affine , moving .header )
255
270
moved .header .set_data_dtype (output_dtype )
256
271
return reorient_image (moved , axcodes )
257
272
@@ -368,11 +383,13 @@ def disp_to_fmap(xyz_nii, ro_time, pe_dir, itk_format=True):
368
383
fmap_nii = nb .Nifti1Image (vsm / scale_factor , xyz_nii .affine )
369
384
fmap_nii .header .set_intent ("estimate" , name = "Delta_B0 [Hz]" )
370
385
fmap_nii .header .set_xyzt_units ("mm" )
371
- fmap_nii .header ["cal_max" ] = max ((
372
- abs (np .asanyarray (fmap_nii .dataobj ).min ()),
373
- np .asanyarray (fmap_nii .dataobj ).max (),
374
- ))
375
- fmap_nii .header ["cal_min" ] = - fmap_nii .header ["cal_max" ]
386
+ fmap_nii .header ["cal_max" ] = max (
387
+ (
388
+ abs (np .asanyarray (fmap_nii .dataobj ).min ()),
389
+ np .asanyarray (fmap_nii .dataobj ).max (),
390
+ )
391
+ )
392
+ fmap_nii .header ["cal_min" ] = - fmap_nii .header ["cal_max" ]
376
393
return fmap_nii
377
394
378
395
@@ -426,10 +443,14 @@ def grid_bspline_weights(target_nii, ctrl_nii, dtype="float32"):
426
443
knots_shape = ctrl_nii .shape [:3 ]
427
444
428
445
# Ensure the cross-product of affines is near zero (i.e., both coordinate systems are aligned)
429
- if not np .allclose (np .linalg .norm (
430
- np .cross (ctrl_nii .affine [:- 1 , :- 1 ].T , target_nii .affine [:- 1 , :- 1 ].T ),
431
- axis = 1 ,
432
- ), 0 , atol = 1e-3 ):
446
+ if not np .allclose (
447
+ np .linalg .norm (
448
+ np .cross (ctrl_nii .affine [:- 1 , :- 1 ].T , target_nii .affine [:- 1 , :- 1 ].T ),
449
+ axis = 1 ,
450
+ ),
451
+ 0 ,
452
+ atol = 1e-3 ,
453
+ ):
433
454
warn ("Image's and B-Spline's grids are not aligned." )
434
455
435
456
target_to_grid = np .linalg .inv (ctrl_nii .affine ) @ target_nii .affine
@@ -481,9 +502,11 @@ def _move_coeff(in_coeff, fmap_ref, transform, fmap_target=None):
481
502
hdr .set_sform (newaff , code = 1 )
482
503
483
504
# Make it easy on viz software to render proper range
484
- hdr ["cal_max" ] = max ((
485
- abs (np .asanyarray (coeff .dataobj ).min ()),
486
- np .asanyarray (coeff .dataobj ).max (),
487
- ))
488
- hdr ["cal_min" ] = - hdr ["cal_max" ]
505
+ hdr ["cal_max" ] = max (
506
+ (
507
+ abs (np .asanyarray (coeff .dataobj ).min ()),
508
+ np .asanyarray (coeff .dataobj ).max (),
509
+ )
510
+ )
511
+ hdr ["cal_min" ] = - hdr ["cal_max" ]
489
512
return coeff .__class__ (coeff .dataobj , newaff , hdr )
0 commit comments