diff --git a/.circleci/config.yml b/.circleci/config.yml index 52ea57ca6..8851f5815 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -72,6 +72,23 @@ jobs: paths: - /src/xcp_d/.circleci/data/ds001419-aroma + download_data_schaefer100: + <<: *dockersetup + steps: + - checkout + - restore_cache: + key: schaefer100-02 + - run: *runinstall + - run: + name: Download BIDS-Atlas dataset + command: | + cd /src/xcp_d/.circleci + python get_data.py $PWD/data schaefer100 + - save_cache: + key: schaefer100-02 + paths: + - /src/xcp_d/.circleci/data/schaefer100 + download_data_pnc: <<: *dockersetup steps: @@ -161,6 +178,8 @@ jobs: fi - restore_cache: key: fmriprepwithoutfreesurfer-03 + - restore_cache: + key: schaefer100-02 - run: *runinstall - run: name: Run full xcp_d on nifti without freesurfer @@ -505,6 +524,8 @@ jobs: key: fmriprepwithoutfreesurfer-03 - restore_cache: key: nibabies-04 + - restore_cache: + key: schaefer100-02 - run: *runinstall - run: name: Run pytest on the tests directory @@ -648,6 +669,13 @@ workflows: tags: only: /.*/ + - download_data_schaefer100: + requires: + - build + filters: + tags: + only: /.*/ + - pnc_cifti: requires: - download_data_pnc @@ -718,6 +746,7 @@ workflows: - nifti_without_freesurfer: requires: - download_data_fmriprepwithoutfreesurfer + - download_data_schaefer100 filters: branches: ignore: @@ -743,6 +772,7 @@ workflows: - download_data_fmriprepwithoutfreesurfer - download_data_ds001419 - download_data_nibabies + - download_data_schaefer100 filters: branches: ignore: diff --git a/.vscode/settings.json b/.vscode/settings.json index 7cf249081..3e91b9ae7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,7 +6,5 @@ "[python]": { "editor.rulers": [99] }, - "python.linting.flake8Enabled": true, - "python.linting.enabled": true, "python.analysis.typeCheckingMode": "off", } diff --git a/README.rst b/README.rst index ad10c8df8..6cc85389d 100644 --- a/README.rst +++ b/README.rst @@ -88,7 +88,7 @@ and other postprocessing/analysis tools are better suited for many types of data XCP-D derivatives are not particularly useful for task-dependent functional connectivity analyses, such as psychophysiological interactions (PPIs) or beta series analyses. It is also not suitable for general task-based analyses, such as standard task GLMs, -as we recommend included nuisance regressors in the GLM step, +as we recommend including nuisance regressors in the GLM step, rather than denoising data prior to the GLM. diff --git a/docs/usage.rst b/docs/usage.rst index 800baf802..818724c90 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -469,42 +469,42 @@ plot_design_matrix.html#create-design-matrices>`_. Then, create a confounds config file to include derivatives from ``custom_confounds``. Something like this should work: -```yaml -name: my_custom_confounds -description: | - Nuisance regressors were task regressors convolved with an HRF and motion parameters. -confounds: - motion: - dataset: preprocessed - query: - space: null - cohort: null - res: null - den: null - desc: confounds - extension: .tsv - suffix: timeseries - columns: - - trans_x - - trans_y - - trans_z - - rot_x - - rot_y - - rot_z - task: - dataset: custom - query: - space: null - cohort: null - res: null - den: null - desc: confounds - extension: .tsv - suffix: timeseries - columns: - - condition1 - - condition2 -``` +.. code-block:: yaml + + name: my_custom_confounds + description: | + Nuisance regressors were task regressors convolved with an HRF and motion parameters. + confounds: + motion: + dataset: preprocessed + query: + space: null + cohort: null + res: null + den: null + desc: confounds + extension: .tsv + suffix: timeseries + columns: + - trans_x + - trans_y + - trans_z + - rot_x + - rot_y + - rot_z + task: + dataset: custom + query: + space: null + cohort: null + res: null + den: null + desc: confounds + extension: .tsv + suffix: timeseries + columns: + - condition1 + - condition2 Command Line XCP-D with Custom Confounds @@ -523,38 +523,89 @@ Last, run XCP-D with your custom configuration file and the path to the custom d --nuisance-regressors /mnt/custom_config.yaml -******************** -Custom Parcellations -******************** +**************** +External Atlases +**************** -While XCP-D comes with many built in parcellations, we understand that many users will want to use -custom parcellations. -If you use the ``-cifti`` option, you can use the Human Connectome Project's ``wb_command`` to -generate the time series: +While XCP-D comes with many built-in parcellations, +we understand that many users will want to use different ones. -.. code-block:: bash +As long as the parcellation is organized in a BIDS-Atlas dataset and is in +fsLR-32k space (for CIFTI processing) or +MNIInfant, MNI152NLin6Asym, or MNI152NLin2009cAsym space (for NIfTI processing), +you can use it with XCP-D. + +.. warning:: + BIDS Extension Proposal 38 (Atlas Specification) has not been integrated in BIDS yet, + so the organization and naming for atlas datasets may change in the future. + + We have attempted to follow the proposed structure in XCP-D, + but we cannot guarantee that this will not change. + +.. tip:: + The main elements from the BIDS-Atlas dataset that XCP-D uses are: + + 1. There must be a dataset_description.json file with DatasetType set to "atlas". + 2. The atlas metadata files must have the same entities as the atlas image files, + as PyBIDS does not support the inheritance principle when querying BIDS-Atlas datasets (yet). + 3. There must be a TSV file for the atlas, with "index" and "label" columns. - wb_command \ - -cifti-parcellate \ - {SUB}_ses-{SESSION}_task-{TASK}_run-{RUN}_space-fsLR_den-91k_desc-residual_bold.dtseries.nii \ - your_parcels.dlabel \ - {SUB}_ses-{SESSION}_task-{TASK}_run-{RUN}_space-fsLR_den-91k_desc-residual_timeseries.ptseries.nii +To do this, use the ``--datasets`` and ``--atlases`` parameters. +The ``--datasets`` parameter should point to the directory containing the BIDS-Atlas dataset, +and the ``--atlases`` parameter should include the names of the atlases in the dataset to use. -After this, if one wishes to have a connectivity matrix: +For example, consider a scenario where you have two BIDS-Atlas datasets, one containing all of the +Schaefer 2018 resolutions and one containing the AAL atlas. +These datasets are in ``/data/atlases/schaefer`` and ``/data/atlases/aal``, respectively. +The file structure for these two datasets might look like this: + +.. code-block:: + + /data/atlases/ + schaefer/ + dataset_description.json + atlas-Schaefer100/ + atlas-Schaefer100_dseg.tsv + atlas-Schaefer100_space-fsLR_den-32k_dseg.dlabel.nii + atlas-Schaefer100_space-fsLR_den-32k_dseg.json + atlas-Schaefer200/ + atlas-Schaefer200_dseg.tsv + atlas-Schaefer200_space-fsLR_den-32k_dseg.dlabel.nii + atlas-Schaefer200_space-fsLR_den-32k_dseg.json + ... + atlas-Schaefer1000/ + atlas-Schaefer1000_dseg.tsv + atlas-Schaefer1000_space-fsLR_den-32k_dseg.dlabel.nii + atlas-Schaefer1000_space-fsLR_den-32k_dseg.json + aal/ + dataset_description.json + atlas-AAL/ + atlas-AAL_dseg.tsv + atlas-AAL_space-fsLR_den-32k_dseg.dlabel.nii + atlas-AAL_space-fsLR_den-32k_dseg.json + +You may want to only apply the Schaefer100 atlas from the ``schaefer`` dataset and the AAL atlas +from the ``aal`` dataset, along with one of XCP-D's built-in atlases (``4S156Parcels``). +Here's what the XCP-D call might look like: .. code-block:: bash - wb_command \ - -cifti-correlation \ - {SUB}_ses-{SESSION}_task-{TASK}_run-{RUN}_space-fsLR_den-91k_desc-residual_timeseries.ptseries.nii \ - {SUB}_ses-{SESSION}_task-{TASK}_run-{RUN}_space-fsLR_den-91k_desc-residual_boldmap.pconn.nii + apptainer run --cleanenv -B /data:/data xcpd_latest.sif \ + /data/dataset/derivatives/fmriprep \ + /data/dataset/derivatives/xcp_d \ + participant \ + --mode linc \ + --datasets schaefer=/data/atlases/schaefer aal==/data/atlases/aal \ + --atlases Schaefer100 AAL 4S156Parcels + +XCP-D will search for ``atlas-Schaefer100``, ``atlas-AAL``, and ``atlas-4S156Parcels`` across the +``schaefer``, ``aal``, and XCP-D's built-in atlas datasets. +If the atlases are found, then they will be used for parcellation. -More information can be found at the HCP -`documentation `_. +.. important:: -If you use the default NIFTI processing pipeline, you can use Nilearn's -`NiftiLabelsMasker `_ + Atlas names must be unique across BIDS-Atlas datasets. + If two atlases have the same name, XCP-D will raise an error. ********************* diff --git a/xcp_d/cli/combineqc.py b/xcp_d/cli/combineqc.py index c689c5e90..8854bfce5 100644 --- a/xcp_d/cli/combineqc.py +++ b/xcp_d/cli/combineqc.py @@ -47,4 +47,4 @@ def main(args=None): if __name__ == "__main__": - raise RuntimeError("this should be run after xcp_d;\nrun XCP-D first") + raise RuntimeError("this should be run after XCP-D;\nrun XCP-D first") diff --git a/xcp_d/cli/parser.py b/xcp_d/cli/parser.py index 8968703ee..b229afe89 100644 --- a/xcp_d/cli/parser.py +++ b/xcp_d/cli/parser.py @@ -134,7 +134,7 @@ def _build_parser(): type=str, nargs="+", help=( - "Search PATH(s) for pre-computed derivatives. " + "Search PATH(s) for derivatives or atlas datasets. " "These may be provided as named folders " "(e.g., `--datasets smriprep=/path/to/smriprep`)." ), @@ -503,10 +503,12 @@ def _build_parser(): action="store", nargs="+", metavar="ATLAS", - choices=all_atlases, default=all_atlases, dest="atlases", - help="Selection of atlases to apply to the data. All are used by default.", + help=( + "Selection of atlases to apply to the data. " + "All of XCP-D's built-in atlases are used by default." + ), ) g_atlases.add_argument( "--skip-parcellation", @@ -934,6 +936,16 @@ def _validate_parameters(opts, build_log, parser): assert opts.output_type in ("censored", "interpolated", "auto") assert opts.process_surfaces in (True, False, "auto") + # Add internal atlas datasets to the list of datasets + opts.datasets = opts.datasets or {} + if opts.atlases: + if "xcpdatlases" not in opts.datasets: + opts.datasets["xcpdatlases"] = load_data("atlases") + + if any(atlas.startswith("4S") for atlas in opts.atlases): + if "xcpd4s" not in opts.datasets: + opts.datasets["xcpd4s"] = Path("/AtlasPack") + # Check parameters based on the mode if opts.mode == "abcd": opts.abcc_qc = True if (opts.abcc_qc == "auto") else opts.abcc_qc diff --git a/xcp_d/cli/parser_utils.py b/xcp_d/cli/parser_utils.py index c0cf81ee1..3e8f168c1 100644 --- a/xcp_d/cli/parser_utils.py +++ b/xcp_d/cli/parser_utils.py @@ -140,7 +140,9 @@ class YesNoAction(Action): def __call__(self, parser, namespace, values, option_string=None): # noqa: U100 """Call the argument.""" lookup = {"y": True, "n": False, None: True, "auto": "auto"} - assert values in lookup.keys(), f"Invalid value '{values}' for {self.dest}" + if values not in lookup: + raise parser.error(f"Invalid value '{values}' for {self.dest}") + setattr(namespace, self.dest, lookup[values]) @@ -159,9 +161,9 @@ def __call__(self, parser, namespace, values, option_string=None): # noqa: U100 name = loc.name if name in d: - raise ValueError(f"Received duplicate derivative name: {name}") + raise parser.error(f"Received duplicate derivative name: {name}") elif name == "preprocessed": - raise ValueError("The 'preprocessed' derivative is reserved for internal use.") + raise parser.error("The 'preprocessed' derivative is reserved for internal use.") d[name] = loc setattr(namespace, self.dest, d) diff --git a/xcp_d/cli/workflow.py b/xcp_d/cli/workflow.py index f9974d501..6e92b220d 100644 --- a/xcp_d/cli/workflow.py +++ b/xcp_d/cli/workflow.py @@ -93,7 +93,7 @@ def build_workflow(config_file, retval): ] if config.execution.datasets: - init_msg += [f"Searching for derivatives: {config.execution.datasets}."] + init_msg += [f"Searching for derivatives and atlases: {config.execution.datasets}."] build_log.log(25, f"\n{' ' * 11}* ".join(init_msg)) diff --git a/xcp_d/config.py b/xcp_d/config.py index c74d91eac..306345773 100644 --- a/xcp_d/config.py +++ b/xcp_d/config.py @@ -380,7 +380,7 @@ class execution(_Config): fmri_dir = None """An existing path to the preprocessing derivatives dataset, which must be BIDS-compliant.""" datasets = {} - """Path(s) to search for pre-computed derivatives""" + """Path(s) to search for other datasets (either derivatives or atlases).""" aggr_ses_reports = None """Maximum number of sessions aggregated in one subject's visual report.""" bids_database_dir = None diff --git a/xcp_d/data/atlas_bids_config.json b/xcp_d/data/atlas_bids_config.json new file mode 100644 index 000000000..f1677f2fe --- /dev/null +++ b/xcp_d/data/atlas_bids_config.json @@ -0,0 +1,44 @@ +{ + "name": "atlas", + "entities": [ + { + "name": "atlas", + "pattern": "[/\\\\]+atlas-([a-zA-Z0-9]+)", + "directory": "{atlas}" + }, + { + "name": "space", + "pattern": "[_/\\\\]+space-([a-zA-Z0-9]+)" + }, + { + "name": "hemi", + "pattern": "hemi-(L|R)" + }, + { + "name": "res", + "pattern": "res-([a-zA-Z0-9]+)" + }, + { + "name": "den", + "pattern": "den-([a-zA-Z0-9]+)" + }, + { + "name": "desc", + "pattern": "desc-([a-zA-Z0-9]+)" + }, + { + "name": "suffix", + "pattern": "(?:^|[_/\\\\])([a-zA-Z0-9]+)\\.[^/\\\\]+$" + }, + { + "name": "extension", + "pattern": "[^./\\\\](\\.[^/\\\\]+)$" + } + ], + "default_path_patterns": [ + "atlas-{atlas}/atlas-{atlas}[_space-{space}][_res-{res}][_desc-{desc}]_{suffix}.{extension|nii.gz}", + "atlas-{atlas}/atlas-{atlas}[_space-{space}][_res-{res}][_den-{den}][_desc-{desc}]_{suffix}.{extension|dlabel.nii}", + "atlas-{atlas}/atlas-{atlas}[_hemi-{hemi}][_space-{space}][_den-{den}][_desc-{desc}]_{suffix}.{extension|label.gii}", + "atlas-{atlas}/atlas-{atlas}[_space-{space}][_desc-{desc}]_{suffix}.{extension|tsv}" + ] +} diff --git a/xcp_d/data/atlases/atlas-Glasser_dseg.tsv b/xcp_d/data/atlases/atlas-Glasser/atlas-Glasser_dseg.tsv similarity index 100% rename from xcp_d/data/atlases/atlas-Glasser_dseg.tsv rename to xcp_d/data/atlases/atlas-Glasser/atlas-Glasser_dseg.tsv diff --git a/xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-Glasser_dseg.json b/xcp_d/data/atlases/atlas-Glasser/atlas-Glasser_space-MNI152NLin6Asym_dseg.json similarity index 100% rename from xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-Glasser_dseg.json rename to xcp_d/data/atlases/atlas-Glasser/atlas-Glasser_space-MNI152NLin6Asym_dseg.json diff --git a/xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-Glasser_res-01_dseg.nii.gz b/xcp_d/data/atlases/atlas-Glasser/atlas-Glasser_space-MNI152NLin6Asym_res-01_dseg.nii.gz similarity index 100% rename from xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-Glasser_res-01_dseg.nii.gz rename to xcp_d/data/atlases/atlas-Glasser/atlas-Glasser_space-MNI152NLin6Asym_res-01_dseg.nii.gz diff --git a/xcp_d/data/atlases/tpl-fsLR_atlas-Glasser_den-32k_dseg.dlabel.nii b/xcp_d/data/atlases/atlas-Glasser/atlas-Glasser_space-fsLR_den-32k_dseg.dlabel.nii similarity index 100% rename from xcp_d/data/atlases/tpl-fsLR_atlas-Glasser_den-32k_dseg.dlabel.nii rename to xcp_d/data/atlases/atlas-Glasser/atlas-Glasser_space-fsLR_den-32k_dseg.dlabel.nii diff --git a/xcp_d/data/atlases/tpl-fsLR_atlas-Glasser_dseg.json b/xcp_d/data/atlases/atlas-Glasser/atlas-Glasser_space-fsLR_dseg.json similarity index 100% rename from xcp_d/data/atlases/tpl-fsLR_atlas-Glasser_dseg.json rename to xcp_d/data/atlases/atlas-Glasser/atlas-Glasser_space-fsLR_dseg.json diff --git a/xcp_d/data/atlases/atlas-Gordon_dseg.tsv b/xcp_d/data/atlases/atlas-Gordon/atlas-Gordon_dseg.tsv similarity index 100% rename from xcp_d/data/atlases/atlas-Gordon_dseg.tsv rename to xcp_d/data/atlases/atlas-Gordon/atlas-Gordon_dseg.tsv diff --git a/xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-Gordon_dseg.json b/xcp_d/data/atlases/atlas-Gordon/atlas-Gordon_space-MNI152NLin6Asym_dseg.json similarity index 100% rename from xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-Gordon_dseg.json rename to xcp_d/data/atlases/atlas-Gordon/atlas-Gordon_space-MNI152NLin6Asym_dseg.json diff --git a/xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-Gordon_res-01_dseg.nii.gz b/xcp_d/data/atlases/atlas-Gordon/atlas-Gordon_space-MNI152NLin6Asym_res-01_dseg.nii.gz similarity index 100% rename from xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-Gordon_res-01_dseg.nii.gz rename to xcp_d/data/atlases/atlas-Gordon/atlas-Gordon_space-MNI152NLin6Asym_res-01_dseg.nii.gz diff --git a/xcp_d/data/atlases/tpl-fsLR_atlas-Gordon_den-32k_dseg.dlabel.nii b/xcp_d/data/atlases/atlas-Gordon/atlas-Gordon_space-fsLR_den-32k_dseg.dlabel.nii similarity index 100% rename from xcp_d/data/atlases/tpl-fsLR_atlas-Gordon_den-32k_dseg.dlabel.nii rename to xcp_d/data/atlases/atlas-Gordon/atlas-Gordon_space-fsLR_den-32k_dseg.dlabel.nii diff --git a/xcp_d/data/atlases/tpl-fsLR_atlas-Gordon_dseg.json b/xcp_d/data/atlases/atlas-Gordon/atlas-Gordon_space-fsLR_dseg.json similarity index 100% rename from xcp_d/data/atlases/tpl-fsLR_atlas-Gordon_dseg.json rename to xcp_d/data/atlases/atlas-Gordon/atlas-Gordon_space-fsLR_dseg.json diff --git a/xcp_d/data/atlases/atlas-HCP_dseg.tsv b/xcp_d/data/atlases/atlas-HCP/atlas-HCP_dseg.tsv similarity index 100% rename from xcp_d/data/atlases/atlas-HCP_dseg.tsv rename to xcp_d/data/atlases/atlas-HCP/atlas-HCP_dseg.tsv diff --git a/xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-HCP_dseg.json b/xcp_d/data/atlases/atlas-HCP/atlas-HCP_space-MNI152NLin6Asym_dseg.json similarity index 100% rename from xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-HCP_dseg.json rename to xcp_d/data/atlases/atlas-HCP/atlas-HCP_space-MNI152NLin6Asym_dseg.json diff --git a/xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-HCP_res-02_dseg.nii.gz b/xcp_d/data/atlases/atlas-HCP/atlas-HCP_space-MNI152NLin6Asym_res-02_dseg.nii.gz similarity index 100% rename from xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-HCP_res-02_dseg.nii.gz rename to xcp_d/data/atlases/atlas-HCP/atlas-HCP_space-MNI152NLin6Asym_res-02_dseg.nii.gz diff --git a/xcp_d/data/atlases/tpl-fsLR_atlas-HCP_den-32k_dseg.dlabel.nii b/xcp_d/data/atlases/atlas-HCP/atlas-HCP_space-fsLR_den-32k_dseg.dlabel.nii similarity index 100% rename from xcp_d/data/atlases/tpl-fsLR_atlas-HCP_den-32k_dseg.dlabel.nii rename to xcp_d/data/atlases/atlas-HCP/atlas-HCP_space-fsLR_den-32k_dseg.dlabel.nii diff --git a/xcp_d/data/atlases/tpl-fsLR_atlas-HCP_dseg.json b/xcp_d/data/atlases/atlas-HCP/atlas-HCP_space-fsLR_dseg.json similarity index 100% rename from xcp_d/data/atlases/tpl-fsLR_atlas-HCP_dseg.json rename to xcp_d/data/atlases/atlas-HCP/atlas-HCP_space-fsLR_dseg.json diff --git a/xcp_d/data/atlases/atlas-MIDB_dseg.tsv b/xcp_d/data/atlases/atlas-MIDB/atlas-MIDB_dseg.tsv similarity index 100% rename from xcp_d/data/atlases/atlas-MIDB_dseg.tsv rename to xcp_d/data/atlases/atlas-MIDB/atlas-MIDB_dseg.tsv diff --git a/xcp_d/data/atlases/tpl-fsLR_atlas-MIDB_den-32k_desc-abcdThresh75_dseg.dlabel.nii b/xcp_d/data/atlases/atlas-MIDB/atlas-MIDB_space-fsLR_den-32k_desc-abcdThresh75_dseg.dlabel.nii similarity index 100% rename from xcp_d/data/atlases/tpl-fsLR_atlas-MIDB_den-32k_desc-abcdThresh75_dseg.dlabel.nii rename to xcp_d/data/atlases/atlas-MIDB/atlas-MIDB_space-fsLR_den-32k_desc-abcdThresh75_dseg.dlabel.nii diff --git a/xcp_d/data/atlases/tpl-fsLR_atlas-MIDB_den-32k_desc-abcdThresh75_dseg.json b/xcp_d/data/atlases/atlas-MIDB/atlas-MIDB_space-fsLR_den-32k_desc-abcdThresh75_dseg.json similarity index 100% rename from xcp_d/data/atlases/tpl-fsLR_atlas-MIDB_den-32k_desc-abcdThresh75_dseg.json rename to xcp_d/data/atlases/atlas-MIDB/atlas-MIDB_space-fsLR_den-32k_desc-abcdThresh75_dseg.json diff --git a/xcp_d/data/atlases/atlas-MyersLabonte_desc-thresh50_dseg.tsv b/xcp_d/data/atlases/atlas-MyersLabonte/atlas-MyersLabonte_desc-thresh50_dseg.tsv similarity index 100% rename from xcp_d/data/atlases/atlas-MyersLabonte_desc-thresh50_dseg.tsv rename to xcp_d/data/atlases/atlas-MyersLabonte/atlas-MyersLabonte_desc-thresh50_dseg.tsv diff --git a/xcp_d/data/atlases/tpl-fsLR_atlas-MyersLabonte_den-32k_desc-thresh50_dseg.dlabel.nii b/xcp_d/data/atlases/atlas-MyersLabonte/atlas-MyersLabonte_space-fsLR_den-32k_desc-thresh50_dseg.dlabel.nii similarity index 100% rename from xcp_d/data/atlases/tpl-fsLR_atlas-MyersLabonte_den-32k_desc-thresh50_dseg.dlabel.nii rename to xcp_d/data/atlases/atlas-MyersLabonte/atlas-MyersLabonte_space-fsLR_den-32k_desc-thresh50_dseg.dlabel.nii diff --git a/xcp_d/data/atlases/tpl-fsLR_atlas-MyersLabonte_den-32k_desc-thresh50_dseg.json b/xcp_d/data/atlases/atlas-MyersLabonte/atlas-MyersLabonte_space-fsLR_den-32k_desc-thresh50_dseg.json similarity index 100% rename from xcp_d/data/atlases/tpl-fsLR_atlas-MyersLabonte_den-32k_desc-thresh50_dseg.json rename to xcp_d/data/atlases/atlas-MyersLabonte/atlas-MyersLabonte_space-fsLR_den-32k_desc-thresh50_dseg.json diff --git a/xcp_d/data/atlases/atlas-Tian_dseg.tsv b/xcp_d/data/atlases/atlas-Tian/atlas-Tian_dseg.tsv similarity index 100% rename from xcp_d/data/atlases/atlas-Tian_dseg.tsv rename to xcp_d/data/atlases/atlas-Tian/atlas-Tian_dseg.tsv diff --git a/xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-Tian_dseg.json b/xcp_d/data/atlases/atlas-Tian/atlas-Tian_space-MNI152NLin6Asym_dseg.json similarity index 100% rename from xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-Tian_dseg.json rename to xcp_d/data/atlases/atlas-Tian/atlas-Tian_space-MNI152NLin6Asym_dseg.json diff --git a/xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-Tian_res-02_dseg.nii.gz b/xcp_d/data/atlases/atlas-Tian/atlas-Tian_space-MNI152NLin6Asym_res-02_dseg.nii.gz similarity index 100% rename from xcp_d/data/atlases/tpl-MNI152NLin6Asym_atlas-Tian_res-02_dseg.nii.gz rename to xcp_d/data/atlases/atlas-Tian/atlas-Tian_space-MNI152NLin6Asym_res-02_dseg.nii.gz diff --git a/xcp_d/data/atlases/tpl-fsLR_atlas-Tian_den-32k_dseg.dlabel.nii b/xcp_d/data/atlases/atlas-Tian/atlas-Tian_space-fsLR_den-32k_dseg.dlabel.nii similarity index 100% rename from xcp_d/data/atlases/tpl-fsLR_atlas-Tian_den-32k_dseg.dlabel.nii rename to xcp_d/data/atlases/atlas-Tian/atlas-Tian_space-fsLR_den-32k_dseg.dlabel.nii diff --git a/xcp_d/data/atlases/tpl-fsLR_atlas-Tian_dseg.json b/xcp_d/data/atlases/atlas-Tian/atlas-Tian_space-fsLR_dseg.json similarity index 100% rename from xcp_d/data/atlases/tpl-fsLR_atlas-Tian_dseg.json rename to xcp_d/data/atlases/atlas-Tian/atlas-Tian_space-fsLR_dseg.json diff --git a/xcp_d/data/atlases/dataset_description.json b/xcp_d/data/atlases/dataset_description.json new file mode 100644 index 000000000..5858f2f9a --- /dev/null +++ b/xcp_d/data/atlases/dataset_description.json @@ -0,0 +1,5 @@ +{ + "BIDSVersion": "1.9.0", + "DatasetType": "atlas", + "Name": "XCP-D atlases" +} \ No newline at end of file diff --git a/xcp_d/data/atlases/tpl-fsLR_atlas-MyersLabonte_den-32k_dseg.dlabel.nii b/xcp_d/data/atlases/tpl-fsLR_atlas-MyersLabonte_den-32k_dseg.dlabel.nii deleted file mode 100644 index eb28f5fa9..000000000 Binary files a/xcp_d/data/atlases/tpl-fsLR_atlas-MyersLabonte_den-32k_dseg.dlabel.nii and /dev/null differ diff --git a/xcp_d/interfaces/bids.py b/xcp_d/interfaces/bids.py index 5fd4b8fe2..eaba8e81d 100644 --- a/xcp_d/interfaces/bids.py +++ b/xcp_d/interfaces/bids.py @@ -2,7 +2,7 @@ import os import shutil -from json import loads +from json import dump, loads import nibabel as nb import numpy as np @@ -178,6 +178,12 @@ class _CopyAtlasInputSpec(BaseInterfaceInputSpec): desc="The atlas file to copy.", mandatory=True, ) + meta_dict = traits.Either( + traits.Dict(), + None, + desc="The atlas metadata dictionary.", + mandatory=False, + ) output_dir = Directory( exists=True, desc="The output directory.", @@ -234,15 +240,15 @@ class CopyAtlas(SimpleInterface): def _run_interface(self, runtime): output_dir = self.inputs.output_dir in_file = self.inputs.in_file + meta_dict = self.inputs.meta_dict name_source = self.inputs.name_source atlas = self.inputs.atlas atlas_out_dir = os.path.join(output_dir, f"atlases/atlas-{atlas}") - if in_file.endswith(".json"): - out_basename = f"atlas-{atlas}_dseg.json" - elif in_file.endswith(".tsv"): - out_basename = f"atlas-{atlas}_dseg.tsv" + if in_file.endswith(".tsv"): + out_basename = f"atlas-{atlas}_dseg" + extension = ".tsv" else: extension = ".nii.gz" if name_source.endswith(".nii.gz") else ".dlabel.nii" space = get_entity(name_source, "space") @@ -254,12 +260,12 @@ def _run_interface(self, runtime): res_str = f"_res-{res}" if res else "" den_str = f"_den-{den}" if den else "" if extension == ".dlabel.nii": - out_basename = f"atlas-{atlas}_space-{space}{den_str}{cohort_str}_dseg{extension}" + out_basename = f"atlas-{atlas}_space-{space}{den_str}{cohort_str}_dseg" elif extension == ".nii.gz": - out_basename = f"atlas-{atlas}_space-{space}{res_str}{cohort_str}_dseg{extension}" + out_basename = f"atlas-{atlas}_space-{space}{res_str}{cohort_str}_dseg" os.makedirs(atlas_out_dir, exist_ok=True) - out_file = os.path.join(atlas_out_dir, out_basename) + out_file = os.path.join(atlas_out_dir, f"{out_basename}{extension}") if out_file.endswith(".nii.gz") and os.path.isfile(out_file): # Check that native-resolution atlas doesn't have a different resolution from the last @@ -277,6 +283,12 @@ def _run_interface(self, runtime): if not os.path.isfile(out_file): shutil.copyfile(in_file, out_file) + # Only write out a sidecar if metadata are provided + if meta_dict: + meta_file = os.path.join(atlas_out_dir, f"{out_basename}.json") + with open(meta_file, "w") as fo: + dump(meta_dict, fo, sort_keys=True, indent=4) + self._results["out_file"] = out_file return runtime diff --git a/xcp_d/interfaces/connectivity.py b/xcp_d/interfaces/connectivity.py index a309a13fd..430a597b7 100644 --- a/xcp_d/interfaces/connectivity.py +++ b/xcp_d/interfaces/connectivity.py @@ -423,6 +423,8 @@ def _run_interface(self, runtime): "4S956Parcels", "4S1056Parcels", ] + external_atlases = [a for a in self.inputs.atlases if a not in priority_list] + priority_list += external_atlases selected_atlases = [] c = 0 for atlas in priority_list: @@ -569,14 +571,16 @@ def _run_interface(self, runtime): if "cifti_label" in node_labels_df.columns: parcel_label_mapper = dict(zip(node_labels_df["cifti_label"], node_labels_df["label"])) elif "label_7network" in node_labels_df.columns: - node_labels_df["label_7network"] = node_labels_df["label_7network"].fillna( + node_labels_df["cifti_label"] = node_labels_df["label_7network"].fillna( node_labels_df["label"] ) - parcel_label_mapper = dict( - zip(node_labels_df["label_7network"], node_labels_df["label"]) - ) + parcel_label_mapper = dict(zip(node_labels_df["cifti_label"], node_labels_df["label"])) else: - raise Exception(atlas_labels) + LOGGER.warning( + "No 'cifti_label' column found in atlas labels file. " + "Assuming labels in TSV exactly match node names in CIFTI atlas." + ) + parcel_label_mapper = dict(zip(node_labels_df["label"], node_labels_df["label"])) if in_file.endswith(".pconn.nii"): ax0 = img.header.get_axis(0) @@ -592,7 +596,7 @@ def _run_interface(self, runtime): df = pd.DataFrame(columns=ax1.name, data=img.get_fdata()) check_axes = [1] - # Check that all labels in the atlas labels DF are present in the TSV file, and vice versa. + # Check that all node labels in the CIFTI are present in the TSV, and vice versa. if 0 in check_axes: # Replace values in index, which should match the keys in the parcel_label_mapper # dictionary, with the corresponding values in the dictionary. diff --git a/xcp_d/tests/conftest.py b/xcp_d/tests/conftest.py index bf80131e0..42f71290f 100644 --- a/xcp_d/tests/conftest.py +++ b/xcp_d/tests/conftest.py @@ -67,6 +67,7 @@ def datasets(data_dir): data_dir, "fmriprepwithoutfreesurfer", ) + dsets["schaefer100"] = os.path.join(data_dir, "schaefer100") return dsets diff --git a/xcp_d/tests/data/test_ds001419_cifti_outputs.txt b/xcp_d/tests/data/test_ds001419_cifti_outputs.txt index a9c124110..16c3e9c6f 100644 --- a/xcp_d/tests/data/test_ds001419_cifti_outputs.txt +++ b/xcp_d/tests/data/test_ds001419_cifti_outputs.txt @@ -1,20 +1,20 @@ atlases atlases/atlas-4S156Parcels -atlases/atlas-4S156Parcels/atlas-4S156Parcels_dseg.json atlases/atlas-4S156Parcels/atlas-4S156Parcels_dseg.tsv atlases/atlas-4S156Parcels/atlas-4S156Parcels_space-fsLR_den-91k_dseg.dlabel.nii +atlases/atlas-4S156Parcels/atlas-4S156Parcels_space-fsLR_den-91k_dseg.json atlases/atlas-4S256Parcels -atlases/atlas-4S256Parcels/atlas-4S256Parcels_dseg.json atlases/atlas-4S256Parcels/atlas-4S256Parcels_dseg.tsv atlases/atlas-4S256Parcels/atlas-4S256Parcels_space-fsLR_den-91k_dseg.dlabel.nii +atlases/atlas-4S256Parcels/atlas-4S256Parcels_space-fsLR_den-91k_dseg.json atlases/atlas-4S356Parcels -atlases/atlas-4S356Parcels/atlas-4S356Parcels_dseg.json atlases/atlas-4S356Parcels/atlas-4S356Parcels_dseg.tsv atlases/atlas-4S356Parcels/atlas-4S356Parcels_space-fsLR_den-91k_dseg.dlabel.nii +atlases/atlas-4S356Parcels/atlas-4S356Parcels_space-fsLR_den-91k_dseg.json atlases/atlas-4S456Parcels -atlases/atlas-4S456Parcels/atlas-4S456Parcels_dseg.json atlases/atlas-4S456Parcels/atlas-4S456Parcels_dseg.tsv atlases/atlas-4S456Parcels/atlas-4S456Parcels_space-fsLR_den-91k_dseg.dlabel.nii +atlases/atlas-4S456Parcels/atlas-4S456Parcels_space-fsLR_den-91k_dseg.json atlases/dataset_description.json dataset_description.json desc-linc_qc.json diff --git a/xcp_d/tests/data/test_fmriprep_without_freesurfer_outputs.txt b/xcp_d/tests/data/test_fmriprep_without_freesurfer_outputs.txt index f1c261373..158582ae5 100644 --- a/xcp_d/tests/data/test_fmriprep_without_freesurfer_outputs.txt +++ b/xcp_d/tests/data/test_fmriprep_without_freesurfer_outputs.txt @@ -1,60 +1,11 @@ atlases -atlases/atlas-4S1056Parcels -atlases/atlas-4S1056Parcels/atlas-4S1056Parcels_dseg.json -atlases/atlas-4S1056Parcels/atlas-4S1056Parcels_dseg.tsv -atlases/atlas-4S1056Parcels/atlas-4S1056Parcels_space-MNI152NLin2009cAsym_dseg.nii.gz atlases/atlas-4S156Parcels -atlases/atlas-4S156Parcels/atlas-4S156Parcels_dseg.json atlases/atlas-4S156Parcels/atlas-4S156Parcels_dseg.tsv +atlases/atlas-4S156Parcels/atlas-4S156Parcels_space-MNI152NLin2009cAsym_dseg.json atlases/atlas-4S156Parcels/atlas-4S156Parcels_space-MNI152NLin2009cAsym_dseg.nii.gz -atlases/atlas-4S256Parcels -atlases/atlas-4S256Parcels/atlas-4S256Parcels_dseg.json -atlases/atlas-4S256Parcels/atlas-4S256Parcels_dseg.tsv -atlases/atlas-4S256Parcels/atlas-4S256Parcels_space-MNI152NLin2009cAsym_dseg.nii.gz -atlases/atlas-4S356Parcels -atlases/atlas-4S356Parcels/atlas-4S356Parcels_dseg.json -atlases/atlas-4S356Parcels/atlas-4S356Parcels_dseg.tsv -atlases/atlas-4S356Parcels/atlas-4S356Parcels_space-MNI152NLin2009cAsym_dseg.nii.gz -atlases/atlas-4S456Parcels -atlases/atlas-4S456Parcels/atlas-4S456Parcels_dseg.json -atlases/atlas-4S456Parcels/atlas-4S456Parcels_dseg.tsv -atlases/atlas-4S456Parcels/atlas-4S456Parcels_space-MNI152NLin2009cAsym_dseg.nii.gz -atlases/atlas-4S556Parcels -atlases/atlas-4S556Parcels/atlas-4S556Parcels_dseg.json -atlases/atlas-4S556Parcels/atlas-4S556Parcels_dseg.tsv -atlases/atlas-4S556Parcels/atlas-4S556Parcels_space-MNI152NLin2009cAsym_dseg.nii.gz -atlases/atlas-4S656Parcels -atlases/atlas-4S656Parcels/atlas-4S656Parcels_dseg.json -atlases/atlas-4S656Parcels/atlas-4S656Parcels_dseg.tsv -atlases/atlas-4S656Parcels/atlas-4S656Parcels_space-MNI152NLin2009cAsym_dseg.nii.gz -atlases/atlas-4S756Parcels -atlases/atlas-4S756Parcels/atlas-4S756Parcels_dseg.json -atlases/atlas-4S756Parcels/atlas-4S756Parcels_dseg.tsv -atlases/atlas-4S756Parcels/atlas-4S756Parcels_space-MNI152NLin2009cAsym_dseg.nii.gz -atlases/atlas-4S856Parcels -atlases/atlas-4S856Parcels/atlas-4S856Parcels_dseg.json -atlases/atlas-4S856Parcels/atlas-4S856Parcels_dseg.tsv -atlases/atlas-4S856Parcels/atlas-4S856Parcels_space-MNI152NLin2009cAsym_dseg.nii.gz -atlases/atlas-4S956Parcels -atlases/atlas-4S956Parcels/atlas-4S956Parcels_dseg.json -atlases/atlas-4S956Parcels/atlas-4S956Parcels_dseg.tsv -atlases/atlas-4S956Parcels/atlas-4S956Parcels_space-MNI152NLin2009cAsym_dseg.nii.gz -atlases/atlas-Glasser -atlases/atlas-Glasser/atlas-Glasser_dseg.json -atlases/atlas-Glasser/atlas-Glasser_dseg.tsv -atlases/atlas-Glasser/atlas-Glasser_space-MNI152NLin2009cAsym_dseg.nii.gz -atlases/atlas-Gordon -atlases/atlas-Gordon/atlas-Gordon_dseg.json -atlases/atlas-Gordon/atlas-Gordon_dseg.tsv -atlases/atlas-Gordon/atlas-Gordon_space-MNI152NLin2009cAsym_dseg.nii.gz -atlases/atlas-HCP -atlases/atlas-HCP/atlas-HCP_dseg.json -atlases/atlas-HCP/atlas-HCP_dseg.tsv -atlases/atlas-HCP/atlas-HCP_space-MNI152NLin2009cAsym_dseg.nii.gz -atlases/atlas-Tian -atlases/atlas-Tian/atlas-Tian_dseg.json -atlases/atlas-Tian/atlas-Tian_dseg.tsv -atlases/atlas-Tian/atlas-Tian_space-MNI152NLin2009cAsym_dseg.nii.gz +atlases/atlas-Schaefer100 +atlases/atlas-Schaefer100/atlas-Schaefer100_dseg.tsv +atlases/atlas-Schaefer100/atlas-Schaefer100_space-MNI152NLin2009cAsym_dseg.nii.gz atlases/dataset_description.json dataset_description.json desc-linc_qc.json @@ -80,14 +31,6 @@ sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_desc-de sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_desc-denoised_bold.json sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_desc-denoised_bold.nii.gz sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_desc-linc_qc.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-reho_bold.tsv sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S156Parcels_stat-coverage_bold.json sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S156Parcels_stat-coverage_bold.tsv sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S156Parcels_stat-mean_timeseries.json @@ -96,102 +39,14 @@ sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S1 sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S156Parcels_stat-pearsoncorrelation_relmat.tsv sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S156Parcels_stat-reho_bold.json sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S156Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Glasser_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Glasser_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Glasser_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Glasser_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Glasser_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Glasser_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Glasser_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Glasser_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Gordon_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Gordon_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Gordon_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Gordon_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Gordon_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Gordon_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Gordon_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Gordon_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-HCP_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-HCP_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-HCP_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-HCP_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-HCP_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-HCP_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-HCP_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-HCP_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Tian_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Tian_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Tian_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Tian_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Tian_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Tian_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Tian_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Tian_stat-reho_bold.tsv +sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-coverage_bold.json +sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-coverage_bold.tsv +sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-mean_timeseries.json +sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-mean_timeseries.tsv +sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-pearsoncorrelation_relmat.json +sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-pearsoncorrelation_relmat.tsv +sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-reho_bold.json +sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-reho_bold.tsv sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_stat-reho_boldmap.json sub-01/func/sub-01_task-mixedgamblestask_run-1_space-MNI152NLin2009cAsym_stat-reho_boldmap.nii.gz sub-01/func/sub-01_task-mixedgamblestask_run-2_desc-abcc_qc.hdf5 @@ -206,14 +61,6 @@ sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_desc-de sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_desc-denoised_bold.json sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_desc-denoised_bold.nii.gz sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_desc-linc_qc.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S1056Parcels_stat-reho_bold.tsv sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S156Parcels_stat-coverage_bold.json sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S156Parcels_stat-coverage_bold.tsv sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S156Parcels_stat-mean_timeseries.json @@ -222,102 +69,14 @@ sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S1 sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S156Parcels_stat-pearsoncorrelation_relmat.tsv sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S156Parcels_stat-reho_bold.json sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S156Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S256Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S356Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S456Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S556Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S656Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S756Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S856Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-4S956Parcels_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Glasser_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Glasser_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Glasser_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Glasser_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Glasser_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Glasser_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Glasser_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Glasser_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Gordon_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Gordon_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Gordon_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Gordon_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Gordon_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Gordon_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Gordon_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Gordon_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-HCP_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-HCP_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-HCP_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-HCP_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-HCP_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-HCP_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-HCP_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-HCP_stat-reho_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Tian_stat-coverage_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Tian_stat-coverage_bold.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Tian_stat-mean_timeseries.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Tian_stat-mean_timeseries.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Tian_stat-pearsoncorrelation_relmat.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Tian_stat-pearsoncorrelation_relmat.tsv -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Tian_stat-reho_bold.json -sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Tian_stat-reho_bold.tsv +sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-coverage_bold.json +sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-coverage_bold.tsv +sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-mean_timeseries.json +sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-mean_timeseries.tsv +sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-pearsoncorrelation_relmat.json +sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-pearsoncorrelation_relmat.tsv +sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-reho_bold.json +sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_seg-Schaefer100_stat-reho_bold.tsv sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_stat-reho_boldmap.json sub-01/func/sub-01_task-mixedgamblestask_run-2_space-MNI152NLin2009cAsym_stat-reho_boldmap.nii.gz sub-01_executive_summary.html diff --git a/xcp_d/tests/data/test_nibabies_outputs.txt b/xcp_d/tests/data/test_nibabies_outputs.txt index 72199ec3a..eafc8c104 100644 --- a/xcp_d/tests/data/test_nibabies_outputs.txt +++ b/xcp_d/tests/data/test_nibabies_outputs.txt @@ -1,59 +1,59 @@ atlases atlases/atlas-4S1056Parcels -atlases/atlas-4S1056Parcels/atlas-4S1056Parcels_dseg.json atlases/atlas-4S1056Parcels/atlas-4S1056Parcels_dseg.tsv +atlases/atlas-4S1056Parcels/atlas-4S1056Parcels_space-MNIInfant_cohort-1_dseg.json atlases/atlas-4S1056Parcels/atlas-4S1056Parcels_space-MNIInfant_cohort-1_dseg.nii.gz atlases/atlas-4S156Parcels -atlases/atlas-4S156Parcels/atlas-4S156Parcels_dseg.json atlases/atlas-4S156Parcels/atlas-4S156Parcels_dseg.tsv +atlases/atlas-4S156Parcels/atlas-4S156Parcels_space-MNIInfant_cohort-1_dseg.json atlases/atlas-4S156Parcels/atlas-4S156Parcels_space-MNIInfant_cohort-1_dseg.nii.gz atlases/atlas-4S256Parcels -atlases/atlas-4S256Parcels/atlas-4S256Parcels_dseg.json atlases/atlas-4S256Parcels/atlas-4S256Parcels_dseg.tsv +atlases/atlas-4S256Parcels/atlas-4S256Parcels_space-MNIInfant_cohort-1_dseg.json atlases/atlas-4S256Parcels/atlas-4S256Parcels_space-MNIInfant_cohort-1_dseg.nii.gz atlases/atlas-4S356Parcels -atlases/atlas-4S356Parcels/atlas-4S356Parcels_dseg.json atlases/atlas-4S356Parcels/atlas-4S356Parcels_dseg.tsv +atlases/atlas-4S356Parcels/atlas-4S356Parcels_space-MNIInfant_cohort-1_dseg.json atlases/atlas-4S356Parcels/atlas-4S356Parcels_space-MNIInfant_cohort-1_dseg.nii.gz atlases/atlas-4S456Parcels -atlases/atlas-4S456Parcels/atlas-4S456Parcels_dseg.json atlases/atlas-4S456Parcels/atlas-4S456Parcels_dseg.tsv +atlases/atlas-4S456Parcels/atlas-4S456Parcels_space-MNIInfant_cohort-1_dseg.json atlases/atlas-4S456Parcels/atlas-4S456Parcels_space-MNIInfant_cohort-1_dseg.nii.gz atlases/atlas-4S556Parcels -atlases/atlas-4S556Parcels/atlas-4S556Parcels_dseg.json atlases/atlas-4S556Parcels/atlas-4S556Parcels_dseg.tsv +atlases/atlas-4S556Parcels/atlas-4S556Parcels_space-MNIInfant_cohort-1_dseg.json atlases/atlas-4S556Parcels/atlas-4S556Parcels_space-MNIInfant_cohort-1_dseg.nii.gz atlases/atlas-4S656Parcels -atlases/atlas-4S656Parcels/atlas-4S656Parcels_dseg.json atlases/atlas-4S656Parcels/atlas-4S656Parcels_dseg.tsv +atlases/atlas-4S656Parcels/atlas-4S656Parcels_space-MNIInfant_cohort-1_dseg.json atlases/atlas-4S656Parcels/atlas-4S656Parcels_space-MNIInfant_cohort-1_dseg.nii.gz atlases/atlas-4S756Parcels -atlases/atlas-4S756Parcels/atlas-4S756Parcels_dseg.json atlases/atlas-4S756Parcels/atlas-4S756Parcels_dseg.tsv +atlases/atlas-4S756Parcels/atlas-4S756Parcels_space-MNIInfant_cohort-1_dseg.json atlases/atlas-4S756Parcels/atlas-4S756Parcels_space-MNIInfant_cohort-1_dseg.nii.gz atlases/atlas-4S856Parcels -atlases/atlas-4S856Parcels/atlas-4S856Parcels_dseg.json atlases/atlas-4S856Parcels/atlas-4S856Parcels_dseg.tsv +atlases/atlas-4S856Parcels/atlas-4S856Parcels_space-MNIInfant_cohort-1_dseg.json atlases/atlas-4S856Parcels/atlas-4S856Parcels_space-MNIInfant_cohort-1_dseg.nii.gz atlases/atlas-4S956Parcels -atlases/atlas-4S956Parcels/atlas-4S956Parcels_dseg.json atlases/atlas-4S956Parcels/atlas-4S956Parcels_dseg.tsv +atlases/atlas-4S956Parcels/atlas-4S956Parcels_space-MNIInfant_cohort-1_dseg.json atlases/atlas-4S956Parcels/atlas-4S956Parcels_space-MNIInfant_cohort-1_dseg.nii.gz atlases/atlas-Glasser -atlases/atlas-Glasser/atlas-Glasser_dseg.json atlases/atlas-Glasser/atlas-Glasser_dseg.tsv +atlases/atlas-Glasser/atlas-Glasser_space-MNIInfant_cohort-1_dseg.json atlases/atlas-Glasser/atlas-Glasser_space-MNIInfant_cohort-1_dseg.nii.gz atlases/atlas-Gordon -atlases/atlas-Gordon/atlas-Gordon_dseg.json atlases/atlas-Gordon/atlas-Gordon_dseg.tsv +atlases/atlas-Gordon/atlas-Gordon_space-MNIInfant_cohort-1_dseg.json atlases/atlas-Gordon/atlas-Gordon_space-MNIInfant_cohort-1_dseg.nii.gz atlases/atlas-HCP -atlases/atlas-HCP/atlas-HCP_dseg.json atlases/atlas-HCP/atlas-HCP_dseg.tsv +atlases/atlas-HCP/atlas-HCP_space-MNIInfant_cohort-1_dseg.json atlases/atlas-HCP/atlas-HCP_space-MNIInfant_cohort-1_dseg.nii.gz atlases/atlas-Tian -atlases/atlas-Tian/atlas-Tian_dseg.json atlases/atlas-Tian/atlas-Tian_dseg.tsv +atlases/atlas-Tian/atlas-Tian_space-MNIInfant_cohort-1_dseg.json atlases/atlas-Tian/atlas-Tian_space-MNIInfant_cohort-1_dseg.nii.gz atlases/dataset_description.json dataset_description.json diff --git a/xcp_d/tests/data/test_pnc_cifti_outputs.txt b/xcp_d/tests/data/test_pnc_cifti_outputs.txt index 41c5cae2a..90c1e7e43 100644 --- a/xcp_d/tests/data/test_pnc_cifti_outputs.txt +++ b/xcp_d/tests/data/test_pnc_cifti_outputs.txt @@ -1,16 +1,16 @@ atlases atlases/atlas-HCP -atlases/atlas-HCP/atlas-HCP_dseg.json atlases/atlas-HCP/atlas-HCP_dseg.tsv atlases/atlas-HCP/atlas-HCP_space-fsLR_den-91k_dseg.dlabel.nii +atlases/atlas-HCP/atlas-HCP_space-fsLR_den-91k_dseg.json atlases/atlas-MyersLabonte -atlases/atlas-MyersLabonte/atlas-MyersLabonte_dseg.json atlases/atlas-MyersLabonte/atlas-MyersLabonte_dseg.tsv atlases/atlas-MyersLabonte/atlas-MyersLabonte_space-fsLR_den-91k_dseg.dlabel.nii +atlases/atlas-MyersLabonte/atlas-MyersLabonte_space-fsLR_den-91k_dseg.json atlases/atlas-Tian -atlases/atlas-Tian/atlas-Tian_dseg.json atlases/atlas-Tian/atlas-Tian_dseg.tsv atlases/atlas-Tian/atlas-Tian_space-fsLR_den-91k_dseg.dlabel.nii +atlases/atlas-Tian/atlas-Tian_space-fsLR_den-91k_dseg.json atlases/dataset_description.json dataset_description.json logs diff --git a/xcp_d/tests/data/test_pnc_cifti_t2wonly_outputs.txt b/xcp_d/tests/data/test_pnc_cifti_t2wonly_outputs.txt index c8ae9972e..45f6ccebb 100644 --- a/xcp_d/tests/data/test_pnc_cifti_t2wonly_outputs.txt +++ b/xcp_d/tests/data/test_pnc_cifti_t2wonly_outputs.txt @@ -1,12 +1,12 @@ atlases atlases/atlas-4S156Parcels -atlases/atlas-4S156Parcels/atlas-4S156Parcels_dseg.json atlases/atlas-4S156Parcels/atlas-4S156Parcels_dseg.tsv atlases/atlas-4S156Parcels/atlas-4S156Parcels_space-fsLR_den-91k_dseg.dlabel.nii +atlases/atlas-4S156Parcels/atlas-4S156Parcels_space-fsLR_den-91k_dseg.json atlases/atlas-MIDB -atlases/atlas-MIDB/atlas-MIDB_dseg.json atlases/atlas-MIDB/atlas-MIDB_dseg.tsv atlases/atlas-MIDB/atlas-MIDB_space-fsLR_den-91k_dseg.dlabel.nii +atlases/atlas-MIDB/atlas-MIDB_space-fsLR_den-91k_dseg.json atlases/dataset_description.json dataset_description.json logs diff --git a/xcp_d/tests/data/test_ukbiobank_outputs.txt b/xcp_d/tests/data/test_ukbiobank_outputs.txt index 0d94a8fc4..805c61aa6 100644 --- a/xcp_d/tests/data/test_ukbiobank_outputs.txt +++ b/xcp_d/tests/data/test_ukbiobank_outputs.txt @@ -1,59 +1,59 @@ atlases atlases/atlas-4S1056Parcels -atlases/atlas-4S1056Parcels/atlas-4S1056Parcels_dseg.json atlases/atlas-4S1056Parcels/atlas-4S1056Parcels_dseg.tsv +atlases/atlas-4S1056Parcels/atlas-4S1056Parcels_space-MNI152NLin6Asym_dseg.json atlases/atlas-4S1056Parcels/atlas-4S1056Parcels_space-MNI152NLin6Asym_dseg.nii.gz atlases/atlas-4S156Parcels -atlases/atlas-4S156Parcels/atlas-4S156Parcels_dseg.json atlases/atlas-4S156Parcels/atlas-4S156Parcels_dseg.tsv +atlases/atlas-4S156Parcels/atlas-4S156Parcels_space-MNI152NLin6Asym_dseg.json atlases/atlas-4S156Parcels/atlas-4S156Parcels_space-MNI152NLin6Asym_dseg.nii.gz atlases/atlas-4S256Parcels -atlases/atlas-4S256Parcels/atlas-4S256Parcels_dseg.json atlases/atlas-4S256Parcels/atlas-4S256Parcels_dseg.tsv +atlases/atlas-4S256Parcels/atlas-4S256Parcels_space-MNI152NLin6Asym_dseg.json atlases/atlas-4S256Parcels/atlas-4S256Parcels_space-MNI152NLin6Asym_dseg.nii.gz atlases/atlas-4S356Parcels -atlases/atlas-4S356Parcels/atlas-4S356Parcels_dseg.json atlases/atlas-4S356Parcels/atlas-4S356Parcels_dseg.tsv +atlases/atlas-4S356Parcels/atlas-4S356Parcels_space-MNI152NLin6Asym_dseg.json atlases/atlas-4S356Parcels/atlas-4S356Parcels_space-MNI152NLin6Asym_dseg.nii.gz atlases/atlas-4S456Parcels -atlases/atlas-4S456Parcels/atlas-4S456Parcels_dseg.json atlases/atlas-4S456Parcels/atlas-4S456Parcels_dseg.tsv +atlases/atlas-4S456Parcels/atlas-4S456Parcels_space-MNI152NLin6Asym_dseg.json atlases/atlas-4S456Parcels/atlas-4S456Parcels_space-MNI152NLin6Asym_dseg.nii.gz atlases/atlas-4S556Parcels -atlases/atlas-4S556Parcels/atlas-4S556Parcels_dseg.json atlases/atlas-4S556Parcels/atlas-4S556Parcels_dseg.tsv +atlases/atlas-4S556Parcels/atlas-4S556Parcels_space-MNI152NLin6Asym_dseg.json atlases/atlas-4S556Parcels/atlas-4S556Parcels_space-MNI152NLin6Asym_dseg.nii.gz atlases/atlas-4S656Parcels -atlases/atlas-4S656Parcels/atlas-4S656Parcels_dseg.json atlases/atlas-4S656Parcels/atlas-4S656Parcels_dseg.tsv +atlases/atlas-4S656Parcels/atlas-4S656Parcels_space-MNI152NLin6Asym_dseg.json atlases/atlas-4S656Parcels/atlas-4S656Parcels_space-MNI152NLin6Asym_dseg.nii.gz atlases/atlas-4S756Parcels -atlases/atlas-4S756Parcels/atlas-4S756Parcels_dseg.json atlases/atlas-4S756Parcels/atlas-4S756Parcels_dseg.tsv +atlases/atlas-4S756Parcels/atlas-4S756Parcels_space-MNI152NLin6Asym_dseg.json atlases/atlas-4S756Parcels/atlas-4S756Parcels_space-MNI152NLin6Asym_dseg.nii.gz atlases/atlas-4S856Parcels -atlases/atlas-4S856Parcels/atlas-4S856Parcels_dseg.json atlases/atlas-4S856Parcels/atlas-4S856Parcels_dseg.tsv +atlases/atlas-4S856Parcels/atlas-4S856Parcels_space-MNI152NLin6Asym_dseg.json atlases/atlas-4S856Parcels/atlas-4S856Parcels_space-MNI152NLin6Asym_dseg.nii.gz atlases/atlas-4S956Parcels -atlases/atlas-4S956Parcels/atlas-4S956Parcels_dseg.json atlases/atlas-4S956Parcels/atlas-4S956Parcels_dseg.tsv +atlases/atlas-4S956Parcels/atlas-4S956Parcels_space-MNI152NLin6Asym_dseg.json atlases/atlas-4S956Parcels/atlas-4S956Parcels_space-MNI152NLin6Asym_dseg.nii.gz atlases/atlas-Glasser -atlases/atlas-Glasser/atlas-Glasser_dseg.json atlases/atlas-Glasser/atlas-Glasser_dseg.tsv +atlases/atlas-Glasser/atlas-Glasser_space-MNI152NLin6Asym_dseg.json atlases/atlas-Glasser/atlas-Glasser_space-MNI152NLin6Asym_dseg.nii.gz atlases/atlas-Gordon -atlases/atlas-Gordon/atlas-Gordon_dseg.json atlases/atlas-Gordon/atlas-Gordon_dseg.tsv +atlases/atlas-Gordon/atlas-Gordon_space-MNI152NLin6Asym_dseg.json atlases/atlas-Gordon/atlas-Gordon_space-MNI152NLin6Asym_dseg.nii.gz atlases/atlas-HCP -atlases/atlas-HCP/atlas-HCP_dseg.json atlases/atlas-HCP/atlas-HCP_dseg.tsv +atlases/atlas-HCP/atlas-HCP_space-MNI152NLin6Asym_dseg.json atlases/atlas-HCP/atlas-HCP_space-MNI152NLin6Asym_dseg.nii.gz atlases/atlas-Tian -atlases/atlas-Tian/atlas-Tian_dseg.json atlases/atlas-Tian/atlas-Tian_dseg.tsv +atlases/atlas-Tian/atlas-Tian_space-MNI152NLin6Asym_dseg.json atlases/atlas-Tian/atlas-Tian_space-MNI152NLin6Asym_dseg.nii.gz atlases/dataset_description.json dataset_description.json diff --git a/xcp_d/tests/test_cli.py b/xcp_d/tests/test_cli.py index f582a7e0a..16003d087 100644 --- a/xcp_d/tests/test_cli.py +++ b/xcp_d/tests/test_cli.py @@ -290,6 +290,7 @@ def test_fmriprep_without_freesurfer(data_dir, output_dir, working_dir): test_name = "test_fmriprep_without_freesurfer" dataset_dir = download_test_data("fmriprepwithoutfreesurfer", data_dir) + atlas_dir = download_test_data("schaefer100", data_dir) tmpdir = os.path.join(output_dir, test_name) out_dir = os.path.join(tmpdir, "xcp_d") work_dir = os.path.join(working_dir, test_name) @@ -301,6 +302,11 @@ def test_fmriprep_without_freesurfer(data_dir, output_dir, working_dir): "--mode=linc", f"-w={work_dir}", "--file-format=nifti", + "--datasets", + f"schaefer={atlas_dir}", + "--atlases", + "4S156Parcels", + "Schaefer100", "--nthreads=2", "--omp-nthreads=2", "--head_radius=40", @@ -338,6 +344,7 @@ def test_fmriprep_without_freesurfer_with_main(data_dir, output_dir, working_dir test_name = "test_fmriprep_without_freesurfer" dataset_dir = download_test_data("fmriprepwithoutfreesurfer", data_dir) + atlas_dir = download_test_data("schaefer100", data_dir) tmpdir = os.path.join(output_dir, f"{test_name}_with_main") out_dir = os.path.join(tmpdir, "xcp_d") work_dir = os.path.join(working_dir, f"{test_name}_with_main") @@ -349,6 +356,11 @@ def test_fmriprep_without_freesurfer_with_main(data_dir, output_dir, working_dir "--mode=linc", f"-w={work_dir}", "--file-format=nifti", + "--datasets", + f"schaefer={atlas_dir}", + "--atlases", + "4S156Parcels", + "Schaefer100", "--nthreads=2", "--omp-nthreads=2", "--head_radius=40", diff --git a/xcp_d/tests/test_cli_run.py b/xcp_d/tests/test_cli_run.py index 9019dcc27..1ad18a867 100644 --- a/xcp_d/tests/test_cli_run.py +++ b/xcp_d/tests/test_cli_run.py @@ -29,6 +29,7 @@ def base_opts(): "output_dir": Path("out"), "work_dir": Path("work"), "analysis_level": "participant", + "datasets": {}, "mode": "linc", "file_format": "auto", "input_type": "auto", diff --git a/xcp_d/tests/test_cli_utils.py b/xcp_d/tests/test_cli_utils.py index 8d6ec3b1c..9c2494ac4 100644 --- a/xcp_d/tests/test_cli_utils.py +++ b/xcp_d/tests/test_cli_utils.py @@ -164,3 +164,71 @@ def test_bids_filter(tmp_path_factory): parser = ArgumentParser() result = parser_utils._bids_filter(None, parser) assert result is None + + +def test_yes_no_action(): + """Test parser_utils.YesNoAction.""" + parser = ArgumentParser() + parser.add_argument("--option", nargs="?", action=parser_utils.YesNoAction) + + # A value of y should be True + args = parser.parse_args(["--option", "y"]) + assert args.option is True + + # A value of n should be False + args = parser.parse_args(["--option", "n"]) + assert args.option is False + + # The parameter without a value should default to True + args = parser.parse_args(["--option"]) + assert args.option is True + + # Auto is an option + args = parser.parse_args(["--option", "auto"]) + assert args.option == "auto" + + # Invalid value raises an error + with pytest.raises(SystemExit): + parser.parse_args(["--option", "invalid"]) + + +def test_to_dict(): + """Test parser_utils.ToDict.""" + parser = ArgumentParser() + parser.add_argument("--option", action=parser_utils.ToDict, nargs="+") + + # Two key-value pairs + args = parser.parse_args(["--option", "key1=value1", "key2=value2"]) + assert args.option == {"key1": Path("value1"), "key2": Path("value2")} + + # Providing the same key twice + with pytest.raises(SystemExit): + parser.parse_args(["--option", "key1=value1", "key1=value2"]) + + # Trying to use one of the reserved keys + with pytest.raises(SystemExit): + parser.parse_args(["--option", "preprocessed=value1"]) + + # Dataset with no name + args = parser.parse_args(["--option", "value1"]) + assert args.option == {"value1": Path("value1")} + + +def test_confounds_action(tmp_path): + """Test parser_utils.ConfoundsAction.""" + parser = ArgumentParser() + parser.add_argument("--confounds", action=parser_utils.ConfoundsAction) + + # A value of auto should be "auto" + args = parser.parse_args(["--confounds", "auto"]) + assert args.confounds == "auto" + + # A valid custom confounds option + valid_path = tmp_path / "valid_confounds.yml" + valid_path.touch() # Create the file + args = parser.parse_args(["--confounds", str(valid_path)]) + assert args.confounds == valid_path + + # Path to a non-existent file should raise an error + with pytest.raises(SystemExit): + parser.parse_args(["--confounds", "/invalid/path/to/confounds.yml"]) diff --git a/xcp_d/tests/test_interfaces_bids.py b/xcp_d/tests/test_interfaces_bids.py index e71b185f4..55d39ea4b 100644 --- a/xcp_d/tests/test_interfaces_bids.py +++ b/xcp_d/tests/test_interfaces_bids.py @@ -5,7 +5,7 @@ import pytest from xcp_d.interfaces import bids -from xcp_d.utils import atlas +from xcp_d.data import load as load_data def test_copy_atlas(tmp_path_factory): @@ -14,13 +14,25 @@ def test_copy_atlas(tmp_path_factory): os.makedirs(os.path.join(tmpdir, "xcp_d"), exist_ok=True) # NIfTI - atlas_file, _, _ = atlas.get_atlas_nifti("Gordon") + atlas_info = { + "image": load_data( + "atlases/atlas-Gordon/atlas-Gordon_space-MNI152NLin6Asym_res-01_dseg.nii.gz" + ), + "labels": load_data("atlases/atlas-Gordon/atlas-Gordon_dseg.tsv"), + "metadata": {"thing": "stuff"}, + "dataset": "xcpdatlases", + } name_source = "sub-01_task-A_run-01_space-MNI152NLin2009cAsym_res-2_desc-z_bold.nii.gz" copyatlas = bids.CopyAtlas( - name_source=name_source, in_file=atlas_file, output_dir=tmpdir, atlas="Y" + name_source=name_source, + in_file=atlas_info["image"], + output_dir=tmpdir, + atlas="Y", + meta_dict=atlas_info["metadata"], ) result = copyatlas.run(cwd=tmpdir) assert os.path.isfile(result.outputs.out_file) + assert os.path.isfile(result.outputs.out_file.replace(".nii.gz", ".json")) assert ( os.path.basename(result.outputs.out_file) == "atlas-Y_space-MNI152NLin2009cAsym_res-2_dseg.nii.gz" @@ -28,24 +40,39 @@ def test_copy_atlas(tmp_path_factory): # Check that the NIfTI file raises an error if the resolution varies # Gordon atlas is 1mm, HCP is 2mm - atlas_file_diff_affine, _, _ = atlas.get_atlas_nifti("HCP") + atlas_info_diff_affine = { + "image": load_data("atlases/atlas-HCP/atlas-HCP_space-MNI152NLin6Asym_res-02_dseg.nii.gz"), + "labels": load_data("atlases/atlas-HCP/atlas-HCP_dseg.tsv"), + "metadata": {"thing": "stuff"}, + "dataset": "xcpdatlases", + } with pytest.raises(ValueError, match="is different from the input file affine"): copyatlas = bids.CopyAtlas( name_source=name_source, - in_file=atlas_file_diff_affine, + in_file=atlas_info_diff_affine["image"], output_dir=tmpdir, atlas="Y", ) copyatlas.run(cwd=tmpdir) # CIFTI - atlas_file, atlas_labels_file, atlas_metadata_file = atlas.get_atlas_cifti("Gordon") + atlas_info = { + "image": load_data("atlases/atlas-Gordon/atlas-Gordon_space-fsLR_den-32k_dseg.dlabel.nii"), + "labels": load_data("atlases/atlas-Gordon/atlas-Gordon_dseg.tsv"), + "metadata": {"thing": "stuff"}, + "dataset": "xcpdatlases", + } name_source = "sub-01_task-imagery_run-01_space-fsLR_den-91k_desc-denoised_bold.dtseries.nii" copyatlas = bids.CopyAtlas( - name_source=name_source, in_file=atlas_file, output_dir=tmpdir, atlas="Y" + name_source=name_source, + in_file=atlas_info["image"], + output_dir=tmpdir, + atlas="Y", + meta_dict=atlas_info["metadata"], ) result = copyatlas.run(cwd=tmpdir) assert os.path.isfile(result.outputs.out_file) + assert os.path.isfile(result.outputs.out_file.replace(".dlabel.nii", ".json")) assert ( os.path.basename(result.outputs.out_file) == "atlas-Y_space-fsLR_den-91k_dseg.dlabel.nii" ) @@ -53,23 +80,14 @@ def test_copy_atlas(tmp_path_factory): # TSV name_source = "sub-01_task-imagery_run-01_space-fsLR_den-91k_desc-denoised_bold.dtseries.nii" copyatlas = bids.CopyAtlas( - name_source=name_source, in_file=atlas_labels_file, output_dir=tmpdir, atlas="Y" + name_source=name_source, in_file=atlas_info["labels"], output_dir=tmpdir, atlas="Y" ) result = copyatlas.run(cwd=tmpdir) assert os.path.isfile(result.outputs.out_file) assert os.path.basename(result.outputs.out_file) == "atlas-Y_dseg.tsv" - # JSON - name_source = "sub-01_task-imagery_run-01_space-fsLR_den-91k_desc-denoised_bold.dtseries.nii" - copyatlas = bids.CopyAtlas( - name_source=name_source, in_file=atlas_metadata_file, output_dir=tmpdir, atlas="Y" - ) - result = copyatlas.run(cwd=tmpdir) - assert os.path.isfile(result.outputs.out_file) - assert os.path.basename(result.outputs.out_file) == "atlas-Y_dseg.json" - # Ensure that out_file isn't overwritten if it already exists - fake_in_file = os.path.join(tmpdir, "fake.json") + fake_in_file = os.path.join(tmpdir, "fake.tsv") with open(fake_in_file, "w") as fo: fo.write("fake") @@ -78,7 +96,7 @@ def test_copy_atlas(tmp_path_factory): ) result = copyatlas.run(cwd=tmpdir) assert os.path.isfile(result.outputs.out_file) - assert os.path.basename(result.outputs.out_file) == "atlas-Y_dseg.json" + assert os.path.basename(result.outputs.out_file) == "atlas-Y_dseg.tsv" # The file should not be overwritten, so the contents shouldn't be "fake" with open(result.outputs.out_file, "r") as fo: assert fo.read() != "fake" diff --git a/xcp_d/tests/test_utils_atlas.py b/xcp_d/tests/test_utils_atlas.py index 0ddfee7e8..9e06adaf4 100644 --- a/xcp_d/tests/test_utils_atlas.py +++ b/xcp_d/tests/test_utils_atlas.py @@ -1,9 +1,10 @@ """Tests for the xcp_d.utils.atlas module.""" -import os +import json import pytest +from xcp_d.data import load as load_data from xcp_d.utils import atlas @@ -15,39 +16,108 @@ def test_get_atlas_names(): assert len(selected_atlases) == 2 -def test_get_atlas_nifti(): - """Test xcp_d.utils.atlas.get_atlas_nifti.""" - selected_atlases = atlas.select_atlases( - atlases=["4S156Parcels", "4S256Parcels", "Tian"], - subset="all", +def test_collect_atlases(datasets, caplog, tmp_path_factory): + """Test xcp_d.utils.atlas.collect_atlases.""" + schaefer_dset = datasets["schaefer100"] + + atlas_datasets = { + "xcpdatlases": str(load_data("atlases")), + } + atlas_cache = atlas.collect_atlases( + datasets=atlas_datasets, + atlases=["Gordon", "Schaefer100"], + file_format="nifti", + bids_filters={}, + ) + assert "Gordon" in atlas_cache + assert "Schaefer100" not in atlas_cache + assert "No atlas images found for Schaefer100" in caplog.text + + # Add the schaefer dataset + atlas_datasets["schaefer100"] = schaefer_dset + atlas_cache = atlas.collect_atlases( + datasets=atlas_datasets, + atlases=["Gordon", "Schaefer100"], + file_format="nifti", + bids_filters={}, + ) + assert "Gordon" in atlas_cache + assert "Schaefer100" in atlas_cache + + # Skip over the schaefer dataset + atlas_datasets["schaefer100"] = schaefer_dset + atlas_cache = atlas.collect_atlases( + datasets=atlas_datasets, + atlases=["Gordon"], + file_format="cifti", + bids_filters={}, + ) + assert "Gordon" in atlas_cache + assert "Schaefer100" not in atlas_cache + + # Add a duplicate atlas + atlas_datasets["duplicate"] = str(load_data("atlases")) + with pytest.raises(ValueError, match="Multiple datasets contain the same atlas 'Gordon'"): + atlas.collect_atlases( + datasets=atlas_datasets, + atlases=["Gordon"], + file_format="nifti", + bids_filters={}, + ) + + # Create a dataset that has atlases, but is missing information + tmpdir = tmp_path_factory.mktemp("test_collect_atlases") + # Make the dataset_description.json + with open(tmpdir / "dataset_description.json", "w") as fo: + json.dump({"DatasetType": "atlas", "BIDSVersion": "1.9.0", "Name": "Test"}, fo) + + # Create fake atlas file + (tmpdir / "atlas-TEST").mkdir() + (tmpdir / "atlas-TEST" / "atlas-TEST_space-MNI152NLin6Asym_res-01_dseg.nii.gz").write_text( + "test" ) - for selected_atlas in selected_atlases: - atlas_file, atlas_labels_file, metadata_file = atlas.get_atlas_nifti(selected_atlas) - assert isinstance(atlas_file, str) - assert isinstance(atlas_labels_file, str) - assert isinstance(metadata_file, str) - assert os.path.isfile(atlas_file) - assert os.path.isfile(atlas_labels_file) - assert os.path.isfile(metadata_file) - - with pytest.raises(FileNotFoundError, match="DNE"): - atlas.get_atlas_nifti("tofail") - - -def test_get_atlas_cifti(): - """Test xcp_d.utils.atlas.get_atlas_cifti.""" - selected_atlases = atlas.select_atlases( - atlases=["4S156Parcels", "4S256Parcels", "MIDB", "MyersLabonte", "Tian"], - subset="all", + + # First there's an image, but no TSV or metadata + with pytest.raises(FileNotFoundError, match="No TSV file found for"): + atlas.collect_atlases( + datasets={"test": tmpdir}, + atlases=["TEST"], + file_format="nifti", + bids_filters={}, + ) + + # Now there's an image and a TSV, but the TSV doesn't have a "label" column + with open(tmpdir / "atlas-TEST" / "atlas-TEST_dseg.tsv", "w") as fo: + fo.write("index\n1\n") + + with pytest.raises(ValueError, match="'label' column not found"): + atlas.collect_atlases( + datasets={"test": tmpdir}, + atlases=["TEST"], + file_format="nifti", + bids_filters={}, + ) + + # Now there's an image and a TSV, but the TSV doesn't have an "index" column + with open(tmpdir / "atlas-TEST" / "atlas-TEST_dseg.tsv", "w") as fo: + fo.write("label\ntest\n") + + with pytest.raises(ValueError, match="'index' column not found"): + atlas.collect_atlases( + datasets={"test": tmpdir}, + atlases=["TEST"], + file_format="nifti", + bids_filters={}, + ) + + # Now there's an image, a TSV, and metadata + with open(tmpdir / "atlas-TEST" / "atlas-TEST_dseg.tsv", "w") as fo: + fo.write("index\tlabel\n1\ttest\n") + + atlas_cache = atlas.collect_atlases( + datasets={"test": tmpdir}, + atlases=["TEST"], + file_format="nifti", + bids_filters={}, ) - for selected_atlas in selected_atlases: - atlas_file, atlas_labels_file, metadata_file = atlas.get_atlas_cifti(selected_atlas) - assert isinstance(atlas_file, str) - assert isinstance(atlas_labels_file, str) - assert isinstance(metadata_file, str) - assert os.path.isfile(atlas_file) - assert os.path.isfile(atlas_labels_file) - assert os.path.isfile(metadata_file) - - with pytest.raises(FileNotFoundError, match="DNE"): - atlas.get_atlas_cifti("tofail") + assert "TEST" in atlas_cache diff --git a/xcp_d/tests/test_utils_boilerplate.py b/xcp_d/tests/test_utils_boilerplate.py index dbe7912c5..808e26332 100644 --- a/xcp_d/tests/test_utils_boilerplate.py +++ b/xcp_d/tests/test_utils_boilerplate.py @@ -191,6 +191,6 @@ def test_describe_atlases(): assert "Tian" in atlas_desc assert "Glasser" in atlas_desc + # This no longer fails. It just adds the missing atlas to the description. atlases = ["4S156Parcels", "4S256Parcels", "Glasser", "fail"] - with pytest.raises(ValueError, match="Unrecognized atlas"): - boilerplate.describe_atlases(atlases) + assert "the fail atlas" in boilerplate.describe_atlases(atlases) diff --git a/xcp_d/tests/test_utils_utils.py b/xcp_d/tests/test_utils_utils.py index 5d85a3897..26bd24b7b 100644 --- a/xcp_d/tests/test_utils_utils.py +++ b/xcp_d/tests/test_utils_utils.py @@ -454,34 +454,63 @@ def test_get_bold2std_and_t1w_xfms(ds001419_data): def test_get_std2bold_xfms(ds001419_data): """Test get_std2bold_xfms. - get_std2bold_xfms finds transforms to go from the input file's space to MNI152NLin6Asym. + get_std2bold_xfms finds transforms to go from a source file's space to the BOLD file's space. """ bold_file_nlin6asym = ds001419_data["nifti_file"] - # MNI152NLin6Asym --> MNI152NLin6Asym - xforms_to_mni = utils.get_std2bold_xfms(bold_file_nlin6asym) + # MNI152NLin6Asym --> MNI152NLin6Asym with source file containing tpl entity + xforms_to_mni = utils.get_std2bold_xfms( + bold_file_nlin6asym, + source_file="tpl-MNI152NLin6Asym_T1w.nii.gz", + source_space=None, + ) assert len(xforms_to_mni) == 1 - # MNI152NLin6Asym --> MNI152NLin2009cAsym - bold_file_nlin2009c = bold_file_nlin6asym.replace( - "space-MNI152NLin6Asym_", - "space-MNI152NLin2009cAsym_", + # MNI152NLin6Asym --> MNI152NLin6Asym with source file containing space entity + xforms_to_mni = utils.get_std2bold_xfms( + bold_file_nlin6asym, + source_file="space-MNI152NLin6Asym_T1w.nii.gz", + source_space=None, ) - xforms_to_mni = utils.get_std2bold_xfms(bold_file_nlin2009c) assert len(xforms_to_mni) == 1 - # MNI152NLin6Asym --> MNIInfant - bold_file_infant = bold_file_nlin6asym.replace( - "space-MNI152NLin6Asym_", - "space-MNIInfant_cohort-1_", - ) - xforms_to_mni = utils.get_std2bold_xfms(bold_file_infant) - assert len(xforms_to_mni) == 2 + SPACES = [ + ("MNI152NLin6Asym", "MNI152NLin6Asym", 1), + ("MNI152NLin6Asym", "MNI152NLin2009cAsym", 1), + ("MNI152NLin6Asym", "MNIInfant", 2), + ("MNI152NLin2009cAsym", "MNI152NLin2009cAsym", 1), + ("MNI152NLin2009cAsym", "MNI152NLin6Asym", 1), + ("MNI152NLin2009cAsym", "MNIInfant", 1), + ("MNIInfant", "MNIInfant", 1), + ("MNIInfant", "MNI152NLin2009cAsym", 1), + ("MNIInfant", "MNI152NLin6Asym", 2), + ] + for space_check in SPACES: + target_space, source_space, n_xforms = space_check + bold_file_target_space = bold_file_nlin6asym.replace( + "space-MNI152NLin6Asym_", + f"space-{target_space}_", + ) + xforms_to_mni = utils.get_std2bold_xfms( + bold_file_target_space, + source_file=None, + source_space=source_space, + ) + assert len(xforms_to_mni) == n_xforms + + # Outside of the supported spaces, we expect an error + # No space or tpl entity in source file + with pytest.raises(ValueError, match="Unknown space"): + utils.get_std2bold_xfms(bold_file_nlin6asym, source_file="T1w.nii.gz", source_space=None) # MNI152NLin6Asym --> tofail bold_file_tofail = bold_file_nlin6asym.replace("space-MNI152NLin6Asym_", "space-tofail_") - with pytest.raises(ValueError, match="Space 'tofail'"): - utils.get_std2bold_xfms(bold_file_tofail) + with pytest.raises(ValueError, match="BOLD space 'tofail' not supported"): + utils.get_std2bold_xfms(bold_file_tofail, source_file=None, source_space="MNI152NLin6Asym") + + # tofail --> MNI152NLin6Asym + with pytest.raises(ValueError, match="Source space 'tofail' not supported"): + utils.get_std2bold_xfms(bold_file_nlin6asym, source_file=None, source_space="tofail") def test_fwhm2sigma(): diff --git a/xcp_d/tests/test_workflows_connectivity.py b/xcp_d/tests/test_workflows_connectivity.py index f8c7420cd..276ed6a25 100644 --- a/xcp_d/tests/test_workflows_connectivity.py +++ b/xcp_d/tests/test_workflows_connectivity.py @@ -9,11 +9,11 @@ from nilearn.maskers import NiftiLabelsMasker from xcp_d import config +from xcp_d.data import load as load_data from xcp_d.interfaces.ants import ApplyTransforms from xcp_d.interfaces.connectivity import _sanitize_nifti_atlas from xcp_d.tests.tests import mock_config from xcp_d.tests.utils import get_nodes -from xcp_d.utils.atlas import get_atlas_cifti, get_atlas_nifti from xcp_d.utils.bids import _get_tr from xcp_d.utils.utils import _create_mem_gb, get_std2bold_xfms from xcp_d.utils.write_save import read_ndata, write_ndata @@ -37,6 +37,10 @@ def test_init_load_atlases_wf_nifti(ds001419_data, tmp_path_factory): config.execution.output_dir = tmpdir config.workflow.file_format = "nifti" config.execution.atlases = ["4S156Parcels", "Glasser"] + config.execution.datasets = { + "xcpdatlases": str(load_data("atlases")), + "xcpd4s": "/AtlasPack", + } config.nipype.omp_nthreads = 1 load_atlases_wf = init_load_atlases_wf(name="load_atlases_wf") @@ -62,6 +66,10 @@ def test_init_load_atlases_wf_cifti(ds001419_data, tmp_path_factory): config.execution.output_dir = tmpdir config.workflow.file_format = "cifti" config.execution.atlases = ["4S156Parcels", "Glasser"] + config.execution.datasets = { + "xcpdatlases": str(load_data("atlases")), + "xcpd4s": "/AtlasPack", + } config.nipype.omp_nthreads = 1 load_atlases_wf = init_load_atlases_wf(name="load_atlases_wf") @@ -111,13 +119,29 @@ def test_init_functional_connectivity_nifti_wf(ds001419_data, tmp_path_factory): # Load atlases atlas_names = ["Gordon", "Glasser"] - atlas_files = [get_atlas_nifti(atlas_name)[0] for atlas_name in atlas_names] - atlas_labels_files = [get_atlas_nifti(atlas_name)[1] for atlas_name in atlas_names] + atlas_files = [ + str( + load_data("atlases/atlas-Gordon/atlas-Gordon_space-MNI152NLin6Asym_res-01_dseg.nii.gz") + ), + str( + load_data( + "atlases/atlas-Glasser/atlas-Glasser_space-MNI152NLin6Asym_res-01_dseg.nii.gz" + ) + ), + ] + atlas_labels_files = [ + str(load_data("atlases/atlas-Gordon/atlas-Gordon_dseg.tsv")), + str(load_data("atlases/atlas-Glasser/atlas-Glasser_dseg.tsv")), + ] # Perform the resampling and parcellation done by init_load_atlases_wf warped_atlases = [] # Get transform(s) from MNI152NLin6Asym to BOLD file's space - transforms_from_MNI152NLin6Asym = get_std2bold_xfms(bold_file) + transforms_from_MNI152NLin6Asym = get_std2bold_xfms( + bold_file, + source_file=None, + source_space="MNI152NLin6Asym", + ) for atlas_file in atlas_files: # Using the generated transforms, apply them to get everything in the correct MNI form warp_atlases_to_bold_space = ApplyTransforms( @@ -262,8 +286,20 @@ def test_init_functional_connectivity_cifti_wf(ds001419_data, tmp_path_factory): # Load atlases atlas_names = ["4S1056Parcels", "4S156Parcels", "4S456Parcels", "Gordon", "Glasser"] - atlas_files = [get_atlas_cifti(atlas_name)[0] for atlas_name in atlas_names] - atlas_labels_files = [get_atlas_cifti(atlas_name)[1] for atlas_name in atlas_names] + atlas_files = [ + "/AtlasPack/atlas-4S1056Parcels/atlas-4S1056Parcels_space-fsLR_den-91k_dseg.dlabel.nii", + "/AtlasPack/atlas-4S156Parcels/atlas-4S156Parcels_space-fsLR_den-91k_dseg.dlabel.nii", + "/AtlasPack/atlas-4S456Parcels/atlas-4S456Parcels_space-fsLR_den-91k_dseg.dlabel.nii", + str(load_data("atlases/atlas-Gordon/atlas-Gordon_space-fsLR_den-32k_dseg.dlabel.nii")), + str(load_data("atlases/atlas-Glasser/atlas-Glasser_space-fsLR_den-32k_dseg.dlabel.nii")), + ] + atlas_labels_files = [ + "/AtlasPack/atlas-4S1056Parcels/atlas-4S1056Parcels_dseg.tsv", + "/AtlasPack/atlas-4S156Parcels/atlas-4S156Parcels_dseg.tsv", + "/AtlasPack/atlas-4S456Parcels/atlas-4S456Parcels_dseg.tsv", + str(load_data("atlases/atlas-Gordon/atlas-Gordon_dseg.tsv")), + str(load_data("atlases/atlas-Glasser/atlas-Glasser_dseg.tsv")), + ] # Create the node and a tmpdir to write its results out to with mock_config(): diff --git a/xcp_d/tests/utils.py b/xcp_d/tests/utils.py index 0cd3d88bb..d1851e390 100644 --- a/xcp_d/tests/utils.py +++ b/xcp_d/tests/utils.py @@ -66,6 +66,9 @@ def download_test_data(dset, data_dir=None): ), "pnc": "https://upenn.box.com/shared/static/ui2847ys49d82pgn5ewai1mowcmsv2br.tar.gz", "ukbiobank": "https://upenn.box.com/shared/static/p5h1eg4p5cd2ef9ehhljlyh1uku0xe97.tar.gz", + "schaefer100": ( + "https://upenn.box.com/shared/static/b9pn9qebr41kteant4ym2q5u4kcbgiy6.tar.gz" + ), } if dset == "*": for k in URLS: diff --git a/xcp_d/utils/atlas.py b/xcp_d/utils/atlas.py index 41c21558a..557b466ea 100644 --- a/xcp_d/utils/atlas.py +++ b/xcp_d/utils/atlas.py @@ -1,5 +1,9 @@ """Functions for working with atlases.""" +from nipype import logging + +LOGGER = logging.getLogger("nipype.utils") + def select_atlases(atlases, subset): """Get a list of atlases to be used for parcellation and functional connectivity analyses. @@ -54,126 +58,122 @@ def select_atlases(atlases, subset): return selected_atlases -def get_atlas_nifti(atlas): - """Select atlas by name from xcp_d/data using load_data. +def collect_atlases(datasets, atlases, file_format, bids_filters={}): + """Collect atlases from a list of BIDS-Atlas datasets. - All atlases are in MNI space. - - NOTE: This is a Node function. + Selection of labels files and metadata does not leverage the inheritance principle. + That probably won't be possible until PyBIDS supports the BIDS-Atlas extension natively. Parameters ---------- - atlas : {"4S156Parcels", "4S256Parcels", "4S356Parcels", "4S456Parcels", \ - "4S556Parcels", "4S656Parcels", "4S756Parcels", "4S856Parcels", \ - "4S956Parcels", "4S1056Parcels", "Glasser", "Gordon", \ - "Tian", "HCP"} - The name of the NIFTI atlas to fetch. + datasets : dict of str:str or str:BIDSLayout pairs + Dictionary of BIDS datasets to search for atlases. + atlases : list of str + List of atlases to collect from across the datasets. + file_format : {"nifti", "cifti"} + The file format of the atlases. + bids_filters : dict + Additional filters to apply to the BIDS query. Returns ------- - atlas_file : :obj:`str` - Path to the atlas file. - atlas_labels_file : :obj:`str` - Path to the atlas labels file. - atlas_metadata_file : :obj:`str` - Path to the atlas metadata file. + atlas_cache : dict + Dictionary of atlases with metadata. + Keys are the atlas names, values are dictionaries with keys: + + - "dataset" : str + Name of the dataset containing the atlas. + - "image" : str + Path to the atlas image. + - "labels" : str + Path to the atlas labels file. + - "metadata" : dict + Metadata associated with the atlas. """ - from os.path import isfile, join - - from xcp_d.data import load as load_data - - if "4S" in atlas: - # 1 mm3 atlases - atlas_fname = f"atlas-{atlas}_space-MNI152NLin6Asym_res-01_dseg.nii.gz" - tsv_fname = f"atlas-{atlas}_dseg.tsv" - elif atlas in ("Glasser", "Gordon"): - # 1 mm3 atlases - atlas_fname = f"tpl-MNI152NLin6Asym_atlas-{atlas}_res-01_dseg.nii.gz" - tsv_fname = f"atlas-{atlas}_dseg.tsv" - else: - # 2 mm3 atlases - atlas_fname = f"tpl-MNI152NLin6Asym_atlas-{atlas}_res-02_dseg.nii.gz" - tsv_fname = f"atlas-{atlas}_dseg.tsv" - - if "4S" in atlas: - atlas_file = join(f"/AtlasPack/atlas-{atlas}", atlas_fname) - atlas_labels_file = join(f"/AtlasPack/atlas-{atlas}", tsv_fname) - atlas_metadata_file = ( - f"/AtlasPack/atlas-{atlas}/atlas-{atlas}_space-MNI152NLin6Asym_res-01_dseg.json" - ) - else: - atlas_file = str(load_data(f"atlases/{atlas_fname}")) - atlas_labels_file = str(load_data(f"atlases/{tsv_fname}")) - atlas_metadata_file = str( - load_data(f"atlases/tpl-MNI152NLin6Asym_atlas-{atlas}_dseg.json") - ) - - if not (isfile(atlas_file) and isfile(atlas_labels_file) and isfile(atlas_metadata_file)): - raise FileNotFoundError( - f"File(s) DNE:\n\t{atlas_file}\n\t{atlas_labels_file}\n\t{atlas_metadata_file}" - ) - - return atlas_file, atlas_labels_file, atlas_metadata_file - - -def get_atlas_cifti(atlas): - """Select atlas by name from xcp_d/data. + import json - All atlases are in 91K space. - - NOTE: This is a Node function. - - Parameters - ---------- - atlas : {"4S156Parcels", "4S256Parcels", "4S356Parcels", "4S456Parcels", \ - "4S556Parcels", "4S656Parcels", "4S756Parcels", "4S856Parcels", \ - "4S956Parcels", "4S1056Parcels", "Glasser", "Gordon", \ - "Tian", "HCP", "MIDB", "MyersLabonte"} - The name of the CIFTI atlas to fetch. - - Returns - ------- - atlas_file : :obj:`str` - Path to the atlas file. - atlas_labels_file : :obj:`str` - The labels TSV file associated with the atlas. - atlas_metadata_file : :obj:`str` - The metadata JSON file associated with the atlas. - """ - from os.path import isfile + import pandas as pd + from bids.layout import BIDSLayout from xcp_d.data import load as load_data - if "4S" in atlas: - atlas_file = f"/AtlasPack/atlas-{atlas}/atlas-{atlas}_space-fsLR_den-91k_dseg.dlabel.nii" - atlas_labels_file = f"/AtlasPack/atlas-{atlas}/atlas-{atlas}_dseg.tsv" - atlas_metadata_file = ( - f"/AtlasPack/atlas-{atlas}/atlas-{atlas}_space-fsLR_den-91k_dseg.json" - ) - elif "MIDB" in atlas: - atlas_file = str( - load_data("atlases/tpl-fsLR_atlas-MIDB_den-32k_desc-abcdThresh75_dseg.dlabel.nii") - ) - atlas_labels_file = str(load_data(f"atlases/atlas-{atlas}_dseg.tsv")) - atlas_metadata_file = str( - load_data("atlases/tpl-fsLR_atlas-MIDB_den-32k_desc-abcdThresh75_dseg.json") - ) - elif "MyersLabonte" in atlas: - atlas_file = str( - load_data("atlases/tpl-fsLR_atlas-MyersLabonte_den-32k_desc-thresh50_dseg.dlabel.nii") - ) - atlas_labels_file = str(load_data("atlases/atlas-MyersLabonte_desc-thresh50_dseg.tsv")) - atlas_metadata_file = str( - load_data("atlases/tpl-fsLR_atlas-MyersLabonte_den-32k_desc-thresh50_dseg.json") - ) - else: - atlas_file = str(load_data(f"atlases/tpl-fsLR_atlas-{atlas}_den-32k_dseg.dlabel.nii")) - atlas_labels_file = str(load_data(f"atlases/atlas-{atlas}_dseg.tsv")) - atlas_metadata_file = str(load_data(f"atlases/tpl-fsLR_atlas-{atlas}_dseg.json")) - - if not (isfile(atlas_file) and isfile(atlas_labels_file) and isfile(atlas_metadata_file)): - raise FileNotFoundError( - f"File(s) DNE:\n\t{atlas_file}\n\t{atlas_labels_file}\n\t{atlas_metadata_file}" - ) + atlas_cfg = load_data("atlas_bids_config.json") + bids_filters = bids_filters or {} - return atlas_file, atlas_labels_file, atlas_metadata_file + atlas_filter = bids_filters.get("atlas", {}) + atlas_filter["extension"] = [".nii.gz", ".nii"] if file_format == "nifti" else ".dlabel.nii" + # Hardcoded spaces for now + if file_format == "cifti": + atlas_filter["space"] = atlas_filter.get("space") or "fsLR" + atlas_filter["den"] = atlas_filter.get("den") or ["32k", "91k"] + else: + atlas_filter["space"] = atlas_filter.get("space") or [ + "MNI152NLin6Asym", + "MNI152NLin2009cAsym", + "MNIInfant", + ] + + atlas_cache = {} + for dataset_name, dataset_path in datasets.items(): + if not isinstance(dataset_path, BIDSLayout): + layout = BIDSLayout(dataset_path, config=[atlas_cfg], validate=False) + else: + layout = dataset_path + + if layout.get_dataset_description().get("DatasetType") != "atlas": + continue + + for atlas in atlases: + atlas_images = layout.get( + atlas=atlas, + **atlas_filter, + return_type="file", + ) + if not atlas_images: + continue + elif len(atlas_images) > 1: + bulleted_list = "\n".join([f" - {img}" for img in atlas_images]) + LOGGER.warning( + f"Multiple atlas images found for {atlas} with query {atlas_filter}:\n" + f"{bulleted_list}\nUsing {atlas_images[0]}." + ) + + if atlas in atlas_cache: + raise ValueError(f"Multiple datasets contain the same atlas '{atlas}'") + + atlas_image = atlas_images[0] + atlas_labels = layout.get_nearest(atlas_image, extension=".tsv", strict=False) + atlas_metadata_file = layout.get_nearest(atlas_image, extension=".json", strict=True) + + if not atlas_labels: + raise FileNotFoundError(f"No TSV file found for {atlas_image}") + + atlas_metadata = None + if atlas_metadata_file: + with open(atlas_metadata_file, "r") as fo: + atlas_metadata = json.load(fo) + + atlas_cache[atlas] = { + "dataset": dataset_name, + "image": atlas_image, + "labels": atlas_labels, + "metadata": atlas_metadata, + } + + for atlas in atlases: + if atlas not in atlas_cache: + LOGGER.warning(f"No atlas images found for {atlas} with query {atlas_filter}") + + for atlas, atlas_info in atlas_cache.items(): + if not atlas_info["labels"]: + raise FileNotFoundError(f"No TSV file found for {atlas_info['image']}") + + # Check the contents of the labels file + df = pd.read_table(atlas_info["labels"]) + if "label" not in df.columns: + raise ValueError(f"'label' column not found in {atlas_info['labels']}") + + if "index" not in df.columns: + raise ValueError(f"'index' column not found in {atlas_info['labels']}") + + return atlas_cache diff --git a/xcp_d/utils/boilerplate.py b/xcp_d/utils/boilerplate.py index 089e288b6..4661fec91 100644 --- a/xcp_d/utils/boilerplate.py +++ b/xcp_d/utils/boilerplate.py @@ -188,10 +188,10 @@ def describe_atlases(atlases): atlas_strings = [] described_atlases = [] - atlases_4s = [atlas for atlas in atlases if atlas.startswith("4S")] + atlases_4s = [atlas for atlas in atlases if str(atlas).startswith("4S")] described_atlases += atlases_4s if atlases_4s: - parcels = [int(atlas[2:-7]) for atlas in atlases_4s] + parcels = [int(str(atlas[2:-7])) for atlas in atlases_4s] s = ( "the Schaefer Supplemented with Subcortical Structures (4S) atlas " "[@Schaefer_2017;@pauli2018high;@king2019functional;@najdenovska2018vivo;" @@ -206,7 +206,7 @@ def describe_atlases(atlases): described_atlases.append(k) undescribed_atlases = [atlas for atlas in atlases if atlas not in described_atlases] - if undescribed_atlases: - raise ValueError(f"Unrecognized atlas(es) in the list: {', '.join(undescribed_atlases)}.") + for atlas in undescribed_atlases: + atlas_strings.append(f"the {atlas} atlas") return list_to_str(atlas_strings) diff --git a/xcp_d/utils/utils.py b/xcp_d/utils/utils.py index a409f11b0..0446a7a9c 100644 --- a/xcp_d/utils/utils.py +++ b/xcp_d/utils/utils.py @@ -130,8 +130,8 @@ def get_bold2std_and_t1w_xfms(bold_file, template_to_anat_xfm): return xforms_to_MNI, xforms_to_MNI_invert, xforms_to_T1w, xforms_to_T1w_invert -def get_std2bold_xfms(bold_file): - """Obtain transforms to warp atlases from MNI152NLin6Asym to the same template as the BOLD. +def get_std2bold_xfms(bold_file, source_file, source_space=None): + """Obtain transforms to warp atlases from a source space to the same template as the BOLD. Since ANTSApplyTransforms takes in the transform files as a stack, these are applied in the reverse order of which they are specified. @@ -142,10 +142,14 @@ def get_std2bold_xfms(bold_file): ---------- bold_file : :obj:`str` The preprocessed BOLD file. + source_file : :obj:`str` + The source file to warp to the BOLD space. + source_space : :obj:`str`, optional + The space of the source file. If None, the space of the source file is inferred and used. Returns ------- - transform_list : list of str + transforms : list of str A list of paths to transform files. Notes @@ -159,8 +163,6 @@ def get_std2bold_xfms(bold_file): Does not include inversion flag output because there is no need (yet). Can easily be added in the future. """ - import os - from templateflow.api import get as get_template from xcp_d.data import load as load_data @@ -169,7 +171,22 @@ def get_std2bold_xfms(bold_file): # Extract the space of the BOLD file bold_space = get_entity(bold_file, "space") - # Load useful inter-template transforms from templateflow + if source_space is None: + # If a source space is not provided, extract the space of the source file + # First try tpl because that won't raise an error + source_space = get_entity(source_file, "tpl") + if source_space is None: + # If tpl isn't available, try space. + # get_entity will raise an error if space isn't there. + source_space = get_entity(source_file, "space") + + if source_space not in ("MNI152NLin6Asym", "MNI152NLin2009cAsym", "MNIInfant"): + raise ValueError(f"Source space '{source_space}' not supported.") + + if bold_space not in ("MNI152NLin6Asym", "MNI152NLin2009cAsym", "MNIInfant"): + raise ValueError(f"BOLD space '{bold_space}' not supported.") + + # Load useful inter-template transforms from templateflow and package data MNI152NLin6Asym_to_MNI152NLin2009cAsym = str( get_template( template="MNI152NLin2009cAsym", @@ -179,33 +196,54 @@ def get_std2bold_xfms(bold_file): **{"from": "MNI152NLin6Asym"}, ), ) + MNI152NLin2009cAsym_to_MNI152NLin6Asym = str( + get_template( + template="MNI152NLin6Asym", + mode="image", + suffix="xfm", + extension=".h5", + **{"from": "MNI152NLin2009cAsym"}, + ), + ) + MNIInfant_to_MNI152NLin2009cAsym = str( + load_data( + "transform/tpl-MNIInfant_from-MNI152NLin2009cAsym_mode-image_xfm.h5", + ) + ) + MNI152NLin2009cAsym_to_MNIInfant = str( + load_data( + "transform/tpl-MNI152NLin2009cAsym_from-MNIInfant_mode-image_xfm.h5", + ) + ) - # Find the appropriate transform(s) - if bold_space == "MNI152NLin6Asym": - # NLin6 --> NLin6 (identity) - transform_list = ["identity"] + if bold_space == source_space: + transforms = ["identity"] + + elif bold_space == "MNI152NLin6Asym": + if source_space == "MNI152NLin2009cAsym": + transforms = [MNI152NLin2009cAsym_to_MNI152NLin6Asym] + elif source_space == "MNIInfant": + transforms = [ + MNI152NLin2009cAsym_to_MNI152NLin6Asym, + MNIInfant_to_MNI152NLin2009cAsym, + ] elif bold_space == "MNI152NLin2009cAsym": - # NLin6 --> NLin2009c - transform_list = [MNI152NLin6Asym_to_MNI152NLin2009cAsym] + if source_space == "MNI152NLin6Asym": + transforms = [MNI152NLin6Asym_to_MNI152NLin2009cAsym] + elif source_space == "MNIInfant": + transforms = [MNIInfant_to_MNI152NLin2009cAsym] elif bold_space == "MNIInfant": - # NLin6 --> NLin2009c --> MNIInfant - MNI152NLin2009cAsym_to_MNI152Infant = str( - load_data( - "transform/tpl-MNIInfant_from-MNI152NLin2009cAsym_mode-image_xfm.h5", - ) - ) - transform_list = [ - MNI152NLin2009cAsym_to_MNI152Infant, - MNI152NLin6Asym_to_MNI152NLin2009cAsym, - ] - - else: - file_base = os.path.basename(bold_file) - raise ValueError(f"Space '{bold_space}' in {file_base} not supported.") + if source_space == "MNI152NLin6Asym": + transforms = [ + MNI152NLin2009cAsym_to_MNIInfant, + MNI152NLin6Asym_to_MNI152NLin2009cAsym, + ] + elif source_space == "MNI152NLin2009cAsym": + transforms = [MNI152NLin2009cAsym_to_MNIInfant] - return transform_list + return transforms def fwhm2sigma(fwhm): diff --git a/xcp_d/workflows/anatomical/parcellation.py b/xcp_d/workflows/anatomical/parcellation.py index eed2dcb27..5e4537ae5 100644 --- a/xcp_d/workflows/anatomical/parcellation.py +++ b/xcp_d/workflows/anatomical/parcellation.py @@ -1,13 +1,13 @@ """Workflows for parcellating anatomical data.""" -from nipype import Function, logging +from nipype import logging from nipype.interfaces import utility as niu from nipype.pipeline import engine as pe from niworkflows.engine.workflows import LiterateWorkflow as Workflow from xcp_d import config from xcp_d.interfaces.bids import DerivativesDataSink -from xcp_d.utils.atlas import get_atlas_cifti, select_atlases +from xcp_d.utils.atlas import select_atlases from xcp_d.utils.doc import fill_doc from xcp_d.workflows.parcellation import init_parcellate_cifti_wf @@ -50,11 +50,10 @@ def init_parcellate_surfaces_wf(files_to_parcellate, name="parcellate_surfaces_w myelin_smoothed """ from xcp_d.interfaces.workbench import CiftiCreateDenseFromTemplate + from xcp_d.utils.atlas import collect_atlases workflow = Workflow(name=name) - atlases = config.execution.atlases - SURF_DESCS = { "sulcal_depth": "sulc", "sulcal_curv": "curv", @@ -73,14 +72,46 @@ def init_parcellate_surfaces_wf(files_to_parcellate, name="parcellate_surfaces_w "cortical_thickness_corr", "myelin", "myelin_smoothed", + # atlases + "atlas_names", + "atlas_datasets", + "atlas_files", + "atlas_labels_files", + "atlas_metadata_files", ], ), name="inputnode", ) - selected_atlases = select_atlases(atlases=atlases, subset="cortical") + builtin_atlases = select_atlases(atlases=config.execution.atlases, subset="all") + external_atlases = sorted(list(set(config.execution.atlases) - set(builtin_atlases))) + builtin_cortical_atlases = select_atlases(atlases=builtin_atlases, subset="cortical") + selected_atlases = builtin_cortical_atlases + external_atlases + atlases = collect_atlases( + datasets=config.execution.datasets, + atlases=selected_atlases, + file_format=config.workflow.file_format, + bids_filters=config.execution.bids_filters, + ) - if not selected_atlases: + # Reorganize the atlas file information + atlas_names, atlas_files, atlas_labels_files, atlas_metadata_files = [], [], [], [] + atlas_datasets = [] + for atlas, atlas_dict in atlases.items(): + config.loggers.workflow.info(f"Loading atlas: {atlas}") + atlas_names.append(atlas) + atlas_datasets.append(atlas_dict["dataset"]) + atlas_files.append(atlas_dict["image"]) + atlas_labels_files.append(atlas_dict["labels"]) + atlas_metadata_files.append(atlas_dict["metadata"]) + + inputnode.inputs.atlas_names = atlas_names + inputnode.inputs.atlas_datasets = atlas_datasets + inputnode.inputs.atlas_files = atlas_files + inputnode.inputs.atlas_labels_files = atlas_labels_files + inputnode.inputs.atlas_metadata_files = atlas_metadata_files + + if not atlases: LOGGER.warning( "No cortical atlases have been selected, so surface metrics will not be parcellated." ) @@ -89,18 +120,6 @@ def init_parcellate_surfaces_wf(files_to_parcellate, name="parcellate_surfaces_w return workflow - # Get CIFTI atlases via load_data - atlas_file_grabber = pe.MapNode( - Function( - input_names=["atlas"], - output_names=["atlas_file", "atlas_labels_file", "atlas_metadata_file"], - function=get_atlas_cifti, - ), - name="atlas_file_grabber", - iterfield=["atlas"], - ) - atlas_file_grabber.inputs.atlas = selected_atlases - for file_to_parcellate in files_to_parcellate: resample_atlas_to_surface = pe.MapNode( CiftiCreateDenseFromTemplate( @@ -112,8 +131,10 @@ def init_parcellate_surfaces_wf(files_to_parcellate, name="parcellate_surfaces_w n_procs=config.nipype.omp_nthreads, ) workflow.connect([ - (inputnode, resample_atlas_to_surface, [(file_to_parcellate, "template_cifti")]), - (atlas_file_grabber, resample_atlas_to_surface, [("atlas_file", "label")]), + (inputnode, resample_atlas_to_surface, [ + ("atlas_files", "label"), + (file_to_parcellate, "template_cifti"), + ]), ]) # fmt:skip parcellate_surface_wf = init_parcellate_cifti_wf( @@ -122,9 +143,9 @@ def init_parcellate_surfaces_wf(files_to_parcellate, name="parcellate_surfaces_w name=f"parcellate_{file_to_parcellate}_wf", ) workflow.connect([ - (inputnode, parcellate_surface_wf, [(file_to_parcellate, "inputnode.in_file")]), - (atlas_file_grabber, parcellate_surface_wf, [ - ("atlas_labels_file", "inputnode.atlas_labels_files"), + (inputnode, parcellate_surface_wf, [ + (file_to_parcellate, "inputnode.in_file"), + ("atlas_labels_files", "inputnode.atlas_labels_files"), ]), (resample_atlas_to_surface, parcellate_surface_wf, [ ("out_file", "inputnode.atlas_files"), @@ -145,10 +166,11 @@ def init_parcellate_surfaces_wf(files_to_parcellate, name="parcellate_surfaces_w mem_gb=1, iterfield=["segmentation", "in_file"], ) - ds_parcellated_surface.inputs.segmentation = selected_atlases - workflow.connect([ - (inputnode, ds_parcellated_surface, [(file_to_parcellate, "source_file")]), + (inputnode, ds_parcellated_surface, [ + (file_to_parcellate, "source_file"), + ("atlas_names", "segmentation"), + ]), (parcellate_surface_wf, ds_parcellated_surface, [ ("outputnode.parcellated_tsv", "in_file"), ]), diff --git a/xcp_d/workflows/anatomical/volume.py b/xcp_d/workflows/anatomical/volume.py index 2260265c3..e9137ec2e 100644 --- a/xcp_d/workflows/anatomical/volume.py +++ b/xcp_d/workflows/anatomical/volume.py @@ -148,7 +148,10 @@ def init_postprocess_anat_wf( else: out = ["T1w"] if t1w_available else [] + ["T2w"] if t2w_available else [] - workflow.__desc__ = f"""\ + workflow.__desc__ = f""" + +#### Anatomical data + Native-space {list_to_str(out)} images were transformed to {target_space} space at 1 mm3 resolution. """ diff --git a/xcp_d/workflows/base.py b/xcp_d/workflows/base.py index 3de5d6d14..7294dca9f 100644 --- a/xcp_d/workflows/base.py +++ b/xcp_d/workflows/base.py @@ -494,6 +494,7 @@ def init_single_subject_wf(subject_id: str): if config.execution.atlases: workflow.connect([ (load_atlases_wf, postprocess_bold_wf, [ + ("outputnode.atlas_names", "inputnode.atlases"), ("outputnode.atlas_files", "inputnode.atlas_files"), ("outputnode.atlas_labels_files", "inputnode.atlas_labels_files"), ]), diff --git a/xcp_d/workflows/bold/cifti.py b/xcp_d/workflows/bold/cifti.py index 03bb716e4..cac45e074 100644 --- a/xcp_d/workflows/bold/cifti.py +++ b/xcp_d/workflows/bold/cifti.py @@ -132,7 +132,6 @@ def init_postprocess_cifti_wf( bandpass_filter = config.workflow.bandpass_filter dummy_scans = config.workflow.dummy_scans despike = config.workflow.despike - atlases = config.execution.atlases TR = run_data["bold_metadata"]["RepetitionTime"] @@ -166,11 +165,11 @@ def init_postprocess_cifti_wf( inputnode.inputs.motion_json = run_data["motion_json"] inputnode.inputs.confounds_files = run_data["confounds"] inputnode.inputs.dummy_scans = dummy_scans - inputnode.inputs.atlases = atlases workflow = Workflow(name=name) - workflow.__desc__ = f"""\ + workflow.__desc__ = f""" +#### Functional data For each of the {num2words(n_runs)} BOLD runs found per subject (across all tasks and sessions), the following post-processing was performed. @@ -324,6 +323,7 @@ def init_postprocess_cifti_wf( (inputnode, postproc_derivatives_wf, [ ("motion_file", "inputnode.preproc_confounds_file"), ("atlas_files", "inputnode.atlas_files"), + ("atlases", "inputnode.atlas_names"), ]), (denoise_bold_wf, postproc_derivatives_wf, [ ("outputnode.denoised_bold", "inputnode.denoised_bold"), @@ -357,7 +357,7 @@ def init_postprocess_cifti_wf( ]), ]) # fmt:skip - if atlases: + if config.execution.atlases: connectivity_wf = init_functional_connectivity_cifti_wf( mem_gb=mem_gbx, exact_scans=exact_scans, diff --git a/xcp_d/workflows/bold/connectivity.py b/xcp_d/workflows/bold/connectivity.py index b51f2e087..9d5db3638 100644 --- a/xcp_d/workflows/bold/connectivity.py +++ b/xcp_d/workflows/bold/connectivity.py @@ -10,7 +10,6 @@ from xcp_d import config from xcp_d.interfaces.bids import DerivativesDataSink from xcp_d.utils.atlas import select_atlases -from xcp_d.utils.boilerplate import describe_atlases from xcp_d.utils.doc import fill_doc from xcp_d.workflows.parcellation import init_parcellate_cifti_wf @@ -72,11 +71,9 @@ def init_functional_connectivity_nifti_wf(mem_gb, name="connectivity_wf"): bandpass_filter = config.workflow.bandpass_filter min_coverage = config.workflow.min_coverage - atlas_str = describe_atlases(config.execution.atlases) - workflow.__desc__ = f""" Processed functional timeseries were extracted from the residual BOLD signal -with *Nilearn's* *NiftiLabelsMasker* for the following atlases: {atlas_str}. +with *Nilearn's* *NiftiLabelsMasker* for the atlases. Corresponding pair-wise functional connectivity between all regions was computed for each atlas, which was operationalized as the Pearson's correlation of each parcel's unsmoothed timeseries. In cases of partial coverage, uncovered voxels (values of all zeros or NaNs) were either @@ -281,11 +278,9 @@ def init_functional_connectivity_cifti_wf(mem_gb, exact_scans, name="connectivit bandpass_filter = config.workflow.bandpass_filter min_coverage = config.workflow.min_coverage - atlas_str = describe_atlases(config.execution.atlases) - workflow.__desc__ = f""" Processed functional timeseries were extracted from residual BOLD using -Connectome Workbench [@marcus2011informatics] for the following atlases: {atlas_str}. +Connectome Workbench [@marcus2011informatics] for the atlases. Corresponding pair-wise functional connectivity between all regions was computed for each atlas, which was operationalized as the Pearson's correlation of each parcel's unsmoothed timeseries with the Connectome Workbench. diff --git a/xcp_d/workflows/bold/nifti.py b/xcp_d/workflows/bold/nifti.py index 430ed84b9..f64b9e94e 100644 --- a/xcp_d/workflows/bold/nifti.py +++ b/xcp_d/workflows/bold/nifti.py @@ -143,7 +143,6 @@ def init_postprocess_nifti_wf( bandpass_filter = config.workflow.bandpass_filter dummy_scans = config.workflow.dummy_scans despike = config.workflow.despike - atlases = config.execution.atlases TR = run_data["bold_metadata"]["RepetitionTime"] @@ -179,11 +178,11 @@ def init_postprocess_nifti_wf( inputnode.inputs.motion_json = run_data["motion_json"] inputnode.inputs.confounds_files = run_data["confounds"] inputnode.inputs.dummy_scans = dummy_scans - inputnode.inputs.atlases = atlases # Load confounds according to the config - workflow.__desc__ = f"""\ + workflow.__desc__ = f""" +#### Functional data For each of the {num2words(n_runs)} BOLD runs found per subject (across all tasks and sessions), the following post-processing was performed. @@ -344,6 +343,7 @@ def init_postprocess_nifti_wf( (inputnode, postproc_derivatives_wf, [ ("motion_file", "inputnode.preproc_confounds_file"), ("atlas_files", "inputnode.atlas_files"), + ("atlases", "inputnode.atlas_names"), ]), (prepare_confounds_wf, postproc_derivatives_wf, [ ("outputnode.confounds_tsv", "inputnode.confounds_tsv"), @@ -376,7 +376,7 @@ def init_postprocess_nifti_wf( ]), ]) # fmt:skip - if atlases: + if config.execution.atlases: connectivity_wf = init_functional_connectivity_nifti_wf(mem_gb=mem_gbx) workflow.connect([ diff --git a/xcp_d/workflows/bold/outputs.py b/xcp_d/workflows/bold/outputs.py index 4be169810..27505e734 100644 --- a/xcp_d/workflows/bold/outputs.py +++ b/xcp_d/workflows/bold/outputs.py @@ -90,7 +90,6 @@ def init_postproc_derivatives_wf( bpf_order = config.workflow.bpf_order fd_thresh = config.workflow.fd_thresh smoothing = config.workflow.smoothing - atlases = config.execution.atlases file_format = config.workflow.file_format output_dir = config.execution.output_dir @@ -125,6 +124,8 @@ def init_postproc_derivatives_wf( "timeseries_ciftis", "correlation_ciftis", "correlation_ciftis_exact", + # info for filenames + "atlas_names", ], ), name="inputnode", @@ -343,7 +344,7 @@ def init_postproc_derivatives_wf( ]) # fmt:skip # Connectivity workflow outputs - if atlases: + if config.execution.atlases: make_atlas_dict = pe.MapNode( niu.Function( function=_make_dictionary, @@ -395,9 +396,11 @@ def init_postproc_derivatives_wf( mem_gb=1, iterfield=["segmentation", "in_file", "meta_dict"], ) - ds_coverage.inputs.segmentation = atlases workflow.connect([ - (inputnode, ds_coverage, [("coverage", "in_file")]), + (inputnode, ds_coverage, [ + ("atlas_names", "segmentation"), + ("coverage", "in_file"), + ]), (make_atlas_dict, ds_coverage, [("metadata", "meta_dict")]), ]) # fmt:skip @@ -435,9 +438,11 @@ def init_postproc_derivatives_wf( mem_gb=1, iterfield=["segmentation", "in_file", "meta_dict"], ) - ds_timeseries.inputs.segmentation = atlases workflow.connect([ - (inputnode, ds_timeseries, [("timeseries", "in_file")]), + (inputnode, ds_timeseries, [ + ("atlas_names", "segmentation"), + ("timeseries", "in_file"), + ]), (add_coverage_to_src, ds_timeseries, [("metadata", "meta_dict")]), (ds_timeseries, outputnode, [("out_file", "timeseries")]), ]) # fmt:skip @@ -483,9 +488,11 @@ def init_postproc_derivatives_wf( mem_gb=1, iterfield=["segmentation", "in_file", "meta_dict"], ) - ds_correlations.inputs.segmentation = atlases workflow.connect([ - (inputnode, ds_correlations, [("correlations", "in_file")]), + (inputnode, ds_correlations, [ + ("atlas_names", "segmentation"), + ("correlations", "in_file"), + ]), (make_corrs_meta_dict, ds_correlations, [("metadata", "meta_dict")]), ]) # fmt:skip @@ -505,9 +512,11 @@ def init_postproc_derivatives_wf( mem_gb=1, iterfield=["segmentation", "in_file", "meta_dict"], ) - ds_coverage_ciftis.inputs.segmentation = atlases workflow.connect([ - (inputnode, ds_coverage_ciftis, [("coverage_ciftis", "in_file")]), + (inputnode, ds_coverage_ciftis, [ + ("atlas_names", "segmentation"), + ("coverage_ciftis", "in_file"), + ]), (add_denoised_to_src, ds_coverage_ciftis, [("metadata", "meta_dict")]), ]) # fmt:skip @@ -545,9 +554,11 @@ def init_postproc_derivatives_wf( mem_gb=1, iterfield=["segmentation", "in_file", "meta_dict"], ) - ds_timeseries_ciftis.inputs.segmentation = atlases workflow.connect([ - (inputnode, ds_timeseries_ciftis, [("timeseries_ciftis", "in_file")]), + (inputnode, ds_timeseries_ciftis, [ + ("atlas_names", "segmentation"), + ("timeseries_ciftis", "in_file"), + ]), (add_ccoverage_to_src, ds_timeseries_ciftis, [("metadata", "meta_dict")]), (ds_timeseries_ciftis, outputnode, [("out_file", "timeseries_ciftis")]), ]) # fmt:skip @@ -595,9 +606,9 @@ def init_postproc_derivatives_wf( mem_gb=1, iterfield=["segmentation", "in_file", "meta_dict"], ) - ds_correlation_ciftis.inputs.segmentation = atlases workflow.connect([ (inputnode, ds_correlation_ciftis, [ + ("atlas_names", "segmentation"), ("correlation_ciftis", "in_file"), ]), (make_ccorrs_meta_dict, ds_correlation_ciftis, [("metadata", "meta_dict")]), @@ -628,8 +639,8 @@ def init_postproc_derivatives_wf( mem_gb=1, iterfield=["segmentation", "in_file"], ) - ds_correlations_exact.inputs.segmentation = atlases workflow.connect([ + (inputnode, ds_correlations_exact, [("atlas_names", "segmentation")]), (select_exact_scan_files, ds_correlations_exact, [("out", "in_file")]), ]) # fmt:skip @@ -657,7 +668,7 @@ def init_postproc_derivatives_wf( (ds_denoised_bold, ds_reho, [(("out_file", _make_xcpd_uri, output_dir), "Sources")]), ]) # fmt:skip - if atlases: + if config.execution.atlases: add_reho_to_src = pe.MapNode( niu.Function( function=_make_dictionary, @@ -691,9 +702,11 @@ def init_postproc_derivatives_wf( mem_gb=1, iterfield=["segmentation", "in_file", "meta_dict"], ) - ds_parcellated_reho.inputs.segmentation = atlases workflow.connect([ - (inputnode, ds_parcellated_reho, [("parcellated_reho", "in_file")]), + (inputnode, ds_parcellated_reho, [ + ("atlas_names", "segmentation"), + ("parcellated_reho", "in_file"), + ]), (add_reho_to_src, ds_parcellated_reho, [("metadata", "meta_dict")]), ]) # fmt:skip @@ -747,7 +760,7 @@ def init_postproc_derivatives_wf( ]), ]) # fmt:skip - if atlases: + if config.execution.atlases: add_alff_to_src = pe.MapNode( niu.Function( function=_make_dictionary, @@ -780,9 +793,11 @@ def init_postproc_derivatives_wf( mem_gb=1, iterfield=["segmentation", "in_file", "meta_dict"], ) - ds_parcellated_alff.inputs.segmentation = atlases workflow.connect([ - (inputnode, ds_parcellated_alff, [("parcellated_alff", "in_file")]), + (inputnode, ds_parcellated_alff, [ + ("atlas_names", "segmentation"), + ("parcellated_alff", "in_file"), + ]), (add_alff_to_src, ds_parcellated_alff, [("metadata", "meta_dict")]), ]) # fmt:skip diff --git a/xcp_d/workflows/bold/plotting.py b/xcp_d/workflows/bold/plotting.py index c45d7ebff..6464a1ceb 100644 --- a/xcp_d/workflows/bold/plotting.py +++ b/xcp_d/workflows/bold/plotting.py @@ -213,16 +213,17 @@ def init_qc_report_wf( ]) # fmt:skip # NIFTI files require a tissue-type segmentation in the same space as the BOLD data. - # Get the set of transforms from MNI152NLin6Asym (the dseg) to the BOLD space. - # Given that XCP-D doesn't process native-space data, this transform will never be used. + # Get the set of transforms from MNI152NLin2009cAsym (the dseg) to the BOLD space. get_mni_to_bold_xfms = pe.Node( Function( - input_names=["bold_file"], - output_names=["transform_list"], + input_names=["bold_file", "source_file", "source_space"], + output_names=["transforms"], function=get_std2bold_xfms, ), name="get_std2native_transform", ) + get_mni_to_bold_xfms.inputs.source_file = None + get_mni_to_bold_xfms.inputs.source_space = "MNI152NLin2009cAsym" workflow.connect([(inputnode, get_mni_to_bold_xfms, [("name_source", "bold_file")])]) # Use MNI152NLin2009cAsym tissue-type segmentation file for carpet plots. @@ -236,29 +237,6 @@ def init_qc_report_wf( ) ) - # Get MNI152NLin2009cAsym --> MNI152NLin6Asym xform. - MNI152NLin2009cAsym_to_MNI152NLin6Asym = str( - get_template( - template="MNI152NLin6Asym", - mode="image", - suffix="xfm", - extension=".h5", - **{"from": "MNI152NLin2009cAsym"}, - ), - ) - - # Add the MNI152NLin2009cAsym --> MNI152NLin6Asym xform to the end of the - # BOLD --> MNI152NLin6Asym xform list, because xforms are applied in reverse order. - add_xfm_to_nlin6asym = pe.Node( - niu.Merge(2), - name="add_xfm_to_nlin6asym", - ) - add_xfm_to_nlin6asym.inputs.in2 = MNI152NLin2009cAsym_to_MNI152NLin6Asym - - workflow.connect([ - (get_mni_to_bold_xfms, add_xfm_to_nlin6asym, [("transform_list", "in1")]), - ]) # fmt:skip - # Transform MNI152NLin2009cAsym dseg file to the same space as the BOLD data. warp_dseg_to_bold = pe.Node( ApplyTransforms( @@ -273,7 +251,7 @@ def init_qc_report_wf( ) workflow.connect([ (inputnode, warp_dseg_to_bold, [("boldref", "reference_image")]), - (add_xfm_to_nlin6asym, warp_dseg_to_bold, [("out", "transforms")]), + (get_mni_to_bold_xfms, warp_dseg_to_bold, [("transforms", "transforms")]), ]) # fmt:skip if config.workflow.linc_qc: diff --git a/xcp_d/workflows/parcellation.py b/xcp_d/workflows/parcellation.py index 794e95183..3e6604358 100644 --- a/xcp_d/workflows/parcellation.py +++ b/xcp_d/workflows/parcellation.py @@ -10,7 +10,6 @@ from xcp_d import config from xcp_d.interfaces.ants import ApplyTransforms from xcp_d.interfaces.nilearn import IndexImage -from xcp_d.utils.atlas import get_atlas_cifti, get_atlas_nifti from xcp_d.utils.doc import fill_doc from xcp_d.utils.utils import get_std2bold_xfms @@ -49,57 +48,96 @@ def init_load_atlases_wf(name="load_atlases_wf"): atlas_labels_files """ from xcp_d.interfaces.bids import CopyAtlas + from xcp_d.utils.atlas import collect_atlases + from xcp_d.utils.boilerplate import describe_atlases workflow = Workflow(name=name) - atlases = config.execution.atlases output_dir = config.execution.output_dir - file_format = config.workflow.file_format + + atlases = collect_atlases( + datasets=config.execution.datasets, + atlases=config.execution.atlases, + file_format=config.workflow.file_format, + bids_filters=config.execution.bids_filters, + ) + + # Reorganize the atlas file information + atlas_names, atlas_files, atlas_labels_files, atlas_metadata = [], [], [], [] + atlas_datasets = [] + for atlas, atlas_dict in atlases.items(): + config.loggers.workflow.info(f"Loading atlas: {atlas}") + atlas_names.append(atlas) + atlas_datasets.append(atlas_dict["dataset"]) + atlas_files.append(atlas_dict["image"]) + atlas_labels_files.append(atlas_dict["labels"]) + atlas_metadata.append(atlas_dict["metadata"]) + + # Write a description + atlas_str = describe_atlases(atlas_names) + workflow.__desc__ = f""" +#### Segmentations + +The following atlases were used in the workflow: {atlas_str}. +""" inputnode = pe.Node( niu.IdentityInterface( fields=[ "name_source", "bold_file", + "atlas_names", + "atlas_datasets", + "atlas_files", + "atlas_labels_files", + "atlas_metadata", ], ), name="inputnode", ) + inputnode.inputs.atlas_names = atlas_names + inputnode.inputs.atlas_datasets = atlas_datasets + inputnode.inputs.atlas_files = atlas_files + inputnode.inputs.atlas_labels_files = atlas_labels_files + inputnode.inputs.atlas_metadata = atlas_metadata + outputnode = pe.Node( niu.IdentityInterface( fields=[ + "atlas_names", "atlas_files", "atlas_labels_files", ], ), name="outputnode", ) + workflow.connect([(inputnode, outputnode, [("atlas_names", "atlas_names")])]) - # get atlases via load_data - atlas_file_grabber = pe.MapNode( - Function( - input_names=["atlas"], - output_names=["atlas_file", "atlas_labels_file", "atlas_metadata_file"], - function=get_atlas_cifti if file_format == "cifti" else get_atlas_nifti, + atlas_buffer = pe.Node( + niu.IdentityInterface( + fields=["atlas_file"], ), - name="atlas_file_grabber", - iterfield=["atlas"], + name="atlas_buffer", ) - atlas_file_grabber.inputs.atlas = atlases - atlas_buffer = pe.Node(niu.IdentityInterface(fields=["atlas_file"]), name="atlas_buffer") - - if file_format == "nifti": - get_transforms_to_bold_space = pe.Node( + if config.workflow.file_format == "nifti": + workflow.__desc__ += ( + " Each atlas was warped to the same space and resolution as the BOLD file." + ) + get_xfms_to_bold_space = pe.MapNode( Function( - input_names=["bold_file"], - output_names=["transformfile"], + input_names=["bold_file", "source_file", "source_space"], + output_names=["transforms"], function=get_std2bold_xfms, ), - name="get_transforms_to_bold_space", + name="get_xfms_to_bold_space", + iterfield=["source_file"], ) workflow.connect([ - (inputnode, get_transforms_to_bold_space, [("name_source", "bold_file")]), + (inputnode, get_xfms_to_bold_space, [ + ("bold_file", "bold_file"), + ("atlas_files", "source_file"), + ]), ]) # fmt:skip # ApplyTransforms needs a 3D image for the reference image. @@ -107,7 +145,6 @@ def init_load_atlases_wf(name="load_atlases_wf"): IndexImage(index=0), name="grab_first_volume", ) - workflow.connect([(inputnode, grab_first_volume, [("bold_file", "in_file")])]) # Using the generated transforms, apply them to get everything in the correct MNI form @@ -119,33 +156,33 @@ def init_load_atlases_wf(name="load_atlases_wf"): num_threads=config.nipype.omp_nthreads, ), name="warp_atlases_to_bold_space", - iterfield=["input_image"], + iterfield=["input_image", "transforms"], mem_gb=2, n_procs=config.nipype.omp_nthreads, ) workflow.connect([ + (inputnode, warp_atlases_to_bold_space, [("atlas_files", "input_image")]), (grab_first_volume, warp_atlases_to_bold_space, [("out_file", "reference_image")]), - (atlas_file_grabber, warp_atlases_to_bold_space, [("atlas_file", "input_image")]), - (get_transforms_to_bold_space, warp_atlases_to_bold_space, [ - ("transformfile", "transforms"), - ]), + (get_xfms_to_bold_space, warp_atlases_to_bold_space, [("transforms", "transforms")]), (warp_atlases_to_bold_space, atlas_buffer, [("output_image", "atlas_file")]), ]) # fmt:skip else: - workflow.connect([(atlas_file_grabber, atlas_buffer, [("atlas_file", "atlas_file")])]) + workflow.connect([(inputnode, atlas_buffer, [("atlas_files", "atlas_file")])]) copy_atlas = pe.MapNode( CopyAtlas(output_dir=output_dir), name="copy_atlas", - iterfield=["in_file", "atlas"], + iterfield=["in_file", "atlas", "meta_dict"], run_without_submitting=True, ) - copy_atlas.inputs.atlas = atlases - workflow.connect([ - (inputnode, copy_atlas, [("name_source", "name_source")]), + (inputnode, copy_atlas, [ + ("name_source", "name_source"), + ("atlas_names", "atlas"), + ("atlas_metadata", "meta_dict"), + ]), (atlas_buffer, copy_atlas, [("atlas_file", "in_file")]), (copy_atlas, outputnode, [("out_file", "atlas_files")]), ]) # fmt:skip @@ -156,27 +193,15 @@ def init_load_atlases_wf(name="load_atlases_wf"): iterfield=["in_file", "atlas"], run_without_submitting=True, ) - copy_atlas_labels_file.inputs.atlas = atlases - workflow.connect([ - (inputnode, copy_atlas_labels_file, [("name_source", "name_source")]), - (atlas_file_grabber, copy_atlas_labels_file, [("atlas_labels_file", "in_file")]), + (inputnode, copy_atlas_labels_file, [ + ("name_source", "name_source"), + ("atlas_names", "atlas"), + ("atlas_labels_files", "in_file"), + ]), (copy_atlas_labels_file, outputnode, [("out_file", "atlas_labels_files")]), ]) # fmt:skip - copy_atlas_metadata = pe.MapNode( - CopyAtlas(output_dir=output_dir), - name="copy_atlas_metadata", - iterfield=["in_file", "atlas"], - run_without_submitting=True, - ) - copy_atlas_metadata.inputs.atlas = atlases - - workflow.connect([ - (inputnode, copy_atlas_metadata, [("name_source", "name_source")]), - (atlas_file_grabber, copy_atlas_metadata, [("atlas_metadata_file", "in_file")]), - ]) # fmt:skip - return workflow