Skip to content

Commit 630b715

Browse files
committed
MAINT: Refactor the workflow to use Nipype iterables
This move will make it easier to integrate SDC, and in particular, the SDC scheduling @mattcieslak was preparing.
1 parent 9d95e73 commit 630b715

File tree

6 files changed

+121
-157
lines changed

6 files changed

+121
-157
lines changed

dmriprep/config/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ class nipype(_Config):
254254
"""Number of processes (compute tasks) that can be run in parallel (multiprocessing only)."""
255255
omp_nthreads = os.cpu_count()
256256
"""Number of CPUs a single process can access for multithreaded execution."""
257+
parameterize_dirs = False
258+
"""The node’s output directory will contain full parameterization of any iterable, otherwise
259+
parameterizations over 32 characters will be replaced by their hash."""
257260
plugin = 'MultiProc'
258261
"""NiPype's execution plugin."""
259262
plugin_args = {
@@ -302,6 +305,7 @@ def init(cls):
302305
'crashfile_format': cls.crashfile_format,
303306
'get_linked_libs': cls.get_linked_libs,
304307
'stop_on_first_crash': cls.stop_on_first_crash,
308+
'parameterize_dirs': cls.parameterize_dirs,
305309
}
306310
})
307311

dmriprep/workflows/base.py

Lines changed: 75 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from ..interfaces import DerivativesDataSink, BIDSDataGrabber
1919
from ..interfaces.reports import SubjectSummary, AboutSummary
2020
from ..utils.bids import collect_data
21-
from .dwi import init_dwi_preproc_wf
21+
from .dwi.base import init_early_b0ref_wf
2222

2323

2424
def init_dmriprep_wf():
@@ -40,42 +40,42 @@ def init_dmriprep_wf():
4040
wf = init_dmriprep_wf()
4141
4242
"""
43-
dmriprep_wf = Workflow(name='dmriprep_wf')
43+
dmriprep_wf = Workflow(name="dmriprep_wf")
4444
dmriprep_wf.base_dir = config.execution.work_dir
4545

4646
freesurfer = config.workflow.run_reconall
4747
if freesurfer:
4848
fsdir = pe.Node(
4949
BIDSFreeSurferDir(
5050
derivatives=config.execution.output_dir,
51-
freesurfer_home=os.getenv('FREESURFER_HOME'),
51+
freesurfer_home=os.getenv("FREESURFER_HOME"),
5252
spaces=config.workflow.spaces.get_fs_spaces()),
53-
name='fsdir_run_%s' % config.execution.run_uuid.replace('-', '_'),
53+
name=f"fsdir_run_{config.execution.run_uuid.replace('-', '_')}",
5454
run_without_submitting=True)
5555
if config.execution.fs_subjects_dir is not None:
5656
fsdir.inputs.subjects_dir = str(config.execution.fs_subjects_dir.absolute())
5757

5858
for subject_id in config.execution.participant_label:
5959
single_subject_wf = init_single_subject_wf(subject_id)
6060

61-
single_subject_wf.config['execution']['crashdump_dir'] = str(
62-
config.execution.output_dir / "dmriprep" / "-".join(("sub", subject_id))
61+
single_subject_wf.config["execution"]["crashdump_dir"] = str(
62+
config.execution.output_dir / "dmriprep" / f"sub-{subject_id}"
6363
/ "log" / config.execution.run_uuid
6464
)
6565

6666
for node in single_subject_wf._get_all_nodes():
6767
node.config = deepcopy(single_subject_wf.config)
6868
if freesurfer:
69-
dmriprep_wf.connect(fsdir, 'subjects_dir',
70-
single_subject_wf, 'inputnode.subjects_dir')
69+
dmriprep_wf.connect(fsdir, "subjects_dir",
70+
single_subject_wf, "fsinputnode.subjects_dir")
7171
else:
7272
dmriprep_wf.add_nodes([single_subject_wf])
7373

7474
# Dump a copy of the config file into the log directory
75-
log_dir = config.execution.output_dir / 'dmriprep' / 'sub-{}'.format(subject_id) \
76-
/ 'log' / config.execution.run_uuid
75+
log_dir = config.execution.output_dir / "dmriprep" / f"sub-{subject_id}" \
76+
/ "log" / config.execution.run_uuid
7777
log_dir.mkdir(exist_ok=True, parents=True)
78-
config.to_filename(log_dir / 'dmriprep.toml')
78+
config.to_filename(log_dir / "dmriprep.toml")
7979

8080
return dmriprep_wf
8181

@@ -102,7 +102,7 @@ def init_single_subject_wf(subject_id):
102102
from dmriprep.config.testing import mock_config
103103
from dmriprep.workflows.base import init_single_subject_wf
104104
with mock_config():
105-
wf = init_single_subject_wf('THP0005')
105+
wf = init_single_subject_wf("THP0005")
106106
107107
Parameters
108108
----------
@@ -115,24 +115,24 @@ def init_single_subject_wf(subject_id):
115115
FreeSurfer's ``$SUBJECTS_DIR``
116116
117117
"""
118-
name = "single_subject_%s_wf" % subject_id
118+
name = f"single_subject_{subject_id}_wf"
119119
subject_data = collect_data(
120120
config.execution.layout,
121121
subject_id)[0]
122122

123-
if 'flair' in config.workflow.ignore:
124-
subject_data['flair'] = []
125-
if 't2w' in config.workflow.ignore:
126-
subject_data['t2w'] = []
123+
if "flair" in config.workflow.ignore:
124+
subject_data["flair"] = []
125+
if "t2w" in config.workflow.ignore:
126+
subject_data["t2w"] = []
127127

128128
anat_only = config.workflow.anat_only
129129

130130
# Make sure we always go through these two checks
131-
if not anat_only and not subject_data['dwi']:
131+
if not anat_only and not subject_data["dwi"]:
132132
raise Exception(f"No DWI data found for participant {subject_id}. "
133133
"All workflows require DWI images.")
134134

135-
if not subject_data['t1w']:
135+
if not subject_data["t1w"]:
136136
raise Exception(f"No T1w images found for participant {subject_id}. "
137137
"All workflows require T1w images.")
138138

@@ -165,34 +165,34 @@ def init_single_subject_wf(subject_id):
165165
166166
"""
167167
spaces = config.workflow.spaces
168-
reportlets_dir = str(config.execution.work_dir / 'reportlets')
168+
reportlets_dir = str(config.execution.work_dir / "reportlets")
169169

170-
inputnode = pe.Node(niu.IdentityInterface(fields=['subjects_dir']),
171-
name='inputnode')
170+
fsinputnode = pe.Node(niu.IdentityInterface(fields=["subjects_dir"]),
171+
name="fsinputnode")
172172

173173
bidssrc = pe.Node(BIDSDataGrabber(subject_data=subject_data, anat_only=anat_only),
174-
name='bidssrc')
174+
name="bidssrc")
175175

176176
bids_info = pe.Node(BIDSInfo(
177-
bids_dir=config.execution.bids_dir, bids_validate=False), name='bids_info')
177+
bids_dir=config.execution.bids_dir, bids_validate=False), name="bids_info")
178178

179179
summary = pe.Node(SubjectSummary(std_spaces=spaces.get_spaces(nonstandard=False),
180180
nstd_spaces=spaces.get_spaces(standard=False)),
181-
name='summary', run_without_submitting=True)
181+
name="summary", run_without_submitting=True)
182182

183183
about = pe.Node(AboutSummary(version=config.environment.version,
184-
command=' '.join(sys.argv)),
185-
name='about', run_without_submitting=True)
184+
command=" ".join(sys.argv)),
185+
name="about", run_without_submitting=True)
186186

187187
ds_report_summary = pe.Node(
188188
DerivativesDataSink(base_directory=reportlets_dir,
189-
desc='summary', keep_dtype=True),
190-
name='ds_report_summary', run_without_submitting=True)
189+
desc="summary", keep_dtype=True),
190+
name="ds_report_summary", run_without_submitting=True)
191191

192192
ds_report_about = pe.Node(
193193
DerivativesDataSink(base_directory=reportlets_dir,
194-
desc='about', keep_dtype=True),
195-
name='ds_report_about', run_without_submitting=True)
194+
desc="about", keep_dtype=True),
195+
name="ds_report_about", run_without_submitting=True)
196196

197197
# Preprocessing of T1w (includes registration to MNI)
198198
anat_preproc_wf = init_anat_preproc_wf(
@@ -205,77 +205,76 @@ def init_single_subject_wf(subject_id):
205205
output_dir=str(config.execution.output_dir),
206206
reportlets_dir=reportlets_dir,
207207
skull_strip_fixed_seed=config.workflow.skull_strip_fixed_seed,
208-
skull_strip_mode='force',
208+
skull_strip_mode="force",
209209
skull_strip_template=Reference.from_string(
210210
config.workflow.skull_strip_template)[0],
211211
spaces=spaces,
212-
t1w=subject_data['t1w'],
212+
t1w=subject_data["t1w"],
213213
)
214214

215215
workflow.connect([
216-
(inputnode, anat_preproc_wf, [('subjects_dir', 'inputnode.subjects_dir')]),
217-
(bidssrc, bids_info, [(('t1w', fix_multi_T1w_source_name), 'in_file')]),
218-
(inputnode, summary, [('subjects_dir', 'subjects_dir')]),
219-
(bidssrc, summary, [('t1w', 't1w'),
220-
('t2w', 't2w'),
221-
('dwi', 'dwi')]),
222-
(bids_info, summary, [('subject', 'subject_id')]),
223-
(bids_info, anat_preproc_wf, [(('subject', _prefix), 'inputnode.subject_id')]),
224-
(bidssrc, anat_preproc_wf, [('t1w', 'inputnode.t1w'),
225-
('t2w', 'inputnode.t2w'),
226-
('roi', 'inputnode.roi'),
227-
('flair', 'inputnode.flair')]),
228-
(bidssrc, ds_report_summary, [(('t1w', fix_multi_T1w_source_name), 'source_file')]),
229-
(summary, ds_report_summary, [('out_report', 'in_file')]),
230-
(bidssrc, ds_report_about, [(('t1w', fix_multi_T1w_source_name), 'source_file')]),
231-
(about, ds_report_about, [('out_report', 'in_file')]),
216+
(fsinputnode, anat_preproc_wf, [("subjects_dir", "inputnode.subjects_dir")]),
217+
(bidssrc, bids_info, [(("t1w", fix_multi_T1w_source_name), "in_file")]),
218+
(fsinputnode, summary, [("subjects_dir", "subjects_dir")]),
219+
(bidssrc, summary, [("t1w", "t1w"),
220+
("t2w", "t2w"),
221+
("dwi", "dwi")]),
222+
(bids_info, summary, [("subject", "subject_id")]),
223+
(bids_info, anat_preproc_wf, [(("subject", _prefix), "inputnode.subject_id")]),
224+
(bidssrc, anat_preproc_wf, [("t1w", "inputnode.t1w"),
225+
("t2w", "inputnode.t2w"),
226+
("roi", "inputnode.roi"),
227+
("flair", "inputnode.flair")]),
228+
(bidssrc, ds_report_summary, [(("t1w", fix_multi_T1w_source_name), "source_file")]),
229+
(summary, ds_report_summary, [("out_report", "in_file")]),
230+
(bidssrc, ds_report_about, [(("t1w", fix_multi_T1w_source_name), "source_file")]),
231+
(about, ds_report_about, [("out_report", "in_file")]),
232232
])
233233

234234
# Overwrite ``out_path_base`` of smriprep's DataSinks
235235
for node in workflow.list_node_names():
236-
if node.split('.')[-1].startswith('ds_'):
237-
workflow.get_node(node).interface.out_path_base = 'dmriprep'
236+
if node.split(".")[-1].startswith("ds_"):
237+
workflow.get_node(node).interface.out_path_base = "dmriprep"
238238

239239
if anat_only:
240240
return workflow
241241

242242
# Append the dMRI section to the existing anatomical excerpt
243243
# That way we do not need to stream down the number of bold datasets
244-
anat_preproc_wf.__postdesc__ = (anat_preproc_wf.__postdesc__ or '') + f"""
244+
anat_preproc_wf.__postdesc__ = (anat_preproc_wf.__postdesc__ or "") + f"""
245245
Diffusion data preprocessing
246246
247247
: For each of the {len(subject_data["dwi"])} dwi scans found per subject
248248
(across all sessions), the following preprocessing was performed."""
249249

250-
for dwi_file in subject_data['dwi']:
251-
dwi_preproc_wf = init_dwi_preproc_wf(dwi_file)
252-
253-
workflow.connect([
254-
(anat_preproc_wf, dwi_preproc_wf,
255-
[(('outputnode.t1w_preproc', _pop), 'inputnode.t1w_preproc'),
256-
('outputnode.t1w_mask', 'inputnode.t1w_mask'),
257-
('outputnode.t1w_dseg', 'inputnode.t1w_dseg'),
258-
('outputnode.t1w_aseg', 'inputnode.t1w_aseg'),
259-
('outputnode.t1w_aparc', 'inputnode.t1w_aparc'),
260-
('outputnode.t1w_tpms', 'inputnode.t1w_tpms'),
261-
('outputnode.template', 'inputnode.template'),
262-
('outputnode.anat2std_xfm', 'inputnode.anat2std_xfm'),
263-
('outputnode.std2anat_xfm', 'inputnode.std2anat_xfm'),
264-
# Undefined if --fs-no-reconall, but this is safe
265-
('outputnode.subjects_dir', 'inputnode.subjects_dir'),
266-
('outputnode.subject_id', 'inputnode.subject_id'),
267-
('outputnode.t1w2fsnative_xfm', 'inputnode.t1w2fsnative_xfm'),
268-
('outputnode.fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm')]),
250+
layout = config.execution.layout
251+
inputnode = pe.Node(niu.IdentityInterface(fields=["dwi_data"]),
252+
name="inputnode")
253+
inputnode.iterables = [(
254+
"dwi_data", tuple([
255+
(dwi, layout.get_bvec(dwi), layout.get_bval(dwi),
256+
layout.get_metadata(dwi)["PhaseEncodingDirection"])
257+
for dwi in subject_data["dwi"]
269258
])
259+
)]
260+
split_info = pe.Node(niu.Function(
261+
function=_unpack, output_names=["dwi_file", "bvec", "bval", "pedir"]),
262+
name="split_info", run_without_submitting=True)
263+
264+
early_b0ref_wf = init_early_b0ref_wf()
265+
workflow.connect([
266+
(inputnode, split_info, [("dwi_data", "in_tuple")]),
267+
(split_info, early_b0ref_wf, [("dwi_file", "inputnode.dwi_file"),
268+
("bvec", "inputnode.in_bvec"),
269+
("bval", "inputnode.in_bval")]),
270+
])
270271

271272
return workflow
272273

273274

274275
def _prefix(subid):
275-
return '-'.join(('sub', subid.lstrip('sub-')))
276+
return "-".join(("sub", subid.lstrip("sub-")))
276277

277278

278-
def _pop(inlist):
279-
if isinstance(inlist, (list, tuple)):
280-
return inlist[0]
281-
return inlist
279+
def _unpack(in_tuple):
280+
return in_tuple

dmriprep/workflows/dwi/__init__.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +0,0 @@
1-
"""Pre-processing dMRI workflows."""
2-
3-
from .base import init_dwi_preproc_wf
4-
from .util import init_dwi_reference_wf
5-
6-
__all__ = [
7-
'init_dwi_preproc_wf',
8-
'init_dwi_reference_wf',
9-
]

0 commit comments

Comments
 (0)