diff --git a/clinica/engine/provenance.py b/clinica/engine/provenance.py new file mode 100644 index 0000000000..0db9d17ff4 --- /dev/null +++ b/clinica/engine/provenance.py @@ -0,0 +1,215 @@ +import json +import functools +from os import read + +from pathlib import Path +from typing import Optional + + +def provenance(func): + from .provenance_utils import get_files_list + + @functools.wraps(func) + def run_wrapper(self, **kwargs): + ret = [] + pipeline_fullname = self.fullname + in_files_paths = get_files_list(self, pipeline_fullname, dict_field="input_to") + + prov_context = get_context(files_paths=in_files_paths) + prov_command = get_command(self, in_files_paths) + + if validate_command(prov_context, prov_command): + ret = func(self) + else: + raise Exception( + "The pipeline selected is incompatible with the input files provenance" + ) + out_files_paths = get_files_list( + self, pipeline_fullname, dict_field="output_from" + ) + register_prov(prov_command, out_files_paths) + + return ret + + return run_wrapper + + +def register_prov(prov_command: dict, out_files: list) -> bool: + + # TODO: iterate over out_files and create a provenance file for each + for file in out_files: + write_prov_file(prov_command, file) + print("Provenance registered succesfully") + return True + + +def get_context(files_paths: str) -> dict: + """ + Return a dictionary with the provenance info related to the files in the files_paths + """ + from clinica.engine.provenance_utils import read_prov, get_associated_prov + + prov_data = {"Entity": [], "Agent": [], "Activity": []} + for path in files_paths: + prov_record = read_prov(get_associated_prov(path)) + if prov_record: + prov_data = append_prov_dict(prov_data, prov_record) + + return prov_data + + +def get_command(self, input_files_paths: list) -> dict: + """ + Read the user command and save information in a dict + """ + import sys + + new_entities = [] + new_agent = get_agent() + for path in input_files_paths: + new_entities.append(get_entity(path)) + new_activity = get_activity(self, new_agent["@id"], new_entities) + + return { + "Agent": [new_agent], + "Activity": [new_activity], + "Entity": new_entities, + } + + +def write_prov_file(prov_command, files_paths): + """ + Write the dictionary data to the file_path + """ + from clinica.engine.provenance_utils import read_prov, get_associated_prov + + for file_path in files_paths: + prov_path = get_associated_prov(file_path) + + if prov_path.exists(): + # append the pipeline provenance information to the old provenance file + prov_main = read_prov(prov_path) + prov_main = append_prov_dict(prov_main, prov_command) + else: + print("help") + # create new provenance file with pipeline information + return "" + + +def append_prov_dict(prov_main: dict, prov_new: dict) -> dict: + """ + Append a specific prov data to the global prov dict + """ + + for k in prov_new.keys(): + for el in prov_new[k]: + if prov_main[k] and el not in prov_main[k]: + prov_main[k].append(el) + return prov_main + + +def get_agent() -> dict: + import clinica + from .provenance_utils import get_agent_id + + agent_version = clinica.__version__ + agent_label = clinica.__name__ + agent_id = get_agent_id(agent_label + agent_version) + + new_agent = {"@id": agent_id, "label": agent_label, "version": agent_version} + + return new_agent + + +def get_activity(self, agent_id: str, entities: list) -> dict: + """ + Add the current command to the list of activities + """ + import sys + from .provenance_utils import get_activity_id + + activity_parameters = self.parameters + activity_label = self.fullname + activity_id = get_activity_id(self.fullname) + activity_command = (sys.argv[1:],) + activity_agent = agent_id + activity_used_files = [e["@id"] for e in entities] + + new_activity = { + "@id": activity_id, + "label": activity_label, + "command": activity_command, + "parameters": activity_parameters, + "wasAssociatedWith": activity_agent, + "used": activity_used_files, + } + + return new_activity + + +def get_entity(img_path: str) -> dict: + """ + Add the current file to the list of entities + """ + from clinica.engine.provenance_utils import get_entity_id + from clinica.engine.provenance_utils import get_last_activity + from pathlib import Path + + entity_id = get_entity_id(img_path) + entity_label = Path(img_path).name + entity_path = img_path + entity_source = get_last_activity(img_path) + + new_entity = { + "@id": entity_id, + "label": entity_label, + "atLocation": entity_path, + "wasGeneratedBy": entity_source, + } + + return new_entity + + +def create_prov_file(command, path): + """ + Create new provenance file based on command + """ + # TODO: create a json-ld object next to the file and add it to the active prov object + return + + +def validate_command(prov_context: dict, prov_command: dict) -> bool: + """ + Check the command is valid on the data being run + """ + flag = True + new_activity_id = prov_command["Activity"][0]["@id"] + new_agent_id = prov_command["Agent"][0]["@id"] + + for entity in prov_context["Entity"]: + old_activity_id = entity["wasGeneratedBy"] + if old_activity_id: + ptr_activity = next( + item + for item in prov_context["Activity"] + if item["@id"] == old_activity_id + ) + old_agent_id = ptr_activity["wasAssociatedWith"] + flag and is_valid( + {(old_agent_id, old_activity_id): (new_agent_id, new_activity_id)} + ) + return flag + + +def is_valid(command: dict) -> bool: + valid_list = [ + { + ("clin:clinica0.5.0", "clin:adni2Bids"): ( + "clin:clinica0.5.0", + "clin:t1-linear", + ) + } + ] + if command in valid_list: + return True + return False diff --git a/clinica/engine/provenance_utils.py b/clinica/engine/provenance_utils.py new file mode 100644 index 0000000000..998e68496e --- /dev/null +++ b/clinica/engine/provenance_utils.py @@ -0,0 +1,116 @@ +from typing import Union, Optional +from pathlib import Path + + +def get_files_list(self, pipeline_fullname: str, dict_field="input_to") -> list: + """ + Calls clinica_file_reader with the appropriate extentions + """ + from clinica.utils.inputs import clinica_file_reader + import clinica.utils.input_files as cif + + dict_field_options = ["input_to", "output_from"] + if dict_field not in dict_field_options: + raise (f"dict_field must be one of {dict_field_options}") + + # retrieve all the data dictionaries from the input_files module + files_dicts = { + k: v + for k, v in vars(cif).items() + if isinstance(v, dict) + and dict_field in v.keys() + and pipeline_fullname in v[dict_field] + } + # TODO: check if bids or caps as output + ret_files = [] + for elem in files_dicts: + ref_dir = ( + self.bids_directory if dict_field == "input_to" else self.caps_directory + ) + current_file = clinica_file_reader( + self.subjects, + self.sessions, + ref_dir, + files_dicts[elem], + raise_exception=False, + ) + if current_file: + ret_files.extend(current_file) + + return ret_files + + +def is_entity_tracked(prov_context: dict, entity_id: str) -> bool: + flag_exists = next( + (True for item in prov_context["Entity"] if item["@id"] == entity_id), + False, + ) + return flag_exists + + +def is_agent_tracked(prov_context: dict, agent_id: str) -> bool: + flag_exists = next( + (True for item in prov_context["Agent"] if item["@id"] == agent_id), + False, + ) + return flag_exists + + +def is_activity_tracked(prov_context: dict, activity_id: str) -> bool: + flag_exists = next( + (True for item in prov_context["Activity"] if item["@id"] == activity_id), + False, + ) + return flag_exists + + +def get_entity_id(file_path: str) -> str: + from pathlib import Path + + entity_id = Path(file_path).with_suffix("").name + return entity_id + + +def get_activity_id(pipeline_name: str) -> str: + return "clin:" + pipeline_name + + +def get_agent_id(agent_name: str) -> str: + return "clin:" + agent_name + + +def get_last_activity(file_path: str) -> Optional[list]: + + """ + Return the last activity executed on the file + """ + + prov_record = read_prov(get_associated_prov(file_path)) + if prov_record and prov_record["Activity"]: + last_activity = prov_record["Activity"][-1]["@id"] + return last_activity + return None + + +def get_associated_prov(file_path: str) -> Path: + + file_path = Path(file_path) + while file_path.suffix != "": + file_path = file_path.with_suffix("") + + associated_jsonld = file_path.with_suffix(".jsonld") + return associated_jsonld + + +def read_prov(prov_path: Path) -> Optional[dict]: + """ + Check if the given file is a valid provenance json-ld + """ + import json + + # TODO: check that the provenance file associations and uses exists + if prov_path.exists(): + with open(prov_path, "r") as fp: + json_ld_data = json.load(fp) + return json_ld_data + return None diff --git a/clinica/pipelines/engine.py b/clinica/pipelines/engine.py index 976bafa29e..bc3a291b3c 100644 --- a/clinica/pipelines/engine.py +++ b/clinica/pipelines/engine.py @@ -9,6 +9,7 @@ import click from nipype.pipeline.engine import Workflow +import clinica.engine.provenance as prov def postset(attribute, value): @@ -236,6 +237,7 @@ def build(self): self.build_output_node() return self + @prov.provenance def run(self, plugin=None, plugin_args=None, update_hash=False, bypass_check=False): """Executes the Pipeline. diff --git a/clinica/pipelines/machine_learning_spatial_svm/spatial_svm_pipeline.py b/clinica/pipelines/machine_learning_spatial_svm/spatial_svm_pipeline.py index f140b59941..9f86ecf4c6 100644 --- a/clinica/pipelines/machine_learning_spatial_svm/spatial_svm_pipeline.py +++ b/clinica/pipelines/machine_learning_spatial_svm/spatial_svm_pipeline.py @@ -95,7 +95,7 @@ def build_input_node(self): "*_T1w_segm-graymatter_space-Ixi549Space_modulated-on_probability.nii.gz", ), "description": "graymatter tissue segmented in T1w MRI in Ixi549 space", - "needed_pipeline": "t1-volume-tissue-segmentation", + "output_from": "t1-volume-tissue-segmentation", } elif self.parameters["orig_input_data"] == "pet-volume": if not ( diff --git a/clinica/pipelines/statistics_volume_correction/statistics_volume_correction_pipeline.py b/clinica/pipelines/statistics_volume_correction/statistics_volume_correction_pipeline.py index a5e13c650a..a58fb4a351 100644 --- a/clinica/pipelines/statistics_volume_correction/statistics_volume_correction_pipeline.py +++ b/clinica/pipelines/statistics_volume_correction/statistics_volume_correction_pipeline.py @@ -41,7 +41,7 @@ def build_input_node(self): { "pattern": self.parameters["t_map"] + "*", "description": "statistics t map", - "needed_pipeline": "statistics-volume", + "output_from": "statistics-volume", }, ) diff --git a/clinica/utils/input_files.py b/clinica/utils/input_files.py index 5a8b235a77..46a0afd90f 100644 --- a/clinica/utils/input_files.py +++ b/clinica/utils/input_files.py @@ -9,150 +9,154 @@ # BIDS -T1W_NII = {"pattern": "sub-*_ses-*_t1w.nii*", "description": "T1w MRI"} +T1W_NII = { + "pattern": "sub-*_ses-*_t1w.nii*", + "description": "T1w MRI", + "input_to": ["t1-linear"], +} # T1-FreeSurfer T1_FS_WM = { "pattern": "t1/freesurfer_cross_sectional/sub-*_ses-*/mri/wm.seg.mgz", "description": "segmentation of white matter (mri/wm.seg.mgz).", - "needed_pipeline": "t1-freesurfer", + "output_from": "t1-freesurfer", } T1_FS_BRAIN = { "pattern": "t1/freesurfer_cross_sectional/sub-*_ses-*/mri/brain.mgz", "description": " extracted brain from T1w MRI (mri/brain.mgz).", - "needed_pipeline": "t1-freesurfer", + "output_from": "t1-freesurfer", } T1_FS_ORIG_NU = { "pattern": "t1/freesurfer_cross_sectional/sub-*_ses-*/mri/orig_nu.mgz", "description": "intensity normalized volume generated after correction for" " non-uniformity in FreeSurfer (mri/orig_nu.mgz).", - "needed_pipeline": "t1-freesurfer", + "output_from": "t1-freesurfer", } T1_FS_LONG_ORIG_NU = { "pattern": "t1/long-*/freesurfer_longitudinal/sub-*_ses-*.long.sub-*_*/mri/orig_nu.mgz", "description": "intensity normalized volume generated after correction for non-uniformity in FreeSurfer (orig_nu.mgz) in longitudinal", - "needed_pipeline": "t1-freesurfer and t1-freesurfer longitudinal", + "output_from": "t1-freesurfer and t1-freesurfer longitudinal", } T1_FS_WM_SURF_R = { "pattern": "t1/freesurfer_cross_sectional/sub-*_ses-*/surf/rh.white", "description": "right white matter/gray matter border surface (rh.white).", - "needed_pipeline": "t1-freesurfer", + "output_from": "t1-freesurfer", } T1_FS_LONG_SURF_R = { "pattern": "t1/long-*/freesurfer_longitudinal/sub-*_ses-*.long.sub-*_*/surf/rh.white", "description": "right white matter/gray matter border surface (rh.white) generated with t1-freesurfer-longitudinal.", - "needed_pipeline": "t1-freesurfer and t1-freesurfer longitudinal", + "output_from": "t1-freesurfer and t1-freesurfer longitudinal", } T1_FS_LONG_SURF_L = { "pattern": "t1/long-*/freesurfer_longitudinal/sub-*_ses-*.long.sub-*_*/surf/lh.white", "description": "left white matter/gray matter border surface (lh.white) generated with t1-freesurfer-longitudinal.", - "needed_pipeline": "t1-freesurfer and t1-freesurfer longitudinal", + "output_from": "t1-freesurfer and t1-freesurfer longitudinal", } T1_FS_WM_SURF_L = { "pattern": "t1/freesurfer_cross_sectional/sub-*_ses-*/surf/lh.white", "description": "left white matter/gray matter border surface (lh.white).", - "needed_pipeline": "t1-freesurfer", + "output_from": "t1-freesurfer", } T1_FS_DESTRIEUX = { "pattern": "t1/freesurfer_cross_sectional/sub-*_ses-*/mri/aparc.a2009s+aseg.mgz", "description": "Destrieux-based segmentation (mri/aparc.a2009s+aseg.mgz).", - "needed_pipeline": "t1-freesurfer", + "output_from": "t1-freesurfer", } T1_FS_DESTRIEUX_PARC_L = { "pattern": "t1/freesurfer_cross_sectional/sub-*_ses-*/label/lh.aparc.a2009s.annot", "description": "left hemisphere surface-based Destrieux parcellation (label/lh.aparc.a2009s.annot).", - "needed_pipeline": "t1-freesurfer", + "output_from": "t1-freesurfer", } T1_FS_LONG_DESTRIEUX_PARC_L = { "pattern": "t1/long-*/freesurfer_longitudinal/sub-*_ses-*.long.sub-*_*/label/lh.aparc.a2009s.annot", "description": "left hemisphere surface-based Destrieux parcellation (label/lh.aparc.a2009s.annot) generated with t1-freesurfer-longitudinal.", - "needed_pipeline": "t1-freesurfer and t1-freesurfer longitudinal", + "output_from": "t1-freesurfer and t1-freesurfer longitudinal", } T1_FS_LONG_DESTRIEUX_PARC_R = { "pattern": "t1/long-*/freesurfer_longitudinal/sub-*_ses-*.long.sub-*_*/label/rh.aparc.a2009s.annot", "description": "right hemisphere surface-based Destrieux parcellation (label/rh.aparc.a2009s.annot) generated with t1-freesurfer-longitudinal.", - "needed_pipeline": "t1-freesurfer and t1-freesurfer longitudinal", + "output_from": "t1-freesurfer and t1-freesurfer longitudinal", } T1_FS_DESTRIEUX_PARC_R = { "pattern": "t1/freesurfer_cross_sectional/sub-*_ses-*/label/rh.aparc.a2009s.annot", "description": "right hemisphere surface-based Destrieux parcellation (label/rh.aparc.a2009s.annot).", - "needed_pipeline": "t1-freesurfer", + "output_from": "t1-freesurfer", } T1_FS_DESIKAN = { "pattern": "t1/freesurfer_cross_sectional/sub-*_ses-*/mri/aparc+aseg.mgz", "description": "Desikan-based segmentation (mri/aparc.a2009s+aseg.mgz).", - "needed_pipeline": "t1-freesurfer", + "output_from": "t1-freesurfer", } T1_FS_DESIKAN_PARC_L = { "pattern": "t1/freesurfer_cross_sectional/sub-*_ses-*/label/lh.aparc.annot", "description": "left hemisphere surface-based Desikan parcellation (label/lh.aparc.annot).", - "needed_pipeline": "t1-freesurfer", + "output_from": "t1-freesurfer", } T1_FS_DESIKAN_PARC_R = { "pattern": "t1/freesurfer_cross_sectional/sub-*_ses-*/label/rh.aparc.annot", "description": "right hemisphere surface-based Desikan parcellation (label/rh.aparc.annot).", - "needed_pipeline": "t1-freesurfer", + "output_from": "t1-freesurfer", } # T1-FreeSurfer-Template T1_FS_T_DESTRIEUX = { "pattern": "freesurfer_unbiased_template/sub-*_long-*/mri/aparc.a2009s+aseg.mgz", "description": "Destrieux-based segmentation (mri/aparc.a2009s+aseg.mgz) from unbiased template.", - "needed_pipeline": "t1-freesurfer-longitudinal or t1-freesurfer-template", + "output_from": "t1-freesurfer-longitudinal or t1-freesurfer-template", } # T1-FreeSurfer-Longitudinal-Correction T1_FS_LONG_DESIKAN_PARC_L = { "pattern": "t1/long-*/freesurfer_longitudinal/sub-*_ses-*.long.sub-*_*/label/lh.aparc.annot", "description": "left hemisphere surface-based Desikan parcellation (label/lh.aparc.annot) generated with t1-freesurfer-longitudinal.", - "needed_pipeline": "t1-freesurfer and t1-freesurfer-longitudinal", + "output_from": "t1-freesurfer and t1-freesurfer-longitudinal", } T1_FS_LONG_DESIKAN_PARC_R = { "pattern": "t1/long-*/freesurfer_longitudinal/sub-*_ses-*.long.sub-*_*/label/rh.aparc.annot", "description": "right hemisphere surface-based Desikan parcellation (label/rh.aparc.annot) generated with t1-freesurfer-longitudinal.", - "needed_pipeline": "t1-freesurfer and t1-freesurfer-longitudinal", + "output_from": "t1-freesurfer and t1-freesurfer-longitudinal", } T1W_LINEAR = { "pattern": "*space-MNI152NLin2009cSym_res-1x1x1_T1w.nii.gz", "description": "T1w image registered in MNI152NLin2009cSym space using t1-linear pipeline", - "needed_pipeline": "t1-linear", + "output_from": "t1-linear", } T1W_LINEAR_CROPPED = { "pattern": "*space-MNI152NLin2009cSym_desc-Crop_res-1x1x1_T1w.nii.gz", "description": "T1W Image registered using t1-linear and cropped " "(matrix size 169×208×179, 1 mm isotropic voxels)", - "needed_pipeline": "t1-linear", + "output_from": "t1-linear", } T1W_EXTENSIVE = { "pattern": "*space-Ixi549Space_desc-SkullStripped_T1w.nii.gz", "description": "T1w image skull-stripped registered in Ixi549Space space using clinicaDL preprocessing pipeline", - "needed_pipeline": "t1-extensive", + "output_from": "t1-extensive", } T1W_TO_MNI_TRANSFROM = { "pattern": "*space-MNI152NLin2009cSym_res-1x1x1_affine.mat", "description": "Transformation matrix from T1W image to MNI space using t1-linear pipeline", - "needed_pipeline": "t1-linear", + "output_from": "t1-linear", } # T1-Volume @@ -172,7 +176,7 @@ def t1_volume_native_tpm(tissue_number): f"*_*_T1w_segm-{INDEX_TISSUE_MAP[tissue_number]}_probability.nii*", ), "description": f"Tissue probability map {INDEX_TISSUE_MAP[tissue_number]} in native space", - "needed_pipeline": "t1-volume-tissue-segmentation", + "output_from": "t1-volume-tissue-segmentation", } return information @@ -191,7 +195,7 @@ def t1_volume_dartel_input_tissue(tissue_number): f"*_*_T1w_segm-{INDEX_TISSUE_MAP[tissue_number]}_dartelinput.nii*", ), "description": f"Dartel input for tissue probability map {INDEX_TISSUE_MAP[tissue_number]} from T1w MRI", - "needed_pipeline": "t1-volume-tissue-segmentation", + "output_from": "t1-volume-tissue-segmentation", } return information @@ -219,7 +223,7 @@ def t1_volume_native_tpm_in_mni(tissue_number, modulation): f"Tissue probability map {INDEX_TISSUE_MAP[tissue_number]} based on " f"native MRI in MNI space (Ixi549) {description_modulation} modulation." ), - "needed_pipeline": "t1-volume-tissue-segmentation", + "output_from": "t1-volume-tissue-segmentation", } return information @@ -247,7 +251,7 @@ def t1_volume_template_tpm_in_mni(group_label, tissue_number, modulation): f"Tissue probability map {INDEX_TISSUE_MAP[tissue_number]} based " f"on {group_label} template in MNI space (Ixi549) {description_modulation} modulation." ), - "needed_pipeline": "t1-volume", + "output_from": "t1-volume", } return information @@ -264,7 +268,7 @@ def t1_volume_deformation_to_template(group_label): f"sub-*_ses-*_T1w_target-{group_label}_transformation-forward_deformation.nii*", ), "description": f"Deformation from native space to group template {group_label} space.", - "needed_pipeline": "t1-volume-create-dartel", + "output_from": "t1-volume-create-dartel", } return information @@ -279,7 +283,7 @@ def t1_volume_i_th_iteration_group_template(group_label, i): f"group-{group_label}_iteration-{i}_template.nii*", ), "description": f"Iteration #{i} of Dartel template {group_label}", - "needed_pipeline": "t1-volume or t1-volume-create-dartel", + "output_from": "t1-volume or t1-volume-create-dartel", } return information @@ -292,7 +296,7 @@ def t1_volume_final_group_template(group_label): f"group-{group_label}", "t1", f"group-{group_label}_template.nii*" ), "description": f"T1w template file of group {group_label}", - "needed_pipeline": "t1-volume or t1-volume-create-dartel", + "output_from": "t1-volume or t1-volume-create-dartel", } return information @@ -329,25 +333,25 @@ def t1_volume_final_group_template(group_label): DWI_PREPROC_NII = { "pattern": "dwi/preprocessing/sub-*_ses-*_dwi_space-*_preproc.nii*", "description": "preprocessed DWI", - "needed_pipeline": "dwi-preprocessing-using-t1 or dwi-preprocessing-using-fieldmap", + "output_from": "dwi-preprocessing-using-t1 or dwi-preprocessing-using-fieldmap", } DWI_PREPROC_BRAINMASK = { "pattern": "dwi/preprocessing/sub-*_ses-*_dwi_space-*_brainmask.nii*", "description": "b0 brainmask", - "needed_pipeline": "dwi-preprocessing-using-t1 or dwi-preprocessing-using-fieldmap", + "output_from": "dwi-preprocessing-using-t1 or dwi-preprocessing-using-fieldmap", } DWI_PREPROC_BVEC = { "pattern": "dwi/preprocessing/sub-*_ses-*_dwi_space-*_preproc.bvec", "description": "preprocessed bvec", - "needed_pipeline": "dwi-preprocessing-using-t1 or dwi-preprocessing-using-fieldmap", + "output_from": "dwi-preprocessing-using-t1 or dwi-preprocessing-using-fieldmap", } DWI_PREPROC_BVAL = { "pattern": "dwi/preprocessing/*_dwi_space-*_preproc.bval", "description": "preprocessed bval", - "needed_pipeline": "dwi-preprocessing-using-t1 or dwi-preprocessing-using-fieldmap", + "output_from": "dwi-preprocessing-using-t1 or dwi-preprocessing-using-fieldmap", } """ PET """ @@ -413,7 +417,7 @@ def pet_volume_normalized_suvr_pet( f"{mask_description} SUVR map (using {suvr_reference_region} region) of {acq_label}-PET " f"{pvc_description} and {fwhm_description} in Ixi549Space space based on {group_label} DARTEL template" ), - "needed_pipeline": "pet-volume", + "output_from": "pet-volume", } return information @@ -426,15 +430,15 @@ def pet_linear_nii(acq_label, suvr_reference_region, uncropped_image): if uncropped_image: description = "" - else : + else: description = "_desc-Crop" information = { "pattern": os.path.join( "pet_linear", - f"*_acq-{acq_label}_pet_space-MNI152NLin2009cSym{description}_res-1x1x1_suvr-{suvr_reference_region}_pet.nii.gz" + f"*_acq-{acq_label}_pet_space-MNI152NLin2009cSym{description}_res-1x1x1_suvr-{suvr_reference_region}_pet.nii.gz", ), "description": "", - "needed_pipeline": "pet-linear" + "output_from": "pet-linear", } return information diff --git a/clinica/utils/inputs.py b/clinica/utils/inputs.py index 4db9fdd27a..a614bc1460 100644 --- a/clinica/utils/inputs.py +++ b/clinica/utils/inputs.py @@ -188,10 +188,10 @@ def clinica_file_reader( sessions: list of sessions (must be same size as subjects, and must correspond ) input_directory: location of the bids or caps directory information: dictionary containing all the relevant information to look for the files. Dict must contains the - following keys : pattern, description. The optional key is: needed_pipeline + following keys : pattern, description. The optional key is: output_from pattern: define the pattern of the final file description: string to describe what the file is - needed_pipeline (optional): string describing the pipeline(s) needed to obtain the related + output_from (optional): string describing the pipeline(s) needed to obtain the related file raise_exception: if True (normal behavior), an exception is raised if errors happen. If not, we return the file list as it is @@ -217,7 +217,7 @@ def clinica_file_reader( caps_directory, {'pattern': 'freesurfer_cross_sectional/sub-*_ses-*/mri/orig_nu.mgz', 'description': 'freesurfer file orig_nu.mgz', - 'needed_pipeline': 't1-freesurfer'}) + 'output_from': 't1-freesurfer'}) gives: ['/caps/subjects/sub-ADNI011S4105/ses-M00/t1/freesurfer_cross_sectional/sub-ADNI011S4105_ses-M00/mri/orig_nu.mgz'] - You have a partial name of the file: @@ -238,7 +238,7 @@ def clinica_file_reader( caps, {'pattern': 'rh.white', 'description': 'right hemisphere of outter cortical surface.', - 'needed_pipeline': 't1-freesurfer'}) + 'output_from': 't1-freesurfer'}) the following error will arise: * More than 1 file found:: /caps/subjects/sub-ADNI011S4105/ses-M00/t1/freesurfer_cross_sectional/fsaverage/surf/rh.white @@ -264,9 +264,9 @@ def clinica_file_reader( elem in information.keys() for elem in ["pattern", "description"] ), "'information' must contain the keys 'pattern' and 'description'" assert all( - elem in ["pattern", "description", "needed_pipeline"] + elem in ["pattern", "description", "output_from", "input_to"] for elem in information.keys() - ), "'information' can only contain the keys 'pattern', 'description' and 'needed_pipeline'" + ), "'information' can only contain the keys 'pattern', 'description', 'output_from' and 'input_to'" pattern = information["pattern"] is_bids = determine_caps_or_bids(input_directory) @@ -319,11 +319,11 @@ def clinica_file_reader( f"Clinica encountered {len(error_encountered)} " f"problem(s) while getting {information['description']}:\n" ) - if "needed_pipeline" in information.keys(): - if information["needed_pipeline"]: + if "output_from" in information.keys(): + if information["output_from"]: error_message += ( "Please note that the following clinica pipeline(s) must " - f"have run to obtain these files: {information['needed_pipeline']}\n" + f"have run to obtain these files: {information['output_from']}\n" ) for msg in error_encountered: error_message += msg @@ -342,10 +342,10 @@ def clinica_group_reader(caps_directory, information, raise_exception=True): Args: caps_directory: input caps directory information: dictionary containing all the relevant information to look for the files. Dict must contains the - following keys : pattern, description, needed_pipeline + following keys : pattern, description, output_from pattern: define the pattern of the final file description: string to describe what the file is - needed_pipeline (optional): string describing the pipeline needed to obtain the file beforehand + output_from (optional): string describing the pipeline needed to obtain the file beforehand raise_exception: if True (normal behavior), an exception is raised if errors happen. If not, we return the file list as it is @@ -363,9 +363,8 @@ def clinica_group_reader(caps_directory, information, raise_exception=True): information, dict ), "A dict must be provided for the argument 'dict'" assert all( - elem in information.keys() - for elem in ["pattern", "description", "needed_pipeline"] - ), "'information' must contain the keys 'pattern', 'description', 'needed_pipeline'" + elem in information.keys() for elem in ["pattern", "description", "output_from"] + ), "'information' must contain the keys 'pattern', 'description', 'output_from'" pattern = information["pattern"] # Some check on the formatting on the data @@ -391,7 +390,7 @@ def clinica_group_reader(caps_directory, information, raise_exception=True): error_string += ( f"\n\tCAPS directory: {caps_directory}\n" "Please note that the following clinica pipeline(s) must have run to obtain these files: " - f"{information['needed_pipeline']}\n" + f"{information['output_from']}\n" ) raise ClinicaCAPSError(error_string) return current_glob_found[0]