From 4e234ca47595f4eb7619e48332b57e0826d05ed9 Mon Sep 17 00:00:00 2001 From: NicolasGensollen Date: Thu, 5 Dec 2024 11:53:35 +0100 Subject: [PATCH] Improve tests --- clinica/utils/testing_utils.py | 72 ++++++++++++++----- ..._t1_volume_tissue_segmentation_pipeline.py | 61 ++++++++++++---- 2 files changed, 102 insertions(+), 31 deletions(-) diff --git a/clinica/utils/testing_utils.py b/clinica/utils/testing_utils.py index bd6fa4346..48518984c 100644 --- a/clinica/utils/testing_utils.py +++ b/clinica/utils/testing_utils.py @@ -10,6 +10,8 @@ from clinica.pipelines.dwi.utils import DWIDataset +from .spm import SPMTissue, get_spm_tissue_from_index + __all__ = [ "build_test_image_cubic_object", "build_bids_directory", @@ -223,7 +225,7 @@ def _build_subjects(directory: Path, configuration: dict) -> None: _build_t1(directory, sub, ses, configuration) if pipeline_name == "t1_volume_tissue_segmentation": _build_t1_volume_tissue_segmentation( - directory, sub, ses, configuration + directory, sub, ses, pipeline_config ) @@ -235,22 +237,58 @@ def _build_t1_volume_tissue_segmentation( directory / "subjects" / sub / ses / "t1" / "spm" / "segmentation" ) segmentation_folder.mkdir(parents=True, exist_ok=True) - folders = ["dartel_input", "native_space", "normalized_space"] - for folder in folders: - (segmentation_folder / folder).mkdir(exist_ok=True) - for tissue in ("csf", "graymatter", "whitematter"): - common_filename_part = f"{sub}_{ses}_T1w_segm-{tissue}" - for folder, filename_end in zip( - folders, - [ - "dartelinput.nii.gz", - "probability.nii.gz", - "space-Ixi549Space_modulated-off_probability.nii.gz", - ], - ): - ( - segmentation_folder / folder / f"{common_filename_part}_{filename_end}" - ).touch() + common_filename_part = f"{sub}_{ses}_T1w_segm-" + _build_t1_volume_tissue_segmentation_native_space( + segmentation_folder, common_filename_part, config + ) + _build_t1_volume_tissue_segmentation_dartel_input( + segmentation_folder, common_filename_part, config + ) + _build_t1_volume_tissue_segmentation_normalized_space( + segmentation_folder, common_filename_part, config + ) + + +def _extract_tissues_from_config(config: dict, key: str) -> list[SPMTissue]: + if (tissue_classes := config.get(key, None)) is not None: + return [get_spm_tissue_from_index(i) for i in tissue_classes] + return [tissue for tissue in SPMTissue] + + +def _build_t1_volume_tissue_segmentation_native_space( + segmentation_folder: Path, common_filename_part: str, config: dict +) -> None: + (segmentation_folder / "native_space").mkdir(exist_ok=True) + for tissue in _extract_tissues_from_config(config, "tissue_classes"): + ( + segmentation_folder + / "native_space" + / f"{common_filename_part}{tissue.value}_probability.nii.gz" + ).touch() + + +def _build_t1_volume_tissue_segmentation_dartel_input( + segmentation_folder: Path, common_filename_part: str, config: dict +) -> None: + (segmentation_folder / "dartel_input").mkdir(exist_ok=True) + for tissue in _extract_tissues_from_config(config, "dartel_tissues"): + ( + segmentation_folder + / "dartel_input" + / f"{common_filename_part}{tissue.value}_dartelinput.nii.gz" + ).touch() + + +def _build_t1_volume_tissue_segmentation_normalized_space( + segmentation_folder: Path, common_filename_part: str, config: dict +) -> None: + (segmentation_folder / "normalized_space").mkdir(exist_ok=True) + for tissue in _extract_tissues_from_config(config, "tissue_classes"): + ( + segmentation_folder + / "normalized_space" + / f"{common_filename_part}{tissue.value}_space-Ixi549Space_modulated-off_probability.nii.gz" + ).touch() def _build_t1_linear(directory: Path, sub: str, ses: str, config: dict) -> None: diff --git a/test/unittests/pipelines/t1_volume_tissue_segmentation/test_t1_volume_tissue_segmentation_pipeline.py b/test/unittests/pipelines/t1_volume_tissue_segmentation/test_t1_volume_tissue_segmentation_pipeline.py index 6aa66a3e8..a6b8a72eb 100644 --- a/test/unittests/pipelines/t1_volume_tissue_segmentation/test_t1_volume_tissue_segmentation_pipeline.py +++ b/test/unittests/pipelines/t1_volume_tissue_segmentation/test_t1_volume_tissue_segmentation_pipeline.py @@ -87,7 +87,7 @@ def test_t1_volume_tissue_segmentation_get_processed_visits_empty(tmp_path, mock def test_t1_volume_tissue_segmentation_get_processed_visits(tmp_path, mocker): """Test the get_processed_visits for T1VolumeTissueSegmentation. - We build the following CAPS dataset: + We build a CAPS dataset with the following structure: caps2 ├── dataset_description.json @@ -98,19 +98,20 @@ def test_t1_volume_tissue_segmentation_get_processed_visits(tmp_path, mocker): └── spm └── segmentation ├── dartel_input - │ ├── sub-01_ses-M000_T1w_segm-csf_dartelinput.nii.gz - │ ├── sub-01_ses-M000_T1w_segm-graymatter_dartelinput.nii.gz - │ └── sub-01_ses-M000_T1w_segm-whitematter_dartelinput.nii.gz + │ └── sub-01_ses-M000_T1w_segm-XXXXXXXX_dartelinput.nii.gz ├── native_space - │ ├── sub-01_ses-M000_T1w_segm-csf_probability.nii.gz - │ ├── sub-01_ses-M000_T1w_segm-graymatter_probability.nii.gz - │ └── sub-01_ses-M000_T1w_segm-whitematter_probability.nii.gz + │ └── sub-01_ses-M000_T1w_segm-YYYYYYYY_probability.nii.gz └── normalized_space - ├── sub-01_ses-M000_T1w_segm-csf_space-Ixi549Space_modulated-off_probability.nii.gz - ├── sub-01_ses-M000_T1w_segm-graymatter_space-Ixi549Space_modulated-off_probability.nii.gz - └── sub-01_ses-M000_T1w_segm-whitematter_space-Ixi549Space_modulated-off_probability.nii.gz - ... same for other subjects and sessions. - Verify that removing files for a given visit will remove it from the "processed" visits. + └── sub-01_ses-M000_T1w_segm-YYYYYYYY_space-Ixi549Space_modulated-off_probability.nii.gz + + And this for several subjects and sessions. + We can control the values for XXXXX and YYYYY through the lists of tissues that we pass + to the CAPS generator (XXXX is controlled by 'dartel_tissues', while YYYY is controlled by 'tissue_classes'). + + The purpose of this test is to verify that, depending on what the user wants in terms of tissues, + the pipeline correctly identifies the already processed visits as the ones having ALL images for + the tissues of interest. If there is at least one image missing, then the visit will be processed + again (and therefore won't be listed in the list of "processed" visits). """ from clinica.pipelines.t1_volume_tissue_segmentation.t1_volume_tissue_segmentation_pipeline import ( T1VolumeTissueSegmentation, @@ -127,7 +128,12 @@ def test_t1_volume_tissue_segmentation_get_processed_visits(tmp_path, mocker): caps = build_caps_directory( tmp_path / "caps", { - "pipelines": {"t1_volume_tissue_segmentation": {}}, + "pipelines": { + "t1_volume_tissue_segmentation": { + "tissue_classes": (1, 2, 3, 4), + "dartel_tissues": (2, 4, 5, 6), + } + }, "subjects": { "sub-01": ["ses-M006"], "sub-02": ["ses-M000", "ses-M012"], @@ -138,15 +144,41 @@ def test_t1_volume_tissue_segmentation_get_processed_visits(tmp_path, mocker): bids_directory=str(bids), caps_directory=str(caps), parameters={ - "tissue_classes": (1, 2, 3), + "tissue_classes": (1, 2, 5, 6), + "dartel_tissues": (2, 4, 6), + }, + ) + # No visit considered already processed since we are asking for tissues 1, 2, 5, and 6 + # and the CAPS folder only contains tissues 1, 2, 3, and 4 + assert pipeline.get_processed_visits() == [] + + pipeline = T1VolumeTissueSegmentation( + bids_directory=str(bids), + caps_directory=str(caps), + parameters={ + "tissue_classes": (1, 2, 3, 4), "dartel_tissues": (1, 2, 3), }, ) + # No visit considered already processed since we are asking for dartel tissues 1, 2, and 3 + # and the CAPS folder only contains tissues 2, 4, 5, and 6 (1 and 3 are missing...) + assert pipeline.get_processed_visits() == [] + + pipeline = T1VolumeTissueSegmentation( + bids_directory=str(bids), + caps_directory=str(caps), + parameters={ + "tissue_classes": (1, 2, 3), + "dartel_tissues": (2, 4, 5), + }, + ) + # All visits are considered processed since we are asking for tissues that are present in the CAPS folder assert pipeline.get_processed_visits() == [ Visit("sub-01", "ses-M006"), Visit("sub-02", "ses-M000"), Visit("sub-02", "ses-M012"), ] + # Delete the folder "dartel_input" altogether for subject 02 session M000 (but keep the other folders) shutil.rmtree( tmp_path @@ -164,6 +196,7 @@ def test_t1_volume_tissue_segmentation_get_processed_visits(tmp_path, mocker): Visit("sub-01", "ses-M006"), Visit("sub-02", "ses-M012"), ] + # Delete a single file in the "native_space" folder for subject 01 session M006 (keep other files and folders) ( tmp_path