From 8dd6315b6099824298484c1eda30e342ab95ad7f Mon Sep 17 00:00:00 2001 From: camillebrianceau <57992134+camillebrianceau@users.noreply.github.com> Date: Thu, 21 Nov 2024 14:51:43 +0100 Subject: [PATCH] Entities implementation (#14) * first ideas for entities * first ideas for entities * first ideas for entities * clean code * clean code --- src/clinicaio/models/__init__.py | 0 src/clinicaio/models/entity.py | 215 +++++++++++++++++++++++++++++++ src/clinicaio/models/enum.py | 44 +++++++ src/clinicaio/models/request.py | 0 4 files changed, 259 insertions(+) create mode 100644 src/clinicaio/models/__init__.py create mode 100644 src/clinicaio/models/entity.py create mode 100644 src/clinicaio/models/enum.py create mode 100644 src/clinicaio/models/request.py diff --git a/src/clinicaio/models/__init__.py b/src/clinicaio/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/clinicaio/models/entity.py b/src/clinicaio/models/entity.py new file mode 100644 index 0000000..9716aa2 --- /dev/null +++ b/src/clinicaio/models/entity.py @@ -0,0 +1,215 @@ + + +from collections import UserString +from typing import Union, Optional +from pathlib import Path +from .enum import SUVRReferenceRegions, Tracer, AnatMRISuffix, PETSuffix, FMapSuffix, Modality, Extension +from enum import Enum +from dataclasses import dataclass +# questions: dependance à Pydantic ?? + + +class Label(UserString): + def __init__(self, value: Union[str, Enum]): + super().__init__(self.validate(value)) + + @classmethod + def validate(cls, value: Union[str, Enum]) -> str: + if isinstance(value, Enum): + return value.value + elif isinstance(value, str) and value.isalnum(): + return value + raise ValueError( + f"Label '{value}' is not a valid BIDS label: it must be string composed only by letters and/or numbers." + ) + +class Index(UserString): + + def __init__(self, value: int, length_as_string: int = 1): + super().__init__(self.validate(value, length_as_string)) + + @classmethod + def validate(cls, value: int, length_as_string: int) -> str: + if not isinstance(value, int): + try: + value = int(value) + except TypeError as exc: + raise ValueError( + f"Index '{value}' is not a valid BIDS index: it must be a non-negative integer." + ) from exc + + return str(value).zfill(length_as_string) + + + +class Entity: + key: Label + value : Union[Label, Index] + + def __str__(self) -> str: + return f"{self.key}-{self.value}" + + +# BIDS Entities + +class SubjectEntity(Entity): + """ + A person or animal participating in the study. + """ + key = Label("sub") + + def __init__(self, value: str): + self.value = Label(value) + + +class SessionEntity(Entity): + """ + A logical grouping of neuroimaging and behavioral data consistent across subjects. + Session can (but doesn't have to) be synonymous to a visit in a longitudinal study. + In general, subjects will stay in the scanner during one session. + However, for example, if a subject has to leave the scanner room and then be re-positioned + on the scanner bed, the set of MRI acquisitions will still be considered as a session + and match sessions acquired in other subjects. Similarly, in situations where different + data types are obtained over several visits (for example fMRI on one day followed by DWI + the day after) those can be grouped in one session. + + Defining multiple sessions is appropriate when several identical or similar data + acquisitions are planned and performed on all -or most- subjects, often in the + case of some intervention between sessions (for example, training). + """ + key = Label("ses") + + def __init__(self, value: str): + self.value = Label(value) + + +class AcquisitionEntity(Entity): + """ + The acq-