Skip to content

Commit 5566157

Browse files
authored
feat(cli): Create --force flag that accepts a list, replacing individual --force-* flags (#3442)
This is in preparation to allow `--force fmap-jacobian` (see #3367). I want to first establish the `--force` flag as a replacement for `--force-bbr`, `--force-no-bbr` and `--force-syn`. cc @mgxd @oesteban I'm not sure if we've talked about this before. I think it's an overall worthwhile cleanup.
2 parents 2106034 + d5743d8 commit 5566157

File tree

8 files changed

+62
-35
lines changed

8 files changed

+62
-35
lines changed

fmriprep/cli/parser.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ def _build_parser(**kwargs):
4848
'aroma_err_on_warn': (None, '24.0.0'),
4949
'bold2t1w_init': ('--bold2anat-init', '24.2.0'),
5050
'bold2t1w_dof': ('--bold2anat-dof', '24.2.0'),
51+
'force_bbr': ('--force bbr', '26.0.0'),
52+
'force_no_bbr': ('--force no-bbr', '26.0.0'),
53+
'force_syn': ('--force syn-sdc', '26.0.0'),
5154
}
5255

5356
class DeprecatedAction(Action):
@@ -338,6 +341,19 @@ def _slice_time_ref(value, parser):
338341
help='Ignore selected aspects of the input dataset to disable corresponding '
339342
'parts of the workflow (a space delimited list)',
340343
)
344+
g_conf.add_argument(
345+
'--force',
346+
required=False,
347+
action='store',
348+
nargs='+',
349+
default=[],
350+
choices=['bbr', 'no-bbr', 'syn-sdc'],
351+
help='Force selected processing choices, overriding automatic selections '
352+
'(a space delimited list).\n'
353+
' * [no-]bbr: Use/disable boundary-based registration for BOLD-to-T1w coregistration\n'
354+
' (No goodness-of-fit checks)\n'
355+
' * syn-sdc: Calculate SyN-SDC correction *in addition* to other fieldmaps\n',
356+
)
341357
g_conf.add_argument(
342358
'--output-spaces',
343359
nargs='*',
@@ -392,17 +408,13 @@ def _slice_time_ref(value, parser):
392408
)
393409
g_conf.add_argument(
394410
'--force-bbr',
395-
action='store_true',
396-
dest='use_bbr',
397-
default=None,
398-
help='Always use boundary-based registration (no goodness-of-fit checks)',
411+
action=DeprecatedAction,
412+
help='Deprecated - use `--force bbr` instead.',
399413
)
400414
g_conf.add_argument(
401415
'--force-no-bbr',
402-
action='store_false',
403-
dest='use_bbr',
404-
default=None,
405-
help='Do not use boundary-based registration (no goodness-of-fit checks)',
416+
action=DeprecatedAction,
417+
help='Deprecated - use `--force no-bbr` instead.',
406418
)
407419
g_conf.add_argument(
408420
'--slice-time-ref',
@@ -624,10 +636,9 @@ def _slice_time_ref(value, parser):
624636
)
625637
g_syn.add_argument(
626638
'--force-syn',
627-
action='store_true',
639+
action=DeprecatedAction,
628640
default=False,
629-
help='EXPERIMENTAL/TEMPORARY: Use SyN correction in addition to '
630-
'fieldmap correction, if available',
641+
help='Deprecated - use `--force syn-sdc` instead.',
631642
)
632643

633644
# FreeSurfer options
@@ -789,6 +800,14 @@ def parse_args(args=None, namespace=None):
789800
config.execution.log_level = int(max(25 - 5 * opts.verbose_count, logging.DEBUG))
790801
config.from_dict(vars(opts), init=['nipype'])
791802

803+
# Consistency checks
804+
if 'bbr' in config.workflow.force and 'no-bbr' in config.workflow.force:
805+
msg = (
806+
'Cannot force and disable boundary-based registration at the same time. '
807+
'Remove `bbr` or `no-bbr` from the `--force` options.'
808+
)
809+
raise ValueError(msg)
810+
792811
if not config.execution.notrack:
793812
import importlib.util
794813

fmriprep/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,8 @@ class workflow(_Config):
581581
"""Adjust pipeline to reuse base template of existing longitudinal freesurfer"""
582582
ignore = None
583583
"""Ignore particular steps for *fMRIPrep*."""
584+
force = None
585+
"""Force particular steps for *fMRIPrep*."""
584586
level = None
585587
"""Level of preprocessing to complete. One of ['minimal', 'resampling', 'full']."""
586588
longitudinal = False

fmriprep/data/reports-spec-func.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ sections:
7878
static: false
7979
subtitle: Susceptibility distortion correction
8080
- bids: {datatype: figures, desc: forcedsyn, suffix: bold}
81-
caption: The dataset contained some fieldmap information, but the argument <code>--force-syn</code>
81+
caption: The dataset contained some fieldmap information, but the argument <code>--force syn-sdc</code>
8282
was used. The higher-priority SDC method was used. Here, we show the results
8383
of performing SyN-based SDC on the EPI for comparison.
8484
static: false

fmriprep/data/reports-spec.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ sections:
104104
static: false
105105
subtitle: Susceptibility distortion correction
106106
- bids: {datatype: figures, desc: forcedsyn, suffix: bold}
107-
caption: The dataset contained some fieldmap information, but the argument <code>--force-syn</code>
107+
caption: The dataset contained some fieldmap information, but the argument <code>--force syn-sdc</code>
108108
was used. The higher-priority SDC method was used. Here, we show the results
109109
of performing SyN-based SDC on the EPI for comparison.
110110
static: false

fmriprep/data/tests/config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ aroma_err_on_warn = false
3434
aroma_melodic_dim = -200
3535
bold2anat_dof = 6
3636
fmap_bspline = false
37+
force = []
3738
force_syn = false
3839
hires = true
3940
ignore = []

fmriprep/workflows/base.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ def init_single_subject_wf(subject_id: str):
557557
bold_data=bold_runs,
558558
ignore_fieldmaps='fieldmaps' in config.workflow.ignore,
559559
use_syn=config.workflow.use_syn_sdc,
560-
force_syn=config.workflow.force_syn,
560+
force_syn='syn-sdc' in config.workflow.force,
561561
filters=config.execution.get().get('bids_filters', {}).get('fmap'),
562562
)
563563

@@ -846,7 +846,7 @@ def map_fieldmap_estimation(
846846
# In the case where fieldmaps are ignored and `--use-syn-sdc` is requested,
847847
# SDCFlows `find_estimators` still receives a full layout (which includes the fmap modality)
848848
# and will not calculate fmapless schemes.
849-
# Similarly, if fieldmaps are ignored and `--force-syn` is requested,
849+
# Similarly, if fieldmaps are ignored and `--force syn-sdc` is requested,
850850
# `fmapless` should be set to True to ensure BOLD targets are found to be corrected.
851851
fmap_estimators = find_estimators(
852852
layout=layout,
@@ -870,7 +870,7 @@ def map_fieldmap_estimation(
870870
if ignore_fieldmaps and any(f.method == fm.EstimatorType.ANAT for f in fmap_estimators):
871871
config.loggers.workflow.info(
872872
'Option "--ignore fieldmaps" was set, but either "--use-syn-sdc" '
873-
'or "--force-syn" were given, so fieldmap-less estimation will be executed.'
873+
'or "--force syn-sdc" were given, so fieldmap-less estimation will be executed.'
874874
)
875875
fmap_estimators = [f for f in fmap_estimators if f.method == fm.EstimatorType.ANAT]
876876

fmriprep/workflows/bold/fit.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,11 +613,18 @@ def init_bold_fit_wf(
613613
]) # fmt:skip
614614

615615
if not boldref2anat_xform:
616+
use_bbr = (
617+
True
618+
if 'bbr' in config.workflow.force
619+
else False
620+
if 'no-bbr' in config.workflow.force
621+
else None
622+
)
616623
# calculate BOLD registration to T1w
617624
bold_reg_wf = init_bold_reg_wf(
618625
bold2anat_dof=config.workflow.bold2anat_dof,
619626
bold2anat_init=config.workflow.bold2anat_init,
620-
use_bbr=config.workflow.use_bbr,
627+
use_bbr=use_bbr,
621628
freesurfer=config.workflow.run_reconall,
622629
omp_nthreads=omp_nthreads,
623630
mem_gb=mem_gb['resampled'],

fmriprep/workflows/tests/test_base.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ def bids_root(tmp_path_factory):
106106

107107
def _make_params(
108108
bold2anat_init: str = 'auto',
109-
use_bbr: bool | None = None,
110109
dummy_scans: int | None = None,
111110
me_output_echos: bool = False,
112111
medial_surface_nan: bool = False,
@@ -115,18 +114,19 @@ def _make_params(
115114
run_msmsulc: bool = True,
116115
skull_strip_t1w: str = 'auto',
117116
use_syn_sdc: str | bool = False,
118-
force_syn: bool = False,
119117
freesurfer: bool = True,
120118
ignore: list[str] = None,
119+
force: list[str] = None,
121120
bids_filters: dict = None,
122121
):
123122
if ignore is None:
124123
ignore = []
124+
if force is None:
125+
force = []
125126
if bids_filters is None:
126127
bids_filters = {}
127128
return (
128129
bold2anat_init,
129-
use_bbr,
130130
dummy_scans,
131131
me_output_echos,
132132
medial_surface_nan,
@@ -135,9 +135,9 @@ def _make_params(
135135
run_msmsulc,
136136
skull_strip_t1w,
137137
use_syn_sdc,
138-
force_syn,
139138
freesurfer,
140139
ignore,
140+
force,
141141
bids_filters,
142142
)
143143

@@ -147,7 +147,6 @@ def _make_params(
147147
@pytest.mark.parametrize(
148148
(
149149
'bold2anat_init',
150-
'use_bbr',
151150
'dummy_scans',
152151
'me_output_echos',
153152
'medial_surface_nan',
@@ -156,21 +155,21 @@ def _make_params(
156155
'run_msmsulc',
157156
'skull_strip_t1w',
158157
'use_syn_sdc',
159-
'force_syn',
160158
'freesurfer',
161159
'ignore',
160+
'force',
162161
'bids_filters',
163162
),
164163
[
165164
_make_params(),
166165
_make_params(bold2anat_init='t1w'),
167166
_make_params(bold2anat_init='t2w'),
168167
_make_params(bold2anat_init='header'),
169-
_make_params(use_bbr=True),
170-
_make_params(use_bbr=False),
171-
_make_params(bold2anat_init='header', use_bbr=True),
168+
_make_params(force=['bbr']),
169+
_make_params(force=['no-bbr']),
170+
_make_params(bold2anat_init='header', force=['bbr']),
172171
# Currently disabled
173-
# _make_params(bold2anat_init="header", use_bbr=False),
172+
# _make_params(bold2anat_init="header", force=['no-bbr']),
174173
_make_params(dummy_scans=2),
175174
_make_params(me_output_echos=True),
176175
_make_params(medial_surface_nan=True),
@@ -180,14 +179,14 @@ def _make_params(
180179
_make_params(cifti_output='91k', run_msmsulc=False),
181180
_make_params(skull_strip_t1w='force'),
182181
_make_params(skull_strip_t1w='skip'),
183-
_make_params(use_syn_sdc='warn', force_syn=True, ignore=['fieldmaps']),
182+
_make_params(use_syn_sdc='warn', ignore=['fieldmaps'], force=['syn-sdc']),
184183
_make_params(freesurfer=False),
185-
_make_params(freesurfer=False, use_bbr=True),
186-
_make_params(freesurfer=False, use_bbr=False),
184+
_make_params(freesurfer=False, force=['bbr']),
185+
_make_params(freesurfer=False, force=['no-bbr']),
187186
# Currently unsupported:
188187
# _make_params(freesurfer=False, bold2anat_init="header"),
189-
# _make_params(freesurfer=False, bold2anat_init="header", use_bbr=True),
190-
# _make_params(freesurfer=False, bold2anat_init="header", use_bbr=False),
188+
# _make_params(freesurfer=False, bold2anat_init="header", force=['bbr']),
189+
# _make_params(freesurfer=False, bold2anat_init="header", force=['no-bbr']),
191190
# Regression test for gh-3154:
192191
_make_params(bids_filters={'sbref': {'suffix': 'sbref'}}),
193192
],
@@ -198,7 +197,6 @@ def test_init_fmriprep_wf(
198197
level: str,
199198
anat_only: bool,
200199
bold2anat_init: str,
201-
use_bbr: bool | None,
202200
dummy_scans: int | None,
203201
me_output_echos: bool,
204202
medial_surface_nan: bool,
@@ -207,16 +205,15 @@ def test_init_fmriprep_wf(
207205
run_msmsulc: bool,
208206
skull_strip_t1w: str,
209207
use_syn_sdc: str | bool,
210-
force_syn: bool,
211208
freesurfer: bool,
212209
ignore: list[str],
210+
force: list[str],
213211
bids_filters: dict,
214212
):
215213
with mock_config(bids_dir=bids_root):
216214
config.workflow.level = level
217215
config.workflow.anat_only = anat_only
218216
config.workflow.bold2anat_init = bold2anat_init
219-
config.workflow.use_bbr = use_bbr
220217
config.workflow.dummy_scans = dummy_scans
221218
config.execution.me_output_echos = me_output_echos
222219
config.workflow.medial_surface_nan = medial_surface_nan
@@ -226,6 +223,7 @@ def test_init_fmriprep_wf(
226223
config.workflow.cifti_output = cifti_output
227224
config.workflow.run_reconall = freesurfer
228225
config.workflow.ignore = ignore
226+
config.workflow.force = force
229227
with patch.dict('fmriprep.config.execution.bids_filters', bids_filters):
230228
wf = init_fmriprep_wf()
231229

0 commit comments

Comments
 (0)