|
8 | 8 | Direct B0 mapping sequences
|
9 | 9 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
10 | 10 | When the fieldmap is directly measured with a prescribed sequence (such as
|
11 |
| -:abbr:`SE (spiral echo)`), we only need to calculate the corresponding B-Spline |
12 |
| -coefficients to adapt the fieldmap to the TOPUP tool. |
| 11 | +:abbr:`SE (spiral echo)`), we only need to calculate the corresponding |
| 12 | +displacements field that accounts for the distortions. |
13 | 13 | This procedure is described with more detail `here
|
14 | 14 | <https://cni.stanford.edu/wiki/GE_Processing#Fieldmaps>`__.
|
15 | 15 |
|
16 | 16 | This corresponds to `this section of the BIDS specification
|
17 | 17 | <https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#a-real-fieldmap-image>`__.
|
18 | 18 |
|
19 | 19 | """
|
| 20 | +import pkg_resources as pkgr |
20 | 21 |
|
21 | 22 | from nipype.pipeline import engine as pe
|
22 | 23 | from nipype.interfaces import utility as niu, fsl
|
23 | 24 | from niworkflows.engine.workflows import LiterateWorkflow as Workflow
|
| 25 | +from niworkflows.interfaces import itk |
24 | 26 | from niworkflows.interfaces.images import IntraModalMerge
|
| 27 | +from niworkflows.interfaces.registration import ANTSApplyTransformsRPT, ANTSRegistrationRPT |
25 | 28 |
|
| 29 | +from ..interfaces.fmap import get_ees as _get_ees, FieldToRadS |
26 | 30 | from .gre import init_fmap_postproc_wf, init_magnitude_wf
|
27 | 31 |
|
28 | 32 |
|
@@ -71,6 +75,10 @@ def init_fmap_wf(omp_nthreads, fmap_bspline, name='fmap_wf'):
|
71 | 75 |
|
72 | 76 | """
|
73 | 77 | workflow = Workflow(name=name)
|
| 78 | + workflow.__desc__ = """\ |
| 79 | +A B0-nonuniformity map (or *fieldmap*) was directly measured with an MRI scheme |
| 80 | +designed with that purpose (typically, a spiral pulse sequence). |
| 81 | +""" |
74 | 82 | inputnode = pe.Node(niu.IdentityInterface(
|
75 | 83 | fields=['magnitude', 'fieldmap']), name='inputnode')
|
76 | 84 | outputnode = pe.Node(niu.IdentityInterface(fields=['fmap', 'fmap_ref', 'fmap_mask']),
|
@@ -101,3 +109,138 @@ def init_fmap_wf(omp_nthreads, fmap_bspline, name='fmap_wf'):
|
101 | 109 | (fmap_postproc_wf, outputnode, [('outputnode.out_fmap', 'fmap')]),
|
102 | 110 | ])
|
103 | 111 | return workflow
|
| 112 | + |
| 113 | + |
| 114 | +def init_fmap2field_wf(omp_nthreads, debug, name='fmap2field_wf'): |
| 115 | + """ |
| 116 | + Convert the estimated fieldmap in Hz into a displacements field. |
| 117 | +
|
| 118 | + This workflow takes in a fieldmap and calculates the corresponding |
| 119 | + displacements field (in other words, an ANTs-compatible warp file). |
| 120 | +
|
| 121 | + Workflow Graph |
| 122 | + .. workflow :: |
| 123 | + :graph2use: orig |
| 124 | + :simple_form: yes |
| 125 | +
|
| 126 | + from sdcflows.workflows.fmap import init_fmap2field_wf |
| 127 | + wf = init_fmap2field_wf(omp_nthreads=8, |
| 128 | + debug=False) |
| 129 | +
|
| 130 | + Parameters |
| 131 | + ---------- |
| 132 | + omp_nthreads : int |
| 133 | + Maximum number of threads an individual process may use. |
| 134 | + debug : bool |
| 135 | + Run fast configurations of registrations. |
| 136 | + name : str |
| 137 | + Unique name of this workflow. |
| 138 | +
|
| 139 | + Inputs |
| 140 | + ------ |
| 141 | + in_reference |
| 142 | + the reference image |
| 143 | + in_reference_brain |
| 144 | + the reference image (skull-stripped) |
| 145 | + metadata |
| 146 | + metadata associated to the ``in_reference`` EPI input |
| 147 | + fmap |
| 148 | + the fieldmap in Hz |
| 149 | + fmap_ref |
| 150 | + the reference (anatomical) image corresponding to ``fmap`` |
| 151 | + fmap_mask |
| 152 | + a brain mask corresponding to ``fmap`` |
| 153 | +
|
| 154 | +
|
| 155 | + Outputs |
| 156 | + ------- |
| 157 | + out_reference |
| 158 | + the ``in_reference`` after unwarping |
| 159 | + out_reference_brain |
| 160 | + the ``in_reference`` after unwarping and skullstripping |
| 161 | + out_warp |
| 162 | + the corresponding :abbr:`DFM (displacements field map)` compatible with |
| 163 | + ANTs |
| 164 | + out_jacobian |
| 165 | + the jacobian of the field (for drop-out alleviation) |
| 166 | + out_mask |
| 167 | + mask of the unwarped input file |
| 168 | +
|
| 169 | + """ |
| 170 | + workflow = Workflow(name=name) |
| 171 | + workflow.__desc__ = """\ |
| 172 | +The *fieldmap* was then co-registered to the target EPI (echo-planar imaging) |
| 173 | +reference run and converted to a displacements field map (amenable to registration |
| 174 | +tools such as ANTs) with FSL's `fugue` and other *SDCflows* tools. |
| 175 | +""" |
| 176 | + inputnode = pe.Node(niu.IdentityInterface( |
| 177 | + fields=['in_reference', 'in_reference_brain', 'metadata', |
| 178 | + 'fmap_ref', 'fmap_mask', 'fmap']), name='inputnode') |
| 179 | + outputnode = pe.Node(niu.IdentityInterface( |
| 180 | + fields=['out_warp']), name='outputnode') |
| 181 | + |
| 182 | + # Register the reference of the fieldmap to the reference |
| 183 | + # of the target image (the one that shall be corrected) |
| 184 | + ants_settings = pkgr.resource_filename('sdcflows', 'data/fmap-any_registration.json') |
| 185 | + if debug: |
| 186 | + ants_settings = pkgr.resource_filename( |
| 187 | + 'sdcflows', 'data/fmap-any_registration_testing.json') |
| 188 | + |
| 189 | + fmap2ref_reg = pe.Node( |
| 190 | + ANTSRegistrationRPT(generate_report=True, from_file=ants_settings, |
| 191 | + output_inverse_warped_image=True, output_warped_image=True), |
| 192 | + name='fmap2ref_reg', n_procs=omp_nthreads) |
| 193 | + |
| 194 | + # Map the VSM into the EPI space |
| 195 | + fmap2ref_apply = pe.Node(ANTSApplyTransformsRPT( |
| 196 | + generate_report=True, dimension=3, interpolation='BSpline', float=True), |
| 197 | + name='fmap2ref_apply') |
| 198 | + |
| 199 | + fmap_mask2ref_apply = pe.Node(ANTSApplyTransformsRPT( |
| 200 | + generate_report=False, dimension=3, interpolation='MultiLabel', |
| 201 | + float=True), |
| 202 | + name='fmap_mask2ref_apply') |
| 203 | + |
| 204 | + # Fieldmap to rads and then to voxels (VSM - voxel shift map) |
| 205 | + torads = pe.Node(FieldToRadS(fmap_range=0.5), name='torads') |
| 206 | + |
| 207 | + get_ees = pe.Node(niu.Function(function=_get_ees, output_names=['ees']), name='get_ees') |
| 208 | + |
| 209 | + gen_vsm = pe.Node(fsl.FUGUE(save_unmasked_shift=True), name='gen_vsm') |
| 210 | + # Convert the VSM into a DFM (displacements field map) |
| 211 | + # or: FUGUE shift to ANTS warping. |
| 212 | + vsm2dfm = pe.Node(itk.FUGUEvsm2ANTSwarp(), name='vsm2dfm') |
| 213 | + |
| 214 | + workflow.connect([ |
| 215 | + (inputnode, fmap2ref_reg, [('fmap_ref', 'moving_image'), |
| 216 | + ('in_reference_brain', 'fixed_image')]), |
| 217 | + (inputnode, fmap2ref_apply, [('fmap', 'input_image'), |
| 218 | + ('in_reference', 'reference_image')]), |
| 219 | + (inputnode, fmap_mask2ref_apply, [('in_reference', 'reference_image'), |
| 220 | + ('fmap_mask', 'input_image')]), |
| 221 | + (inputnode, get_ees, [('in_reference', 'in_file'), |
| 222 | + ('metadata', 'in_meta')]), |
| 223 | + (inputnode, gen_vsm, [(('metadata', _get_pedir_fugue), 'unwarp_direction')]), |
| 224 | + (inputnode, vsm2dfm, [(('metadata', _get_pedir_bids), 'pe_dir')]), |
| 225 | + (fmap2ref_reg, fmap2ref_apply, [ |
| 226 | + ('composite_transform', 'transforms')]), |
| 227 | + (fmap2ref_reg, fmap_mask2ref_apply, [ |
| 228 | + ('composite_transform', 'transforms')]), |
| 229 | + (fmap2ref_apply, torads, [('output_image', 'in_file')]), |
| 230 | + (fmap_mask2ref_apply, gen_vsm, [('output_image', 'mask_file')]), |
| 231 | + (get_ees, gen_vsm, [('ees', 'dwell_time')]), |
| 232 | + (gen_vsm, vsm2dfm, [('shift_out_file', 'in_file')]), |
| 233 | + (torads, gen_vsm, [('out_file', 'fmap_in_file')]), |
| 234 | + (vsm2dfm, outputnode, [('out_file', 'out_warp')]), |
| 235 | + ]) |
| 236 | + return workflow |
| 237 | + |
| 238 | + |
| 239 | +# Helper functions |
| 240 | +# ------------------------------------------------------------ |
| 241 | +def _get_pedir_bids(in_dict): |
| 242 | + return in_dict['PhaseEncodingDirection'] |
| 243 | + |
| 244 | + |
| 245 | +def _get_pedir_fugue(in_dict): |
| 246 | + return in_dict['PhaseEncodingDirection'].replace('i', 'x').replace('j', 'y').replace('k', 'z') |
0 commit comments