diff --git a/clinica/pipelines/pet/linear/pipeline.py b/clinica/pipelines/pet/linear/pipeline.py index a54f531d9..4c83aea3b 100644 --- a/clinica/pipelines/pet/linear/pipeline.py +++ b/clinica/pipelines/pet/linear/pipeline.py @@ -40,6 +40,8 @@ def get_processed_visits(self) -> list[Visit]: The pipeline will further skip these visits and run processing only for the remaining visits. """ + from functools import reduce + from clinica.utils.filemanip import extract_visits from clinica.utils.input_files import ( pet_linear_nii, @@ -59,21 +61,23 @@ def get_processed_visits(self) -> list[Visit]: uncropped_image=self.parameters.get("uncropped_image", False), ), ) - visits_having_pet_image = extract_visits(pet_registered_image) + visits = [set(extract_visits(pet_registered_image))] transformation, _ = clinica_file_reader( self.subjects, self.sessions, self.caps_directory, pet_linear_transformation_matrix(tracer=self.parameters["acq_label"]), ) - visits_having_transformation = extract_visits(transformation) - return sorted( - list( - set(visits_having_pet_image).intersection( - set(visits_having_transformation) - ) + visits.append(set(extract_visits(transformation))) + if self.parameters.get("save_PETinT1w", False): + pet_image_in_t1w_space, _ = clinica_file_reader( + self.subjects, + self.sessions, + self.caps_directory, + pet_linear_nii(acq_label=self.parameters["acq_label"], space="T1w"), ) - ) + visits.append(set(extract_visits(pet_image_in_t1w_space))) + return sorted(list(reduce(lambda x, y: x.intersection(y), visits))) def get_input_fields(self) -> List[str]: """Specify the list of possible inputs of this pipeline. diff --git a/clinica/utils/input_files.py b/clinica/utils/input_files.py index aed1c8807..c23d42ea7 100644 --- a/clinica/utils/input_files.py +++ b/clinica/utils/input_files.py @@ -680,25 +680,56 @@ def pet_volume_normalized_suvr_pet( return information +def _clean_pattern(pattern: str, character: str = "*") -> str: + """Removes multiple '*' wildcards in provided pattern.""" + cleaned = [] + for c in pattern: + if not cleaned or not cleaned[-1] == c == character: + cleaned.append(c) + return "".join(cleaned) + + def pet_linear_nii( - acq_label: Union[str, Tracer], - suvr_reference_region: Union[str, SUVRReferenceRegion], - uncropped_image: bool, + acq_label: Optional[Union[str, Tracer]] = None, + suvr_reference_region: Optional[Union[str, SUVRReferenceRegion]] = None, + uncropped_image: bool = False, + space: str = "mni", + resolution: Optional[int] = None, ) -> dict: from pathlib import Path - acq_label = Tracer(acq_label) - region = SUVRReferenceRegion(suvr_reference_region) - - if uncropped_image: - description = "" - else: + tracer_filter = "*" + tracer_description = "" + if acq_label: + acq_label = Tracer(acq_label) + tracer_filter = f"_trc-{acq_label.value}" + tracer_description = f" obtained with tracer {acq_label.value}" + region_filter = "*" + region_description = "" + if suvr_reference_region: + region = SUVRReferenceRegion(suvr_reference_region) + region_filter = f"_suvr-{region.value}" + region_description = f" for SUVR region {region.value}" + space_filer = f"_space-{'MNI152NLin2009cSym' if space == 'mni' else 'T1w'}" + space_description = f" affinely registered to the {'MNI152NLin2009cSym template' if space == 'mni' else 'associated T1w image'}" + description = "*" + if space == "mni" and not uncropped_image: description = "_desc-Crop" - + resolution_filter = "*" + resolution_description = "" + if resolution: + resolution_explicit = f"{resolution}x{resolution}x{resolution}" + resolution_filter = f"_res-{resolution_explicit}" + resolution_description = f" of resolution {resolution_explicit}" information = { "pattern": Path("pet_linear") - / f"*_trc-{acq_label.value}_pet_space-MNI152NLin2009cSym{description}_res-1x1x1_suvr-{region.value}_pet.nii.gz", - "description": "", + / _clean_pattern( + f"*{tracer_filter}_pet{space_filer}{description}{resolution_filter}{region_filter}_pet.nii.gz" + ), + "description": ( + f"{'Cropped ' if space == 'mni' and not uncropped_image else ''}PET nifti image{resolution_description}" + f"{tracer_description}{region_description}{space_description} resulting from the pet-linear pipeline" + ), "needed_pipeline": "pet-linear", } return information diff --git a/test/unittests/utils/test_input_files.py b/test/unittests/utils/test_input_files.py index 1625ca53e..9c5ee5356 100644 --- a/test/unittests/utils/test_input_files.py +++ b/test/unittests/utils/test_input_files.py @@ -87,3 +87,72 @@ def test_dwi_dti_query_error(): match="'foo' is not a valid DTIBasedMeasure", ): dwi_dti("foo") + + +@pytest.mark.parametrize( + "input_parameters,expected_description,expected_pattern", + [ + ( + {}, + ( + "Cropped PET nifti image affinely registered to the MNI152NLin2009cSym " + "template resulting from the pet-linear pipeline" + ), + "pet_linear/*_pet_space-MNI152NLin2009cSym_desc-Crop*_pet.nii.gz", + ), + ( + { + "acq_label": "18FFDG", + }, + ( + "Cropped PET nifti image obtained with tracer 18FFDG affinely registered to the " + "MNI152NLin2009cSym template resulting from the pet-linear pipeline" + ), + "pet_linear/*_trc-18FFDG_pet_space-MNI152NLin2009cSym_desc-Crop*_pet.nii.gz", + ), + ( + { + "acq_label": "18FAV45", + "suvr_reference_region": "pons", + "uncropped_image": True, + "resolution": 2, + }, + ( + "PET nifti image of resolution 2x2x2 obtained with tracer 18FAV45 for SUVR region " + "pons affinely registered to the MNI152NLin2009cSym template resulting from the pet-linear pipeline" + ), + "pet_linear/*_trc-18FAV45_pet_space-MNI152NLin2009cSym*_res-2x2x2_suvr-pons_pet.nii.gz", + ), + ( + { + "suvr_reference_region": "pons2", + "resolution": 2, + "space": "T1w", + }, + ( + "PET nifti image of resolution 2x2x2 for SUVR region pons2 affinely " + "registered to the associated T1w image resulting from the pet-linear pipeline" + ), + "pet_linear/*_pet_space-T1w*_res-2x2x2_suvr-pons2_pet.nii.gz", + ), + ( + { + "acq_label": "18FFDG", + "space": "T1w", + }, + ( + "PET nifti image obtained with tracer 18FFDG affinely registered to the " + "associated T1w image resulting from the pet-linear pipeline" + ), + "pet_linear/*_trc-18FFDG_pet_space-T1w*_pet.nii.gz", + ), + ], +) +def test_pet_linear_nii(input_parameters, expected_description, expected_pattern): + from clinica.utils.input_files import pet_linear_nii + + query = pet_linear_nii(**input_parameters) + + assert query["description"] == expected_description + assert query["needed_pipeline"] == "pet-linear" + assert str(query["pattern"]) == expected_pattern