Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sdcflows/workflows/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def init_fmap_preproc_wf(
sloppy=False,
debug=False,
name="fmap_preproc_wf",
**kwargs,
):
"""
Create and combine estimator workflows.
Expand Down Expand Up @@ -110,6 +111,7 @@ def init_fmap_preproc_wf(
omp_nthreads=omp_nthreads,
debug=debug,
sloppy=sloppy,
**kwargs,
)
source_files = [
str(f.path) for f in estimator.sources if f.suffix not in ("T1w", "T2w")
Expand Down
9 changes: 8 additions & 1 deletion sdcflows/workflows/fit/fieldmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@
INPUT_FIELDS = ("magnitude", "fieldmap")


def init_fmap_wf(omp_nthreads=1, sloppy=False, debug=False, mode="phasediff", name="fmap_wf"):
def init_fmap_wf(
omp_nthreads=1,
sloppy=False,
debug=False,
mode="phasediff",
name="fmap_wf",
**kwargs,
):
"""
Estimate the fieldmap based on a field-mapping MRI acquisition.
Expand Down
1 change: 1 addition & 0 deletions sdcflows/workflows/fit/pepolar.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def init_topup_wf(
sloppy=False,
debug=False,
name="pepolar_estimate_wf",
**kwargs,
):
"""
Create the PEPOLAR field estimation workflow based on FSL's ``topup``.
Expand Down
87 changes: 48 additions & 39 deletions sdcflows/workflows/fit/syn.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
"""
Estimating the susceptibility distortions without fieldmaps.
"""

from nipype.pipeline import engine as pe
from nipype.interfaces import utility as niu
from nipype.interfaces.base import Undefined
from niworkflows.engine.workflows import LiterateWorkflow as Workflow

from ... import data
Expand All @@ -46,6 +48,8 @@
debug=False,
name="syn_sdc_wf",
omp_nthreads=1,
sd_prior=True,
**kwargs,
):
"""
Build the *fieldmap-less* susceptibility-distortion estimation workflow.
Expand Down Expand Up @@ -117,7 +121,6 @@
FixHeaderRegistration as Registration,
)
from niworkflows.interfaces.nibabel import (
Binarize,
IntensityClip,
RegridToZooms,
)
Expand Down Expand Up @@ -171,7 +174,6 @@
name="warp_dir",
)
warp_dir.inputs.nlevels = 2
atlas_msk = pe.Node(Binarize(thresh_low=atlas_threshold), name="atlas_msk")
anat_dilmsk = pe.Node(BinaryDilation(), name="anat_dilmsk")
amask2epi = pe.Node(
ApplyTransforms(interpolation="MultiLabel", transforms="identity"),
Expand Down Expand Up @@ -208,7 +210,7 @@
)

fixed_masks = pe.Node(
niu.Merge(3),
niu.Merge(2),
name="fixed_masks",
mem_gb=DEFAULT_MEMORY_MIN_GB,
run_without_submitting=True,
Expand All @@ -220,9 +222,7 @@

# SyN Registration Core
syn = pe.Node(
Registration(
from_file=data.load(f"sd_syn{'_sloppy' * sloppy}.json")
),
Registration(from_file=data.load(f"sd_syn{'_sloppy' * sloppy}.json")),
name="syn",
n_procs=omp_nthreads,
)
Expand All @@ -233,9 +233,7 @@
syn.inputs.args = "--write-interval-volumes 2"

# Extract the corresponding fieldmap in Hz
extract_field = pe.Node(
DisplacementsField2Fieldmap(), name="extract_field"
)
extract_field = pe.Node(DisplacementsField2Fieldmap(), name="extract_field")

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

Expand Down Expand Up @@ -267,7 +265,6 @@
workflow.connect([
(inputnode, readout_time, [(("epi_ref", _pop), "in_file"),
(("epi_ref", _pull), "metadata")]),
(inputnode, atlas_msk, [("sd_prior", "in_file")]),
(inputnode, clip_epi, [(("epi_ref", _pop), "in_file")]),
(inputnode, unwarp, [(("epi_ref", _pop), "in_data")]),
(inputnode, amask2epi, [("epi_mask", "reference_image")]),
Expand All @@ -293,7 +290,6 @@
(lap_epi, lap_epi_norm, [("output_image", "in_file")]),
(lap_epi_norm, epi_merge, [("out", "in2")]),
(find_zooms, zooms_epi, [("out", "zooms")]),
(atlas_msk, fixed_masks, [("out_mask", "in3")]),
(anat_dilmsk, amask2epi, [("out_file", "input_image")]),
(amask2epi, epi_umask, [("output_image", "in2")]),
(readout_time, warp_dir, [("pe_direction", "pe_dir")]),
Expand Down Expand Up @@ -331,6 +327,7 @@
omp_nthreads=1,
auto_bold_nss=False,
t1w_inversion=False,
sd_prior=True,
):
"""
Prepare EPI references and co-registration to anatomical for SyN.
Expand All @@ -356,6 +353,8 @@
of BOLD images.
t1w_inversion : :obj:`bool`
Run T1w intensity inversion so that it looks more like a T2 contrast.
sd_prior : :obj:`bool`
Enable using a prior map to regularize the SyN cost function.

Inputs
------
Expand Down Expand Up @@ -426,26 +425,6 @@

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

# Mapping & preparing prior knowledge
# Concatenate transform files:
# 1) MNI -> anat; 2) ATLAS -> MNI
transform_list = pe.Node(
niu.Merge(3),
name="transform_list",
mem_gb=DEFAULT_MEMORY_MIN_GB,
run_without_submitting=True,
)
transform_list.inputs.in3 = data.load("fmap_atlas_2_MNI152NLin2009cAsym_affine.mat")
prior2epi = pe.Node(
ApplyTransforms(
invert_transform_flags=[True, False, False],
input_image=str(data.load("fmap_atlas.nii.gz")),
),
name="prior2epi",
n_procs=omp_nthreads,
mem_gb=0.3,
)

anat2epi = pe.Node(
ApplyTransforms(invert_transform_flags=[True]),
name="anat2epi",
Expand Down Expand Up @@ -502,9 +481,44 @@

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

# fmt:off
if sd_prior:
# Mapping & preparing prior knowledge
# Concatenate transform files:
# 1) MNI -> anat; 2) ATLAS -> MNI
transform_list = pe.Node(

Check warning on line 488 in sdcflows/workflows/fit/syn.py

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L488

Added line #L488 was not covered by tests
niu.Merge(3),
name="transform_list",
mem_gb=DEFAULT_MEMORY_MIN_GB,
run_without_submitting=True,
)
transform_list.inputs.in3 = data.load(

Check warning on line 494 in sdcflows/workflows/fit/syn.py

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L494

Added line #L494 was not covered by tests
"fmap_atlas_2_MNI152NLin2009cAsym_affine.mat"
)
prior2epi = pe.Node(

Check warning on line 497 in sdcflows/workflows/fit/syn.py

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L497

Added line #L497 was not covered by tests
ApplyTransforms(
invert_transform_flags=[True, False, False],
input_image=str(data.load("fmap_atlas.nii.gz")),
),
name="prior2epi",
n_procs=omp_nthreads,
mem_gb=0.3,
)

workflow.connect([

Check warning on line 507 in sdcflows/workflows/fit/syn.py

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L507

Added line #L507 was not covered by tests
(inputnode, transform_list, [("std2anat_xfm", "in2")]),
(epi2anat, transform_list, [("forward_transforms", "in1")]),
(transform_list, prior2epi, [("out", "transforms")]),
(sampling_ref, prior2epi, [("out_file", "reference_image")]),
(prior2epi, outputnode, [("output_image", "sd_prior")]),
]) # fmt:skip

else:
# no prior to be used
# MG: Future goal is to allow using alternative mappings
# i.e. in the case of infants, where priors change depending on development
outputnode.inputs.sd_prior = Undefined

Check warning on line 519 in sdcflows/workflows/fit/syn.py

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L519

Added line #L519 was not covered by tests

workflow.connect([
(inputnode, transform_list, [("std2anat_xfm", "in2")]),
(inputnode, epi_reference_wf, [("in_epis", "inputnode.in_files")]),
(inputnode, merge_output, [("in_meta", "meta_list")]),
(inputnode, anat_dilmsk, [("mask_anat", "in_file")]),
Expand All @@ -523,9 +537,6 @@
(epi_dilmsk, epi2anat, [
(("out_file", _remove_first_mask), "moving_image_masks")]),
(deob_epi, sampling_ref, [("out_file", "fixed_image")]),
(epi2anat, transform_list, [("forward_transforms", "in1")]),
(transform_list, prior2epi, [("out", "transforms")]),
(sampling_ref, prior2epi, [("out_file", "reference_image")]),
(ref_anat, anat2epi, [("output_image", "input_image")]),
(epi2anat, anat2epi, [("forward_transforms", "transforms")]),
(sampling_ref, anat2epi, [("out_file", "reference_image")]),
Expand All @@ -536,9 +547,7 @@
(mask_dtype, outputnode, [("out", "anat_mask")]),
(merge_output, outputnode, [("out", "epi_ref")]),
(epi_brain, outputnode, [("out_mask", "epi_mask")]),
(prior2epi, outputnode, [("output_image", "sd_prior")]),
])
# fmt:on
]) # fmt:skip

if debug:
from niworkflows.interfaces.nibabel import RegridToZooms
Expand Down
11 changes: 9 additions & 2 deletions sdcflows/workflows/fit/tests/test_syn.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@

@pytest.mark.veryslow
@pytest.mark.slow
def test_syn_wf(tmpdir, datadir, workdir, outdir, sloppy_mode):
@pytest.mark.parametrize("sd_prior", [True, False])
def test_syn_wf(tmpdir, datadir, workdir, outdir, sloppy_mode, sd_prior):
"""Build and run an SDC-SyN workflow."""
derivs_path = datadir / "ds000054" / "derivatives"
smriprep = derivs_path / "smriprep-0.6" / "sub-100185" / "anat"
Expand All @@ -42,6 +43,7 @@ def test_syn_wf(tmpdir, datadir, workdir, outdir, sloppy_mode):
debug=sloppy_mode,
auto_bold_nss=True,
t1w_inversion=True,
sd_prior=sd_prior,
)
prep_wf.inputs.inputnode.in_epis = [
str(
Expand Down Expand Up @@ -72,7 +74,12 @@ def test_syn_wf(tmpdir, datadir, workdir, outdir, sloppy_mode):
smriprep / "sub-100185_desc-brain_mask.nii.gz"
)

syn_wf = init_syn_sdc_wf(debug=sloppy_mode, sloppy=sloppy_mode, omp_nthreads=4)
syn_wf = init_syn_sdc_wf(
debug=sloppy_mode,
sloppy=sloppy_mode,
omp_nthreads=4,
sd_prior=sd_prior,
)

# fmt: off
wf.connect([
Expand Down
Loading