Skip to content

Commit

Permalink
Add masks to confounds.
Browse files Browse the repository at this point in the history
  • Loading branch information
tsalo committed Nov 13, 2023
1 parent 6c30ce2 commit 8aad465
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 13 deletions.
3 changes: 2 additions & 1 deletion aslprep/tests/run_local_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ def run_tests(test_regex, test_mark):
mounted_code = "/usr/local/miniconda/lib/python3.9/site-packages/aslprep"
run_str = "docker run --rm -ti "
run_str += f"-v {local_patch}:/usr/local/miniconda/lib/python3.9/site-packages/aslprep "
run_str += "-v /Users/taylor/Documents/tsalo/nipype/nipype:/usr/local/miniconda/lib/python3.9/site-packages/nipype "
run_str += "--entrypoint pytest "
run_str += "pennlinc/aslprep:unstable "
run_str += "tsalo/aslprep:unstable "
run_str += (
f"{mounted_code}/aslprep "
f"--data_dir={mounted_code}/aslprep/tests/test_data "
Expand Down
6 changes: 6 additions & 0 deletions aslprep/workflows/asl/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,8 @@ def init_asl_preproc_wf(asl_file, has_fieldmap=False):
]),
(asl_confounds_wf, carpetplot_wf, [
("outputnode.confounds_file", "inputnode.confounds_file"),
("outputnode.crown_mask", "inputnode.crown_mask"),
(("outputnode.acompcor_masks", _last), "inputnode.acompcor_mask"),
]),
])
# fmt:on
Expand Down Expand Up @@ -1348,3 +1350,7 @@ def get_img_orientation(imgf):
"""Return the image orientation as a string."""
img = nb.load(imgf)
return "".join(nb.aff2axcodes(img.affine))


def _last(inlist):
return inlist[-1]
129 changes: 117 additions & 12 deletions aslprep/workflows/asl/confounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@


def init_asl_confounds_wf(
mem_gb,
name="asl_confounds_wf",
mem_gb: float,
freesurfer: bool = False,
name: str = "asl_confounds_wf",
):
"""Build a workflow to generate and write out confounding signals.
Expand Down Expand Up @@ -47,12 +48,11 @@ def init_asl_confounds_wf(
Size of asl file in GB - please note that this size
should be calculated after resamplings that may extend
the FoV
metadata : :obj:`dict`
BIDS metadata for asl file
freesurfer : :obj:`bool`
True if FreeSurfer derivatives were used.
name : :obj:`str`
Name of workflow (default: ``asl_confounds_wf``)
Inputs
------
asl
Expand Down Expand Up @@ -80,7 +80,14 @@ def init_asl_confounds_wf(
TSV of all aggregated confounds
confounds_metadata
Confounds metadata dictionary.
crown_mask
Mask of brain edge voxels
acompcor_masks
"""
from fmriprep.interfaces.confounds import aCompCorMasks
from niworkflows.interfaces.morphology import BinaryDilation, BinarySubtraction
from niworkflows.interfaces.nibabel import ApplyMask, Binarize

workflow = Workflow(name=name)
workflow.__desc__ = """\
Several confounding timeseries were calculated, including both framewise displacement
Expand Down Expand Up @@ -114,9 +121,18 @@ def init_asl_confounds_wf(
name="dvars",
mem_gb=mem_gb,
)
# fmt:off
workflow.connect([
(inputnode, dvars, [
("asl", "in_file"),
("asl_mask", "in_mask"),
]),
])
# fmt:on

# Frame displacement
fdisp = pe.Node(nac.FramewiseDisplacement(parameter_source="SPM"), name="fdisp", mem_gb=mem_gb)
workflow.connect([(inputnode, fdisp, [("movpar_file", "in_file")])])

# Global and segment regressors
# signals_class_labels = ["csf", "white_matter", "global_signal"]
Expand Down Expand Up @@ -148,15 +164,8 @@ def init_asl_confounds_wf(
)
concat = pe.Node(GatherConfounds(), name="concat", mem_gb=0.01, run_without_submitting=True)

# Expand model to include derivatives and quadratics
# fmt:off
workflow.connect([
# Connect inputnode to each non-anatomical confound node
(inputnode, dvars, [
("asl", "in_file"),
("asl_mask", "in_mask"),
]),
(inputnode, fdisp, [("movpar_file", "in_file")]),
# Collate computed confounds together
(inputnode, add_motion_headers, [("movpar_file", "in_file")]),
(inputnode, add_rmsd_header, [("rmsd_file", "in_file")]),
Expand All @@ -172,6 +181,79 @@ def init_asl_confounds_wf(
])
# fmt:on

# Project T1w mask into BOLD space and merge with BOLD brainmask
t1w_mask_tfm = pe.Node(
ApplyTransforms(interpolation="MultiLabel"),
name="t1w_mask_tfm",
)
union_mask = pe.Node(niu.Function(function=_binary_union), name="union_mask")

# Create the crown mask
dilated_mask = pe.Node(BinaryDilation(), name="dilated_mask")
subtract_mask = pe.Node(BinarySubtraction(), name="subtract_mask")

# fmt:off
workflow.connect([
# Brain mask
(inputnode, t1w_mask_tfm, [
("t1w_mask", "input_image"),
("bold_mask", "reference_image"),
("t1_bold_xform", "transforms"),
]),
(inputnode, union_mask, [("bold_mask", "mask1")]),
(t1w_mask_tfm, union_mask, [("output_image", "mask2")]),
(union_mask, dilated_mask, [("out", "in_mask")]),
(union_mask, subtract_mask, [("out", "in_subtract")]),
(dilated_mask, subtract_mask, [("out_mask", "in_base")]),
(subtract_mask, outputnode, [("out_mask", "crown_mask")]),
])
# fmt:on

# Generate aCompCor probseg maps
acc_masks = pe.Node(aCompCorMasks(is_aseg=freesurfer), name="acc_masks")

# fmt:off
workflow.connect([
(inputnode, acc_masks, [
("t1w_tpms", "in_vfs"),
(("bold", _get_zooms), "bold_zooms"),
]),
])
# fmt:on

# Resample probseg maps in BOLD space via T1w-to-BOLD transform
acc_msk_tfm = pe.MapNode(
ApplyTransforms(interpolation="Gaussian"),
iterfield=["input_image"],
name="acc_msk_tfm",
mem_gb=0.1,
)
# fmt:off
workflow.connect([
(inputnode, acc_msk_tfm, [
("t1_bold_xform", "transforms"),
("bold_mask", "reference_image"),
]),
(acc_masks, acc_msk_tfm, [("out_masks", "input_image")]),
])
# fmt:on

acc_msk_brain = pe.MapNode(ApplyMask(), name="acc_msk_brain", iterfield=["in_file"])
# fmt:off
workflow.connect([
(inputnode, acc_msk_brain, [("bold_mask", "in_mask")]),
(acc_msk_tfm, acc_msk_brain, [("output_image", "in_file")]),
])
# fmt:on

acc_msk_bin = pe.MapNode(Binarize(thresh_low=0.99), name="acc_msk_bin", iterfield=["in_file"])
# fmt:off
workflow.connect([
(acc_msk_brain, acc_msk_bin, [("out_file", "in_file")]),
(acc_msk_bin, outputnode, [("out_file", "acompcor_masks")]),
])
# fmt:on

return workflow


Expand Down Expand Up @@ -281,3 +363,26 @@ def init_carpetplot_wf(mem_gb, metadata, name="carpetplot_wf"):
# fmt:on

return workflow


def _binary_union(mask1, mask2):
"""Generate the union of two masks."""
from pathlib import Path

import nibabel as nb
import numpy as np

img = nb.load(mask1)
mskarr1 = np.asanyarray(img.dataobj, dtype=int) > 0
mskarr2 = np.asanyarray(nb.load(mask2).dataobj, dtype=int) > 0
out = img.__class__(mskarr1 | mskarr2, img.affine, img.header)
out.set_data_dtype("uint8")
out_name = Path("mask_union.nii.gz").absolute()
out.to_filename(out_name)
return str(out_name)


def _get_zooms(in_file):
import nibabel as nb

return tuple(nb.load(in_file).header.get_zooms()[:3])

0 comments on commit 8aad465

Please sign in to comment.