Skip to content

Commit

Permalink
[ENH] Use enumeration for SUVR reference regions (#1207)
Browse files Browse the repository at this point in the history
* define the SUVRReferenceRegion enum

* use it in CLI

* use it in non regression tests

* fix broken unit tests

* other usages

* update documentation

* fix broken unit tests

* do not break API

* apply suggestion from code review
  • Loading branch information
NicolasGensollen authored Jun 13, 2024
1 parent 42dcbd5 commit 154ab4a
Show file tree
Hide file tree
Showing 18 changed files with 225 additions and 229 deletions.
5 changes: 3 additions & 2 deletions clinica/pipelines/cli_param/argument.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Common CLI arguments used by Clinica pipelines."""
import click

from clinica.utils.pet import LIST_SUVR_REFERENCE_REGIONS, Tracer
from clinica.utils.pet import SUVRReferenceRegion, Tracer

acq_label = click.argument(
"acq_label",
Expand Down Expand Up @@ -48,5 +48,6 @@
)

suvr_reference_region = click.argument(
"suvr_reference_region", type=click.Choice(LIST_SUVR_REFERENCE_REGIONS)
"suvr_reference_region",
type=click.Choice(SUVRReferenceRegion),
)
4 changes: 2 additions & 2 deletions clinica/pipelines/cli_param/option.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import click

from clinica.utils.pet import LIST_SUVR_REFERENCE_REGIONS, ReconstructionMethod, Tracer
from clinica.utils.pet import ReconstructionMethod, SUVRReferenceRegion, Tracer

from .option_group import option

Expand Down Expand Up @@ -118,7 +118,7 @@
suvr_reference_region = option(
"-suvr",
"--suvr_reference_region",
type=click.Choice(LIST_SUVR_REFERENCE_REGIONS),
type=click.Choice(SUVRReferenceRegion),
help=(
"Intensity normalization using the average PET uptake in reference regions "
"resulting in a standardized uptake value ratio (SUVR) map. It can be "
Expand Down
16 changes: 7 additions & 9 deletions clinica/pipelines/pet_linear/pet_linear_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Functions used by nipype interface.
import os

from nibabel.nifti1 import Nifti1Image
from clinica.utils.pet import SUVRReferenceRegion


def init_input_node(pet):
Expand Down Expand Up @@ -173,7 +171,9 @@ def rename_into_caps(
_rename_pet_into_caps,
_rename_transformation_into_caps,
)
from clinica.utils.pet import SUVRReferenceRegion

suvr_reference_region = SUVRReferenceRegion(suvr_reference_region)
bids_entities = _get_bids_entities_without_suffix(pet_filename_bids, suffix="pet")
if output_dir:
bids_entities = os.path.join(output_dir, bids_entities)
Expand Down Expand Up @@ -201,12 +201,10 @@ def _get_bids_entities_without_suffix(filename: str, suffix: str) -> str:


def _rename_pet_into_caps(
entities: str, filename: str, cropped: bool, suvr_reference_region: str
entities: str, filename: str, cropped: bool, region: SUVRReferenceRegion
) -> str:
"""Rename into CAPS PET."""
return _rename(
filename, entities, _get_pet_bids_components(cropped, suvr_reference_region)
)
return _rename(filename, entities, _get_pet_bids_components(cropped, region))


def _rename_transformation_into_caps(entities: str, filename: str) -> str:
Expand All @@ -232,14 +230,14 @@ def _rename(filename: str, entities: str, suffix: str):
return rename.run().outputs.out_file


def _get_pet_bids_components(cropped: bool, suvr_reference_region: str) -> str:
def _get_pet_bids_components(cropped: bool, region: SUVRReferenceRegion) -> str:
"""Return a string composed of the PET-specific entities (space, resolution,
desc, and suvr), suffix, and extension.
"""
space = "_space-MNI152NLin2009cSym"
resolution = "_res-1x1x1"
desc = "_desc-Crop" if cropped else ""
suvr = f"_suvr-{suvr_reference_region}"
suvr = f"_suvr-{region.value}"

return f"{space}{desc}{resolution}{suvr}_pet.nii.gz"

Expand Down
49 changes: 28 additions & 21 deletions clinica/pipelines/pet_surface/pet_surface_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -887,9 +887,9 @@ def get_wf(
white_surface_left,
white_surface_right,
working_directory_subjects,
acq_label,
acq_label: str,
csv_segmentation,
suvr_reference_region,
suvr_reference_region: str,
matscript_folder_inverse_deformation,
destrieux_left,
destrieux_right,
Expand Down Expand Up @@ -934,12 +934,19 @@ def get_wf(

import clinica.pipelines.pet_surface.pet_surface_utils as utils
from clinica.utils.filemanip import get_subject_id, load_volume, unzip_nii
from clinica.utils.pet import get_suvr_mask, read_psf_information
from clinica.utils.pet import (
SUVRReferenceRegion,
Tracer,
get_suvr_mask,
read_psf_information,
)
from clinica.utils.spm import get_tpm, use_spm_standalone_if_available
from clinica.utils.ux import print_begin_image

using_spm_standalone = use_spm_standalone_if_available()

suvr_reference_region = SUVRReferenceRegion(suvr_reference_region)
acq_label = Tracer(acq_label)
image_id = get_subject_id(pet)
try:
load_volume(pet)
Expand All @@ -961,7 +968,7 @@ def get_wf(
unzip_orig_nu = unzip_pet.clone(name="unzip_orig_nu")

unzip_mask = unzip_pet.clone(name="unzip_mask")
unzip_mask.inputs.in_file = get_suvr_mask(suvr_reference_region)
unzip_mask.inputs.in_file = str(get_suvr_mask(suvr_reference_region))

coreg = pe.Node(Coregister(), name="coreg")

Expand Down Expand Up @@ -1057,7 +1064,7 @@ def get_wf(
),
name="pons_normalization",
)
pons_normalization.inputs.pet_tracer = acq_label
pons_normalization.inputs.pet_tracer = acq_label.value

# read_psf_information expects a list of subjects/sessions and returns a list of PSF
psf_info = read_psf_information(pvc_psf_tsv, [subject_id], [session_id], acq_label)[
Expand Down Expand Up @@ -1286,36 +1293,36 @@ def get_output_dir(is_longitudinal, caps_dir, subject_id, session_id):
(
r"(.*(sub-.*)\/(ses-.*)\/pet\/surface)\/projection_native\/.*_hemi_([a-z]+).*",
r"\1/\2_\3_trc-"
+ acq_label
+ acq_label.value
+ r"_pet_space-native_suvr-"
+ suvr_reference_region
+ suvr_reference_region.value
+ r"_pvc-iy_hemi-\4_projection.mgh",
),
# Projection in fsaverage
(
r"(.*(sub-.*)\/(ses-.*)\/pet\/surface)\/projection_fsaverage\/.*_hemi_([a-z]+).*_fwhm_([0-9]+).*",
r"\1/\2_\3_trc-"
+ acq_label
+ acq_label.value
+ r"_pet_space-fsaverage_suvr-"
+ suvr_reference_region
+ suvr_reference_region.value
+ r"_pvc-iy_hemi-\4_fwhm-\5_projection.mgh",
),
# TSV file for Destrieux atlas
(
r"(.*(sub-.*)\/(ses-.*)\/pet\/surface)\/destrieux_tsv\/destrieux.tsv",
r"\1/atlas_statistics/\2_\3_trc-"
+ acq_label
+ acq_label.value
+ "_pet_space-destrieux_pvc-iy_suvr-"
+ suvr_reference_region
+ suvr_reference_region.value
+ "_statistics.tsv",
),
# TSV file for Desikan atlas
(
r"(.*(sub-.*)\/(ses-.*)\/pet\/surface)\/desikan_tsv\/desikan.tsv",
r"\1/atlas_statistics/\2_\3_trc-"
+ acq_label
+ acq_label.value
+ "_pet_space-desikan_pvc-iy_suvr-"
+ suvr_reference_region
+ suvr_reference_region.value
+ "_statistics.tsv",
),
]
Expand All @@ -1329,36 +1336,36 @@ def get_output_dir(is_longitudinal, caps_dir, subject_id, session_id):
(
r"(.*(sub-.*)\/(ses-.*)\/pet\/(long-.*)\/surface_longitudinal)\/projection_native\/.*_hemi_([a-z]+).*",
r"\1/\2_\3_\4_trc-"
+ acq_label
+ acq_label.value
+ r"_pet_space-native_suvr-"
+ suvr_reference_region
+ suvr_reference_region.value
+ r"_pvc-iy_hemi-\5_projection.mgh",
),
# Projection in fsaverage
(
r"(.*(sub-.*)\/(ses-.*)\/pet\/(long-.*)\/surface_longitudinal)\/projection_fsaverage\/.*_hemi_([a-z]+).*_fwhm_([0-9]+).*",
r"\1/\2_\3_\4_trc-"
+ acq_label
+ acq_label.value
+ r"_pet_space-fsaverage_suvr-"
+ suvr_reference_region
+ suvr_reference_region.value
+ r"_pvc-iy_hemi-\5_fwhm-\6_projection.mgh",
),
# TSV file for Destrieux atlas
(
r"(.*(sub-.*)\/(ses-.*)\/pet\/(long-.*)\/surface_longitudinal)\/destrieux_tsv\/destrieux.tsv",
r"\1/atlas_statistics/\2_\3_\4_trc-"
+ acq_label
+ acq_label.value
+ "_pet_space-destrieux_pvc-iy_suvr-"
+ suvr_reference_region
+ suvr_reference_region.value
+ "_statistics.tsv",
),
# TSV file for Desikan atlas
(
r"(.*(sub-.*)\/(ses-.*)\/pet\/(long-.*)\/surface_longitudinal)\/desikan_tsv\/desikan.tsv",
r"\1/atlas_statistics/\2_\3_\4_trc-"
+ acq_label
+ acq_label.value
+ "_pet_space-desikan_pvc-iy_suvr-"
+ suvr_reference_region
+ suvr_reference_region.value
+ "_statistics.tsv",
),
]
Expand Down
55 changes: 33 additions & 22 deletions clinica/utils/input_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Optional, Union

from clinica.pipelines.dwi.dti.utils import DTIBasedMeasure
from clinica.utils.pet import ReconstructionMethod, Tracer
from clinica.utils.pet import ReconstructionMethod, SUVRReferenceRegion, Tracer

# BIDS

Expand Down Expand Up @@ -577,8 +577,8 @@ def dwi_dti(measure: Union[str, DTIBasedMeasure], space: Optional[str] = None) -


def bids_pet_nii(
tracer: Optional[Tracer] = None,
reconstruction: Optional[ReconstructionMethod] = None,
tracer: Optional[Union[str, Tracer]] = None,
reconstruction: Optional[Union[str, ReconstructionMethod]] = None,
) -> dict:
"""Return the query dict required to capture PET scans.
Expand All @@ -603,12 +603,16 @@ def bids_pet_nii(
"""
from pathlib import Path

trc = "" if tracer is None else f"_trc-{tracer.value}"
rec = "" if reconstruction is None else f"_rec-{reconstruction.value}"
description = f"PET data"
if tracer:
trc = ""
if tracer is not None:
tracer = Tracer(tracer)
trc = f"_trc-{tracer.value}"
description += f" with {tracer.value} tracer"
if reconstruction:
rec = ""
if reconstruction is not None:
reconstruction = ReconstructionMethod(reconstruction)
rec = f"_rec-{reconstruction.value}"
description += f" and reconstruction method {reconstruction.value}"

return {
Expand All @@ -621,15 +625,18 @@ def bids_pet_nii(


def pet_volume_normalized_suvr_pet(
acq_label,
group_label,
suvr_reference_region,
use_brainmasked_image,
use_pvc_data,
fwhm=0,
):
acq_label: Union[str, Tracer],
group_label: str,
suvr_reference_region: Union[str, SUVRReferenceRegion],
use_brainmasked_image: bool,
use_pvc_data: bool,
fwhm: int = 0,
) -> dict:
from pathlib import Path

acq_label = Tracer(acq_label)
region = SUVRReferenceRegion(suvr_reference_region)

if use_brainmasked_image:
mask_key_value = "_mask-brain"
mask_description = "brain-masked"
Expand All @@ -651,36 +658,40 @@ def pet_volume_normalized_suvr_pet(
fwhm_key_value = ""
fwhm_description = "with no smoothing"

suvr_key_value = f"_suvr-{suvr_reference_region}"
suvr_key_value = f"_suvr-{region.value}"

information = {
"pattern": Path("pet")
/ "preprocessing"
/ f"group-{group_label}"
/ f"*_trc-{acq_label}_pet_space-Ixi549Space{pvc_key_value}{suvr_key_value}{mask_key_value}{fwhm_key_value}_pet.nii*",
/ f"*_trc-{acq_label.value}_pet_space-Ixi549Space{pvc_key_value}{suvr_key_value}{mask_key_value}{fwhm_key_value}_pet.nii*",
"description": (
f"{mask_description} SUVR map (using {suvr_reference_region} region) of {acq_label}-PET "
f"{mask_description} SUVR map (using {region.value} region) of {acq_label.value}-PET "
f"{pvc_description} and {fwhm_description} in Ixi549Space space based on {group_label} DARTEL template"
),
"needed_pipeline": "pet-volume",
}
return information


# pet-linear


def pet_linear_nii(acq_label, suvr_reference_region, uncropped_image):
def pet_linear_nii(
acq_label: Union[str, Tracer],
suvr_reference_region: Union[str, SUVRReferenceRegion],
uncropped_image: bool,
) -> dict:
from pathlib import Path

acq_label = Tracer(acq_label)
region = SUVRReferenceRegion(suvr_reference_region)

if uncropped_image:
description = ""
else:
description = "_desc-Crop"

information = {
"pattern": Path("pet_linear")
/ f"*_trc-{acq_label}_pet_space-MNI152NLin2009cSym{description}_res-1x1x1_suvr-{suvr_reference_region}_pet.nii.gz",
/ f"*_trc-{acq_label.value}_pet_space-MNI152NLin2009cSym{description}_res-1x1x1_suvr-{region.value}_pet.nii.gz",
"description": "",
"needed_pipeline": "pet-linear",
}
Expand Down
Loading

0 comments on commit 154ab4a

Please sign in to comment.