Skip to content

Commit 2a87027

Browse files
committed
fix: add recenter node, improve documentation
1 parent 96305cf commit 2a87027

File tree

2 files changed

+47
-10
lines changed

2 files changed

+47
-10
lines changed

sdcflows/interfaces/fmap.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,16 @@ class _Phasediff2FieldmapOutputSpec(TraitedSpec):
187187

188188

189189
class Phasediff2Fieldmap(SimpleInterface):
190-
"""Convert a phase difference map into a fieldmap in Hz."""
190+
"""
191+
Convert a phase difference map into a fieldmap in Hz.
192+
193+
This interface is equivalent to running the following steps:
194+
#. Convert from rad to rad/s
195+
(``niflow.nipype1.workflows.dmri.fsl.utils.rads2radsec``)
196+
#. FUGUE execution: fsl.FUGUE(save_fmap=True)
197+
#. Conversion from rad/s to Hz (divide by 2pi, ``rsec2hz``).
198+
199+
"""
191200

192201
input_spec = _Phasediff2FieldmapInputSpec
193202
output_spec = _Phasediff2FieldmapOutputSpec

sdcflows/workflows/phdiff.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,22 @@
2929

3030

3131
def init_phdiff_wf(omp_nthreads, name='phdiff_wf'):
32-
"""
32+
r"""
3333
Distortion correction of EPI sequences using phase-difference maps.
3434
3535
Estimates the fieldmap using a phase-difference image and one or more
3636
magnitude images corresponding to two or more :abbr:`GRE (Gradient Echo sequence)`
37-
acquisitions. The `original code was taken from nipype
37+
acquisitions.
38+
The most delicate bit of this workflow is the phase-unwrapping process: phase maps
39+
are clipped in the range :math:`[0 \dotsb 2 \cdot \pi )`.
40+
To find the integer number of offsets that make a region continously smooth with
41+
its neighbour, FSL PRELUDE is run [Jenkinson1998]_.
42+
FSL PRELUDE takes wrapped maps in the range 0 to 6.28, `as per the user guide
43+
<https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FUGUE/Guide#Step_2_-_Getting_.28wrapped.29_phase_in_radians>`__.
44+
For the phase-difference maps, recentering back to :math:`[-\pi \dotsb \pi )` is necessary.
45+
After some massaging and the application of the effective echo spacing parameter,
46+
the phase-difference maps can be converted into a *B0 field map* in Hz units.
47+
The `original code was taken from nipype
3848
<https://github.com/nipy/nipype/blob/0.12.1/nipype/workflows/dmri/fsl/artifacts.py#L514>`_.
3949
4050
Workflow Graph
@@ -98,11 +108,15 @@ def init_phdiff_wf(omp_nthreads, name='phdiff_wf'):
98108
# nan2zeros=True, args='-kernel sphere 5 -dilM'), name='MskDilate')
99109

100110
# phase diff -> radians
101-
phmap2rads = pe.Node(PhaseMap2rads(), name='phmap2rads')
111+
phmap2rads = pe.Node(PhaseMap2rads(), name='phmap2rads',
112+
run_without_submitting=True)
102113

103114
# FSL PRELUDE will perform phase-unwrapping
104115
prelude = pe.Node(fsl.PRELUDE(), name='prelude')
105116

117+
recenter = pe.Node(niu.Function(function=_recenter),
118+
name='recenter', run_without_submitting=True)
119+
106120
denoise = pe.Node(fsl.SpatialFilter(operation='median', kernel_shape='sphere',
107121
kernel_size=3), name='denoise')
108122

@@ -112,11 +126,6 @@ def init_phdiff_wf(omp_nthreads, name='phdiff_wf'):
112126

113127
compfmap = pe.Node(Phasediff2Fieldmap(), name='compfmap')
114128

115-
# The phdiff2fmap interface is equivalent to:
116-
# rad2rsec (using rads2radsec from niflow.nipype1.workflows.dmri.fsl.utils)
117-
# pre_fugue = pe.Node(fsl.FUGUE(save_fmap=True), name='ComputeFieldmapFUGUE')
118-
# rsec2hz (divide by 2pi)
119-
120129
workflow.connect([
121130
(inputnode, compfmap, [('metadata', 'metadata')]),
122131
(inputnode, magmrg, [('magnitude', 'in_files')]),
@@ -126,7 +135,8 @@ def init_phdiff_wf(omp_nthreads, name='phdiff_wf'):
126135
(bet, prelude, [('mask_file', 'mask_file')]),
127136
(inputnode, phmap2rads, [('phasediff', 'in_file')]),
128137
(phmap2rads, prelude, [('out_file', 'phase_file')]),
129-
(prelude, denoise, [('unwrapped_phase_file', 'in_file')]),
138+
(prelude, recenter, [('unwrapped_phase_file', 'in_file')]),
139+
(recenter, denoise, [('out', 'in_file')]),
130140
(denoise, demean, [('out_file', 'in_file')]),
131141
(demean, cleanup_wf, [('out', 'inputnode.in_file')]),
132142
(bet, cleanup_wf, [('mask_file', 'inputnode.in_mask')]),
@@ -137,3 +147,21 @@ def init_phdiff_wf(omp_nthreads, name='phdiff_wf'):
137147
])
138148

139149
return workflow
150+
151+
152+
def _recenter(in_file, offset=None):
153+
"""Recenter the phase-map distribution to the -pi..pi range."""
154+
from os.path import basename
155+
import numpy as np
156+
import nibabel as nb
157+
from nipype.utils.filemanip import fname_presuffix
158+
159+
if offset is None:
160+
offset = np.pi
161+
162+
nii = nb.load(in_file)
163+
data = nii.get_fdata(dtype='float32') - offset
164+
165+
out_file = fname_presuffix(basename(in_file), suffix='_recentered')
166+
nb.Nifti1Image(data, nii.affine, nii.header).to_filename(out_file)
167+
return out_file

0 commit comments

Comments
 (0)