Skip to content

Commit 31d646b

Browse files
committed
enh: revise multiecho identification, sync with nipreps/niworkflows#408
Resolves: nipreps#2179.
1 parent 8474286 commit 31d646b

File tree

3 files changed

+43
-39
lines changed

3 files changed

+43
-39
lines changed

fmriprep/workflows/bold/base.py

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from nipype.pipeline import engine as pe
1818
from nipype.interfaces import utility as niu
1919

20+
from niworkflows.utils.misc import select_first
21+
2022
from ...utils.meepi import combine_meepi_source
2123

2224
from ...interfaces import DerivativesDataSink
@@ -141,52 +143,59 @@ def init_func_preproc_wf(bold_file):
141143
from niworkflows.interfaces.utils import DictMerge
142144
from sdcflows.workflows.base import init_sdc_estimate_wf, fieldmap_wrangler
143145

144-
ref_file = bold_file
145146
mem_gb = {'filesize': 1, 'resampled': 1, 'largemem': 1}
146147
bold_tlen = 10
147-
multiecho = isinstance(bold_file, list)
148148

149149
# Have some options handy
150-
layout = config.execution.layout
151150
omp_nthreads = config.nipype.omp_nthreads
152151
freesurfer = config.workflow.run_reconall
153152
spaces = config.workflow.spaces
154153
output_dir = str(config.execution.output_dir)
155154

155+
# Extract BIDS entities and metadata from bold reference file
156+
ref_file = select_first(bold_file)
157+
layout = config.execution.layout
158+
entities = layout.parse_file_entities(ref_file)
159+
metadata = layout.get_metadata(ref_file)
160+
161+
# Drop echo entity for future queries, have a boolean shorthand
162+
multiecho = bool(entities.pop("echo", None))
163+
if multiecho and (isinstance(bold_file, str) or len(bold_file) == 1):
164+
multiecho = False
165+
config.loggers.warning(
166+
f"Only one echo <{ref_file}> found in a seemingly multi-echo dataset. "
167+
"Falling back to single-echo mode."
168+
)
169+
bold_file = ref_file # Just in case - drop the list
170+
156171
if multiecho:
157-
tes = [layout.get_metadata(echo)['EchoTime'] for echo in bold_file]
158-
ref_file = dict(zip(tes, bold_file))[min(tes)]
172+
# reorder echoes from shortest to largest
173+
tes, bold_file = zip(*sorted([
174+
(layout.get_metadata(bf)["EchoTime"], bf) for bf in bold_file
175+
]))
176+
ref_file = bold_file[0] # Reset reference to be the shortest TE
159177

160178
if os.path.isfile(ref_file):
161179
bold_tlen, mem_gb = _create_mem_gb(ref_file)
162180

163181
wf_name = _get_wf_name(ref_file)
164182
config.loggers.workflow.debug(
165-
'Creating bold processing workflow for "%s" (%.2f GB / %d TRs). '
183+
'Creating bold processing workflow for <%s> (%.2f GB / %d TRs). '
166184
'Memory resampled/largemem=%.2f/%.2f GB.',
167185
ref_file, mem_gb['filesize'], bold_tlen, mem_gb['resampled'], mem_gb['largemem'])
168186

169187
# Find associated sbref, if possible
170-
entities = layout.parse_file_entities(ref_file)
171-
if multiecho:
172-
# Remove echo entity to collect all echoes
173-
_ = entities.pop('echo', None)
174188
entities['suffix'] = 'sbref'
175189
entities['extension'] = ['nii', 'nii.gz'] # Overwrite extensions
176-
177190
sbref_files = layout.get(return_type='file', **entities)
178-
refbase = os.path.basename(ref_file)
179-
if 'sbref' in config.workflow.ignore:
180-
config.loggers.workflow.info("Single-band reference files ignored.")
181-
elif sbref_files:
182-
sbbase = ','.join([os.path.basename(sbf) for sbf in sbref_files])
183-
config.loggers.workflow.info("Using single-band reference file(s) %s.",
184-
sbbase)
185-
else:
186-
config.loggers.workflow.info("No single-band-reference found for %s.",
187-
refbase)
188191

189-
metadata = layout.get_metadata(ref_file)
192+
sbref_msg = f"No single-band-reference found for {os.path.basename(ref_file)}."
193+
if sbref_files and 'sbref' in config.workflow.ignore:
194+
sbref_msg = "Single-band reference file(s) found and ignored."
195+
elif sbref_files:
196+
sbref_msg = "Using single-band reference file(s) {}.".format(
197+
','.join([os.path.basename(sbf) for sbf in sbref_files]))
198+
config.loggers.workflow.info(sbref_msg)
190199

191200
# Find fieldmaps. Options: (phase1|phase2|phasediff|epi|fieldmap|syn)
192201
fmaps = None
@@ -286,16 +295,13 @@ def init_func_preproc_wf(bold_file):
286295
])
287296

288297
# Generate a tentative boldref
289-
bold_reference_wf = init_bold_reference_wf(omp_nthreads=omp_nthreads)
298+
bold_reference_wf = init_bold_reference_wf(
299+
omp_nthreads=omp_nthreads,
300+
bold_file=bold_file,
301+
sbref_files=sbref_files,
302+
multiecho=multiecho,
303+
)
290304
bold_reference_wf.inputs.inputnode.dummy_scans = config.workflow.dummy_scans
291-
if sbref_files is not None:
292-
from niworkflows.interfaces.images import ValidateImage
293-
val_sbref = pe.MapNode(ValidateImage(), name='val_sbref',
294-
iterfield=['in_file'])
295-
val_sbref.inputs.in_file = sbref_files
296-
workflow.connect([
297-
(val_sbref, bold_reference_wf, [('out_file', 'inputnode.sbref_file')]),
298-
])
299305

300306
# Top-level BOLD splitter
301307
bold_split = pe.Node(FSLSplit(dimension='t'), name='bold_split',
@@ -408,8 +414,6 @@ def init_func_preproc_wf(bold_file):
408414
workflow.connect([
409415
(inputnode, t1w_brain, [('t1w_preproc', 'in_file'),
410416
('t1w_mask', 'in_mask')]),
411-
# Generate early reference
412-
(inputnode, bold_reference_wf, [('bold_file', 'inputnode.bold_file')]),
413417
# BOLD buffer has slice-time corrected if it was run, original otherwise
414418
(boldbuffer, bold_split, [('bold_file', 'in_file')]),
415419
# HMC

fmriprep/workflows/bold/t2s.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def init_bold_t2s_wf(echo_times, mem_gb, omp_nthreads,
3636
3737
Parameters
3838
----------
39-
echo_times : :obj:`list`
39+
echo_times : :obj:`list` or :obj:`tuple`
4040
list of TEs associated with each echo
4141
mem_gb : :obj:`float`
4242
Size of BOLD file in GB
@@ -60,12 +60,12 @@ def init_bold_t2s_wf(echo_times, mem_gb, omp_nthreads,
6060

6161
workflow = Workflow(name=name)
6262
workflow.__desc__ = """\
63-
A T2* map was estimated from the preprocessed BOLD by fitting to a monoexponential signal
64-
decay model with nonlinear regression, using T2*/S0 estimates from a log-linear
63+
A T2\\* map was estimated from the preprocessed BOLD by fitting to a monoexponential signal
64+
decay model with nonlinear regression, using T2\\*/S0 estimates from a log-linear
6565
regression fit as initial values.
6666
For each voxel, the maximal number of echoes with reliable signal in that voxel were
6767
used to fit the model.
68-
The calculated T2* map was then used to optimally combine preprocessed BOLD across
68+
The calculated T2\\* map was then used to optimally combine preprocessed BOLD across
6969
echoes following the method described in [@posse_t2s].
7070
The optimally combined time series was carried forward as the *preprocessed BOLD*.
7171
"""
@@ -76,7 +76,7 @@ def init_bold_t2s_wf(echo_times, mem_gb, omp_nthreads,
7676

7777
LOGGER.log(25, 'Generating T2* map and optimally combined ME-EPI time series.')
7878

79-
t2smap_node = pe.Node(T2SMap(echo_times=echo_times), name='t2smap_node')
79+
t2smap_node = pe.Node(T2SMap(echo_times=list(echo_times)), name='t2smap_node')
8080

8181
workflow.connect([
8282
(inputnode, t2smap_node, [('bold_file', 'in_files')]),

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ install_requires =
2727
nipype >= 1.5
2828
nitime
2929
nitransforms >= 20.0.0rc3,<20.2
30-
niworkflows @ git+https://github.com/tsalo/niworkflows.git@10c184a110c135edf6412ed2c54ce1ff2a6d068a
30+
niworkflows @ git+https://github.com/tsalo/niworkflows.git@9d78796373163d2881f80407ff9d5aee9c65f77e
3131
numpy
3232
pandas
3333
psutil >= 5.4

0 commit comments

Comments
 (0)