diff --git a/clinica/iotools/utils/data_handling.py b/clinica/iotools/utils/data_handling.py index 2d5845a47..6ab74836d 100644 --- a/clinica/iotools/utils/data_handling.py +++ b/clinica/iotools/utils/data_handling.py @@ -269,6 +269,8 @@ def _add_data_to_merge_file_from_caps( "dwi-dti": dwi_dti_pipeline, } merged_summary_df = pd.DataFrame() + if "group_selection" in kwargs and kwargs["group_selection"] is None: + kwargs.pop("group_selection") if not pipelines: for pipeline_name, pipeline_fn in pipeline_options.items(): cprint(f"Extracting from CAPS pipeline output: {pipeline_name}...") diff --git a/clinica/iotools/utils/pipeline_handling.py b/clinica/iotools/utils/pipeline_handling.py index dfbb927ac..7daa596b7 100644 --- a/clinica/iotools/utils/pipeline_handling.py +++ b/clinica/iotools/utils/pipeline_handling.py @@ -12,7 +12,7 @@ def _get_atlas_name(atlas_path: Path, pipeline: str) -> str: """Helper function for _extract_metrics_from_pipeline.""" if pipeline == "dwi_dti": splitter = "_dwi_space-" - elif pipeline in ("t1_freesurfer_longitudinal", "t1_freesurfer"): + elif pipeline in ("t1_freesurfer_longitudinal", "t1-freesurfer"): splitter = "_parcellation-" elif pipeline in ("t1-volume", "pet-volume"): splitter = "_space-" @@ -46,7 +46,7 @@ def _get_mod_path(ses_path: Path, pipeline: str) -> Optional[Path]: / "freesurfer_longitudinal" / "regional_measures" ) - if pipeline == "t1_freesurfer": + if pipeline == "t1-freesurfer": return ses_path / "t1" / "freesurfer_cross_sectional" / "regional_measures" if pipeline == "t1-volume": return ses_path / "t1" / "spm" / "dartel" @@ -189,15 +189,15 @@ def _extract_metrics_from_pipeline( except KeyError: raise KeyError("Fields `participant_id` and `session_id` are required.") - if group_selection is None: - try: - group_selection = [f.name for f in (caps_dir / "groups").iterdir()] - except FileNotFoundError: - return df, None - else: - group_selection = [f"group-{group}" for group in group_selection] ignore_groups = group_selection == [""] - + if not ignore_groups: + if group_selection is None: + try: + group_selection = [f.name for f in (caps_dir / "groups").iterdir()] + except FileNotFoundError: + return df, None + else: + group_selection = [f"group-{group}" for group in group_selection] subjects_dir = caps_dir / "subjects" records = [] for participant_id, session_id in df.index.values: @@ -227,6 +227,20 @@ def _extract_metrics_from_pipeline( ) ) for atlas_path in atlas_paths: + if metric == "segmentationVolumes": + from clinica.iotools.converters.adni_to_bids.adni_utils import ( + replace_sequence_chars, + ) + + atlas_df = pd.read_csv(atlas_path, sep="\t") + label_list = [ + f"t1-freesurfer_segmentation-volumes_ROI-{replace_sequence_chars(roi_name)}_volume" + for roi_name in atlas_df.label_name.values + ] + values = atlas_df["label_value"].to_numpy() + for label, value in zip(label_list, values): + records[-1][label] = value + continue if not _skip_atlas( atlas_path, pipeline, pvc_restriction, tracers_selection ): @@ -279,7 +293,7 @@ def _extract_metrics_from_pipeline( t1_freesurfer_pipeline = functools.partial( _extract_metrics_from_pipeline, metrics=["thickness", "segmentationVolumes"], - pipeline="t1_freesurfer", + pipeline="t1-freesurfer", group_selection=[""], ) diff --git a/test/unittests/iotools/utils/test_pipeline_handling.py b/test/unittests/iotools/utils/test_pipeline_handling.py index d16e579e5..1d07f6f68 100644 --- a/test/unittests/iotools/utils/test_pipeline_handling.py +++ b/test/unittests/iotools/utils/test_pipeline_handling.py @@ -1,4 +1,5 @@ import operator +from pathlib import Path import pandas as pd import pytest @@ -46,7 +47,7 @@ def test_get_mod_path_errors(tmp_path): "pipeline,expected_path", [ ("dwi_dti", ["dwi", "dti_based_processing", "atlas_statistics"]), - ("t1_freesurfer", ["t1", "freesurfer_cross_sectional", "regional_measures"]), + ("t1-freesurfer", ["t1", "freesurfer_cross_sectional", "regional_measures"]), ("t1-volume", ["t1", "spm", "dartel"]), ("pet-volume", ["pet", "preprocessing"]), ], @@ -210,3 +211,139 @@ def test_extract_metrics_from_pipeline(tmp_path): "group_id", "regions_number", } + + +def test_t1_freesurfer_pipeline_nothing_found(tmp_path): + from pandas.testing import assert_frame_equal + + from clinica.iotools.utils.pipeline_handling import t1_freesurfer_pipeline + + caps = tmp_path / "caps" + caps.mkdir() + merged_df = pd.DataFrame( + { + "participant_id": ["sub-01"], + "session_id": ["ses-M000"], + "age": [85], + } + ) + merged_df.set_index( + ["participant_id", "session_id"], inplace=True, verify_integrity=True + ) + merged_df_with_t1_freesurfer_metrics, summary = t1_freesurfer_pipeline( + caps, merged_df + ) + merged_df_with_t1_freesurfer_metrics.set_index( + ["participant_id", "session_id"], inplace=True, verify_integrity=True + ) + + assert_frame_equal(merged_df_with_t1_freesurfer_metrics, merged_df) + assert summary.empty + + +def test_t1_freesurfer_longitudinal_pipeline_nothing_found(tmp_path): + from pandas.testing import assert_frame_equal + + from clinica.iotools.utils.pipeline_handling import ( + t1_freesurfer_longitudinal_pipeline, + ) + + caps = tmp_path / "caps" + caps.mkdir() + merged_df = pd.DataFrame( + { + "participant_id": ["sub-01"], + "session_id": ["ses-M000"], + "age": [85], + } + ) + merged_df.set_index( + ["participant_id", "session_id"], inplace=True, verify_integrity=True + ) + merged_df_with_t1_freesurfer_metrics, summary = t1_freesurfer_longitudinal_pipeline( + caps, merged_df + ) + merged_df_with_t1_freesurfer_metrics.set_index( + ["participant_id", "session_id"], inplace=True, verify_integrity=True + ) + + assert_frame_equal(merged_df_with_t1_freesurfer_metrics, merged_df) + assert summary.empty + + +def write_fake_statistics(tsv_file: Path): + fake = pd.DataFrame( + { + "label_name": ["foo", "bar", "baz"], + "label_value": [1.2, 2.3, 3.4], + } + ) + fake.to_csv(tsv_file, sep="\t") + + +def test_t1_freesurfer_pipeline(tmp_path): + from clinica.iotools.utils.pipeline_handling import t1_freesurfer_pipeline + + caps = tmp_path / "caps" + regional_measures_folder = ( + caps + / "subjects" + / "sub-01" + / "ses-M000" + / "t1" + / "freesurfer_cross_sectional" + / "regional_measures" + ) + regional_measures_folder.mkdir(parents=True) + for file in ( + "sub-01_ses-M000_parcellation-desikan_thickness.tsv", + "sub-01_ses-M000_parcellation-desikan_area.tsv", + "sub-01_ses-M000_parcellation-destrieux_thickness.tsv", + "sub-01_ses-M000_segmentationVolumes.tsv", + ): + write_fake_statistics(regional_measures_folder / file) + + assert len([f for f in regional_measures_folder.iterdir()]) == 4 + merged_df = pd.DataFrame( + { + "participant_id": ["sub-01"], + "session_id": ["ses-M000"], + "age": [85], + } + ) + merged_df.set_index( + ["participant_id", "session_id"], inplace=True, verify_integrity=True + ) + merged_df_with_t1_freesurfer_metrics, summary = t1_freesurfer_pipeline( + caps, merged_df + ) + merged_df_with_t1_freesurfer_metrics.set_index( + ["participant_id", "session_id"], inplace=True, verify_integrity=True + ) + + assert len(merged_df_with_t1_freesurfer_metrics) == 1 + assert set(merged_df_with_t1_freesurfer_metrics.columns) == { + "age", + "t1-freesurfer_atlas-desikan_ROI-bar_thickness", + "t1-freesurfer_atlas-desikan_ROI-baz_thickness", + "t1-freesurfer_atlas-desikan_ROI-foo_thickness", + "t1-freesurfer_atlas-destrieux_ROI-bar_thickness", + "t1-freesurfer_atlas-destrieux_ROI-baz_thickness", + "t1-freesurfer_atlas-destrieux_ROI-foo_thickness", + "t1-freesurfer_segmentation-volumes_ROI-bar_volume", + "t1-freesurfer_segmentation-volumes_ROI-baz_volume", + "t1-freesurfer_segmentation-volumes_ROI-foo_volume", + } + assert summary.pipeline_name.to_list() == ["t1-freesurfer"] * 3 + assert summary.atlas_id.to_list() == ["desikan", "destrieux", "volumes"] + assert summary.regions_number.to_list() == [3, 3, 3] + assert summary.first_column_name.to_list() == [ + "t1-freesurfer_atlas-desikan_ROI-foo_thickness", + "t1-freesurfer_atlas-destrieux_ROI-foo_thickness", + "t1-freesurfer_segmentation-volumes_ROI-foo_volume", + ] + assert summary.last_column_name.to_list() == [ + "t1-freesurfer_atlas-desikan_ROI-baz_thickness", + "t1-freesurfer_atlas-destrieux_ROI-baz_thickness", + "t1-freesurfer_segmentation-volumes_ROI-baz_volume", + ]