Skip to content
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

ENH: Add flag for STC reference time and set in all cases #2520

Merged
merged 6 commits into from
Sep 2, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
26 changes: 26 additions & 0 deletions fmriprep/cli/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,21 @@ def _bids_filter(value):
if value and Path(value).exists():
return loads(Path(value).read_text(), object_hook=_filter_pybids_none_any)

def _slice_time_ref(value, parser):
if value == "start":
value = 0
elif value == "middle":
value = 1
try:
value = float(value)
except ValueError:
raise parser.error("Slice time reference must be number, 'start', or 'middle'. "
f"Received {value}.")
if not 0 <= val <= 1:
raise parser.error(f"Slice time reference must be in range 0-1. Received {value}.")
return value


verstr = f"fMRIPrep v{config.environment.version}"
currentv = Version(config.environment.version)
is_release = not any(
Expand Down Expand Up @@ -329,6 +344,17 @@ def _bids_filter(value):
help="Replace medial wall values with NaNs on functional GIFTI files. Only "
"performed for GIFTI files mapped to a freesurfer subject (fsaverage or fsnative).",
)
g_conf.add_argument(
"--slice-time-ref",
required=False,
action="store",
default=None,
type=_slice_time_ref,
help="The time of the reference slice to correct BOLD values to, as a fraction "
"acquisition time. 0 indicates the start, 0.5 the midpoint, and 1 the end "
"of acquisition. The alias `start` corresponds to 0, and `middle` to 0.5. "
"The default value is 0.5.",
)
g_conf.add_argument(
"--dummy-scans",
required=False,
Expand Down
5 changes: 5 additions & 0 deletions fmriprep/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,11 @@ class workflow(_Config):
skull_strip_t1w = "force"
"""Skip brain extraction of the T1w image (default is ``force``, meaning that
*fMRIPrep* will run brain extraction of the T1w)."""
slice_time_ref = 0.5
"""The time of the reference slice to correct BOLD values to, as a fraction
acquisition time. 0 indicates the start, 0.5 the midpoint, and 1 the end
of acquisition. The alias `start` corresponds to 0, and `middle` to 0.5.
The default value is 0.5."""
spaces = None
"""Keeps the :py:class:`~niworkflows.utils.spaces.SpatialReferences`
instance keeping standard and nonstandard spaces."""
Expand Down
19 changes: 13 additions & 6 deletions fmriprep/workflows/bold/stc.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,29 @@ def init_bold_stc_wf(metadata, name='bold_stc_wf'):
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
from niworkflows.interfaces.header import CopyXForm

slice_times = metadata["SliceTiming"]
first, last = min(slice_times), max(slice_times)
frac = config.workflow.slice_time_ref
tzero = first + frac * (last - first)

afni_ver = ''.join('%02d' % v for v in afni.Info().version() or [])
workflow = Workflow(name=name)
workflow.__desc__ = """\
BOLD runs were slice-time corrected using `3dTshift` from
AFNI {afni_ver} [@afni, RRID:SCR_005927].
""".format(afni_ver=''.join(['%02d' % v for v in afni.Info().version() or []]))
workflow.__desc__ = f"""\
BOLD runs were slice-time corrected to {tzero:0.3g}s ({frac:g} of slice acquisition range
{first:.3g}s-{last:.3g}s) using `3dTshift` from AFNI {afni_ver} [@afni, RRID:SCR_005927].
"""
inputnode = pe.Node(niu.IdentityInterface(fields=['bold_file', 'skip_vols']), name='inputnode')
outputnode = pe.Node(niu.IdentityInterface(fields=['stc_file']), name='outputnode')

LOGGER.log(25, 'Slice-timing correction will be included.')
LOGGER.log(25, f'BOLD series will be slice-timing corrected to an offset of {tzero:.3g}.')

Choose a reason for hiding this comment

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

For the example in lines 69/70 (2s TR, 0-0.9), this would then yield an offset of 0.45, correct? I think that printing/logging this as an output rather than the intended time offset (1s) may be a bit confusing. I'd opt for printing/logging the intended time offset.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't know what you mean by the "intended time offset" here. The default 3dTshift behavior sets tzero to the middle of the acquisition time (0.45), as does the proposed default behavior here. I'm not sure under what circumstances you would expect to see 1s.

Choose a reason for hiding this comment

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

Sorry for not being clearer! The point was that it would perhaps be easier for users to know what the temporal offset is not in terms of percent but in terms of time. Else they might be confused that putting in 0.5 for a TR of 2s returns 0.45 and not 1s. Hope that’s clearer?


# It would be good to fingerprint memory use of afni.TShift
slice_timing_correction = pe.Node(
TShift(outputtype='NIFTI_GZ',
tr=f"{metadata['RepetitionTime']}s",
slice_timing=metadata['SliceTiming'],
slice_encoding_direction=metadata.get('SliceEncodingDirection', 'k')),
slice_encoding_direction=metadata.get('SliceEncodingDirection', 'k'),
tzero=tzero),
name='slice_timing_correction')

copy_xform = pe.Node(CopyXForm(), name='copy_xform', mem_gb=0.1)
Expand Down