Skip to content

Commit ecf8580

Browse files
tsaloeffigies
andauthored
ENH: Support named derivative paths (#3264)
Closes #3260. ## Changes proposed in this pull request - Copy over `ToDict` action from fmriprep-docker over to the main CLI parser. - Modify the Config to handle named derivative paths. - E.g., `--derivatives anat=/path/to/smriprep` --> `DatasetLinks = {'anat': '/path/to/smriprep'}` - Infer derivative name from the folder name of the derivative path if it isn't labeled. - E.g., `--derivatives /path/to/smriprep` --> `DatasetLinks = {'smriprep': '/path/to/smriprep'}` --------- Co-authored-by: Chris Markiewicz <[email protected]>
1 parent 8eb8215 commit ecf8580

File tree

5 files changed

+74
-11
lines changed

5 files changed

+74
-11
lines changed

fmriprep/cli/parser.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,23 @@ def __call__(self, parser, namespace, values, option_string=None):
6262
print(msg, file=sys.stderr)
6363
delattr(namespace, self.dest)
6464

65+
class ToDict(Action):
66+
def __call__(self, parser, namespace, values, option_string=None):
67+
d = {}
68+
for spec in values:
69+
try:
70+
name, loc = spec.split('=')
71+
loc = Path(loc)
72+
except ValueError:
73+
loc = Path(spec)
74+
name = loc.name
75+
76+
if name in d:
77+
raise ValueError(f'Received duplicate derivative name: {name}')
78+
79+
d[name] = loc
80+
setattr(namespace, self.dest, d)
81+
6582
def _path_exists(path, parser):
6683
"""Ensure a given path exists."""
6784
if path is None or not Path(path).exists():
@@ -222,11 +239,15 @@ def _slice_time_ref(value, parser):
222239
g_bids.add_argument(
223240
'-d',
224241
'--derivatives',
225-
action='store',
226-
metavar='PATH',
227-
type=Path,
228-
nargs='*',
229-
help='Search PATH(s) for pre-computed derivatives.',
242+
action=ToDict,
243+
metavar='PACKAGE=PATH',
244+
type=str,
245+
nargs='+',
246+
help=(
247+
'Search PATH(s) for pre-computed derivatives. '
248+
'These may be provided as named folders '
249+
'(e.g., `--derivatives smriprep=/path/to/smriprep`).'
250+
),
230251
)
231252
g_bids.add_argument(
232253
'--bids-database-dir',

fmriprep/cli/tests/test_parser.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,45 @@ def test_use_syn_sdc(tmp_path, args, expectation):
228228
assert opts.use_syn_sdc == expectation
229229

230230
_reset_config()
231+
232+
233+
def test_derivatives(tmp_path):
234+
"""Check the correct parsing of the derivatives argument."""
235+
bids_path = tmp_path / 'data'
236+
out_path = tmp_path / 'out'
237+
args = [str(bids_path), str(out_path), 'participant']
238+
bids_path.mkdir()
239+
240+
parser = _build_parser()
241+
242+
# Providing --derivatives without a path should raise an error
243+
temp_args = args + ['--derivatives']
244+
with pytest.raises((SystemExit, ArgumentError)):
245+
parser.parse_args(temp_args)
246+
_reset_config()
247+
248+
# Providing --derivatives without names should automatically label them
249+
temp_args = args + ['--derivatives', str(bids_path / 'derivatives/smriprep')]
250+
opts = parser.parse_args(temp_args)
251+
assert opts.derivatives == {'smriprep': bids_path / 'derivatives/smriprep'}
252+
_reset_config()
253+
254+
# Providing --derivatives with names should use them
255+
temp_args = args + [
256+
'--derivatives',
257+
f'anat={str(bids_path / "derivatives/smriprep")}',
258+
]
259+
opts = parser.parse_args(temp_args)
260+
assert opts.derivatives == {'anat': bids_path / 'derivatives/smriprep'}
261+
_reset_config()
262+
263+
# Providing multiple unlabeled derivatives with the same name should raise an error
264+
temp_args = args + [
265+
'--derivatives',
266+
str(bids_path / 'derivatives_01/smriprep'),
267+
str(bids_path / 'derivatives_02/smriprep'),
268+
]
269+
with pytest.raises(ValueError, match='Received duplicate derivative name'):
270+
parser.parse_args(temp_args)
271+
272+
_reset_config()

fmriprep/cli/workflow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def build_workflow(config_file, retval):
116116
]
117117

118118
if config.execution.derivatives:
119-
init_msg += [f'Searching for derivatives: {config.execution.derivatives}.']
119+
init_msg += [f'Searching for derivatives: {list(config.execution.derivatives.values())}.']
120120

121121
if config.execution.fs_subjects_dir:
122122
init_msg += [f"Pre-run FreeSurfer's SUBJECTS_DIR: {config.execution.fs_subjects_dir}."]

fmriprep/config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ class execution(_Config):
380380

381381
bids_dir = None
382382
"""An existing path to the dataset, which must be BIDS-compliant."""
383-
derivatives = []
383+
derivatives = {}
384384
"""Path(s) to search for pre-computed derivatives"""
385385
bids_database_dir = None
386386
"""Path to the directory containing SQLite database indices for the input BIDS dataset."""
@@ -526,8 +526,8 @@ def _process_value(value):
526526
cls.bids_filters[acq][k] = _process_value(v)
527527

528528
dataset_links = {'raw': cls.bids_dir}
529-
for i_deriv, deriv_path in enumerate(cls.derivatives):
530-
dataset_links[f'deriv-{i_deriv}'] = deriv_path
529+
for deriv_name, deriv_path in cls.derivatives.items():
530+
dataset_links[deriv_name] = deriv_path
531531
cls.dataset_links = dataset_links
532532

533533
if 'all' in cls.debug:

fmriprep/workflows/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ def init_single_subject_wf(subject_id: str):
249249

250250
std_spaces = spaces.get_spaces(nonstandard=False, dim=(3,))
251251
std_spaces.append('fsnative')
252-
for deriv_dir in config.execution.derivatives:
252+
for deriv_dir in config.execution.derivatives.values():
253253
anatomical_cache.update(
254254
collect_anat_derivatives(
255255
derivatives_dir=deriv_dir,
@@ -653,7 +653,7 @@ def init_single_subject_wf(subject_id: str):
653653

654654
entities = extract_entities(bold_series)
655655

656-
for deriv_dir in config.execution.derivatives:
656+
for deriv_dir in config.execution.derivatives.values():
657657
functional_cache.update(
658658
collect_derivatives(
659659
derivatives_dir=deriv_dir,

0 commit comments

Comments
 (0)