Skip to content

Commit 69e7efc

Browse files
committed
ENH: *fieldmap-less* integration from *SDCFlows* 2.0
Currently, this is blocked by the absence of a general solution to the reference generation issue (see nipreps/niworkflows#601). Once adequate DWI references are connected to the estimation workflow, this feature should work out. We can test the new feature with ds000206. Resolves: #111.
1 parent 25d9723 commit 69e7efc

File tree

5 files changed

+91
-51
lines changed

5 files changed

+91
-51
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ jobs:
314314
-v /tmp/ds000206/work:/work \
315315
--user $(id -u):$(id -g) \
316316
nipreps/dmriprep:latest /data /out participant -vv $eddy \
317-
--fs-subjects-dir /data/derivatives/freesurfer-6.0.1 --sloppy \
317+
--fs-subjects-dir /data/derivatives/freesurfer-6.0.1 --sloppy --use-syn-sdc \
318318
--notrack --skip-bids-validation -w /work --omp-nthreads 2 --nprocs 2
319319
- store_artifacts:
320320
path: /tmp/ds000206/derivatives/dmriprep

dmriprep/cli/parser.py

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def _bids_filter(value):
225225
default="register",
226226
choices=["register", "header"],
227227
help='Either "register" (the default) to initialize volumes at center or "header"'
228-
" to use the header information when coregistering DWI to T1w images.",
228+
" to use the header information when co-registering DWI to T1w images.",
229229
)
230230

231231
# ANTs options
@@ -243,35 +243,27 @@ def _bids_filter(value):
243243
"run-to-run replicability when used with --omp-nthreads 1",
244244
)
245245

246-
# Fieldmap options
247-
g_fmap = parser.add_argument_group("Specific options for handling fieldmaps")
248-
g_fmap.add_argument(
249-
"--fmap-bspline",
250-
action="store_true",
251-
default=False,
252-
help="fit a B-Spline field using least-squares (experimental)",
253-
)
254-
g_fmap.add_argument(
255-
"--fmap-no-demean",
256-
action="store_false",
257-
default=True,
258-
help="do not remove median (within mask) from fieldmap",
259-
)
260-
261246
# SyN-unwarp options
262247
g_syn = parser.add_argument_group("Specific options for SyN distortion correction")
263248
g_syn.add_argument(
264249
"--use-syn-sdc",
265250
action="store_true",
251+
dest="use_syn",
266252
default=False,
267-
help="EXPERIMENTAL: Use fieldmap-free distortion correction",
253+
help="""\
254+
Attempt to set-up fieldmap-less estimation of fieldmaps via nonlinear registration with ANTs \
255+
if no other fieldmap estimation method is available. Fieldmap-less estimation will not be used \
256+
when sufficient fieldmap information (B0 mapping with SEI or GRE, or PEPOLAR estimation with \
257+
EPIs) is retrieved from the BIDS structure for a given subject.
258+
""",
268259
)
269260
g_syn.add_argument(
270261
"--force-syn",
271262
action="store_true",
272263
default=False,
273-
help="EXPERIMENTAL/TEMPORARY: Use SyN correction in addition to "
274-
"fieldmap correction, if available",
264+
help="""\
265+
Force estimation of fieldmaps with the fieldmap-less approach. The use of this feature \
266+
is discouraged.""",
275267
)
276268

277269
# FreeSurfer options
@@ -334,7 +326,7 @@ def _bids_filter(value):
334326
action="store_true",
335327
default=False,
336328
help="only generate reports, don't run workflows. This will only rerun report "
337-
"aggregation, not reportlet generation for specific nodes.",
329+
"aggregation, not 'reportlet' generation for specific nodes.",
338330
)
339331
g_other.add_argument(
340332
"--run-uuid",

dmriprep/interfaces/vectors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class _CheckGradientTableOutputSpec(TraitedSpec):
3030
full_sphere = traits.Bool()
3131
pole = traits.Tuple(traits.Float, traits.Float, traits.Float)
3232
b0_ixs = traits.List(traits.Int)
33+
b0_mask = traits.List(traits.Bool)
3334

3435

3536
class CheckGradientTable(SimpleInterface):
@@ -81,6 +82,7 @@ def _run_interface(self, runtime):
8182
pole = table.pole
8283
self._results["pole"] = tuple(pole)
8384
self._results["full_sphere"] = np.all(pole == 0.0)
85+
self._results["b0_mask"] = table.b0mask.tolist()
8486
self._results["b0_ixs"] = np.where(table.b0mask)[0].tolist()
8587

8688
cwd = Path(runtime.cwd).absolute()

dmriprep/workflows/base.py

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -313,13 +313,19 @@ def init_single_subject_wf(subject_id):
313313
fmap_estimators = find_estimators(
314314
layout=config.execution.layout,
315315
subject=subject_id,
316-
fmapless=False,
316+
fmapless=config.workflow.use_syn,
317+
force_fmapless=config.workflow.force_syn,
317318
)
318319

319-
# Add fieldmap-less estimators
320-
if not fmap_estimators and config.workflow.use_syn:
321-
# estimators = [fm.FieldmapEstimation()]
322-
raise NotImplementedError
320+
if (
321+
any(f.method == fm.EstimatorType.ANAT for f in fmap_estimators)
322+
and "MNI152NLin2009cAsym" not in spaces.get_spaces(nonstandard=False, dim=(3,))
323+
):
324+
# Although this check would go better within parser, allow datasets with fieldmaps
325+
# not to require spatial standardization of the T1w.
326+
raise RuntimeError("""\
327+
Argument '--use-sdc-syn' requires having 'MNI152NLin2009cAsym' as one output standard space. \
328+
Please add the 'MNI152NLin2009cAsym' keyword to the '--output-spaces' argument""")
323329

324330
# Nuts and bolts: initialize individual run's pipeline
325331
dwi_preproc_list = []
@@ -354,6 +360,10 @@ def init_single_subject_wf(subject_id):
354360
dwi_preproc_list.append(dwi_preproc_wf)
355361

356362
if not fmap_estimators:
363+
config.loggers.workflow.warning(
364+
"Data for fieldmap estimation not present. Please note that these data "
365+
"will not be corrected for susceptibility distortions."
366+
)
357367
return workflow
358368

359369
# SDC Step 2: Manually add further estimators (e.g., fieldmap-less)
@@ -371,7 +381,6 @@ def init_single_subject_wf(subject_id):
371381
BIDS structure for this particular subject.
372382
"""
373383

374-
# TODO: Requires nipreps/sdcflows#147
375384
for dwi_preproc_wf in dwi_preproc_list:
376385
# fmt: off
377386
workflow.connect([
@@ -392,38 +401,75 @@ def init_single_subject_wf(subject_id):
392401

393402
# Step 3: Manually connect PEPOLAR
394403
for estimator in fmap_estimators:
395-
if estimator.method != fm.EstimatorType.PEPOLAR:
404+
config.loggers.workflow.info(f"""\
405+
Setting-up fieldmap "{estimator.bids_id}" ({estimator.method}) with \
406+
<{', '.join(s.path.name for s in estimator.sources)}>""")
407+
if estimator.method in (fm.EstimatorType.MAPPED, fm.EstimatorType.PHASEDIFF):
396408
continue
397409

398410
suffices = set(s.suffix for s in estimator.sources)
399411

400-
if sorted(suffices) == ["epi"]:
412+
if estimator.method == fm.EstimatorType.PEPOLAR and sorted(suffices) == ["epi"]:
401413
getattr(fmap_wf.inputs, f"in_{estimator.bids_id}").in_data = [
402414
str(s.path) for s in estimator.sources
403415
]
404416
getattr(fmap_wf.inputs, f"in_{estimator.bids_id}").metadata = [
405417
s.metadata for s in estimator.sources
406418
]
407-
else:
408-
raise NotImplementedError
409-
# from niworkflows.interfaces.utility import KeySelect
410-
# est_id = estimator.bids_id
411-
# estim_select = pe.MapNode(
412-
# KeySelect(fields=["metadata", "dwi_reference", "dwi_mask", "gradients_rasb",]),
413-
# name=f"fmap_select_{est_id}",
414-
# run_without_submitting=True,
415-
# iterfields=["key"]
416-
# )
417-
# estim_select.inputs.key = [
418-
# str(s.path) for s in estimator.sources if s.suffix in ("epi", "dwi", "sbref")
419-
# ]
420-
# # fmt:off
421-
# workflow.connect([
422-
# (referencenode, estim_select, [("dwi_file", "keys"),
423-
# ("metadata", "metadata"),
424-
# ("dwi_reference", "dwi_reference"),
425-
# ("gradients_rasb", "gradients_rasb")]),
426-
# ])
427-
# # fmt:on
419+
continue
420+
421+
if estimator.method == fm.EstimatorType.PEPOLAR:
422+
raise NotImplementedError(
423+
"Sophisticated PEPOLAR schemes (e.g., using DWI+EPI) are unsupported."
424+
)
425+
426+
if estimator.method == fm.EstimatorType.ANAT:
427+
from sdcflows.interfaces.brainmask import BrainExtraction
428+
from sdcflows.workflows.fit.syn import init_syn_preprocessing_wf
429+
from ...interfaces.vectors import CheckGradientTable
430+
431+
sources = [str(s.path) for s in estimator.sources]
432+
layout = config.execution.layout
433+
syn_preprocessing_wf = init_syn_preprocessing_wf(
434+
omp_nthreads=config.nipype.omp_nthreads,
435+
auto_bold_nss=False,
436+
)
437+
syn_preprocessing_wf.inputs.in_epis = sources
438+
syn_preprocessing_wf.inputs.in_meta = [
439+
layout.get_metadata(s) for s in sources
440+
]
441+
442+
b0_masks = pe.MapNode(CheckGradientTable(), name="b0_masks",
443+
iterfield=("dwi_file", "in_bvec", "in_bval"))
444+
b0_masks.inputs.dwi_file = sources
445+
b0_masks.inputs.in_bvec = [str(layout.get_bvec(s)) for s in sources]
446+
b0_masks.inputs.in_bval = [str(layout.get_bval(s)) for s in sources]
447+
448+
epi_brain = pe.Node(BrainExtraction(), name="epi_brain")
449+
450+
# fmt:off
451+
workflow.connect([
452+
(anat_preproc_wf, syn_preprocessing_wf, [
453+
("outputnode.t1w_preproc", "inputnode.in_anat"),
454+
("outputnode.t1w_mask", "inputnode.mask_anat"),
455+
]),
456+
(b0_masks, syn_preprocessing_wf, [("b0_mask", "inputnode.t_masks")]),
457+
(syn_preprocessing_wf, fmap_wf, [
458+
("outputnode.epi_ref", "inputnode.epi_ref"),
459+
("outputnode.anat_ref", "inputnode.anat_ref"),
460+
("outputnode.anat_mask", "inputnode.anat_mask"),
461+
("outputnode.anat2epi_xfm", "inputnode.anat2epi_xfm"),
462+
]),
463+
(syn_preprocessing_wf, epi_brain, [("outputnode.epi_ref", "in_file")]),
464+
(anat_preproc_wf, fmap_wf, [
465+
("outputnode.std2anat_xfm", "inputnode.std2anat_xfm"),
466+
]),
467+
(epi_brain, fmap_wf, [("out_mask", "inputnode.epi_mask")]),
468+
])
469+
# fmt:on
470+
471+
raise RuntimeError(
472+
f"Unknown fieldmap estimation strategy <{estimator}>."
473+
)
428474

429475
return workflow

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ install_requires =
2929
numpy
3030
pybids >= 0.11.1
3131
pyyaml
32-
sdcflows ~= 2.0.0
32+
sdcflows ~= 2.0.1
3333
smriprep >= 0.8.0rc2
3434
svgutils != 0.3.2
3535
templateflow ~= 0.6

0 commit comments

Comments
 (0)