Skip to content

Commit

Permalink
Allow bids-filter-file terms for mesh and morphometry files (PennLINC…
Browse files Browse the repository at this point in the history
  • Loading branch information
tsalo authored Jul 24, 2024
1 parent 0555ad7 commit ff2ee5a
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 143 deletions.
6 changes: 5 additions & 1 deletion docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ This argument must point to a JSON file, containing filters that will be fed int
The keys in this JSON file are unique to XCP-D.
They are our internal terms for different inputs that will be selected from the preprocessed
dataset.
The full list of keys can be found in the file ``xcp_d/data/io_spec.yaml``.

``"bold"`` determines which preprocessed BOLD files will be chosen.
You can set a number of entities here, including "session", "task", "space", "resolution", and
Expand All @@ -166,7 +167,7 @@ We recommend NOT setting the datatype, suffix, or file extension in the filter f
``"t1w"`` selects a native T1w-space, preprocessed T1w file.

``"t2w"`` selects a native T1w-space, preprocessed T2w file.
If not T1w file is available, this file will be in T2w space.
If a T1w file is not available, this file will be in T2w space.

``"anat_dseg"`` selects a segmentation file in the same space as the BOLD data.
This file is not used for anything.
Expand All @@ -182,6 +183,9 @@ The standard space that will be used depends on the ``"bold"`` files that are se
``"template_to_anat_xfm"`` selects a transform from standard space to T1w/T2w space.
Again, the standard space is determined based on other files.

There are additional keys that control how mesh and morphometry files are selected.
Please refer to ``io_spec.yaml`` for more information on them.


Example bids-filter-file
========================
Expand Down
152 changes: 152 additions & 0 deletions xcp_d/data/io_spec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
queries:
base:
# brain mask in same standard space as BOLD data
anat_brainmask:
datatype: anat
desc: brain
extension: .nii.gz
suffix: mask
# native anatomical-space dseg file
anat_dseg:
datatype: anat
desc: null
extension: .nii.gz
space: null
suffix: dseg
# transform from native anatomical (T1w or T2w) space to same standard space as BOLD
# "to" entity will be set later
anat_to_template_xfm:
datatype: anat
from:
- T1w
- T2w
suffix: xfm
# all preprocessed BOLD files in the right space/resolution/density
bold:
datatype: func
desc:
- preproc
- null
suffix: bold
# native T1w-space, preprocessed T1w file
t1w:
datatype: anat
desc: preproc
extension: .nii.gz
space: null
suffix: T1w
# native T2w-space or T1w-space, preprocessed T2w file
t2w:
datatype: anat
desc: preproc
extension: .nii.gz
space:
- null
- T1w
suffix: T2w
# transform from standard space to anatomical (T1w or T2w) space
# "from" entity will be set later
template_to_anat_xfm:
datatype: anat
suffix: xfm
to:
- T1w
- T2w
mesh:
# Pial surface mesh in fsnative space (fsLR-space mesh will be searched for separately)
lh_pial_surf:
datatype: anat
desc: null
extension: .surf.gii
hemi: L
suffix: pial
# Subject's surface sphere to be used to warp fsnative meshes to fsLR space
lh_subject_sphere:
datatype: anat
desc: reg
extension: .surf.gii
hemi: L
space: null
suffix: sphere
# White matter surface mesh in fsnative space (fsLR-space mesh will be searched for separately)
lh_wm_surf:
datatype: anat
desc: null
extension: .surf.gii
hemi: L
suffix:
- smoothwm
- white
# Pial surface mesh in fsnative space (fsLR-space mesh will be searched for separately)
rh_pial_surf:
datatype: anat
desc: null
extension: .surf.gii
hemi: R
suffix: pial
# Subject's surface sphere to be used to warp fsnative meshes to fsLR space
rh_subject_sphere:
datatype: anat
desc: reg
extension: .surf.gii
hemi: R
space: null
suffix: sphere
# White matter surface mesh in fsnative space (fsLR-space mesh will be searched for separately)
rh_wm_surf:
datatype: anat
desc: null
extension: .surf.gii
hemi: R
suffix:
- smoothwm
- white
morphometry:
# Cortical thickness in fsLR space CIFTI
cortical_thickness:
datatype: anat
den: 91k
desc: null
extension: .dscalar.nii
space: fsLR
suffix: thickness
# Corrected cortical thickness in fsLR space CIFTI
cortical_thickness_corr:
datatype: anat
den: 91k
desc: corrected
extension: .dscalar.nii
space: fsLR
suffix: thickness
# Myelin map in fsLR space CIFTI
myelin:
datatype: anat
den: 91k
desc: null
extension: .dscalar.nii
space: fsLR
suffix: myelinw
# Smoothed myelin map in fsLR space CIFTI
myelin_smoothed:
datatype: anat
den: 91k
desc: smoothed
extension: .dscalar.nii
space: fsLR
suffix: myelinw
# Sulcal curvature in fsLR space CIFTI
sulcal_curv:
datatype: anat
den: 91k
desc: null
extension: .dscalar.nii
space: fsLR
suffix: curv
# Sulcal depth in fsLR space CIFTI
sulcal_depth:
datatype: anat
den: 91k
desc: null
extension: .dscalar.nii
space: fsLR
suffix: sulc
79 changes: 75 additions & 4 deletions xcp_d/tests/test_utils_bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,17 @@ def test_collect_mesh_data(datasets, tmp_path_factory):
"""Test collect_mesh_data."""
# Dataset without mesh files
layout = BIDSLayout(datasets["fmriprep_without_freesurfer"], validate=False)
mesh_available, standard_space_mesh, _, _ = xbids.collect_mesh_data(layout, "1648798153")
mesh_available, standard_space_mesh, _, _ = xbids.collect_mesh_data(
layout, "1648798153", bids_filters={}
)
assert mesh_available is False
assert standard_space_mesh is False

# Dataset with native-space mesh files (one file matching each query)
layout = BIDSLayout(datasets["pnc"], validate=False)
mesh_available, standard_space_mesh, _, _ = xbids.collect_mesh_data(layout, "1648798153")
mesh_available, standard_space_mesh, _, _ = xbids.collect_mesh_data(
layout, "1648798153", bids_filters={}
)
assert mesh_available is True
assert standard_space_mesh is False

Expand All @@ -153,7 +157,9 @@ def test_collect_mesh_data(datasets, tmp_path_factory):
(std_mesh_dir / "sub-1648798153/ses-PNC1/anat").joinpath(f).touch()

layout = BIDSLayout(std_mesh_dir, validate=False)
mesh_available, standard_space_mesh, _, _ = xbids.collect_mesh_data(layout, "1648798153")
mesh_available, standard_space_mesh, _, _ = xbids.collect_mesh_data(
layout, "1648798153", bids_filters={}
)
assert mesh_available is True
assert standard_space_mesh is True

Expand All @@ -179,7 +185,72 @@ def test_collect_mesh_data(datasets, tmp_path_factory):

layout = BIDSLayout(std_mesh_dir, validate=False)
with pytest.raises(ValueError, match="More than one surface found"):
xbids.collect_mesh_data(layout, "1648798153")
xbids.collect_mesh_data(layout, "1648798153", bids_filters={})

# If we include BIDS filters, we should be able to ignore the existing files
layout = BIDSLayout(datasets["pnc"], validate=False)
mesh_available, standard_space_mesh, _, _ = xbids.collect_mesh_data(
layout,
"1648798153",
bids_filters={
"lh_pial_surf": {"acquisition": "test"},
"rh_pial_surf": {"acquisition": "test"},
"lh_wm_surf": {"acquisition": "test"},
"rh_wm_surf": {"acquisition": "test"},
"lh_subject_sphere": {"acquisition": "test"},
"rh_subject_sphere": {"acquisition": "test"},
},
)
assert mesh_available is False
assert standard_space_mesh is False


def test_collect_morphometry_data(datasets, tmp_path_factory):
"""Test collect_morphometry_data."""
# Dataset without morphometry files
layout = BIDSLayout(datasets["fmriprep_without_freesurfer"], validate=False)
morph_file_types, _ = xbids.collect_morphometry_data(layout, "1648798153", bids_filters={})
assert morph_file_types == []

# Dataset with morphometry files (one file matching each query)
layout = BIDSLayout(datasets["pnc"], validate=False)
morph_file_types, _ = xbids.collect_morphometry_data(layout, "1648798153", bids_filters={})
assert morph_file_types == ["cortical_thickness", "sulcal_curv", "sulcal_depth"]

# Dataset with multiple files matching each query (raises an error)
bad_morph_dir = tmp_path_factory.mktemp("bad_morph")
shutil.copyfile(
os.path.join(datasets["pnc"], "dataset_description.json"),
bad_morph_dir / "dataset_description.json",
)
os.makedirs(bad_morph_dir / "sub-1648798153/ses-PNC1/anat", exist_ok=True)
files = [
"sub-1648798153_ses-PNC1_acq-refaced_space-fsLR_den-91k_thickness.dscalar.nii",
"sub-1648798153_ses-PNC1_acq-refaced2_space-fsLR_den-91k_thickness.dscalar.nii",
"sub-1648798153_ses-PNC1_acq-refaced_space-fsLR_den-91k_sulc.dscalar.nii",
"sub-1648798153_ses-PNC1_acq-refaced2_space-fsLR_den-91k_sulc.dscalar.nii",
"sub-1648798153_ses-PNC1_acq-refaced_space-fsLR_den-91k_curv.dscalar.nii",
"sub-1648798153_ses-PNC1_acq-refaced2_space-fsLR_den-91k_curv.dscalar.nii",
]
for f in files:
(bad_morph_dir / "sub-1648798153/ses-PNC1/anat").joinpath(f).touch()

layout = BIDSLayout(bad_morph_dir, validate=False)
with pytest.raises(ValueError, match="More than one .* found"):
xbids.collect_morphometry_data(layout, "1648798153", bids_filters={})

# If we include BIDS filters, we should be able to ignore the existing files
layout = BIDSLayout(datasets["pnc"], validate=False)
morph_file_types, _ = xbids.collect_morphometry_data(
layout,
"1648798153",
bids_filters={
"cortical_thickness": {"acquisition": "test"},
"sulcal_curv": {"acquisition": "test"},
"sulcal_depth": {"acquisition": "test"},
},
)
assert morph_file_types == []


def test_write_dataset_description(datasets, tmp_path_factory, caplog):
Expand Down
Loading

0 comments on commit ff2ee5a

Please sign in to comment.