Skip to content

Commit 4b1f566

Browse files
authored
Merge pull request #480 from mgxd/enh/syn-no-prior
ENH: Allow running SyN SDC without using prior
2 parents 5a55a81 + d2de7ba commit 4b1f566

File tree

5 files changed

+68
-42
lines changed

5 files changed

+68
-42
lines changed

sdcflows/workflows/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def init_fmap_preproc_wf(
3939
sloppy=False,
4040
debug=False,
4141
name="fmap_preproc_wf",
42+
**kwargs,
4243
):
4344
"""
4445
Create and combine estimator workflows.
@@ -110,6 +111,7 @@ def init_fmap_preproc_wf(
110111
omp_nthreads=omp_nthreads,
111112
debug=debug,
112113
sloppy=sloppy,
114+
**kwargs,
113115
)
114116
source_files = [
115117
str(f.path) for f in estimator.sources if f.suffix not in ("T1w", "T2w")

sdcflows/workflows/fit/fieldmap.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@
2929
INPUT_FIELDS = ("magnitude", "fieldmap")
3030

3131

32-
def init_fmap_wf(omp_nthreads=1, sloppy=False, debug=False, mode="phasediff", name="fmap_wf"):
32+
def init_fmap_wf(
33+
omp_nthreads=1,
34+
sloppy=False,
35+
debug=False,
36+
mode="phasediff",
37+
name="fmap_wf",
38+
**kwargs,
39+
):
3340
"""
3441
Estimate the fieldmap based on a field-mapping MRI acquisition.
3542

sdcflows/workflows/fit/pepolar.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def init_topup_wf(
4040
sloppy=False,
4141
debug=False,
4242
name="pepolar_estimate_wf",
43+
**kwargs,
4344
):
4445
"""
4546
Create the PEPOLAR field estimation workflow based on FSL's ``topup``.

sdcflows/workflows/fit/syn.py

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
"""
2424
Estimating the susceptibility distortions without fieldmaps.
2525
"""
26+
2627
from nipype.pipeline import engine as pe
2728
from nipype.interfaces import utility as niu
29+
from nipype.interfaces.base import Undefined
2830
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
2931

3032
from ... import data
@@ -46,6 +48,8 @@ def init_syn_sdc_wf(
4648
debug=False,
4749
name="syn_sdc_wf",
4850
omp_nthreads=1,
51+
sd_prior=True,
52+
**kwargs,
4953
):
5054
"""
5155
Build the *fieldmap-less* susceptibility-distortion estimation workflow.
@@ -117,7 +121,6 @@ def init_syn_sdc_wf(
117121
FixHeaderRegistration as Registration,
118122
)
119123
from niworkflows.interfaces.nibabel import (
120-
Binarize,
121124
IntensityClip,
122125
RegridToZooms,
123126
)
@@ -171,7 +174,6 @@ def init_syn_sdc_wf(
171174
name="warp_dir",
172175
)
173176
warp_dir.inputs.nlevels = 2
174-
atlas_msk = pe.Node(Binarize(thresh_low=atlas_threshold), name="atlas_msk")
175177
anat_dilmsk = pe.Node(BinaryDilation(), name="anat_dilmsk")
176178
amask2epi = pe.Node(
177179
ApplyTransforms(interpolation="MultiLabel", transforms="identity"),
@@ -208,7 +210,7 @@ def init_syn_sdc_wf(
208210
)
209211

210212
fixed_masks = pe.Node(
211-
niu.Merge(3),
213+
niu.Merge(2),
212214
name="fixed_masks",
213215
mem_gb=DEFAULT_MEMORY_MIN_GB,
214216
run_without_submitting=True,
@@ -220,9 +222,7 @@ def init_syn_sdc_wf(
220222

221223
# SyN Registration Core
222224
syn = pe.Node(
223-
Registration(
224-
from_file=data.load(f"sd_syn{'_sloppy' * sloppy}.json")
225-
),
225+
Registration(from_file=data.load(f"sd_syn{'_sloppy' * sloppy}.json")),
226226
name="syn",
227227
n_procs=omp_nthreads,
228228
)
@@ -233,9 +233,7 @@ def init_syn_sdc_wf(
233233
syn.inputs.args = "--write-interval-volumes 2"
234234

235235
# Extract the corresponding fieldmap in Hz
236-
extract_field = pe.Node(
237-
DisplacementsField2Fieldmap(), name="extract_field"
238-
)
236+
extract_field = pe.Node(DisplacementsField2Fieldmap(), name="extract_field")
239237

240238
unwarp = pe.Node(ApplyCoeffsField(jacobian=False), name="unwarp")
241239

@@ -267,7 +265,6 @@ def init_syn_sdc_wf(
267265
workflow.connect([
268266
(inputnode, readout_time, [(("epi_ref", _pop), "in_file"),
269267
(("epi_ref", _pull), "metadata")]),
270-
(inputnode, atlas_msk, [("sd_prior", "in_file")]),
271268
(inputnode, clip_epi, [(("epi_ref", _pop), "in_file")]),
272269
(inputnode, unwarp, [(("epi_ref", _pop), "in_data")]),
273270
(inputnode, amask2epi, [("epi_mask", "reference_image")]),
@@ -293,7 +290,6 @@ def init_syn_sdc_wf(
293290
(lap_epi, lap_epi_norm, [("output_image", "in_file")]),
294291
(lap_epi_norm, epi_merge, [("out", "in2")]),
295292
(find_zooms, zooms_epi, [("out", "zooms")]),
296-
(atlas_msk, fixed_masks, [("out_mask", "in3")]),
297293
(anat_dilmsk, amask2epi, [("out_file", "input_image")]),
298294
(amask2epi, epi_umask, [("output_image", "in2")]),
299295
(readout_time, warp_dir, [("pe_direction", "pe_dir")]),
@@ -331,6 +327,7 @@ def init_syn_preprocessing_wf(
331327
omp_nthreads=1,
332328
auto_bold_nss=False,
333329
t1w_inversion=False,
330+
sd_prior=True,
334331
):
335332
"""
336333
Prepare EPI references and co-registration to anatomical for SyN.
@@ -356,6 +353,8 @@ def init_syn_preprocessing_wf(
356353
of BOLD images.
357354
t1w_inversion : :obj:`bool`
358355
Run T1w intensity inversion so that it looks more like a T2 contrast.
356+
sd_prior : :obj:`bool`
357+
Enable using a prior map to regularize the SyN cost function.
359358
360359
Inputs
361360
------
@@ -426,26 +425,6 @@ def init_syn_preprocessing_wf(
426425

427426
deob_epi = pe.Node(Deoblique(), name="deob_epi")
428427

429-
# Mapping & preparing prior knowledge
430-
# Concatenate transform files:
431-
# 1) MNI -> anat; 2) ATLAS -> MNI
432-
transform_list = pe.Node(
433-
niu.Merge(3),
434-
name="transform_list",
435-
mem_gb=DEFAULT_MEMORY_MIN_GB,
436-
run_without_submitting=True,
437-
)
438-
transform_list.inputs.in3 = data.load("fmap_atlas_2_MNI152NLin2009cAsym_affine.mat")
439-
prior2epi = pe.Node(
440-
ApplyTransforms(
441-
invert_transform_flags=[True, False, False],
442-
input_image=str(data.load("fmap_atlas.nii.gz")),
443-
),
444-
name="prior2epi",
445-
n_procs=omp_nthreads,
446-
mem_gb=0.3,
447-
)
448-
449428
anat2epi = pe.Node(
450429
ApplyTransforms(invert_transform_flags=[True]),
451430
name="anat2epi",
@@ -502,9 +481,44 @@ def _remove_first_mask(in_file):
502481

503482
sampling_ref = pe.Node(GenerateSamplingReference(), name="sampling_ref")
504483

505-
# fmt:off
484+
if sd_prior:
485+
# Mapping & preparing prior knowledge
486+
# Concatenate transform files:
487+
# 1) MNI -> anat; 2) ATLAS -> MNI
488+
transform_list = pe.Node(
489+
niu.Merge(3),
490+
name="transform_list",
491+
mem_gb=DEFAULT_MEMORY_MIN_GB,
492+
run_without_submitting=True,
493+
)
494+
transform_list.inputs.in3 = data.load(
495+
"fmap_atlas_2_MNI152NLin2009cAsym_affine.mat"
496+
)
497+
prior2epi = pe.Node(
498+
ApplyTransforms(
499+
invert_transform_flags=[True, False, False],
500+
input_image=str(data.load("fmap_atlas.nii.gz")),
501+
),
502+
name="prior2epi",
503+
n_procs=omp_nthreads,
504+
mem_gb=0.3,
505+
)
506+
507+
workflow.connect([
508+
(inputnode, transform_list, [("std2anat_xfm", "in2")]),
509+
(epi2anat, transform_list, [("forward_transforms", "in1")]),
510+
(transform_list, prior2epi, [("out", "transforms")]),
511+
(sampling_ref, prior2epi, [("out_file", "reference_image")]),
512+
(prior2epi, outputnode, [("output_image", "sd_prior")]),
513+
]) # fmt:skip
514+
515+
else:
516+
# no prior to be used
517+
# MG: Future goal is to allow using alternative mappings
518+
# i.e. in the case of infants, where priors change depending on development
519+
outputnode.inputs.sd_prior = Undefined
520+
506521
workflow.connect([
507-
(inputnode, transform_list, [("std2anat_xfm", "in2")]),
508522
(inputnode, epi_reference_wf, [("in_epis", "inputnode.in_files")]),
509523
(inputnode, merge_output, [("in_meta", "meta_list")]),
510524
(inputnode, anat_dilmsk, [("mask_anat", "in_file")]),
@@ -523,9 +537,6 @@ def _remove_first_mask(in_file):
523537
(epi_dilmsk, epi2anat, [
524538
(("out_file", _remove_first_mask), "moving_image_masks")]),
525539
(deob_epi, sampling_ref, [("out_file", "fixed_image")]),
526-
(epi2anat, transform_list, [("forward_transforms", "in1")]),
527-
(transform_list, prior2epi, [("out", "transforms")]),
528-
(sampling_ref, prior2epi, [("out_file", "reference_image")]),
529540
(ref_anat, anat2epi, [("output_image", "input_image")]),
530541
(epi2anat, anat2epi, [("forward_transforms", "transforms")]),
531542
(sampling_ref, anat2epi, [("out_file", "reference_image")]),
@@ -536,9 +547,7 @@ def _remove_first_mask(in_file):
536547
(mask_dtype, outputnode, [("out", "anat_mask")]),
537548
(merge_output, outputnode, [("out", "epi_ref")]),
538549
(epi_brain, outputnode, [("out_mask", "epi_mask")]),
539-
(prior2epi, outputnode, [("output_image", "sd_prior")]),
540-
])
541-
# fmt:on
550+
]) # fmt:skip
542551

543552
if debug:
544553
from niworkflows.interfaces.nibabel import RegridToZooms

sdcflows/workflows/fit/tests/test_syn.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030

3131
@pytest.mark.veryslow
3232
@pytest.mark.slow
33-
def test_syn_wf(tmpdir, datadir, workdir, outdir, sloppy_mode):
33+
@pytest.mark.parametrize("sd_prior", [True, False])
34+
def test_syn_wf(tmpdir, datadir, workdir, outdir, sloppy_mode, sd_prior):
3435
"""Build and run an SDC-SyN workflow."""
3536
derivs_path = datadir / "ds000054" / "derivatives"
3637
smriprep = derivs_path / "smriprep-0.6" / "sub-100185" / "anat"
@@ -42,6 +43,7 @@ def test_syn_wf(tmpdir, datadir, workdir, outdir, sloppy_mode):
4243
debug=sloppy_mode,
4344
auto_bold_nss=True,
4445
t1w_inversion=True,
46+
sd_prior=sd_prior,
4547
)
4648
prep_wf.inputs.inputnode.in_epis = [
4749
str(
@@ -72,7 +74,12 @@ def test_syn_wf(tmpdir, datadir, workdir, outdir, sloppy_mode):
7274
smriprep / "sub-100185_desc-brain_mask.nii.gz"
7375
)
7476

75-
syn_wf = init_syn_sdc_wf(debug=sloppy_mode, sloppy=sloppy_mode, omp_nthreads=4)
77+
syn_wf = init_syn_sdc_wf(
78+
debug=sloppy_mode,
79+
sloppy=sloppy_mode,
80+
omp_nthreads=4,
81+
sd_prior=sd_prior,
82+
)
7683

7784
# fmt: off
7885
wf.connect([

0 commit comments

Comments
 (0)