Skip to content

Commit 007b488

Browse files
committed
ENH: Separate out inline functions into dedicated interfaces
1 parent 80ccd1f commit 007b488

File tree

2 files changed

+149
-24
lines changed

2 files changed

+149
-24
lines changed

fmriprep/interfaces/bids.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from bids.utils import listify
66
from nipype.interfaces.base import (
77
DynamicTraitedSpec,
8+
File,
89
SimpleInterface,
910
TraitedSpec,
1011
isdefined,
@@ -60,3 +61,130 @@ def _run_interface(self, runtime):
6061
self._results['out'] = out
6162

6263
return runtime
64+
65+
66+
class _BIDSSourceFileInputSpec(TraitedSpec):
67+
bids_info = traits.Dict(
68+
mandatory=True,
69+
desc='BIDS information dictionary',
70+
)
71+
precomputed = traits.Dict({}, usedefault=True, desc='Precomputed BIDS information')
72+
sessionwise = traits.Bool(False, usedefault=True, desc='Keep session information')
73+
anat_type = traits.Enum('t1w', 't2w', usedefault=True, desc='Anatomical reference type')
74+
75+
76+
class _BIDSSourceFileOutputSpec(TraitedSpec):
77+
source_file = File(desc='Source file')
78+
79+
80+
class BIDSSourceFile(SimpleInterface):
81+
input_spec = _BIDSSourceFileInputSpec
82+
output_spec = _BIDSSourceFileOutputSpec
83+
84+
def _run_interface(self, runtime):
85+
src = self.inputs.bids_info[self.inputs.anat_type]
86+
87+
if not src and self.inputs.precomputed.get(f'{self.inputs.anat_type}_preproc'):
88+
src = self.inputs.bids_info['bold']
89+
self._results['source_file'] = _create_multi_source_file(src)
90+
return runtime
91+
92+
self._results['source_file'] = _create_multi_source_file(
93+
src,
94+
sessionwise=self.inputs.sessionwise,
95+
)
96+
return runtime
97+
98+
99+
class _CreateFreeSurferIDInputSpec(TraitedSpec):
100+
subject_id = traits.Str(mandatory=True, desc='BIDS Subject ID')
101+
session_id = traits.Str(desc='BIDS session ID')
102+
103+
104+
class _CreateFreeSurferIDOutputSpec(TraitedSpec):
105+
subject_id = traits.Str(desc='FreeSurfer subject ID')
106+
107+
108+
class CreateFreeSurferID(SimpleInterface):
109+
input_spec = _CreateFreeSurferIDInputSpec
110+
output_spec = _CreateFreeSurferIDOutputSpec
111+
112+
def _run_interface(self, runtime):
113+
self._results['subject_id'] = _create_fs_id(
114+
self.inputs.subject_id,
115+
self.inputs.session_id or None,
116+
)
117+
return runtime
118+
119+
120+
def _create_multi_source_file(in_files, sessionwise=False):
121+
"""
122+
Create a generic source name from multiple input files.
123+
124+
If sessionwise is True, session information from the first file is retained in the name.
125+
126+
Examples
127+
--------
128+
>>> _create_multi_source_name([
129+
... '/path/to/sub-045_ses-test_T1w.nii.gz',
130+
... '/path/to/sub-045_ses-retest_T1w.nii.gz'])
131+
'/path/to/sub-045_T1w.nii.gz'
132+
>>> _create_multi_source_name([
133+
... '/path/to/sub-045_ses-1_run-1_T1w.nii.gz',
134+
... '/path/to/sub-045_ses-1_run-2_T1w.nii.gz'],
135+
... sessionwise=True)
136+
'/path/to/sub-045_ses-1_T1w.nii.gz'
137+
"""
138+
import re
139+
from pathlib import Path
140+
141+
from nipype.utils.filemanip import filename_to_list
142+
143+
if not isinstance(in_files, tuple | list):
144+
return in_files
145+
elif len(in_files) == 1:
146+
return in_files[0]
147+
148+
p = Path(filename_to_list(in_files)[0])
149+
try:
150+
subj = re.search(r'(?<=^sub-)[a-zA-Z0-9]*', p.name).group()
151+
suffix = re.search(r'(?<=_)\w+(?=\.)', p.name).group()
152+
except AttributeError as e:
153+
raise AttributeError('Could not extract BIDS information') from e
154+
155+
prefix = f'sub-{subj}'
156+
157+
if sessionwise:
158+
ses = re.search(r'(?<=_ses-)[a-zA-Z0-9]*', p.name)
159+
if ses:
160+
prefix += f'_ses-{ses.group()}'
161+
return str(p.parent / f'{prefix}_{suffix}.nii.gz')
162+
163+
164+
def _create_fs_id(subject_id, session_id=None):
165+
"""
166+
Create FreeSurfer subject ID.
167+
168+
Examples
169+
--------
170+
>>> _create_fs_id('01')
171+
'sub-01'
172+
>>> _create_fs_id('sub-01')
173+
'sub-01'
174+
>>> _create_fs_id('01', 'pre')
175+
'sub-01_ses-pre
176+
"""
177+
178+
if not subject_id.startswith('sub-'):
179+
subject_id = f'sub-{subject_id}'
180+
181+
if session_id:
182+
ses_str = session_id
183+
if isinstance(session_id, list):
184+
from smriprep.utils.misc import stringify_sessions
185+
186+
ses_str = stringify_sessions(session_id)
187+
if not ses_str.startswith('ses-'):
188+
ses_str = f'ses-{ses_str}'
189+
subject_id += f'_{ses_str}'
190+
return subject_id

fmriprep/workflows/base.py

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,6 @@ def init_single_subject_wf(
176176
from niworkflows.interfaces.nilearn import NILEARN_VERSION
177177
from niworkflows.interfaces.utility import KeySelect
178178
from niworkflows.utils.bids import collect_data
179-
from niworkflows.utils.misc import fix_multi_T1w_source_name
180179
from niworkflows.utils.spaces import Reference
181180
from smriprep.workflows.anatomical import init_anat_fit_wf
182181
from smriprep.workflows.outputs import (
@@ -193,6 +192,7 @@ def init_single_subject_wf(
193192
)
194193

195194
from fmriprep.workflows.bold.base import init_bold_wf
195+
from fmriprep.interfaces.bids import BIDSSourceFile, CreateFreeSurferID
196196

197197
if name is None:
198198
name = f'sub_{subject_id}_wf'
@@ -300,10 +300,20 @@ def init_single_subject_wf(
300300
name='bidssrc',
301301
)
302302

303+
src_file = pe.Node(
304+
BIDSSourceFile(
305+
precomputed=anatomical_cache,
306+
sessionwise=config.workflow.subject_anatomical_reference == 'sessionwise',
307+
),
308+
name='source_anatomical',
309+
)
310+
303311
bids_info = pe.Node(
304312
BIDSInfo(bids_dir=config.execution.bids_dir, bids_validate=False), name='bids_info'
305313
)
306314

315+
create_fs_id = pe.Node(CreateFreeSurferID(), name='create_fs_id')
316+
307317
summary = pe.Node(
308318
SubjectSummary(
309319
std_spaces=spaces.get_spaces(nonstandard=False),
@@ -372,17 +382,15 @@ def init_single_subject_wf(
372382
'No T1w image found; using precomputed T1w image: %s', anatomical_cache['t1w_preproc']
373383
)
374384
workflow.connect([
375-
(bidssrc, bids_info, [(('bold', fix_multi_T1w_source_name), 'in_file')]),
376385
(anat_fit_wf, summary, [('outputnode.t1w_preproc', 't1w')]),
377386
(anat_fit_wf, ds_report_summary, [('outputnode.t1w_preproc', 'source_file')]),
378387
(anat_fit_wf, ds_report_about, [('outputnode.t1w_preproc', 'source_file')]),
379388
]) # fmt:skip
380389
else:
381390
workflow.connect([
382-
(bidssrc, bids_info, [(('t1w', fix_multi_T1w_source_name), 'in_file')]),
383-
(bidssrc, summary, [('t1w', 't1w')]),
384-
(bidssrc, ds_report_summary, [(('t1w', fix_multi_T1w_source_name), 'source_file')]),
385-
(bidssrc, ds_report_about, [(('t1w', fix_multi_T1w_source_name), 'source_file')]),
391+
(src_file, summary, [('source_file', 't1w')]),
392+
(src_file, ds_report_summary, [('source_file', 'source_file')]),
393+
(src_file, ds_report_about, [('source_file', 'source_file')]),
386394
]) # fmt:skip
387395

388396
workflow.connect([
@@ -393,7 +401,13 @@ def init_single_subject_wf(
393401
('roi', 'inputnode.roi'),
394402
('flair', 'inputnode.flair'),
395403
]),
396-
(bids_info, anat_fit_wf, [(('subject', _prefix, 'session'), 'inputnode.subject_id')]),
404+
(bidssrc, src_file, [('out_dict', 'bids_info')]),
405+
(src_file, bids_info, [('source_file', 'in_file')]),
406+
(bids_info, create_fs_id, [
407+
('subject', 'subject_id'),
408+
('session', 'session_id'),
409+
]),
410+
(create_fs_id, anat_fit_wf, [('subject_id', 'inputnode.subject_id')]),
397411
# Reporting connections
398412
(inputnode, summary, [('subjects_dir', 'subjects_dir')]),
399413
(bidssrc, summary, [('t2w', 't2w'), ('bold', 'bold')]),
@@ -959,23 +973,6 @@ def map_fieldmap_estimation(
959973
return fmap_estimators, estimator_map
960974

961975

962-
def _prefix(subject_id, session_id=None):
963-
"""Create FreeSurfer subject ID."""
964-
if not subject_id.startswith('sub-'):
965-
subject_id = f'sub-{subject_id}'
966-
967-
if session_id:
968-
ses_str = session_id
969-
if isinstance(session_id, list):
970-
from smriprep.utils.misc import stringify_sessions
971-
972-
ses_str = stringify_sessions(session_id)
973-
if not ses_str.startswith('ses-'):
974-
ses_str = f'ses-{ses_str}'
975-
subject_id += f'_{ses_str}'
976-
return subject_id
977-
978-
979976
def clean_datasinks(workflow: pe.Workflow) -> pe.Workflow:
980977
# Overwrite ``out_path_base`` of smriprep's DataSinks
981978
for node in workflow.list_node_names():

0 commit comments

Comments
 (0)