Skip to content

ENH: initial support for duecredit #608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions fmriprep/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,23 @@
__description__,
__longdesc__
)
from .due import due, Url, Doi

import warnings

# cmp is not used by fmriprep, so ignore nipype-generated warnings
warnings.filterwarnings('ignore', r'cmp not installed')

due.cite(
# Chicken/egg problem with Zenodo here regarding DOI. Might need
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the status regarding this? There is a DOI for the Zenodo entry now, right?

# custom Zenodo? TODO: add DOI for a Zenodo entry when available
Doi("10.5281/zenodo.996169"),
Url('http://fmriprep.readthedocs.io'),
description="A Robust Preprocessing Pipeline for fMRI Data",
version=__version__,
# Most likely that eventually you might not need to explicitly demand
# citing the module merely on input, but since it is unlikely to be imported
# unless used, forcing citation "always"
cite_module=True,
path="fmriprep"
)
73 changes: 73 additions & 0 deletions fmriprep/due.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# emacs: at the end of the file
# ex: set sts=4 ts=4 sw=4 et:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### #
"""

Stub file for a guaranteed safe import of duecredit constructs: if duecredit
is not available.

To use it, place it into your project codebase to be imported, e.g. copy as

cp stub.py /path/tomodule/module/due.py

Note that it might be better to avoid naming it duecredit.py to avoid shadowing
installed duecredit.

Then use in your code as

from .due import due, Doi, BibTeX

See https://github.com/duecredit/duecredit/blob/master/README.md for examples.

Origin: Originally a part of the duecredit
Copyright: 2015-2016 DueCredit developers
License: BSD-2
"""

__version__ = '0.0.5'


class InactiveDueCreditCollector(object):
"""Just a stub at the Collector which would not do anything"""
def _donothing(self, *args, **kwargs):
"""Perform no good and no bad"""
pass

def dcite(self, *args, **kwargs):
"""If I could cite I would"""
def nondecorating_decorator(func):
return func
return nondecorating_decorator

cite = load = add = _donothing

def __repr__(self):
return self.__class__.__name__ + '()'


def _donothing_func(*args, **kwargs):
"""Perform no good and no bad"""
pass


try:
from duecredit import due, BibTeX, Doi, Url
if 'due' in locals() and not hasattr(due, 'cite'):
raise RuntimeError(
"Imported due lacks .cite. DueCredit is now disabled")
except Exception as e:
if type(e).__name__ != 'ImportError':
import logging
logging.getLogger("duecredit").error(
"Failed to import duecredit due to %s" % str(e))
# Initiate due stub
due = InactiveDueCreditCollector()
BibTeX = Doi = Url = _donothing_func

# Emacs mode definitions
# Local Variables:
# mode: python
# py-indent-offset: 4
# tab-width: 4
# indent-tabs-mode: nil
# End:
2 changes: 1 addition & 1 deletion fmriprep/interfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
from .surf import NormalizeSurf, GiftiNameSource, GiftiSetAnatomicalStructure
from .reports import SubjectSummary, FunctionalSummary, AboutSummary
from .utils import TPM2ROI, ConcatROIs, CombineROIs, AddTSVHeader
from .fmap import FieldEnhance
from .fmap import FieldEnhance, PhaseDiff2Fieldmap
from .confounds import GatherConfounds, ICAConfounds
from .itk import MCFLIRT2ITK, MultiApplyTransforms
92 changes: 92 additions & 0 deletions fmriprep/interfaces/fmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from niworkflows.nipype.interfaces.base import (
BaseInterfaceInputSpec, TraitedSpec, File, isdefined, traits,
SimpleInterface)
from ..due import Doi

LOGGER = logging.getLogger('interface')

Expand Down Expand Up @@ -127,6 +128,50 @@ def _run_interface(self, runtime):
return runtime


class PhaseDiff2FieldmapInputSpec(BaseInterfaceInputSpec):
in_file = traits.File(exists=True, mandatory=True, desc='Input phdiff file')
delta_te = traits.Float(mandatory=True, desc='Echo time (TE) difference')


class PhaseDiff2FieldmapOutputSpec(TraitedSpec):
out_file = traits.File(exists=True)


class PhaseDiff2Fieldmap(SimpleInterface):
r"""
Converts the input phase-difference map into a fieldmap in Hz,
using the eq. (1) of [Hutton2002]_:

.. math::

\Delta B_0 (\text{T}^{-1}) = \frac{\Delta \Theta}{2\pi\gamma \Delta\text{TE}}


In this case, we do not take into account the gyromagnetic ratio of the
proton (:math:`\gamma`), since it will be applied inside TOPUP:

.. math::

\Delta B_0 (\text{Hz}) = \frac{\Delta \Theta}{2\pi \Delta\text{TE}}


.. [Hutton2002] Hutton et al., Image Distortion Correction in fMRI: A Quantitative
Evaluation, NeuroImage 16(1):217-240, 2002. doi:`10.1006/nimg.2001.1054
<http://dx.doi.org/10.1006/nimg.2001.1054>`_.

"""
input_spec = PhaseDiff2FieldmapInputSpec
output_spec = PhaseDiff2FieldmapOutputSpec
references_ = [{
'entry': Doi('10.1006/nimg.2001.1054'),
'description': "Conversion of the input phase-difference map into a fieldmap in Hz",
'tags': ['edu']}]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this tag mean?


def _run_interface(self, runtime):
self._results['out_file'] = phdiff2fmap(self.inputs.in_file, self.inputs.delta_te)
return runtime


def _despike2d(data, thres, neigh=None):
"""
despiking as done in FSL fugue
Expand Down Expand Up @@ -181,3 +226,50 @@ def _unwrap(fmap_data, mag_file, mask=None):

unwrapped = nb.load(res.outputs.unwrapped_phase_file).get_data() * (fmapmax / pi)
return unwrapped


# The phdiff2fmap interface is equivalent to:
# rad2rsec (using rads2radsec from nipype.workflows.dmri.fsl.utils)
# pre_fugue = pe.Node(fsl.FUGUE(save_fmap=True), name='ComputeFieldmapFUGUE')
# rsec2hz (divide by 2pi)
def phdiff2fmap(in_file, delta_te, out_file=None):
r"""
Converts the input phase-difference map into a fieldmap in Hz,
using the eq. (1) of [Hutton2002]_:

.. math::

\Delta B_0 (\text{T}^{-1}) = \frac{\Delta \Theta}{2\pi\gamma \Delta\text{TE}}


In this case, we do not take into account the gyromagnetic ratio of the
proton (:math:`\gamma`), since it will be applied inside TOPUP:

.. math::

\Delta B_0 (\text{Hz}) = \frac{\Delta \Theta}{2\pi \Delta\text{TE}}


.. [Hutton2002] Hutton et al., Image Distortion Correction in fMRI: A Quantitative
Evaluation, NeuroImage 16(1):217-240, 2002. doi:`10.1006/nimg.2001.1054
<http://dx.doi.org/10.1006/nimg.2001.1054>`_.

"""
import numpy as np
import nibabel as nb
import os.path as op
import math

# GYROMAG_RATIO_H_PROTON_MHZ = 42.576

if out_file is None:
fname, fext = op.splitext(op.basename(in_file))
if fext == '.gz':
fname, _ = op.splitext(fname)
out_file = op.abspath('./%s_fmap.nii.gz' % fname)

image = nb.load(in_file)
data = (image.get_data().astype(np.float32) / (2. * math.pi * delta_te))

nb.Nifti1Image(data, image.affine, image.header).to_filename(out_file)
return out_file
6 changes: 6 additions & 0 deletions fmriprep/workflows/bold.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
from ..workflows.util import (
init_enhance_and_skullstrip_bold_wf, init_skullstrip_bold_wf,
init_bbreg_wf, init_fsl_bbr_wf)
from ..due import due, Doi


DEFAULT_MEMORY_MIN_GB = 0.01
Expand Down Expand Up @@ -1286,6 +1287,11 @@ def _aslist(in_value):
return workflow


@due.dcite(
Doi('10.1371/journal.pone.0152472'),
description="Susceptibility distortion correction warp estimation",
tags=['implementation']
)
def init_nonlinear_sdc_wf(bold_file, freesurfer, bold2t1w_dof,
template, omp_nthreads, bold_pe='j',
atlas_threshold=3, name='nonlinear_sdc_wf'):
Expand Down
58 changes: 5 additions & 53 deletions fmriprep/workflows/fieldmap/phdiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
cleanup_edge_pipeline
from niworkflows.interfaces.masks import BETRPT

from ...interfaces import ReadSidecarJSON, IntraModalMerge, DerivativesDataSink
from ...interfaces import ReadSidecarJSON, IntraModalMerge, DerivativesDataSink, PhaseDiff2Fieldmap


def init_phdiff_wf(reportlets_dir, omp_nthreads, name='phdiff_wf'):
Expand Down Expand Up @@ -92,12 +92,7 @@ def _pick1st(inlist):

cleanup_wf = cleanup_edge_pipeline(name="cleanup_wf")

compfmap = pe.Node(niu.Function(function=phdiff2fmap), name='compfmap')

# The phdiff2fmap interface is equivalent to:
# rad2rsec (using rads2radsec from nipype.workflows.dmri.fsl.utils)
# pre_fugue = pe.Node(fsl.FUGUE(save_fmap=True), name='ComputeFieldmapFUGUE')
# rsec2hz (divide by 2pi)
phdiff2fmap = pe.Node(PhaseDiff2Fieldmap(), name='phdiff2fmap')

workflow = pe.Workflow(name=name)
workflow.connect([
Expand All @@ -110,13 +105,13 @@ def _pick1st(inlist):
(inputnode, pha2rads, [('phasediff', 'in_file')]),
(pha2rads, prelude, [('out', 'phase_file')]),
(meta, dte, [('out_dict', 'in_values')]),
(dte, compfmap, [('out', 'delta_te')]),
(dte, phdiff2fmap, [('out', 'delta_te')]),
(prelude, denoise, [('unwrapped_phase_file', 'in_file')]),
(denoise, demean, [('out_file', 'in_file')]),
(demean, cleanup_wf, [('out', 'inputnode.in_file')]),
(bet, cleanup_wf, [('mask_file', 'inputnode.in_mask')]),
(cleanup_wf, compfmap, [('outputnode.out_file', 'in_file')]),
(compfmap, outputnode, [('out', 'fmap')]),
(cleanup_wf, phdiff2fmap, [('outputnode.out_file', 'in_file')]),
(phdiff2fmap, outputnode, [('out_file', 'fmap')]),
(bet, outputnode, [('mask_file', 'fmap_mask'),
('out_file', 'fmap_ref')]),
(inputnode, ds_fmap_mask, [('phasediff', 'source_file')]),
Expand All @@ -130,49 +125,6 @@ def _pick1st(inlist):
# Helper functions
# ------------------------------------------------------

def phdiff2fmap(in_file, delta_te, out_file=None):
r"""
Converts the input phase-difference map into a fieldmap in Hz,
using the eq. (1) of [Hutton2002]_:

.. math::

\Delta B_0 (\text{T}^{-1}) = \frac{\Delta \Theta}{2\pi\gamma \Delta\text{TE}}


In this case, we do not take into account the gyromagnetic ratio of the
proton (:math:`\gamma`), since it will be applied inside TOPUP:

.. math::

\Delta B_0 (\text{Hz}) = \frac{\Delta \Theta}{2\pi \Delta\text{TE}}


.. [Hutton2002] Hutton et al., Image Distortion Correction in fMRI: A Quantitative
Evaluation, NeuroImage 16(1):217-240, 2002. doi:`10.1006/nimg.2001.1054
<https://doi.org/10.1006/nimg.2001.1054>`_.

"""
import numpy as np
import nibabel as nb
import os.path as op
import math

# GYROMAG_RATIO_H_PROTON_MHZ = 42.576

if out_file is None:
fname, fext = op.splitext(op.basename(in_file))
if fext == '.gz':
fname, _ = op.splitext(fname)
out_file = op.abspath('./%s_fmap.nii.gz' % fname)

image = nb.load(in_file)
data = (image.get_data().astype(np.float32) / (2. * math.pi * delta_te))

nb.Nifti1Image(data, image.affine, image.header).to_filename(out_file)
return out_file


def _delta_te(in_values, te1=None, te2=None):
if isinstance(in_values, float):
te2 = in_values
Expand Down