33"""
44Estimating the susceptibility distortions without fieldmaps.
55
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+
615.. _sdc_fieldmapless :
716
817Fieldmap-less approaches
5564
5665
5766def 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 ,
5972):
6073 """
6174 Build the *fieldmap-less* susceptibility-distortion estimation workflow.
@@ -99,7 +112,7 @@ def init_syn_sdc_wf(
99112 A preprocessed, skull-stripped anatomical (T1w or T2w) image.
100113 std2anat_xfm : :obj:`str`
101114 inverse registration transform of T1w image to MNI template
102- anat2bold_xfm : :obj:`str`
115+ anat2epi_xfm : :obj:`str`
103116 transform mapping coordinates from the EPI space to the anatomical
104117 space (i.e., the transform to resample anatomical info into EPI space.)
105118
@@ -120,7 +133,14 @@ def init_syn_sdc_wf(
120133 FixHeaderRegistration as Registration ,
121134 )
122135 from niworkflows .interfaces .nibabel import Binarize
136+ from ...interfaces .epi import EPIMask
123137 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+ )
124144
125145 workflow = Workflow (name = name )
126146 workflow .__desc__ = f"""\
@@ -137,12 +157,13 @@ def init_syn_sdc_wf(
137157"""
138158 inputnode = pe .Node (
139159 niu .IdentityInterface (
140- ["epi_ref" , "epi_mask" , "anat_brain" , "std2anat_xfm" , "anat2bold_xfm " ]
160+ ["epi_ref" , "epi_mask" , "anat_brain" , "std2anat_xfm" , "anat2epi_xfm " ]
141161 ),
142162 name = "inputnode" ,
143163 )
144164 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" ,
146167 )
147168
148169 invert_t1w = pe .Node (Rescale (invert = True ), name = "invert_t1w" , mem_gb = 0.3 )
@@ -174,29 +195,54 @@ def init_syn_sdc_wf(
174195 n_procs = omp_nthreads ,
175196 )
176197
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
178215
179216 # fmt: off
180217 workflow .connect ([
181- (inputnode , transform_list , [("anat2bold_xfm " , "in1" ),
218+ (inputnode , transform_list , [("anat2epi_xfm " , "in1" ),
182219 ("std2anat_xfm" , "in2" )]),
183220 (inputnode , invert_t1w , [("anat_brain" , "in_file" ),
184221 (("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" )]),
186224 (inputnode , syn , [(("epi_ref" , _pop ), "moving_image" ),
187225 ("epi_mask" , "moving_image_masks" ),
188226 (("epi_ref" , _warp_dir ), "restrict_deformation" )]),
227+ (inputnode , unwarp_ref , [(("epi_ref" , _pop ), "reference_image" ),
228+ (("epi_ref" , _pop ), "input_image" )]),
189229 (inputnode , prior2epi , [(("epi_ref" , _pop ), "reference_image" )]),
230+ (inputnode , extract_field , [("epi_ref" , "epi_meta" )]),
190231 (invert_t1w , anat2epi , [("out_file" , "input_image" )]),
191232 (transform_list , prior2epi , [("out" , "transforms" )]),
192233 (prior2epi , atlas_msk , [("output_image" , "in_file" )]),
193234 (anat2epi , syn , [("output_image" , "fixed_image" )]),
194235 (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 " )]),
196237 (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" )]),
199241 (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" )]),
200246 ])
201247 # fmt: on
202248
@@ -231,3 +277,41 @@ def _fixed_masks_arg(mask):
231277
232278 """
233279 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 * (- 1.0 if epi_meta [1 ]["PhaseEncodingDirection" ].endswith ("-" ) else 1.0 )
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