From d8b9ea4d3b226fdb7c82320820dca753769bc84c Mon Sep 17 00:00:00 2001 From: AliceJoubert Date: Wed, 15 Jan 2025 14:55:44 +0100 Subject: [PATCH 1/4] Try with . separator --- clinica/utils/longitudinal.py | 8 ++++---- test/unittests/utils/test_longitudinal.py | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/clinica/utils/longitudinal.py b/clinica/utils/longitudinal.py index 082caf44a..4f0331845 100644 --- a/clinica/utils/longitudinal.py +++ b/clinica/utils/longitudinal.py @@ -26,16 +26,16 @@ def get_long_id(session_ids: List[str]) -> str: >>> get_long_id(['ses-M000']) 'long-M000' >>> get_long_id(['ses-M000', 'ses-M018', 'ses-M036']) - 'long-M000M018M036' + 'long-M000.M018.M036' >>> get_long_id(['ses-M018', 'ses-M036', 'ses-M000']) - 'long-M000M018M036' + 'long-M000.M018.M036' """ if not all([session_id.startswith("ses-") for session_id in session_ids]): raise ValueError( "Expected a list of session IDs of the form ses-XXX, " f"but received {session_ids} instead." ) - return "long-" + "".join( + return "long-" + ".".join( [session_id.lstrip("ses-") for session_id in sorted(session_ids)] ) @@ -62,7 +62,7 @@ def get_participants_long_id( -------- >>> from clinica.utils.longitudinal import get_participants_long_id >>> get_participants_long_id(['sub-CLNC01', 'sub-CLNC01', 'sub-CLNC02'], ['ses-M000', 'ses-M018', 'ses-M000']) - ['long-M000M018', 'long-M000M018', 'long-M000'] + ['long-M000.M018', 'long-M000.M018', 'long-M000'] """ from .participant import get_unique_subjects diff --git a/test/unittests/utils/test_longitudinal.py b/test/unittests/utils/test_longitudinal.py index 5d39e9706..0d6aff9e5 100644 --- a/test/unittests/utils/test_longitudinal.py +++ b/test/unittests/utils/test_longitudinal.py @@ -5,9 +5,9 @@ "session_ids,expected", [ (["ses-M000"], "long-M000"), - (["ses-M000", "ses-M018", "ses-M036"], "long-M000M018M036"), - (["ses-M018", "ses-M036", "ses-M000"], "long-M000M018M036"), - (["ses-foo", "ses-bar", "ses-baz", "ses-foobar"], "long-barbazfoofoobar"), + (["ses-M000", "ses-M018", "ses-M036"], "long-M000.M018.M036"), + (["ses-M018", "ses-M036", "ses-M000"], "long-M000.M018.M036"), + (["ses-foo", "ses-bar", "ses-baz", "ses-foobar"], "long-bar.baz.foo.foobar"), ], ) def test_get_long_id(session_ids, expected): @@ -40,7 +40,7 @@ def test_get_long_id_errors(session_ids): ( ["sub-CLNC01", "sub-CLNC01", "sub-CLNC02"], ["ses-M000", "ses-M018", "ses-M000"], - ["long-M000M018", "long-M000M018", "long-M000"], + ["long-M000.M018", "long-M000.M018", "long-M000"], ) ], ) @@ -64,8 +64,8 @@ def test_save_long_id(tmp_path): save_long_id(["ses-M000", "ses-M018", "ses-M036"], output_dir) - assert (output_dir / "long-M000M018M036_sessions.tsv").exists() - saved_ids = (output_dir / "long-M000M018M036_sessions.tsv").read_text() + assert (output_dir / "long-M000.M018.M036_sessions.tsv").exists() + saved_ids = (output_dir / "long-M000.M018.M036_sessions.tsv").read_text() assert saved_ids == "session_id\nses-M000\nses-M018\nses-M036\n" From 1be7ce30b44f400914c6475eb33b4e99f7b17cfe Mon Sep 17 00:00:00 2001 From: AliceJoubert Date: Fri, 21 Feb 2025 17:09:22 +0100 Subject: [PATCH 2/4] Change to + --- clinica/utils/longitudinal.py | 8 ++++---- test/unittests/utils/test_longitudinal.py | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/clinica/utils/longitudinal.py b/clinica/utils/longitudinal.py index 4f0331845..e5d347dab 100644 --- a/clinica/utils/longitudinal.py +++ b/clinica/utils/longitudinal.py @@ -26,16 +26,16 @@ def get_long_id(session_ids: List[str]) -> str: >>> get_long_id(['ses-M000']) 'long-M000' >>> get_long_id(['ses-M000', 'ses-M018', 'ses-M036']) - 'long-M000.M018.M036' + 'long-M000+M018+M036' >>> get_long_id(['ses-M018', 'ses-M036', 'ses-M000']) - 'long-M000.M018.M036' + 'long-M000+M018+M036' """ if not all([session_id.startswith("ses-") for session_id in session_ids]): raise ValueError( "Expected a list of session IDs of the form ses-XXX, " f"but received {session_ids} instead." ) - return "long-" + ".".join( + return "long-" + "+".join( [session_id.lstrip("ses-") for session_id in sorted(session_ids)] ) @@ -62,7 +62,7 @@ def get_participants_long_id( -------- >>> from clinica.utils.longitudinal import get_participants_long_id >>> get_participants_long_id(['sub-CLNC01', 'sub-CLNC01', 'sub-CLNC02'], ['ses-M000', 'ses-M018', 'ses-M000']) - ['long-M000.M018', 'long-M000.M018', 'long-M000'] + ['long-M000+M018', 'long-M000+M018', 'long-M000'] """ from .participant import get_unique_subjects diff --git a/test/unittests/utils/test_longitudinal.py b/test/unittests/utils/test_longitudinal.py index 0d6aff9e5..41dca392e 100644 --- a/test/unittests/utils/test_longitudinal.py +++ b/test/unittests/utils/test_longitudinal.py @@ -5,9 +5,9 @@ "session_ids,expected", [ (["ses-M000"], "long-M000"), - (["ses-M000", "ses-M018", "ses-M036"], "long-M000.M018.M036"), - (["ses-M018", "ses-M036", "ses-M000"], "long-M000.M018.M036"), - (["ses-foo", "ses-bar", "ses-baz", "ses-foobar"], "long-bar.baz.foo.foobar"), + (["ses-M000", "ses-M018", "ses-M036"], "long-M000+M018+M036"), + (["ses-M018", "ses-M036", "ses-M000"], "long-M000+M018+M036"), + (["ses-foo", "ses-bar", "ses-baz", "ses-foobar"], "long-bar+baz+foo+foobar"), ], ) def test_get_long_id(session_ids, expected): @@ -40,7 +40,7 @@ def test_get_long_id_errors(session_ids): ( ["sub-CLNC01", "sub-CLNC01", "sub-CLNC02"], ["ses-M000", "ses-M018", "ses-M000"], - ["long-M000.M018", "long-M000.M018", "long-M000"], + ["long-M000+M018", "long-M000+M018", "long-M000"], ) ], ) @@ -64,8 +64,8 @@ def test_save_long_id(tmp_path): save_long_id(["ses-M000", "ses-M018", "ses-M036"], output_dir) - assert (output_dir / "long-M000.M018.M036_sessions.tsv").exists() - saved_ids = (output_dir / "long-M000.M018.M036_sessions.tsv").read_text() + assert (output_dir / "long-M000+M018+M036_sessions.tsv").exists() + saved_ids = (output_dir / "long-M000+M018+M036_sessions.tsv").read_text() assert saved_ids == "session_id\nses-M000\nses-M018\nses-M036\n" From 20320f4eea7d93d24e88ed588084a6cdae6b0acf Mon Sep 17 00:00:00 2001 From: AliceJoubert Date: Tue, 25 Feb 2025 16:20:30 +0100 Subject: [PATCH 3/4] Change regex pattern to fit with '+' separator --- .../anatomical/freesurfer/longitudinal/correction/utils.py | 2 +- .../anatomical/freesurfer/longitudinal/template/pipeline.py | 2 +- clinica/pipelines/anatomical/freesurfer/longitudinal/utils.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clinica/pipelines/anatomical/freesurfer/longitudinal/correction/utils.py b/clinica/pipelines/anatomical/freesurfer/longitudinal/correction/utils.py index bc56c521c..ce22bae18 100644 --- a/clinica/pipelines/anatomical/freesurfer/longitudinal/correction/utils.py +++ b/clinica/pipelines/anatomical/freesurfer/longitudinal/correction/utils.py @@ -282,7 +282,7 @@ def get_processed_images( atlas: Optional[str] = None, ) -> list[str]: """ - Extract image IDs (e.g. ["sub-CLNC01_ses-M000_long-M000M018", "sub-CLNC01_ses-M018_long-M000M018"]) of outputs + Extract image IDs (e.g. ["sub-CLNC01_ses-M000_long-M000+M018", "sub-CLNC01_ses-M018_long-M000+M018"]) of outputs already processed by T1FreeSurferLongitudinalCorrection pipeline. """ image_ids = [] diff --git a/clinica/pipelines/anatomical/freesurfer/longitudinal/template/pipeline.py b/clinica/pipelines/anatomical/freesurfer/longitudinal/template/pipeline.py index a1d5b34e8..1fa6a0f92 100644 --- a/clinica/pipelines/anatomical/freesurfer/longitudinal/template/pipeline.py +++ b/clinica/pipelines/anatomical/freesurfer/longitudinal/template/pipeline.py @@ -35,7 +35,7 @@ def get_processed_images( list_participant_id, list_long_id, caps_directory, T1_FS_T_DESTRIEUX ) image_ids = [ - re.search(r"(sub-[a-zA-Z0-9]+)_(long-[a-zA-Z0-9]+)", file).group() + re.search(r"(sub-[a-zA-Z0-9]+)_(long-[a-zA-Z0-9+]+)", file).group() for file in t1_freesurfer_files ] return image_ids diff --git a/clinica/pipelines/anatomical/freesurfer/longitudinal/utils.py b/clinica/pipelines/anatomical/freesurfer/longitudinal/utils.py index f24d899a4..63066347a 100644 --- a/clinica/pipelines/anatomical/freesurfer/longitudinal/utils.py +++ b/clinica/pipelines/anatomical/freesurfer/longitudinal/utils.py @@ -71,7 +71,7 @@ def grab_image_ids_from_caps_directory( part_ids = ["sub-CLNC01", "sub-CLNC01", "sub-CLNC01" ] sess_ids = ["ses-M000", "ses-M018", "ses-M036" ] - long_ids = ["long-M000M018", "long-M000M018", "long-M000M018"] + long_ids = ["long-M000+M018", "long-M000+M018", "long-M000+M018"] (sub-CLNC02 does not have longitudinal ID so it does not appear on the result) Parameters From 73d8306cedfe36cab7cf8678fc16015b30d41e04 Mon Sep 17 00:00:00 2001 From: AliceJoubert Date: Tue, 25 Feb 2025 16:25:32 +0100 Subject: [PATCH 4/4] Modify docs --- docs/CAPS/Introduction.md | 20 ++++++++++---------- docs/Pipelines/T1_FreeSurfer_Longitudinal.md | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/CAPS/Introduction.md b/docs/CAPS/Introduction.md index 4b8bf7d14..6ae4b9ba8 100644 --- a/docs/CAPS/Introduction.md +++ b/docs/CAPS/Introduction.md @@ -34,7 +34,7 @@ You will find this notion when working on longitudinal datasets. This is simply a label that will define the set of sessions considered. Unlike group, longitudinal template is dedicated to intra-subject analysis. Besides, the longitudinal label for a participant is defined concatenating the different session labels in alphabetical order so that the longitudinal label will be unique. -For instance, if the intra-subject template is computed on `M018` and `M000` sessions, the longitudinal ID will be `long-M000M018`. +For instance, if the intra-subject template is computed on `M018` and `M000` sessions, the longitudinal ID will be `long-M000+M018`. ## Differences with BIDS @@ -123,8 +123,8 @@ groups/ This CAPS folder contains the outputs of longitudinal segmentations performed with [FreeSurfer](../Software/Third-party.md#freesurfer) for a fictional participant `CLNC01` at sessions `M000` and `M018`. First, the [`t1-freesurfer` pipeline](../Pipelines/T1_FreeSurfer.md) is run on the two sessions. -Then, the [`t1-freesurfer-longitudinal` pipeline](../Pipelines/T1_FreeSurfer_Longitudinal.md) will compute the intra-subject template `sub-CLNC01_long-M000M018` using the `M000` and `M018` sessions. -This template is finally used to longitudinally correct the segmentations, whose results are stored in the `sub-CLNC01_ses-M000.long.sub-CLNC01_long-M000M018` and `sub-CLNC01_ses-M018.long.sub-CLNC01_long-M000M018` folders. +Then, the [`t1-freesurfer-longitudinal` pipeline](../Pipelines/T1_FreeSurfer_Longitudinal.md) will compute the intra-subject template `sub-CLNC01_long-M000+M018` using the `M000` and `M018` sessions. +This template is finally used to longitudinally correct the segmentations, whose results are stored in the `sub-CLNC01_ses-M000.long.sub-CLNC01_long-M000+M018` and `sub-CLNC01_ses-M018.long.sub-CLNC01_long-M000+M018` folders. Of note, the `.long.` naming comes from [FreeSurfer](../Software/Third-party.md#freesurfer) when running the longitudinal `recon-all` command. @@ -132,10 +132,10 @@ Of note, the `.long.` naming comes from [FreeSurfer] dataset_description.json subjects/ ├── sub-CLNC01/ -│ ├── long-M000M018/ -│ │ ├── long-M000M018_sessions.tsv +│ ├── long-M000+M018/ +│ │ ├── long-M000+M018_sessions.tsv │ │ └── freesurfer_unbiased_template/ -│ │ └── sub-CLNC01_long-M000M018/ +│ │ └── sub-CLNC01_long-M000+M018/ │ │ ├── base-tps/ │ │ ├── label/ │ │ ├── mri/ @@ -150,10 +150,10 @@ subjects/ │ │ │ ├── mri/ │ │ │ ├── stats/ │ │ │ └── surf/ -│ │ └── long-M000M018/ +│ │ └── long-M000+M018/ │ │ └── freesurfer_longitudinal/ │ │ ├── regional_measures/ -│ │ └── sub-CLNC01_ses-M000.long.sub-CLNC01_long-M000M018/ +│ │ └── sub-CLNC01_ses-M000.long.sub-CLNC01_long-M000+M018/ │ │ ├── label/ │ │ ├── mri/ │ │ ├── stats/ @@ -167,10 +167,10 @@ subjects/ │ │ ├── mri/ │ │ ├── stats/ │ │ └── surf/ -│ └── long-M000M018 +│ └── long-M000+M018 │ └── freesurfer_longitudinal │ ├── regional_measures -│ └── sub-CLNC01_ses-M018.long.sub-CLNC01_long-M000M018/ +│ └── sub-CLNC01_ses-M018.long.sub-CLNC01_long-M000+M018/ │ ├── label/ │ ├── mri/ │ ├── stats/ diff --git a/docs/Pipelines/T1_FreeSurfer_Longitudinal.md b/docs/Pipelines/T1_FreeSurfer_Longitudinal.md index 2ee45a617..19859254c 100644 --- a/docs/Pipelines/T1_FreeSurfer_Longitudinal.md +++ b/docs/Pipelines/T1_FreeSurfer_Longitudinal.md @@ -106,7 +106,7 @@ Results stored in the following folder of the [CAPS hierarchy](../../CAPS/Specifications/#t1-freesurfer-longitudinal-freesurfer-based-longitudinal-processing-of-t1-weighted-mr-images): `subjects///freesurfer_unbiased_template/_`. -`` is an identifier defined by concatenating all the sessions associated with the current `` (e.g. if the template for participant `sub-CLNC01` is built from sessions `M00`, `M01`, `M05`, then `` will be `M00M01M05`). +`` is an identifier defined by concatenating all the sessions associated with the current `` (e.g. if the template for participant `sub-CLNC01` is built from sessions `M00`, `M01`, `M05`, then `` will be `M00+M01+M05`). See [CAPS specifications](../../CAPS/Introduction/#subject-and-group-naming) for full definition and example of ``. This folder contains the standard output structure of the `recon-all` command (`label/`, `mri/`, `surf/`, etc.) already explained in the [`t1-freesurfer`](../T1_FreeSurfer) pipeline.