diff --git a/sdcflows/workflows/base.py b/sdcflows/workflows/base.py index fd8dc1a7ad..67bc9761bc 100644 --- a/sdcflows/workflows/base.py +++ b/sdcflows/workflows/base.py @@ -151,6 +151,7 @@ def init_sdc_estimate_wf(fmaps, epi_meta, omp_nthreads=1, debug=False, ignore=No # FIELDMAP path elif 'fieldmap' in fmaps or 'phasediff' in fmaps: + from .fmap import init_fmap2field_wf from .unwarp import init_sdc_unwarp_wf # SyN works without this metadata @@ -179,21 +180,28 @@ def init_sdc_estimate_wf(fmaps, epi_meta, omp_nthreads=1, debug=False, ignore=No m for m, _ in fmaps['phasediff']['magnitude']] fmap_wf.inputs.inputnode.phasediff = fmaps['phasediff']['phases'] + fmap2field_wf = init_fmap2field_wf(omp_nthreads=omp_nthreads, debug=debug) + fmap2field_wf.inputs.inputnode.metadata = epi_meta + sdc_unwarp_wf = init_sdc_unwarp_wf( omp_nthreads=omp_nthreads, debug=debug, name='sdc_unwarp_wf') - sdc_unwarp_wf.inputs.inputnode.metadata = epi_meta workflow.connect([ + (inputnode, fmap2field_wf, [ + ('epi_file', 'inputnode.in_reference'), + ('epi_brain', 'inputnode.in_reference_brain')]), (inputnode, sdc_unwarp_wf, [ ('epi_file', 'inputnode.in_reference'), - ('epi_brain', 'inputnode.in_reference_brain'), - ('epi_mask', 'inputnode.in_mask')]), - (fmap_wf, sdc_unwarp_wf, [ + ('epi_brain', 'inputnode.in_reference_brain')]), + (fmap_wf, fmap2field_wf, [ ('outputnode.fmap', 'inputnode.fmap'), ('outputnode.fmap_ref', 'inputnode.fmap_ref'), ('outputnode.fmap_mask', 'inputnode.fmap_mask')]), + (fmap2field_wf, sdc_unwarp_wf, [ + ('outputnode.out_warp', 'inputnode.in_warp')]), + ]) elif not only_syn: raise ValueError('Fieldmaps of types %s are not supported' % diff --git a/sdcflows/workflows/fmap.py b/sdcflows/workflows/fmap.py index 9da412a463..b959239c89 100644 --- a/sdcflows/workflows/fmap.py +++ b/sdcflows/workflows/fmap.py @@ -8,8 +8,8 @@ Direct B0 mapping sequences ~~~~~~~~~~~~~~~~~~~~~~~~~~~ When the fieldmap is directly measured with a prescribed sequence (such as -:abbr:`SE (spiral echo)`), we only need to calculate the corresponding B-Spline -coefficients to adapt the fieldmap to the TOPUP tool. +:abbr:`SE (spiral echo)`), we only need to calculate the corresponding +displacements field that accounts for the distortions. This procedure is described with more detail `here `__. @@ -17,12 +17,16 @@ `__. """ +import pkg_resources as pkgr from nipype.pipeline import engine as pe from nipype.interfaces import utility as niu, fsl from niworkflows.engine.workflows import LiterateWorkflow as Workflow +from niworkflows.interfaces import itk from niworkflows.interfaces.images import IntraModalMerge +from niworkflows.interfaces.registration import ANTSApplyTransformsRPT, ANTSRegistrationRPT +from ..interfaces.fmap import get_ees as _get_ees, FieldToRadS from .gre import init_fmap_postproc_wf, init_magnitude_wf @@ -71,6 +75,10 @@ def init_fmap_wf(omp_nthreads, fmap_bspline, name='fmap_wf'): """ workflow = Workflow(name=name) + workflow.__desc__ = """\ +A B0-nonuniformity map (or *fieldmap*) was directly measured with an MRI scheme +designed with that purpose (typically, a spiral pulse sequence). +""" inputnode = pe.Node(niu.IdentityInterface( fields=['magnitude', 'fieldmap']), name='inputnode') 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'): (fmap_postproc_wf, outputnode, [('outputnode.out_fmap', 'fmap')]), ]) return workflow + + +def init_fmap2field_wf(omp_nthreads, debug, name='fmap2field_wf'): + """ + Convert the estimated fieldmap in Hz into a displacements field. + + This workflow takes in a fieldmap and calculates the corresponding + displacements field (in other words, an ANTs-compatible warp file). + + Workflow Graph + .. workflow :: + :graph2use: orig + :simple_form: yes + + from sdcflows.workflows.fmap import init_fmap2field_wf + wf = init_fmap2field_wf(omp_nthreads=8, + debug=False) + + Parameters + ---------- + omp_nthreads : int + Maximum number of threads an individual process may use. + debug : bool + Run fast configurations of registrations. + name : str + Unique name of this workflow. + + Inputs + ------ + in_reference + the reference image + in_reference_brain + the reference image (skull-stripped) + metadata + metadata associated to the ``in_reference`` EPI input + fmap + the fieldmap in Hz + fmap_ref + the reference (anatomical) image corresponding to ``fmap`` + fmap_mask + a brain mask corresponding to ``fmap`` + + + Outputs + ------- + out_reference + the ``in_reference`` after unwarping + out_reference_brain + the ``in_reference`` after unwarping and skullstripping + out_warp + the corresponding :abbr:`DFM (displacements field map)` compatible with + ANTs + out_jacobian + the jacobian of the field (for drop-out alleviation) + out_mask + mask of the unwarped input file + + """ + workflow = Workflow(name=name) + workflow.__desc__ = """\ +The *fieldmap* was then co-registered to the target EPI (echo-planar imaging) +reference run and converted to a displacements field map (amenable to registration +tools such as ANTs) with FSL's `fugue` and other *SDCflows* tools. +""" + inputnode = pe.Node(niu.IdentityInterface( + fields=['in_reference', 'in_reference_brain', 'metadata', + 'fmap_ref', 'fmap_mask', 'fmap']), name='inputnode') + outputnode = pe.Node(niu.IdentityInterface( + fields=['out_warp']), name='outputnode') + + # Register the reference of the fieldmap to the reference + # of the target image (the one that shall be corrected) + ants_settings = pkgr.resource_filename('sdcflows', 'data/fmap-any_registration.json') + if debug: + ants_settings = pkgr.resource_filename( + 'sdcflows', 'data/fmap-any_registration_testing.json') + + fmap2ref_reg = pe.Node( + ANTSRegistrationRPT(generate_report=True, from_file=ants_settings, + output_inverse_warped_image=True, output_warped_image=True), + name='fmap2ref_reg', n_procs=omp_nthreads) + + # Map the VSM into the EPI space + fmap2ref_apply = pe.Node(ANTSApplyTransformsRPT( + generate_report=True, dimension=3, interpolation='BSpline', float=True), + name='fmap2ref_apply') + + fmap_mask2ref_apply = pe.Node(ANTSApplyTransformsRPT( + generate_report=False, dimension=3, interpolation='MultiLabel', + float=True), + name='fmap_mask2ref_apply') + + # Fieldmap to rads and then to voxels (VSM - voxel shift map) + torads = pe.Node(FieldToRadS(fmap_range=0.5), name='torads') + + get_ees = pe.Node(niu.Function(function=_get_ees, output_names=['ees']), name='get_ees') + + gen_vsm = pe.Node(fsl.FUGUE(save_unmasked_shift=True), name='gen_vsm') + # Convert the VSM into a DFM (displacements field map) + # or: FUGUE shift to ANTS warping. + vsm2dfm = pe.Node(itk.FUGUEvsm2ANTSwarp(), name='vsm2dfm') + + workflow.connect([ + (inputnode, fmap2ref_reg, [('fmap_ref', 'moving_image'), + ('in_reference_brain', 'fixed_image')]), + (inputnode, fmap2ref_apply, [('fmap', 'input_image'), + ('in_reference', 'reference_image')]), + (inputnode, fmap_mask2ref_apply, [('in_reference', 'reference_image'), + ('fmap_mask', 'input_image')]), + (inputnode, get_ees, [('in_reference', 'in_file'), + ('metadata', 'in_meta')]), + (inputnode, gen_vsm, [(('metadata', _get_pedir_fugue), 'unwarp_direction')]), + (inputnode, vsm2dfm, [(('metadata', _get_pedir_bids), 'pe_dir')]), + (fmap2ref_reg, fmap2ref_apply, [ + ('composite_transform', 'transforms')]), + (fmap2ref_reg, fmap_mask2ref_apply, [ + ('composite_transform', 'transforms')]), + (fmap2ref_apply, torads, [('output_image', 'in_file')]), + (fmap_mask2ref_apply, gen_vsm, [('output_image', 'mask_file')]), + (get_ees, gen_vsm, [('ees', 'dwell_time')]), + (gen_vsm, vsm2dfm, [('shift_out_file', 'in_file')]), + (torads, gen_vsm, [('out_file', 'fmap_in_file')]), + (vsm2dfm, outputnode, [('out_file', 'out_warp')]), + ]) + return workflow + + +# Helper functions +# ------------------------------------------------------------ +def _get_pedir_bids(in_dict): + return in_dict['PhaseEncodingDirection'] + + +def _get_pedir_fugue(in_dict): + return in_dict['PhaseEncodingDirection'].replace('i', 'x').replace('j', 'y').replace('k', 'z') diff --git a/sdcflows/workflows/outputs.py b/sdcflows/workflows/outputs.py new file mode 100644 index 0000000000..1492861880 --- /dev/null +++ b/sdcflows/workflows/outputs.py @@ -0,0 +1,80 @@ +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +"""Writing out outputs.""" +from nipype.pipeline import engine as pe +from nipype.interfaces import utility as niu +from niworkflows.interfaces.bids import DerivativesDataSink + + +def init_sdc_unwarp_report_wf(name='sdc_unwarp_report_wf', forcedsyn=False): + """ + Save a reportlet showing how SDC unwarping performed. + + This workflow generates and saves a reportlet showing the effect of fieldmap + unwarping a BOLD image. + + .. workflow:: + :graph2use: orig + :simple_form: yes + + from sdcflows.workflows.outputs import init_sdc_unwarp_report_wf + wf = init_sdc_unwarp_report_wf() + + Parameters + ---------- + name : str, optional + Workflow name (default: ``sdc_unwarp_report_wf``) + forcedsyn : bool, optional + Whether SyN-SDC was forced. + + Inputs + ------ + in_pre + Reference image, before unwarping + in_post + Reference image, after unwarping + in_seg + Segmentation of preprocessed structural image, including + gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF) + in_xfm + Affine transform from T1 space to BOLD space (ITK format) + + """ + from niworkflows.interfaces import SimpleBeforeAfter + from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms + from niworkflows.interfaces.images import extract_wm + + DEFAULT_MEMORY_MIN_GB = 0.01 + + workflow = pe.Workflow(name=name) + + inputnode = pe.Node(niu.IdentityInterface( + fields=['in_pre', 'in_post', 'in_seg', 'in_xfm']), name='inputnode') + + map_seg = pe.Node(ApplyTransforms( + dimension=3, float=True, interpolation='MultiLabel'), + name='map_seg', mem_gb=0.3) + + sel_wm = pe.Node(niu.Function(function=extract_wm), name='sel_wm', + mem_gb=DEFAULT_MEMORY_MIN_GB) + + bold_rpt = pe.Node(SimpleBeforeAfter(), name='bold_rpt', + mem_gb=0.1) + ds_report_sdc = pe.Node( + DerivativesDataSink(desc='sdc' if not forcedsyn else 'forcedsyn', + suffix='bold'), name='ds_report_sdc', + mem_gb=DEFAULT_MEMORY_MIN_GB, run_without_submitting=True + ) + + workflow.connect([ + (inputnode, bold_rpt, [('in_post', 'after'), + ('in_pre', 'before')]), + (bold_rpt, ds_report_sdc, [('out_report', 'in_file')]), + (inputnode, map_seg, [('in_post', 'reference_image'), + ('in_seg', 'input_image'), + ('in_xfm', 'transforms')]), + (map_seg, sel_wm, [('output_image', 'in_seg')]), + (sel_wm, bold_rpt, [('out', 'wm_seg')]), + ]) + + return workflow diff --git a/sdcflows/workflows/pepolar.py b/sdcflows/workflows/pepolar.py index c29a1cfebf..c425c71f57 100644 --- a/sdcflows/workflows/pepolar.py +++ b/sdcflows/workflows/pepolar.py @@ -103,9 +103,9 @@ def init_pepolar_unwarp_wf(omp_nthreads=1, matched_pe=False, """ workflow = Workflow(name=name) workflow.__desc__ = """\ -A deformation field to correct for susceptibility distortions was estimated -based on two echo-planar imaging (EPI) references with opposing phase-encoding -directions, using `3dQwarp` @afni (AFNI {afni_ver}). +A B0-nonuniformity map (or *fieldmap*) was estimated based on two (or more) +echo-planar imaging (EPI) references with opposing phase-encoding +directions, with `3dQwarp` @afni (AFNI {afni_ver}). """.format(afni_ver=''.join(['%02d' % v for v in afni.Info().version() or []])) inputnode = pe.Node(niu.IdentityInterface( diff --git a/sdcflows/workflows/phdiff.py b/sdcflows/workflows/phdiff.py index 83f0fee807..df97c342b6 100644 --- a/sdcflows/workflows/phdiff.py +++ b/sdcflows/workflows/phdiff.py @@ -33,7 +33,7 @@ def init_phdiff_wf(omp_nthreads, name='phdiff_wf'): magnitude images corresponding to two or more :abbr:`GRE (Gradient Echo sequence)` acquisitions. The most delicate bit of this workflow is the phase-unwrapping process: phase maps - are clipped in the range :math:`[0 \dotsb 2 \cdot \pi )`. + are clipped in the range :math:`[0 \dotsb 2\pi )`. To find the integer number of offsets that make a region continously smooth with its neighbour, FSL PRELUDE is run [Jenkinson2003]_. FSL PRELUDE takes wrapped maps in the range 0 to 6.28, `as per the user guide @@ -83,11 +83,11 @@ def init_phdiff_wf(omp_nthreads, name='phdiff_wf'): """ workflow = Workflow(name=name) workflow.__desc__ = """\ -A deformation field to correct for susceptibility distortions was estimated -based on a field map that was co-registered to the EPI (echo-planar imaging) reference -run, using a custom workflow of *SDCFlows* derived from D. Greve's `epidewarp.fsl` -[script](http://www.nmr.mgh.harvard.edu/~greve/fbirn/b0/epidewarp.fsl) and -further improvements of HCP Pipelines [@hcppipelines]. +A B0-nonuniformity map (or *fieldmap*) was estimated based on a phase-difference map +calculated with a dual-echo GRE (gradient-recall echo) sequence, processed with a +custom workflow of *SDCFlows* inspired by the +[`epidewarp.fsl` script](http://www.nmr.mgh.harvard.edu/~greve/fbirn/b0/epidewarp.fsl) +and further improvements in HCP Pipelines [@hcppipelines]. """ inputnode = pe.Node(niu.IdentityInterface(fields=['magnitude', 'phasediff']), diff --git a/sdcflows/workflows/unwarp.py b/sdcflows/workflows/unwarp.py index ae74732b77..6c0d2e45a0 100644 --- a/sdcflows/workflows/unwarp.py +++ b/sdcflows/workflows/unwarp.py @@ -16,31 +16,25 @@ displacements field map -- a nifti warp file compatible with ANTs (mm) """ - -import pkg_resources as pkgr - from nipype.pipeline import engine as pe -from nipype.interfaces import ants, fsl, utility as niu +from nipype.interfaces import fsl, utility as niu from niworkflows.engine.workflows import LiterateWorkflow as Workflow -from niworkflows.interfaces import itk from niworkflows.interfaces.images import FilledImageLike -from niworkflows.interfaces.registration import ANTSApplyTransformsRPT, ANTSRegistrationRPT -from niworkflows.interfaces.bids import DerivativesDataSink +from niworkflows.interfaces.registration import ANTSApplyTransformsRPT from niworkflows.func.util import init_enhance_and_skullstrip_bold_wf -from ..interfaces.fmap import get_ees as _get_ees, FieldToRadS - def init_sdc_unwarp_wf(omp_nthreads, debug, name='sdc_unwarp_wf'): """ Apply the warping given by a displacements fieldmap. - This workflow takes in a displacements fieldmap and calculates the corresponding - displacements field (in other words, an ANTs-compatible warp file). + This workflow takes in a displacements field through which the + input reference can be corrected for susceptibility-derived distortion. - It also calculates a new mask for the input dataset that takes into account the distortions. - The mask is restricted to the field of view of the fieldmap since outside of it corrections - could not be performed. + It also calculates a new mask for the input dataset, which takes into + account the (corrected) distortions. + The mask is restricted to the field of view of the fieldmap since outside + of it corrections could not be performed. .. workflow :: :graph2use: orig @@ -61,21 +55,13 @@ def init_sdc_unwarp_wf(omp_nthreads, debug, name='sdc_unwarp_wf'): Inputs ------ + in_warp : os.pathlike + The :abbr:`DFM (displacements field map)` that corrects for + susceptibility-derived distortions estimated elsewhere. in_reference - the reference image + the reference image to be unwarped. in_reference_brain - the reference image (skull-stripped) - in_mask - a brain mask corresponding to ``in_reference`` - metadata - metadata associated to the ``in_reference`` EPI input - fmap - the fieldmap in Hz - fmap_ref - the reference (anatomical) image corresponding to ``fmap`` - fmap_mask - a brain mask corresponding to ``fmap`` - + a skull-stripped version corresponding to the ``in_reference``. Outputs ------- @@ -84,62 +70,18 @@ def init_sdc_unwarp_wf(omp_nthreads, debug, name='sdc_unwarp_wf'): out_reference_brain the ``in_reference`` after unwarping and skullstripping out_warp - the corresponding :abbr:`DFM (displacements field map)` compatible with - ANTs - out_jacobian - the jacobian of the field (for drop-out alleviation) + the ``in_warp`` field is forwarded for compatibility out_mask mask of the unwarped input file """ workflow = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface( - fields=['in_reference', 'in_reference_brain', 'in_mask', 'metadata', - 'fmap_ref', 'fmap_mask', 'fmap']), name='inputnode') + fields=['in_warp', 'in_reference', 'in_reference_brain']), + name='inputnode') outputnode = pe.Node(niu.IdentityInterface( - fields=['out_reference', 'out_reference_brain', 'out_warp', 'out_mask', - 'out_jacobian']), name='outputnode') - - # Register the reference of the fieldmap to the reference - # of the target image (the one that shall be corrected) - ants_settings = pkgr.resource_filename('sdcflows', 'data/fmap-any_registration.json') - if debug: - ants_settings = pkgr.resource_filename( - 'sdcflows', 'data/fmap-any_registration_testing.json') - fmap2ref_reg = pe.Node( - ANTSRegistrationRPT(generate_report=True, from_file=ants_settings, - output_inverse_warped_image=True, output_warped_image=True), - name='fmap2ref_reg', n_procs=omp_nthreads) - - ds_report_reg = pe.Node(DerivativesDataSink( - desc='magnitude', suffix='bold'), name='ds_report_reg', - mem_gb=0.01, run_without_submitting=True) - - # Map the VSM into the EPI space - fmap2ref_apply = pe.Node(ANTSApplyTransformsRPT( - generate_report=True, dimension=3, interpolation='BSpline', float=True), - name='fmap2ref_apply') - - fmap_mask2ref_apply = pe.Node(ANTSApplyTransformsRPT( - generate_report=False, dimension=3, interpolation='MultiLabel', - float=True), - name='fmap_mask2ref_apply') - - ds_report_vsm = pe.Node(DerivativesDataSink( - desc='fieldmap', suffix='bold'), name='ds_report_vsm', - mem_gb=0.01, run_without_submitting=True) - - # Fieldmap to rads and then to voxels (VSM - voxel shift map) - torads = pe.Node(FieldToRadS(fmap_range=0.5), name='torads') - - get_ees = pe.Node(niu.Function(function=_get_ees, output_names=['ees']), name='get_ees') - - gen_vsm = pe.Node(fsl.FUGUE(save_unmasked_shift=True), name='gen_vsm') - # Convert the VSM into a DFM (displacements field map) - # or: FUGUE shift to ANTS warping. - vsm2dfm = pe.Node(itk.FUGUEvsm2ANTSwarp(), name='vsm2dfm') - jac_dfm = pe.Node(ants.CreateJacobianDeterminantImage( - imageDimension=3, outputImage='jacobian.nii.gz'), name='jac_dfm') + fields=['out_reference', 'out_reference_brain', 'out_warp', 'out_mask']), + name='outputnode') unwarp_reference = pe.Node(ANTSApplyTransformsRPT(dimension=3, generate_report=False, @@ -148,143 +90,29 @@ def init_sdc_unwarp_wf(omp_nthreads, debug, name='sdc_unwarp_wf'): name='unwarp_reference') fieldmap_fov_mask = pe.Node(FilledImageLike(dtype='uint8'), name='fieldmap_fov_mask') - fmap_fov2ref_apply = pe.Node(ANTSApplyTransformsRPT( generate_report=False, dimension=3, interpolation='NearestNeighbor', float=True), name='fmap_fov2ref_apply') apply_fov_mask = pe.Node(fsl.ApplyMask(), name="apply_fov_mask") - enhance_and_skullstrip_bold_wf = init_enhance_and_skullstrip_bold_wf(omp_nthreads=omp_nthreads, pre_mask=True) - workflow.connect([ - (inputnode, fmap2ref_reg, [('fmap_ref', 'moving_image')]), - (inputnode, fmap2ref_apply, [('in_reference', 'reference_image')]), - (fmap2ref_reg, fmap2ref_apply, [ - ('composite_transform', 'transforms')]), - (inputnode, fmap_mask2ref_apply, [('in_reference', 'reference_image')]), - (fmap2ref_reg, fmap_mask2ref_apply, [ - ('composite_transform', 'transforms')]), - (fmap2ref_apply, ds_report_vsm, [('out_report', 'in_file')]), - (inputnode, fmap2ref_reg, [('in_reference_brain', 'fixed_image')]), - (fmap2ref_reg, ds_report_reg, [('out_report', 'in_file')]), - (inputnode, fmap2ref_apply, [('fmap', 'input_image')]), - (inputnode, fmap_mask2ref_apply, [('fmap_mask', 'input_image')]), - (fmap2ref_apply, torads, [('output_image', 'in_file')]), - (inputnode, get_ees, [('in_reference', 'in_file'), - ('metadata', 'in_meta')]), - (fmap_mask2ref_apply, gen_vsm, [('output_image', 'mask_file')]), - (get_ees, gen_vsm, [('ees', 'dwell_time')]), - (inputnode, gen_vsm, [(('metadata', _get_pedir_fugue), 'unwarp_direction')]), - (inputnode, vsm2dfm, [(('metadata', _get_pedir_bids), 'pe_dir')]), - (torads, gen_vsm, [('out_file', 'fmap_in_file')]), - (vsm2dfm, unwarp_reference, [('out_file', 'transforms')]), - (inputnode, unwarp_reference, [('in_reference', 'reference_image')]), - (inputnode, unwarp_reference, [('in_reference', 'input_image')]), - (vsm2dfm, outputnode, [('out_file', 'out_warp')]), - (vsm2dfm, jac_dfm, [('out_file', 'deformationField')]), - (inputnode, fieldmap_fov_mask, [('fmap_ref', 'in_file')]), + (inputnode, unwarp_reference, [ + ('in_warp', 'transforms'), + ('in_reference', 'reference_image'), + ('in_reference', 'input_image')]), + (inputnode, fieldmap_fov_mask, [('in_warp', 'in_file')]), (fieldmap_fov_mask, fmap_fov2ref_apply, [('out_file', 'input_image')]), (inputnode, fmap_fov2ref_apply, [('in_reference', 'reference_image')]), - (fmap2ref_reg, fmap_fov2ref_apply, [('composite_transform', 'transforms')]), (fmap_fov2ref_apply, apply_fov_mask, [('output_image', 'mask_file')]), (unwarp_reference, apply_fov_mask, [('output_image', 'in_file')]), (apply_fov_mask, enhance_and_skullstrip_bold_wf, [('out_file', 'inputnode.in_file')]), - (fmap_mask2ref_apply, enhance_and_skullstrip_bold_wf, - [('output_image', 'inputnode.pre_mask')]), (apply_fov_mask, outputnode, [('out_file', 'out_reference')]), (enhance_and_skullstrip_bold_wf, outputnode, [ ('outputnode.mask_file', 'out_mask'), ('outputnode.skull_stripped_file', 'out_reference_brain')]), - (jac_dfm, outputnode, [('jacobian_image', 'out_jacobian')]), - (gen_vsm, vsm2dfm, [('shift_out_file', 'in_file')]), - ]) - return workflow - - -def init_fmap_unwarp_report_wf(name='fmap_unwarp_report_wf', forcedsyn=False): - """ - Save a reportlet showing how SDC unwarping performed. - - This workflow generates and saves a reportlet showing the effect of fieldmap - unwarping a BOLD image. - - .. workflow:: - :graph2use: orig - :simple_form: yes - - from sdcflows.workflows.unwarp import init_fmap_unwarp_report_wf - wf = init_fmap_unwarp_report_wf() - - **Parameters** - - name : str, optional - Workflow name (default: fmap_unwarp_report_wf) - forcedsyn : bool, optional - Whether SyN-SDC was forced. - - **Inputs** - - in_pre - Reference image, before unwarping - in_post - Reference image, after unwarping - in_seg - Segmentation of preprocessed structural image, including - gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF) - in_xfm - Affine transform from T1 space to BOLD space (ITK format) - - """ - from niworkflows.interfaces import SimpleBeforeAfter - from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms - from niworkflows.interfaces.images import extract_wm - - DEFAULT_MEMORY_MIN_GB = 0.01 - - workflow = Workflow(name=name) - - inputnode = pe.Node(niu.IdentityInterface( - fields=['in_pre', 'in_post', 'in_seg', 'in_xfm']), name='inputnode') - - map_seg = pe.Node(ApplyTransforms( - dimension=3, float=True, interpolation='MultiLabel'), - name='map_seg', mem_gb=0.3) - - sel_wm = pe.Node(niu.Function(function=extract_wm), name='sel_wm', - mem_gb=DEFAULT_MEMORY_MIN_GB) - - bold_rpt = pe.Node(SimpleBeforeAfter(), name='bold_rpt', - mem_gb=0.1) - ds_report_sdc = pe.Node( - DerivativesDataSink(desc='sdc' if not forcedsyn else 'forcedsyn', - suffix='bold'), name='ds_report_sdc', - mem_gb=DEFAULT_MEMORY_MIN_GB, run_without_submitting=True - ) - - workflow.connect([ - (inputnode, bold_rpt, [('in_post', 'after'), - ('in_pre', 'before')]), - (bold_rpt, ds_report_sdc, [('out_report', 'in_file')]), - (inputnode, map_seg, [('in_post', 'reference_image'), - ('in_seg', 'input_image'), - ('in_xfm', 'transforms')]), - (map_seg, sel_wm, [('output_image', 'in_seg')]), - (sel_wm, bold_rpt, [('out', 'wm_seg')]), + (inputnode, outputnode, [('in_warp', 'out_warp')]), ]) - return workflow - - -# Helper functions -# ------------------------------------------------------------ - - -def _get_pedir_bids(in_dict): - return in_dict['PhaseEncodingDirection'] - - -def _get_pedir_fugue(in_dict): - return in_dict['PhaseEncodingDirection'].replace('i', 'x').replace('j', 'y').replace('k', 'z')