3
3
"""
4
4
Estimating the susceptibility distortions without fieldmaps.
5
5
6
+ .. testsetup::
7
+
8
+ >>> tmpdir = getfixture('tmpdir')
9
+ >>> tmp = tmpdir.chdir() # changing to a temporary directory
10
+ >>> data = np.zeros((10, 10, 10, 1, 3))
11
+ >>> data[..., 1] = 1
12
+ >>> nb.Nifti1Image(data, None, None).to_filename(
13
+ ... tmpdir.join('field.nii.gz').strpath)
14
+
6
15
.. _sdc_fieldmapless :
7
16
8
17
Fieldmap-less approaches
55
64
56
65
57
66
def init_syn_sdc_wf (
58
- * , atlas_threshold = 3 , debug = False , name = "syn_sdc_wf" , omp_nthreads = 1 ,
67
+ * ,
68
+ atlas_threshold = 3 ,
69
+ debug = False ,
70
+ name = "syn_sdc_wf" ,
71
+ omp_nthreads = 1 ,
59
72
):
60
73
"""
61
74
Build the *fieldmap-less* susceptibility-distortion estimation workflow.
@@ -99,7 +112,7 @@ def init_syn_sdc_wf(
99
112
A preprocessed, skull-stripped anatomical (T1w or T2w) image.
100
113
std2anat_xfm : :obj:`str`
101
114
inverse registration transform of T1w image to MNI template
102
- anat2bold_xfm : :obj:`str`
115
+ anat2epi_xfm : :obj:`str`
103
116
transform mapping coordinates from the EPI space to the anatomical
104
117
space (i.e., the transform to resample anatomical info into EPI space.)
105
118
@@ -120,7 +133,14 @@ def init_syn_sdc_wf(
120
133
FixHeaderRegistration as Registration ,
121
134
)
122
135
from niworkflows .interfaces .nibabel import Binarize
136
+ from ...interfaces .epi import EPIMask
123
137
from ...utils .misc import front as _pop
138
+ from ...interfaces .bspline import (
139
+ BSplineApprox ,
140
+ DEFAULT_LF_ZOOMS_MM ,
141
+ DEFAULT_HF_ZOOMS_MM ,
142
+ DEFAULT_ZOOMS_MM ,
143
+ )
124
144
125
145
workflow = Workflow (name = name )
126
146
workflow .__desc__ = f"""\
@@ -137,12 +157,13 @@ def init_syn_sdc_wf(
137
157
"""
138
158
inputnode = pe .Node (
139
159
niu .IdentityInterface (
140
- ["epi_ref" , "epi_mask" , "anat_brain" , "std2anat_xfm" , "anat2bold_xfm " ]
160
+ ["epi_ref" , "epi_mask" , "anat_brain" , "std2anat_xfm" , "anat2epi_xfm " ]
141
161
),
142
162
name = "inputnode" ,
143
163
)
144
164
outputnode = pe .Node (
145
- niu .IdentityInterface (["fmap" , "fmap_ref" , "fmap_mask" ]), name = "outputnode" ,
165
+ niu .IdentityInterface (["fmap" , "fmap_ref" , "fmap_coeff" , "fmap_mask" ]),
166
+ name = "outputnode" ,
146
167
)
147
168
148
169
invert_t1w = pe .Node (Rescale (invert = True ), name = "invert_t1w" , mem_gb = 0.3 )
@@ -174,29 +195,54 @@ def init_syn_sdc_wf(
174
195
n_procs = omp_nthreads ,
175
196
)
176
197
177
- unwarp_ref = pe .Node (ApplyTransforms (interpolation = "BSpline" ), name = "unwarp_ref" ,)
198
+ unwarp_ref = pe .Node (
199
+ ApplyTransforms (interpolation = "BSpline" ),
200
+ name = "unwarp_ref" ,
201
+ )
202
+
203
+ epi_mask = pe .Node (EPIMask (), name = "epi_mask" )
204
+
205
+ # Extract nonzero component
206
+ extract_field = pe .Node (niu .Function (function = _extract_field ), name = "extract_field" )
207
+
208
+ # Regularize with B-Splines
209
+ bs_filter = pe .Node (BSplineApprox (), n_procs = omp_nthreads , name = "bs_filter" )
210
+ bs_filter .interface ._always_run = debug
211
+ bs_filter .inputs .bs_spacing = (
212
+ [DEFAULT_LF_ZOOMS_MM , DEFAULT_HF_ZOOMS_MM ] if not debug else [DEFAULT_ZOOMS_MM ]
213
+ )
214
+ bs_filter .inputs .extrapolate = not debug
178
215
179
216
# fmt: off
180
217
workflow .connect ([
181
- (inputnode , transform_list , [("anat2bold_xfm " , "in1" ),
218
+ (inputnode , transform_list , [("anat2epi_xfm " , "in1" ),
182
219
("std2anat_xfm" , "in2" )]),
183
220
(inputnode , invert_t1w , [("anat_brain" , "in_file" ),
184
221
(("epi_ref" , _pop ), "ref_file" )]),
185
- (inputnode , anat2epi , [(("epi_ref" , _pop ), "reference_image" )]),
222
+ (inputnode , anat2epi , [(("epi_ref" , _pop ), "reference_image" ),
223
+ ("anat2epi_xfm" , "transforms" )]),
186
224
(inputnode , syn , [(("epi_ref" , _pop ), "moving_image" ),
187
225
("epi_mask" , "moving_image_masks" ),
188
226
(("epi_ref" , _warp_dir ), "restrict_deformation" )]),
227
+ (inputnode , unwarp_ref , [(("epi_ref" , _pop ), "reference_image" ),
228
+ (("epi_ref" , _pop ), "input_image" )]),
189
229
(inputnode , prior2epi , [(("epi_ref" , _pop ), "reference_image" )]),
230
+ (inputnode , extract_field , [("epi_ref" , "epi_meta" )]),
190
231
(invert_t1w , anat2epi , [("out_file" , "input_image" )]),
191
232
(transform_list , prior2epi , [("out" , "transforms" )]),
192
233
(prior2epi , atlas_msk , [("output_image" , "in_file" )]),
193
234
(anat2epi , syn , [("output_image" , "fixed_image" )]),
194
235
(atlas_msk , syn , [(("out_mask" , _fixed_masks_arg ), "fixed_image_masks" )]),
195
- (syn , outputnode , [("forward_transforms" , "fmap " )]),
236
+ (syn , extract_field , [("forward_transforms" , "in_file " )]),
196
237
(syn , unwarp_ref , [("forward_transforms" , "transforms" )]),
197
- (inputnode , unwarp_ref , [(("epi_ref" , _pop ), "reference_image" ),
198
- (("epi_ref" , _pop ), "input_image" )]),
238
+ (unwarp_ref , epi_mask , [("output_image" , "in_file" )]),
239
+ (extract_field , bs_filter , [("out" , "in_data" )]),
240
+ (epi_mask , bs_filter , [("out_file" , "in_mask" )]),
199
241
(unwarp_ref , outputnode , [("output_image" , "fmap_ref" )]),
242
+ (epi_mask , outputnode , [("out_file" , "fmap_mask" )]),
243
+ (bs_filter , outputnode , [
244
+ ("out_extrapolated" if not debug else "out_field" , "fmap" ),
245
+ ("out_coeff" , "fmap_coeff" )]),
200
246
])
201
247
# fmt: on
202
248
@@ -231,3 +277,41 @@ def _fixed_masks_arg(mask):
231
277
232
278
"""
233
279
return ["NULL" , mask ]
280
+
281
+
282
+ def _extract_field (in_file , epi_meta ):
283
+ """
284
+ Extract the nonzero component of the deformation field estimated by ANTs.
285
+
286
+ Examples
287
+ --------
288
+ >>> nii = nb.load(
289
+ ... _extract_field(
290
+ ... ["field.nii.gz"],
291
+ ... ("epi.nii.gz", {"PhaseEncodingDirection": "j-", "TotalReadoutTime": 0.005}))
292
+ ... )
293
+ >>> nii.shape
294
+ (10, 10, 10)
295
+
296
+ >>> np.allclose(nii.get_fdata(), 200)
297
+ True
298
+
299
+ """
300
+ from nipype .utils .filemanip import fname_presuffix
301
+ import numpy as np
302
+ import nibabel as nb
303
+ from sdcflows .utils .epimanip import get_trt
304
+
305
+ fieldnii = nb .load (in_file [0 ])
306
+ trt = get_trt (epi_meta [1 ], in_file = epi_meta [0 ])
307
+ data = (
308
+ np .squeeze (fieldnii .get_fdata (dtype = "float32" ))[
309
+ ..., "ijk" .index (epi_meta [1 ]["PhaseEncodingDirection" ][0 ])
310
+ ]
311
+ / trt
312
+ )
313
+ out_file = fname_presuffix (in_file [0 ], suffix = "_fieldmap" )
314
+ nii = nb .Nifti1Image (data , fieldnii .affine , None )
315
+ nii .header .set_xyzt_units (fieldnii .header .get_xyzt_units ()[0 ])
316
+ nii .to_filename (out_file )
317
+ return out_file
0 commit comments