diff --git a/CPAC/pipeline/engine.py b/CPAC/pipeline/engine.py index 83c76c04ec..f84459e754 100644 --- a/CPAC/pipeline/engine.py +++ b/CPAC/pipeline/engine.py @@ -1785,12 +1785,12 @@ def ingress_pipeconfig_paths(cfg, rpool, unique_id, creds_path=None): val = val.replace('${func_resolution}', cfg.registration_workflows[ 'functional_registration']['func_registration_to_template'][ 'output_resolution'][tag]) - + if desc: - template_name = lookup_identifier(val) + template_name, _template_desc = lookup_identifier(val) if template_name: desc = f"{template_name} - {desc}" - json_info['Description'] = f"{desc} - {val}" + json_info['Description'] = f"{desc} - {val}" if resolution: resolution = cfg.get_nested(cfg, res_keys) json_info['Resolution'] = resolution diff --git a/CPAC/pipeline/schema.py b/CPAC/pipeline/schema.py index 7ede283724..2473820bc6 100644 --- a/CPAC/pipeline/schema.py +++ b/CPAC/pipeline/schema.py @@ -23,7 +23,7 @@ from pathvalidate import sanitize_filename from voluptuous import All, ALLOW_EXTRA, Any, Capitalize, Coerce, \ ExactSequence, ExclusiveInvalid, In, Length, Lower, \ - Match, Maybe, Optional, Range, Required, Schema + Match, Maybe, Optional, Range, Required, Schema, Title from CPAC import docs_prefix from CPAC.utils.datatypes import ListFromItem from CPAC.utils.utils import YAML_BOOLS @@ -204,6 +204,8 @@ def str_to_bool1_1(x): # pylint: disable=invalid-name }, }, dict # TODO: specify other valid ANTs parameters )] +target_space = All(Coerce(ListFromItem), + [All(Title, In(valid_options['target_space']))]) def permutation_message(key, options): @@ -865,7 +867,7 @@ def sanitize(filename): }, 'amplitude_low_frequency_fluctuation': { 'run': bool1_1, - 'target_space': [In(valid_options['target_space'])], + 'target_space': target_space, 'highpass_cutoff': [float], 'lowpass_cutoff': [float], }, @@ -884,7 +886,7 @@ def sanitize(filename): }, 'regional_homogeneity': { 'run': bool1_1, - 'target_space': [In(valid_options['target_space'])], + 'target_space': target_space, 'cluster_size': In({7, 19, 27}), }, 'post_processing': { diff --git a/CPAC/resources/configs/pipeline_config_rbc-options.yml b/CPAC/resources/configs/pipeline_config_rbc-options.yml index 1e8780388a..75e18d104e 100644 --- a/CPAC/resources/configs/pipeline_config_rbc-options.yml +++ b/CPAC/resources/configs/pipeline_config_rbc-options.yml @@ -118,6 +118,10 @@ functional_preproc: nuisance_corrections: 2-nuisance_regression: + # this is a fork point + # run: [On, Off] - this will run both and fork the pipeline + run: [On] + # Select which nuisance signal corrections to apply Regressors: - Name: 36-parameter @@ -230,12 +234,18 @@ amplitude_low_frequency_fluctuation: # Calculate Amplitude of Low Frequency Fluctuations (ALFF) and fractional ALFF (f/ALFF) for all voxels. run: On + # space: Template or Native + target_space: [Template] + regional_homogeneity: # ReHo # Calculate Regional Homogeneity (ReHo) for all voxels. run: On + # space: Template or Native + target_space: [Template] + network_centrality: # Calculate Degree, Eigenvector Centrality, or Functional Connectivity Density. diff --git a/CPAC/resources/templates/BIDS_identifiers.tsv b/CPAC/resources/templates/BIDS_identifiers.tsv index a9140a54a6..381bc034ff 100644 --- a/CPAC/resources/templates/BIDS_identifiers.tsv +++ b/CPAC/resources/templates/BIDS_identifiers.tsv @@ -1,13 +1,30 @@ -/code/CPAC/resources/templates/tpl-MNI152NLin2009cAsym_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_T1w_reference.nii.gz MNI152NLin2009cAsym -/code/CPAC/resources/templates/tpl-MNI152NLin2009cAsym_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_desc-brain_T1w.nii.gz MNI152NLin2009cAsym -/code/CPAC/resources/templates/tpl-MNI152NLin2009cAsym_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_desc-brain_mask.nii.gz MNI152NLin2009cAsym -/code/CPAC/resources/templates/tpl-MNI152NLin2009cAsym_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_desc-fMRIPrep_boldref.nii.gz MNI152NLin2009cAsym -/code/CPAC/resources/templates/tpl-MNI152NLin2009cAsym_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_label-brain_probseg.nii.gz MNI152NLin2009cAsym -/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*.nii.gz MNI152NLin6ASym -/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_brain.nii.gz MNI152NLin6ASym -/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_brain_mask.nii.gz MNI152NLin6ASym -/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_brain_mask_dil.nii.gz MNI152NLin6ASym -/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_symmetric.nii.gz MNI152NLin6Sym -/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_brain_symmetric.nii.gz MNI152NLin6Sym -/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_brain_mask_symmetric.nii.gz MNI152NLin6Sym -/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_brain_mask_symmetric_dil.nii.gz MNI152NLin6Sym +/code/CPAC/resources/templates/tpl-MNI152NLin2009cAsym_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_T1w_reference.nii.gz MNI152NLin2009cAsym +/code/CPAC/resources/templates/tpl-MNI152NLin2009cAsym_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_desc-brain_T1w.nii.gz MNI152NLin2009cAsym +/code/CPAC/resources/templates/tpl-MNI152NLin2009cAsym_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_desc-brain_mask.nii.gz MNI152NLin2009cAsym +/code/CPAC/resources/templates/tpl-MNI152NLin2009cAsym_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_desc-fMRIPrep_boldref.nii.gz MNI152NLin2009cAsym +/code/CPAC/resources/templates/tpl-MNI152NLin2009cAsym_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_label-brain_probseg.nii.gz MNI152NLin2009cAsym +/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*.nii.gz MNI152NLin6ASym +/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_brain.nii.gz MNI152NLin6ASym +/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_brain_mask.nii.gz MNI152NLin6ASym +/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_brain_mask_dil.nii.gz MNI152NLin6ASym +/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_symmetric.nii.gz MNI152NLin6Sym +/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_brain_symmetric.nii.gz MNI152NLin6Sym +/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_brain_mask_symmetric.nii.gz MNI152NLin6Sym +/usr/share/fsl/5.0/data/standard/MNI152_T1_(res-){0,1}[0-9]+(\.[0-9]*){0,1}[a-z]*(x[0-9]+(\.[0-9]*){0,1}[a-z]*)*_brain_mask_symmetric_dil.nii.gz MNI152NLin6Sym +/ndmg_atlases/label/Human/AAL_space-MNI152NLin6_res-2x2x2.nii.gz AAL +/ndmg_atlases/label/Human/Brodmann_space-MNI152NLin6_res-2x2x2.nii.gz Brodmann +/cpac_templates/CC200.nii.gz CC 200 +/cpac_templates/CC400.nii.gz CC 400 +/ndmg_atlases/label/Human/Glasser_space-MNI152NLin6_res-2x2x2.nii.gz Glasser +/ndmg_atlases/label/Human/Slab907_space-MNI152NLin6_res-2x2x2.nii.gz Slab +/ndmg_atlases/label/Human/HarvardOxfordcort-maxprob-thr25_space-MNI152NLin6_res-2x2x2.nii.gz HOCPA th25 +/ndmg_atlases/label/Human/HarvardOxfordsub-maxprob-thr25_space-MNI152NLin6_res-2x2x2.nii.gz HOSPA th25 +/ndmg_atlases/label/Human/Juelich_space-MNI152NLin6_res-2x2x2.nii.gz Juelich +/cpac_templates/Schaefer2018_space-FSLMNI152_res-2mm_desc-200Parcels17NetworksOrder.nii.gz Schaefer2018 200p17n +/cpac_templates/Schaefer2018_space-FSLMNI152_res-2mm_desc-300Parcels17NetworksOrder.nii.gz Schaefer2018 300p17n +/cpac_templates/Schaefer2018_space-FSLMNI152_res-2mm_desc-400Parcels17NetworksOrder.nii.gz Schaefer2018 400p17n +/cpac_templates/Schaefer2018_space-FSLMNI152_res-2mm_desc-1000Parcels17NetworksOrder.nii.gz Schaefer2018 1000p17n +/ndmg_atlases/label/Human/Yeo-7_space-MNI152NLin6_res-2x2x2.nii.gz Yeo 7 +/ndmg_atlases/label/Human/Yeo-7-liberal_space-MNI152NLin6_res-2x2x2.nii.gz Yeo 7liberal +/ndmg_atlases/label/Human/Yeo-17_space-MNI152NLin6_res-2x2x2.nii.gz Yeo 17 +/ndmg_atlases/label/Human/Yeo-17-liberal_space-MNI152NLin6_res-2x2x2.nii.gz Yeo 17liberal diff --git a/CPAC/resources/templates/__init__.py b/CPAC/resources/templates/__init__.py index 6cf5942ded..35eb3ce592 100644 --- a/CPAC/resources/templates/__init__.py +++ b/CPAC/resources/templates/__init__.py @@ -1,4 +1,4 @@ '''Template resources for C-PAC''' -from .lookup_table import lookup_identifier +from .lookup_table import format_identifier, lookup_identifier -__all__ = ['lookup_identifier'] +__all__ = ['format_identifier', 'lookup_identifier'] diff --git a/CPAC/resources/templates/lookup_table.py b/CPAC/resources/templates/lookup_table.py index b30f02dd86..7b7a490d5b 100644 --- a/CPAC/resources/templates/lookup_table.py +++ b/CPAC/resources/templates/lookup_table.py @@ -1,17 +1,58 @@ -'''Utilities for determining BIDS standard template identifiers +# Copyright (C) 2022 C-PAC Developers + +# This file is part of C-PAC. + +# C-PAC is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. + +# C-PAC is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. + +# You should have received a copy of the GNU Lesser General Public +# License along with C-PAC. If not, see . +"""Utilities for determining BIDS standard template identifiers (https://bids-specification.readthedocs.io/en/stable/99-appendices/08-coordinate-systems.html#standard-template-identifiers) -from in-container template paths''' -import os -import re -import numpy as np +from in-container template paths""" +from os import path as op +from re import search +from typing import Optional, Tuple +from numpy import loadtxt + +LOOKUP_TABLE = {row[0]: (row[1], str(row[2]) if row[2] else None) for row in + loadtxt(op.join(op.dirname(__file__), 'BIDS_identifiers.tsv'), + dtype='str', delimiter='\t')} + + +def format_identifier(identifier: str, desc: Optional[str] = None) -> str: + '''Function to create an identifier string from a name and description + + Parameters + ---------- + identifier : str + + desc : str, optional + + Returns + ------- + str -LOOKUP_TABLE = {row[0]: row[1] for row in - np.loadtxt(os.path.join(os.path.dirname(__file__), - 'BIDS_identifiers.tsv'), - dtype='str', delimiter='\t')} + Examples + -------- + >>> format_identifier('CC', '200') + 'CC_desc-200' + >>> format_identifier('AAL') + 'AAL' + ''' + if desc: + return f'{identifier}_desc-{desc}' + return identifier -def lookup_identifier(template_path: str) -> str: +def lookup_identifier(template_path: str) -> Tuple[str, None]: '''Function to return a standard template identifier for a packaged template, if known. Otherwise, returns the literal string 'template' @@ -24,19 +65,23 @@ def lookup_identifier(template_path: str) -> str: ------- identifier : str + desc : str or None + Examples -------- >>> lookup_identifier('/usr/share/fsl/5.0/data/standard/' ... 'MNI152_T1_1mm_brain.nii.gz') - 'MNI152NLin6ASym' + ('MNI152NLin6ASym', None) >>> lookup_identifier('/code/CPAC/resources/templates/' ... 'tpl-MNI152NLin2009cAsym_res-01_label-brain_' ... 'probseg.nii.gz') - 'MNI152NLin2009cAsym' + ('MNI152NLin2009cAsym', None) >>> lookup_identifier('/cpac_templates/chd8_functional_template_sk.nii') - 'template' + ('template', None) + >>> lookup_identifier('/cpac_templates/CC200.nii.gz') + ('CC', '200') ''' for key, value in LOOKUP_TABLE.items(): - if re.search(key, template_path) is not None: + if search(key, template_path) is not None: return value - return 'template' + return 'template', None diff --git a/CPAC/utils/datasource.py b/CPAC/utils/datasource.py index eb4ffb837d..ca4082b810 100644 --- a/CPAC/utils/datasource.py +++ b/CPAC/utils/datasource.py @@ -17,11 +17,12 @@ import csv import json import re -from typing import Optional, Tuple +from typing import Tuple from nipype import logging from nipype.interfaces import utility as util -from nipype.interfaces.fsl import MultiImageMaths from CPAC.pipeline import nipype_pipeline_engine as pe +from CPAC.resources.templates.lookup_table import format_identifier, \ + lookup_identifier from CPAC.utils import function from CPAC.utils.interfaces.function import Function from CPAC.utils.utils import get_scan_params @@ -1016,33 +1017,36 @@ def create_roi_mask_dataflow(masks, wf_name='datasource_roi_mask'): if mask_file.strip() == '' or mask_file.startswith('#'): continue - base_file = os.path.basename(mask_file) + name, desc = lookup_identifier(mask_file) - try: - valid_extensions = ['.nii', '.nii.gz'] - - base_name = [ - base_file[:-len(ext)] - for ext in valid_extensions - if base_file.endswith(ext) - ][0] - - if base_name in mask_dict: - raise ValueError( - 'Files with same name not allowed: %s %s' % ( - mask_file, - mask_dict[base_name] - ) - ) + if name == 'template': + base_file = os.path.basename(mask_file) - mask_dict[base_name] = mask_file + try: + valid_extensions = ['.nii', '.nii.gz'] + + base_name = [ + base_file[:-len(ext)] + for ext in valid_extensions + if base_file.endswith(ext) + ][0] + + except IndexError: + # pylint: disable=raise-missing-from + raise ValueError('Error in spatial_map_dataflow: File ' + f'extension of {base_file} not ".nii" or ' + '.nii.gz') + + except Exception as e: + raise e + else: + base_name = format_identifier(name, desc) - except IndexError as e: - raise Exception('Error in spatial_map_dataflow: ' - 'File extension not in .nii and .nii.gz') + if base_name in mask_dict: + raise ValueError('Duplicate templates/atlases not allowed: ' + f'{mask_file} {mask_dict[base_name]}') - except Exception as e: - raise e + mask_dict[base_name] = mask_file wf = pe.Workflow(name=wf_name) diff --git a/CPAC/utils/utils.py b/CPAC/utils/utils.py index bd652830c5..ce97efe602 100644 --- a/CPAC/utils/utils.py +++ b/CPAC/utils/utils.py @@ -182,7 +182,7 @@ def create_id_string(unique_id, resource, scan_id=None, template_desc=None, """ if atlas_id: - if '_' in atlas_id: + if not (atlas_id.count('_') == 1 and '_desc-' in atlas_id): atlas_id = atlas_id.replace('_', '') resource = f'atlas-{atlas_id}_{resource}'