Skip to content
Draft
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
11 changes: 3 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,6 @@ RUN mkdir -p /opt/afni-latest \
-name "3dAutomask" -or \
-name "3dvolreg" \) -delete

# Convert3d 1.4.0
FROM downloader as c3d
RUN mkdir /opt/convert3d && \
curl -fsSL --retry 5 https://sourceforge.net/projects/c3d/files/c3d/Experimental/c3d-1.4.0-Linux-gcc64.tar.gz/download \
| tar -xz -C /opt/convert3d --strip-components 1

# Micromamba
FROM downloader as micromamba
WORKDIR /
Expand All @@ -100,7 +94,7 @@ RUN /opt/conda/envs/sdcflows/bin/pip install --no-cache-dir -r /tmp/requirements
#
# Main stage
#
FROM --platform=linux/amd64 ${BASE_IMAGE} as sdcflows
FROM --platform=linux/amd64 ${BASE_IMAGE} as dev

# Configure apt
ENV DEBIAN_FRONTEND="noninteractive" \
Expand Down Expand Up @@ -155,7 +149,6 @@ RUN apt-get update -qq \

# Install files from stages
COPY --from=afni /opt/afni-latest /opt/afni-latest
COPY --from=c3d /opt/convert3d/bin/c3d_affine_tool /usr/bin/c3d_affine_tool

# AFNI config
ENV PATH="/opt/afni-latest:$PATH" \
Expand Down Expand Up @@ -207,6 +200,8 @@ ENV SUBJECTS_DIR="$FREESURFER_HOME/subjects" \
ENV MKL_NUM_THREADS=1 \
OMP_NUM_THREADS=1

FROM dev as sdcflows

# Installing SDCFlows
COPY --from=src /src/dist/*.whl .
RUN pip install --no-cache-dir $( ls *.whl )[all]
Expand Down
82 changes: 56 additions & 26 deletions sdcflows/workflows/fit/syn.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,9 @@
debug=False,
name="syn_preprocessing_wf",
omp_nthreads=1,
coregister=True,
auto_bold_nss=False,
t1w_inversion=False,
t1w_inversion=None,
sd_prior=True,
):
"""
Expand All @@ -365,11 +366,15 @@
Name for this workflow
omp_nthreads : :obj:`int`
Parallelize internal tasks across the number of CPUs given by this option.
coregister: :class:`bool`
Run BOLD-to-Anat coregistration. If set to ``False``, ``epi2anat_xfm`` must be
provided.
auto_bold_nss : :obj:`bool`
Set up the reference workflow to automatically execute nonsteady states detection
of BOLD images.
t1w_inversion : :obj:`bool`
Run T1w intensity inversion so that it looks more like a T2 contrast.
(DEPRECATED. Does nothing.)
sd_prior : :obj:`bool`
Enable using a prior map to regularize the SyN cost function.

Expand Down Expand Up @@ -418,6 +423,13 @@
from ...interfaces.utils import Deoblique, DenoiseImage
from ...interfaces.brainmask import BrainExtraction, BinaryDilation

if t1w_inversion is not None:
import warnings

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

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L427

Added line #L427 was not covered by tests
warnings.warn(
"The `t1w_inversion` argument is deprecated and does nothing.",
DeprecationWarning,
)

workflow = Workflow(name=name)

inputnode = pe.Node(
Expand All @@ -429,6 +441,7 @@
"in_anat",
"mask_anat",
"std2anat_xfm",
"epi2anat_xfm",
]
),
name="inputnode",
Expand Down Expand Up @@ -476,28 +489,44 @@
DenoiseImage(copy_header=True), name="ref_anat", n_procs=omp_nthreads
)

epi2anat = pe.Node(
Registration(from_file=data.load("affine.json")),
name="epi2anat",
n_procs=omp_nthreads,
)
epi2anat.inputs.output_warped_image = debug
epi2anat.inputs.output_inverse_warped_image = debug
if debug:
epi2anat.inputs.args = "--write-interval-volumes 5"

def _remove_first_mask(in_file):
if not isinstance(in_file, list):
in_file = [in_file]

in_file.insert(0, "NULL")
return in_file

anat_dilmsk = pe.Node(BinaryDilation(), name="anat_dilmsk")
epi_dilmsk = pe.Node(BinaryDilation(), name="epi_dilmsk")

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

if coregister:
epi2anat = pe.Node(

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

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L498

Added line #L498 was not covered by tests
Registration(from_file=data.load("affine.json")),
name="epi2anat",
n_procs=omp_nthreads,
)
epi2anat.inputs.output_warped_image = debug
epi2anat.inputs.output_inverse_warped_image = debug

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

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L503-L504

Added lines #L503 - L504 were not covered by tests
if debug:
epi2anat.inputs.args = "--write-interval-volumes 5"

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

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L506

Added line #L506 was not covered by tests

def _remove_first_mask(in_file):

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

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L508

Added line #L508 was not covered by tests
if not isinstance(in_file, list):
in_file = [in_file]

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

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L510

Added line #L510 was not covered by tests

in_file.insert(0, "NULL")
return in_file

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

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L512-L513

Added lines #L512 - L513 were not covered by tests

workflow.connect([

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

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L515

Added line #L515 was not covered by tests
(ref_anat, epi2anat, [("output_image", "fixed_image")]),
(anat_dilmsk, epi2anat, [("out_file", "fixed_image_masks")]),
(deob_epi, epi2anat, [("out_file", "moving_image")]),
(epi_dilmsk, epi2anat, [
(("out_file", _remove_first_mask), "moving_image_masks")]),
(epi2anat, anat2epi, [("forward_transforms", "transforms")]),
(epi2anat, mask2epi, [("forward_transforms", "transforms")]),
]) # fmt:skip
else:
workflow.connect([

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

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L525

Added line #L525 was not covered by tests
(inputnode, anat2epi, [("epi2anat_xfm", "transforms")]),
(inputnode, mask2epi, [("epi2anat_xfm", "transforms")]),
])

if sd_prior:
from niworkflows.interfaces.nibabel import Binarize

Expand Down Expand Up @@ -527,13 +556,21 @@

workflow.connect([
(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, prior_msk, [("output_image", "in_file")]),
(prior_msk, outputnode, [("out_mask", "sd_prior")]),
]) # fmt:skip

if coregister:
workflow.connect([

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

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L566

Added line #L566 was not covered by tests
(epi2anat, transform_list, [("forward_transforms", "in1")]),
]) # fmt:skip
else:
workflow.connect([

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

View check run for this annotation

Codecov / codecov/patch

sdcflows/workflows/fit/syn.py#L570

Added line #L570 was not covered by tests
(inputnode, transform_list, [("epi2anat_xfm", "in1")]),
])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
])
]) # fmt:skip


else:
# no prior to be used -> set anatomical mask as prior
workflow.connect(mask_dtype, "out", outputnode, "sd_prior")
Expand All @@ -551,16 +588,9 @@
(clip_anat, ref_anat, [("out_file", "input_image")]),
(deob_epi, epi_brain, [("out_file", "in_file")]),
(epi_brain, epi_dilmsk, [("out_mask", "in_file")]),
(ref_anat, epi2anat, [("output_image", "fixed_image")]),
(anat_dilmsk, epi2anat, [("out_file", "fixed_image_masks")]),
(deob_epi, epi2anat, [("out_file", "moving_image")]),
(epi_dilmsk, epi2anat, [
(("out_file", _remove_first_mask), "moving_image_masks")]),
(deob_epi, sampling_ref, [("out_file", "fixed_image")]),
(ref_anat, anat2epi, [("output_image", "input_image")]),
(epi2anat, anat2epi, [("forward_transforms", "transforms")]),
(sampling_ref, anat2epi, [("out_file", "reference_image")]),
(epi2anat, mask2epi, [("forward_transforms", "transforms")]),
(sampling_ref, mask2epi, [("out_file", "reference_image")]),
(mask2epi, mask_dtype, [("output_image", "in_file")]),
(anat2epi, outputnode, [("output_image", "anat_ref")]),
Expand Down
21 changes: 17 additions & 4 deletions sdcflows/workflows/fit/tests/test_syn.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import json

import acres
import numpy as np
import nibabel as nb
import pytest
Expand All @@ -42,7 +43,14 @@

@pytest.mark.veryslow
@pytest.mark.slow
def test_syn_wf(tmpdir, datadir, workdir, outdir, sloppy_mode):
@pytest.mark.parametrize(
("n_bold", "coregister", "sd_prior"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
("n_bold", "coregister", "sd_prior"),
("n_bold", "coregister"),

[
(1, True),
(2, True),
]
)
def test_syn_wf(tmpdir, datadir, workdir, outdir, sloppy_mode, n_bold, coregister):
"""Build and run an SDC-SyN workflow."""
derivs_path = datadir / "ds000054" / "derivatives"
smriprep = derivs_path / "smriprep-0.6" / "sub-100185" / "anat"
Expand All @@ -53,7 +61,7 @@ def test_syn_wf(tmpdir, datadir, workdir, outdir, sloppy_mode):
omp_nthreads=4,
debug=sloppy_mode,
auto_bold_nss=True,
t1w_inversion=True,
coregister=coregister,
)
prep_wf.inputs.inputnode.in_epis = [
str(
Expand All @@ -70,10 +78,10 @@ def test_syn_wf(tmpdir, datadir, workdir, outdir, sloppy_mode):
/ "func"
/ "sub-100185_task-machinegame_run-02_bold.nii.gz"
),
]
][:n_bold]
prep_wf.inputs.inputnode.in_meta = [
json.loads((datadir / "ds000054" / "task-machinegame_bold.json").read_text()),
] * 2
] * n_bold
prep_wf.inputs.inputnode.std2anat_xfm = str(
smriprep / "sub-100185_from-MNI152NLin2009cAsym_to-T1w_mode-image_xfm.h5"
)
Expand All @@ -83,6 +91,11 @@ def test_syn_wf(tmpdir, datadir, workdir, outdir, sloppy_mode):
prep_wf.inputs.inputnode.mask_anat = str(
smriprep / "sub-100185_desc-brain_mask.nii.gz"
)
if not coregister:
test_data = acres.Loader('sdcflows.tests')
prep_wf.inputs.inputnode.epi_ref = str(
test_data('data/anat2epi_xfm.txt')
)

syn_wf = init_syn_sdc_wf(
debug=sloppy_mode,
Expand Down
Loading