Skip to content

Commit

Permalink
Load T1w-to-standard transform to same space as volumetric BOLD scan (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
tsalo authored Sep 25, 2023
1 parent da6d866 commit 778c96f
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 240 deletions.
4 changes: 2 additions & 2 deletions xcp_d/tests/data/test_ds001419_cifti_outputs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ xcp_d/space-fsLR_atlas-HCP_den-91k_dseg.dlabel.nii
xcp_d/sub-01
xcp_d/sub-01.html
xcp_d/sub-01/anat
xcp_d/sub-01/anat/sub-01_space-MNI152NLin6Asym_desc-preproc_T1w.nii.gz
xcp_d/sub-01/anat/sub-01_space-MNI152NLin6Asym_dseg.nii.gz
xcp_d/sub-01/anat/sub-01_space-MNI152NLin2009cAsym_desc-preproc_T1w.nii.gz
xcp_d/sub-01/anat/sub-01_space-MNI152NLin2009cAsym_dseg.nii.gz
xcp_d/sub-01/func
xcp_d/sub-01/func/sub-01_task-imagery_desc-dcan_qc.hdf5
xcp_d/sub-01/func/sub-01_task-imagery_desc-filtered_motion.tsv
Expand Down
11 changes: 8 additions & 3 deletions xcp_d/tests/test_utils_bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ def test_collect_participants(datasets):
This also covers BIDSError and BIDSWarning.
"""
bids_dir = datasets["ds001419"]

# Pass in non-BIDS folder to get BIDSError.
with pytest.raises(xbids.BIDSError, match="Could not find participants"):
xbids.collect_participants(os.path.dirname(bids_dir), participant_label="fail")

with pytest.raises(xbids.BIDSError, match="Could not find participants"):
xbids.collect_participants(bids_dir, participant_label="fail")

Expand All @@ -30,7 +35,7 @@ def test_collect_participants(datasets):
assert found_labels == ["01"]


def test_collect_data_pnc(datasets):
def test_collect_data_ds001419(datasets):
"""Test the collect_data function."""
bids_dir = datasets["ds001419"]

Expand Down Expand Up @@ -69,8 +74,8 @@ def test_collect_data_pnc(datasets):
assert "space-fsLR" in subj_data["bold"][0]
assert "space-" not in subj_data["t1w"]
assert os.path.basename(subj_data["t1w"]) == "sub-01_desc-preproc_T1w.nii.gz"
assert "to-MNI152NLin6Asym" in subj_data["anat_to_template_xfm"]
assert "from-MNI152NLin6Asym" in subj_data["template_to_anat_xfm"]
assert "to-MNI152NLin2009cAsym" in subj_data["anat_to_template_xfm"]
assert "from-MNI152NLin2009cAsym" in subj_data["template_to_anat_xfm"]


def test_collect_data_nibabies(datasets):
Expand Down
18 changes: 9 additions & 9 deletions xcp_d/tests/test_utils_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,18 +155,18 @@ def test_denoise_with_nilearn(ds001419_data, tmp_path_factory):

def test_list_to_str():
"""Test the list_to_str function."""
lst = ["a"]
string = utils.list_to_str(lst)
string = utils.list_to_str(["a"])
assert string == "a"

lst = ["a", "b"]
string = utils.list_to_str(lst)
string = utils.list_to_str(["a", "b"])
assert string == "a and b"

lst = ["a", "b", "c"]
string = utils.list_to_str(lst)
string = utils.list_to_str(["a", "b", "c"])
assert string == "a, b, and c"

with pytest.raises(ValueError, match="Zero-length list provided."):
utils.list_to_str([])


def test_get_bold2std_and_t1w_xfms(ds001419_data):
"""Test get_bold2std_and_t1w_xfms."""
Expand Down Expand Up @@ -331,7 +331,7 @@ def test_get_std2bold_xfms(ds001419_data):
"""
bold_file_nlin2009c = ds001419_data["nifti_file"]

# MNI152NLin2009cAsym --> MNI152NLin6Asym
# MNI152NLin6Asym --> MNI152NLin2009cAsym
xforms_to_mni = utils.get_std2bold_xfms(bold_file_nlin2009c)
assert len(xforms_to_mni) == 1

Expand All @@ -343,15 +343,15 @@ def test_get_std2bold_xfms(ds001419_data):
xforms_to_mni = utils.get_std2bold_xfms(bold_file_nlin6asym)
assert len(xforms_to_mni) == 1

# MNIInfant --> MNI152NLin6Asym
# MNI152NLin6Asym --> MNIInfant
bold_file_infant = bold_file_nlin2009c.replace(
"space-MNI152NLin2009cAsym_",
"space-MNIInfant_cohort-1_",
)
xforms_to_mni = utils.get_std2bold_xfms(bold_file_infant)
assert len(xforms_to_mni) == 2

# tofail --> MNI152NLin6Asym
# MNI152NLin6Asym --> tofail
bold_file_tofail = bold_file_nlin2009c.replace("space-MNI152NLin2009cAsym_", "space-tofail_")
with pytest.raises(ValueError, match="Space 'tofail'"):
utils.get_std2bold_xfms(bold_file_tofail)
Expand Down
56 changes: 39 additions & 17 deletions xcp_d/utils/bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"nibabies": {
"cifti": ["fsLR"],
"nifti": [
"MNIInfant",
"MNI152NLin6Asym",
"MNIInfant",
"MNI152NLin2009cAsym",
],
},
Expand Down Expand Up @@ -279,18 +279,34 @@ def collect_data(
if cifti:
# Select the appropriate volumetric space for the CIFTI template.
# This space will be used in the executive summary and T1w/T2w workflows.
temp_query = queries["anat_to_template_xfm"].copy()
volumetric_space = ASSOCIATED_TEMPLATES[space]
allowed_spaces = INPUT_TYPE_ALLOWED_SPACES.get(
input_type,
DEFAULT_ALLOWED_SPACES,
)["nifti"]

temp_bold_query = queries["bold"].copy()
temp_bold_query.pop("den", None)
temp_bold_query["extension"] = ".nii.gz"

temp_xfm_query = queries["anat_to_template_xfm"].copy()

temp_query["to"] = volumetric_space
transform_files = layout.get(**temp_query)
if not transform_files:
for volspace in allowed_spaces:
temp_bold_query["space"] = volspace
bold_data = layout.get(**temp_bold_query)
temp_xfm_query["to"] = volspace
transform_files = layout.get(**temp_xfm_query)

if bold_data and transform_files:
# will leave the best available space in the query
break

if not bold_data or not transform_files:
raise FileNotFoundError(
f"No nifti transforms found to allowed space ({volumetric_space})"
f"No BOLD NIfTI or transforms found to allowed space ({volspace})"
)

queries["anat_to_template_xfm"]["to"] = volumetric_space
queries["template_to_anat_xfm"]["from"] = volumetric_space
queries["anat_to_template_xfm"]["to"] = volspace
queries["template_to_anat_xfm"]["from"] = volspace
else:
# use the BOLD file's space if the BOLD file is a nifti.
queries["anat_to_template_xfm"]["to"] = queries["bold"]["space"]
Expand Down Expand Up @@ -542,19 +558,20 @@ def collect_morphometry_data(layout, participant_label):


@fill_doc
def collect_run_data(layout, input_type, bold_file, cifti, primary_anat):
def collect_run_data(layout, bold_file, cifti, primary_anat, target_space):
"""Collect data associated with a given BOLD file.
Parameters
----------
%(layout)s
%(input_type)s
bold_file : :obj:`str`
Path to the BOLD file.
%(cifti)s
Whether to collect files associated with a CIFTI image (True) or a NIFTI (False).
primary_anat : {"T1w", "T2w"}
The anatomical modality to use for the anat-to-native transform.
target_space
Used to find NIfTIs in the appropriate space if ``cifti`` is ``True``.
Returns
-------
Expand Down Expand Up @@ -598,24 +615,29 @@ def collect_run_data(layout, input_type, bold_file, cifti, primary_anat):
suffix="xfm",
)
else:
allowed_nifti_spaces = INPUT_TYPE_ALLOWED_SPACES.get(
input_type,
DEFAULT_ALLOWED_SPACES,
)["nifti"]
# Split cohort out of the space for MNIInfant templates.
cohort = None
if "+" in target_space:
target_space, cohort = target_space.split("+")

run_data["boldref"] = layout.get_nearest(
bids_file.path,
strict=False,
space=allowed_nifti_spaces,
space=target_space,
cohort=cohort,
suffix="boldref",
extension=[".nii", ".nii.gz"],
invalid_filters="allow",
)
run_data["nifti_file"] = layout.get_nearest(
bids_file.path,
strict=False,
space=allowed_nifti_spaces,
space=target_space,
cohort=cohort,
desc="preproc",
suffix="bold",
extension=[".nii", ".nii.gz"],
invalid_filters="allow",
)

LOGGER.log(
Expand Down
8 changes: 8 additions & 0 deletions xcp_d/workflows/anatomical.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
)
from xcp_d.utils.bids import get_freesurfer_dir, get_freesurfer_sphere
from xcp_d.utils.doc import fill_doc
from xcp_d.utils.utils import list_to_str
from xcp_d.workflows.execsummary import (
init_brainsprite_figures_wf,
init_execsummary_anatomical_plots_wf,
Expand Down Expand Up @@ -204,6 +205,13 @@ def init_postprocess_anat_wf(
workflow.connect([(inputnode, ds_t2w_std, [("t2w", "in_file")])])

else:
out = (
["T1w"] if t1w_available else [] + ["T2w"] if t2w_available else [] + ["segmentation"]
)
workflow.__desc__ = f"""\
Native-space {list_to_str(out)} images were transformed to {target_space} space at 1 mm3
resolution.
"""
warp_anat_dseg_to_template = pe.Node(
ApplyTransforms(
num_threads=2,
Expand Down
6 changes: 3 additions & 3 deletions xcp_d/workflows/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,11 +669,11 @@ def init_subject_wf(

for j_run, bold_file in enumerate(task_files):
run_data = collect_run_data(
layout,
input_type,
bold_file,
layout=layout,
bold_file=bold_file,
cifti=cifti,
primary_anat=primary_anat,
target_space=target_space,
)

post_scrubbing_duration = flag_bad_run(
Expand Down
Loading

0 comments on commit 778c96f

Please sign in to comment.