#!/usr/bin/env python
# -*- coding: utf-8 -*-
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
"""
.. _sdc_unwarp :
Unwarping
~~~~~~~~~
.. topic :: Abbreviations
fmap
fieldmap
VSM
voxel-shift map -- a 3D nifti where displacements are in pixels (not mm)
DFM
displacements field map -- a nifti warp file compatible with ANTs (mm)
"""
import pkg_resources as pkgr
from niworkflows.nipype.pipeline import engine as pe
from niworkflows.nipype.interfaces import ants, fsl, utility as niu
from niworkflows.interfaces.registration import ANTSApplyTransformsRPT, ANTSRegistrationRPT
from ...interfaces import itk, ReadSidecarJSON, DerivativesDataSink
from ...interfaces.fmap import get_ees as _get_ees
from ..bold.util import init_enhance_and_skullstrip_bold_wf
[docs]def init_sdc_unwarp_wf(reportlets_dir, omp_nthreads, fmap_bspline,
fmap_demean, debug, name='sdc_unwarp_wf'):
"""
This workflow takes in a displacements fieldmap and calculates the corresponding
displacements field (in other words, an ANTs-compatible warp file).
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.
.. workflow ::
:graph2use: orig
:simple_form: yes
from fmriprep.workflows.fieldmap.unwarp import init_sdc_unwarp_wf
wf = init_sdc_unwarp_wf(reportlets_dir='.', omp_nthreads=8,
fmap_bspline=False, fmap_demean=True,
debug=False)
Inputs
in_reference
the reference image
in_mask
a brain mask corresponding to ``in_reference``
name_source
path to the original _bold file being unwarped
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 = pe.Workflow(name=name)
inputnode = pe.Node(niu.IdentityInterface(
fields=['in_reference', 'in_reference_brain', 'in_mask', 'name_source',
'fmap_ref', 'fmap_mask', 'fmap']), name='inputnode')
outputnode = pe.Node(niu.IdentityInterface(
fields=['out_reference', 'out_reference_brain', 'out_warp', 'out_mask',
'out_jacobian']), name='outputnode')
meta = pe.Node(ReadSidecarJSON(), name='meta',
mem_gb=0.01, run_without_submitting=True)
# Register the reference of the fieldmap to the reference
# of the target image (the one that shall be corrected)
ants_settings = pkgr.resource_filename('fmriprep', 'data/fmap-any_registration.json')
if debug:
ants_settings = pkgr.resource_filename(
'fmriprep', '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_reg = pe.Node(DerivativesDataSink(
base_directory=reportlets_dir, suffix='fmap_reg'), name='ds_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='NearestNeighbor',
float=True),
name='fmap_mask2ref_apply')
ds_reg_vsm = pe.Node(DerivativesDataSink(
base_directory=reportlets_dir, suffix='fmap_reg_vsm'), name='ds_reg_vsm',
mem_gb=0.01, run_without_submitting=True)
# Fieldmap to rads and then to voxels (VSM - voxel shift map)
torads = pe.Node(niu.Function(function=_hz2rads), 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')
unwarp_reference = pe.Node(ANTSApplyTransformsRPT(dimension=3,
generate_report=False,
float=True,
interpolation='LanczosWindowedSinc'),
name='unwarp_reference')
fieldmap_fov_mask = pe.Node(niu.Function(function=_fill_with_ones), 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)
workflow.connect([
(inputnode, meta, [('name_source', 'in_file')]),
(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')]),
(inputnode, ds_reg_vsm, [('name_source', 'source_file')]),
(fmap2ref_apply, ds_reg_vsm, [('out_report', 'in_file')]),
(inputnode, fmap2ref_reg, [('in_reference_brain', 'fixed_image')]),
(inputnode, ds_reg, [('name_source', 'source_file')]),
(fmap2ref_reg, ds_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')]),
(meta, get_ees, [('out_dict', 'in_meta')]),
(inputnode, get_ees, [('name_source', 'in_file')]),
(get_ees, gen_vsm, [('ees', 'dwell_time')]),
(meta, gen_vsm, [(('out_dict', _get_pedir_fugue), 'unwarp_direction')]),
(meta, vsm2dfm, [(('out_dict', _get_pedir_bids), 'pe_dir')]),
(torads, gen_vsm, [('out', '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')]),
(fieldmap_fov_mask, fmap_fov2ref_apply, [('out', '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')]),
(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')]),
])
if not fmap_bspline:
workflow.connect([
(fmap_mask2ref_apply, gen_vsm, [('output_image', 'mask_file')])
])
if fmap_demean:
# Demean within mask
demean = pe.Node(niu.Function(function=_demean), name='demean')
workflow.connect([
(gen_vsm, demean, [('shift_out_file', 'in_file')]),
(fmap_mask2ref_apply, demean, [('output_image', 'in_mask')]),
(demean, vsm2dfm, [('out', 'in_file')]),
])
else:
workflow.connect([
(gen_vsm, vsm2dfm, [('shift_out_file', 'in_file')]),
])
return workflow
[docs]def init_fmap_unwarp_report_wf(reportlets_dir, name='fmap_unwarp_report_wf'):
"""
This workflow generates and saves a reportlet showing the effect of fieldmap
unwarping a BOLD image.
.. workflow::
:graph2use: orig
:simple_form: yes
from fmriprep.workflows.fieldmap.unwarp import init_fmap_unwarp_report_wf
wf = init_fmap_unwarp_report_wf(reportlets_dir='.')
**Parameters**
reportlets_dir : str
Directory in which to save reportlets
name : str, optional
Workflow name (default: fmap_unwarp_report_wf)
**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)
name_source
BOLD series NIfTI file
Used to recover original information lost during processing
"""
from niworkflows.nipype.pipeline import engine as pe
from niworkflows.nipype.interfaces import utility as niu
from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms
from niworkflows.interfaces import SimpleBeforeAfter
from ...interfaces.images import extract_wm
from ...interfaces import DerivativesDataSink
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_source']), name='inputnode')
map_seg = pe.Node(ApplyTransforms(
dimension=3, float=True, interpolation='NearestNeighbor'),
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)
bold_rpt_ds = pe.Node(
DerivativesDataSink(base_directory=reportlets_dir,
suffix='variant-hmcsdc_preproc'), name='bold_rpt_ds',
mem_gb=DEFAULT_MEMORY_MIN_GB,
run_without_submitting=True
)
workflow.connect([
(inputnode, bold_rpt, [('in_post', 'after'),
('in_pre', 'before')]),
(inputnode, bold_rpt_ds, [('name_source', 'source_file')]),
(bold_rpt, bold_rpt_ds, [('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
# 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')
def _hz2rads(in_file, out_file=None):
"""Transform a fieldmap in Hz into rad/s"""
import os
from math import pi
import nibabel as nb
from niworkflows.nipype.utils.filemanip import fname_presuffix
if out_file is None:
out_file = fname_presuffix(in_file, suffix='_rads',
newpath=os.getcwd())
nii = nb.load(in_file)
data = nii.get_data() * 2.0 * pi
nb.Nifti1Image(data, nii.get_affine(),
nii.get_header()).to_filename(out_file)
return out_file
def _demean(in_file, in_mask, out_file=None):
import os
import numpy as np
import nibabel as nb
from niworkflows.nipype.utils.filemanip import fname_presuffix
if out_file is None:
out_file = fname_presuffix(in_file, suffix='_demeaned',
newpath=os.getcwd())
nii = nb.load(in_file)
msk = nb.load(in_mask).get_data()
data = nii.get_data()
data -= np.median(data[msk > 0])
nb.Nifti1Image(data, nii.affine, nii.header).to_filename(
out_file)
return out_file
def _fill_with_ones(in_file):
import nibabel as nb
import numpy as np
import os
nii = nb.load(in_file)
data = np.ones(nii.shape)
out_name = os.path.abspath("out.nii.gz")
nb.Nifti1Image(data, nii.affine, nii.header).to_filename(out_name)
return out_name