diff --git a/docs/changes/latest.inc b/docs/changes/latest.inc index e382ecd74..ec67e1585 100644 --- a/docs/changes/latest.inc +++ b/docs/changes/latest.inc @@ -31,6 +31,8 @@ Enhancements - Implement matrix storage in SQliteFeatureStorage (:gh:`42` by `Fede Raimondo`_). +- Implement coordinate register, list and load (:gh:`11` by `Fede Raimondo`_). + Bugs ~~~~ diff --git a/junifer/data/VOIs/meta/CogAC_VOIs.txt b/junifer/data/VOIs/meta/CogAC_VOIs.txt new file mode 100644 index 000000000..05184cea7 --- /dev/null +++ b/junifer/data/VOIs/meta/CogAC_VOIs.txt @@ -0,0 +1,19 @@ +36.0 22.0 -4.0 RaIns +2.0 16.0 48.0 preSMA +48.0 12.0 30.0 rIFGp +36.0 2.0 54.0 rdPMC +48.0 30.0 24.0 rIFGa +-38.0 -44.0 46.0 lIPS +-24.0 -66.0 48.0 lSPL +40.0 -46.0 46.0 rIPS +60.0 -44.0 24.0 rIPC +30.0 -62.0 52.0 rSPL +-44.0 10.0 30.0 lIFG +-34.0 20.0 -4.0 LaIns +-26.0 2.0 52.0 ldPMC +6.0 -18.0 -2.0 rThal +-40.0 -66.0 -10.0 lIOG +48.0 19.0 6.0 rIFG +8.0 29.0 30.0 aMCC +-45.0 27.0 30.0 lIFG +11.0 7.0 7.0 rNcaud \ No newline at end of file diff --git a/junifer/data/VOIs/meta/CogAR_VOIs.txt b/junifer/data/VOIs/meta/CogAR_VOIs.txt new file mode 100644 index 000000000..2a0044c69 --- /dev/null +++ b/junifer/data/VOIs/meta/CogAR_VOIs.txt @@ -0,0 +1,9 @@ +-40 -64 -12 Fusiform_L +36 22 -4 Insula_R +-44 10 32 Precentral_L +60 -44 24 Temporal_Sup_R +0 18 48 Supp_Motor_Area_L +-36 -46 46 Parietal_Inf_L +38 -46 44 Parietal_Inf_R +-26 0 54 Frontal_Mid_L + diff --git a/junifer/data/VOIs/meta/DMNBuckner_VOIs.txt b/junifer/data/VOIs/meta/DMNBuckner_VOIs.txt new file mode 100644 index 000000000..631879bbd --- /dev/null +++ b/junifer/data/VOIs/meta/DMNBuckner_VOIs.txt @@ -0,0 +1,7 @@ +0 -53 26 PCC +0 52 -6 MPFC +-48 -62 36 lAG +46 -62 32 rAG +-24 -22 -20 lHF +24 -22 -20 rHF + diff --git a/junifer/data/VOIs/meta/Empathy_VOIs.txt b/junifer/data/VOIs/meta/Empathy_VOIs.txt new file mode 100644 index 000000000..bb7da896b --- /dev/null +++ b/junifer/data/VOIs/meta/Empathy_VOIs.txt @@ -0,0 +1,22 @@ +2.0 56.0 18.0 dmPFC +-8.0 54.0 34.0 dmPFC +36.0 22.0 -8.0 raI +-30.0 20.0 4.0 laI +50.0 12.0 -8.0 rIFG +54.0 16.0 20.0 rIFG/Area44 +50.0 30.0 4.0 rIFG/Area45 +-44.0 24.0 -6.0 lIFG +-4.0 18.0 50.0 SMA +-2.0 28.0 20.0 aMCC +-4.0 42.0 18.0 rACC +-2.0 -32.0 28.0 PCC +52.0 -58.0 22.0 rTPJ +-56.0 -58.0 22.0 lTPJ +22.0 -2.0 -16.0 rAm +54.0 -8.0 -16.0 rMTG +52.0 -36.0 2.0 rpSTS +-12.0 -4.0 12.0 laTh +6.0 -32.0 2.0 rpTh +26.0 -26.0 -12.0 rHippo +2.0 -20.0 -12.0 Midbrain +14.0 4.0 0.0 rGP diff --git a/junifer/data/VOIs/meta/Motor_VOIs.txt b/junifer/data/VOIs/meta/Motor_VOIs.txt new file mode 100644 index 000000000..7659dcc91 --- /dev/null +++ b/junifer/data/VOIs/meta/Motor_VOIs.txt @@ -0,0 +1,10 @@ +-39.0 -21.0 54.0 lSMC* +41.0 -16.0 57.0 rSMC* +-3.0 -2.0 54.0 SMA +-57.0 2.0 32.0 lPMCv +-53.0 -24.0 21.0 lIPC +45.0 -38.0 48.0 rIPC +-23.0 -7.0 1.0 lBG +25.0 -8.0 3.0 rBG +-22.0 -52.0 26.0 lCba +18.0 -54.0 -22.0 rCba diff --git a/junifer/data/VOIs/meta/MultiTask_VOIs.txt b/junifer/data/VOIs/meta/MultiTask_VOIs.txt new file mode 100644 index 000000000..bc7fa5676 --- /dev/null +++ b/junifer/data/VOIs/meta/MultiTask_VOIs.txt @@ -0,0 +1,10 @@ +-34 22 -4 leftInsula +34 24 0 rightInsula +-26 0 52 Frontal_Mid_L +44 38 28 Frontal_Inf_Tri_R +46 10 28 Frontal_Inf_Oper_R +-6 18 50 Supp_Motor_Area_L +-34 -52 56 Parietal_Inf_L +32 -52 50 Parietal_Inf_R +32 6 58 Frontal_Mid_R + diff --git a/junifer/data/VOIs/meta/PhysioStress_VOIs.txt b/junifer/data/VOIs/meta/PhysioStress_VOIs.txt new file mode 100644 index 000000000..2967c228f --- /dev/null +++ b/junifer/data/VOIs/meta/PhysioStress_VOIs.txt @@ -0,0 +1,18 @@ +38.0 18.0 0.0 rIns +52.0 12.0 -4.0 rSTG +60.0 6.0 2.0 rTP +22.0 0.0 -4.0 rPall +-38.0 14.0 4.0 lIns +-58.0 0.0 6.0 lOP4 +-20.0 6.0 2.0 lPut +4.0 6.0 46.0 rSMA +0.0 14.0 36.0 lMCC +-42.0 -18.0 18.0 lOP3 +-54.0 -24.0 24.0 lSMG +-36.0 -20.0 2.0 lIns +-14.0 -12.0 10.0 lTh +10.0 -18.0 4.0 rTh +56.0 -24.0 24.0 lSMG +44.0 -14.0 16.0 rOP3 +38.0 50.0 12.0 rMFG +-24.0 -66.0 -26.0 lCb diff --git a/junifer/data/VOIs/meta/Rew_VOIs.txt b/junifer/data/VOIs/meta/Rew_VOIs.txt new file mode 100644 index 000000000..cadddcf20 --- /dev/null +++ b/junifer/data/VOIs/meta/Rew_VOIs.txt @@ -0,0 +1,25 @@ +12.0 10.0 -6.0 rNAc +-10.0 8.0 -4.0 lPall +36.0 20.0 -6.0 rIns +-32.0 20.0 -4.0 lIns +0.0 24.0 40.0 dmPFC +0.0 54.0 -8.0 medOFC +24.0 -2.0 -16.0 rAm +6.0 -14.0 8.0 rTh +-6.0 -16.0 8.0 lTh +0.0 8.0 48.0 SMA +8.0 -18.0 -10.0 rBrainStem +-6.0 -18.0 -10.0 lBrainStem +2.0 44.0 20.0 ACC +-24.0 2.0 52.0 lMFG +-38.0 -4.0 6.0 lIns(Id3) +24.0 40.0 -14.0 rMidOFC(Fo3) +-16.0 42.0 -14.0 lMidOFC(Fo3) +40.0 32.0 32.0 raMFG +-28.0 -56.0 48.0 lIPL(IPS) +28.0 -58.0 50.0 rAG +0.0 -32.0 32.0 PCC +-36.0 50.0 10.0 lFP +-46.0 42.0 -4.0 lLOFC +30.0 4.0 50.0 rpMFG +-22.0 30.0 48.0 lSFG diff --git a/junifer/data/VOIs/meta/Somatosensory_VOIs.txt b/junifer/data/VOIs/meta/Somatosensory_VOIs.txt new file mode 100644 index 000000000..3fa59e405 --- /dev/null +++ b/junifer/data/VOIs/meta/Somatosensory_VOIs.txt @@ -0,0 +1,10 @@ +-48.0 -20.0 20.0 S1_L +-54.0 -20.0 48.0 postcentralG_S1_L +-44.0 -26.0 58.0 postcentralG_S1_L +-38.0 -12.0 4.0 Ins_claustrum_L +-40.0 4.0 10.0 pars_opercularis_Ins_L +56.0 -22.0 20.0 SMG_R +56.0 -34.0 18.0 pSTG_R +56.0 -38.0 28.0 IPL_SMG_R +60.0 -20.0 32.0 postcentralG_R +-4.0 14.0 36.0 MCC_L diff --git a/junifer/data/VOIs/meta/ToM_VOIs.txt b/junifer/data/VOIs/meta/ToM_VOIs.txt new file mode 100644 index 000000000..c520734b7 --- /dev/null +++ b/junifer/data/VOIs/meta/ToM_VOIs.txt @@ -0,0 +1,15 @@ +0.0 52.0 -12.0 vmPFC +2.0 58.0 12.0 FP +-8.0 56.0 30.0 dmPFC +2.0 -56.0 30.0 Prc +56.0 -50.0 18.0 rTPJ +-48.0 -56.0 24.0 lTPJ +54.0 -2.0 -20.0 rTP +-54.0 -2.0 -24.0 lTP +52.0 -18.0 -12.0 rMTG +-54.0 -28.0 -4.0 lMTG +50.0 -34.0 0.0 rpSTS +-58.0 -44.0 4.0 lpSTS +54.0 28.0 6.0 rIFG +-48.0 30.0 -12.0 lIFG +48.0 -72.0 8.0 rV5 \ No newline at end of file diff --git a/junifer/data/VOIs/meta/VigAtt_VOIs.txt b/junifer/data/VOIs/meta/VigAtt_VOIs.txt new file mode 100644 index 000000000..5efc95faf --- /dev/null +++ b/junifer/data/VOIs/meta/VigAtt_VOIs.txt @@ -0,0 +1,16 @@ +-2.0 8.0 50.0 aParacentralL +8.0 32.0 46.0 rmpSFG +0.0 26.0 34.0 dMCC +50.0 8.0 32.0 rIFJ +40.0 22.0 -4.0 raI +46.0 36.0 20.0 rIFS +-40.0 -12.0 60.0 lPrecentralG +-46.0 -68.0 -6.0 lIOG +-48.0 8.0 30.0 lIFJ +62.0 -38.0 17.0 rTPJ +8.0 -12.0 6.0 rTh +32.0 -90.0 4.0 rMOG +-42.0 12.0 -2.0 laI +-10.0 -14.0 6.0 lTh +6.0 -58.0 -18.0 Cb +44.0 -44.0 46.0 rIPL diff --git a/junifer/data/VOIs/meta/WM_VOIs.txt b/junifer/data/VOIs/meta/WM_VOIs.txt new file mode 100644 index 000000000..ae2839eda --- /dev/null +++ b/junifer/data/VOIs/meta/WM_VOIs.txt @@ -0,0 +1,23 @@ +-32.0 22.0 -2.0 aIns_l +-48.0 10.0 26.0 IFG_l +-46.0 26.0 24.0 lPFCc_l +-38.0 50.0 10.0 lPFCr_l +36.0 22.0 -6.0 aIns_r +50.0 14.0 24.0 IFG_r +44.0 34.0 32.0 lPFCc_r +38.0 54.0 6.0 lPFCr_l +2.0 18.0 48.0 pmFC +-28.0 0.0 56.0 psFC +30.0 2.0 56.0 psFC +-42.0 -42.0 46.0 IPS_l +-34.0 -52.0 48.0 SPL_l +-24.0 -66.0 54.0 SPLp_l +42.0 -44.0 44.0 IPSa_r +32.0 -58.0 48.0 IPSp_r +16.0 -66.0 56.0 SPLp_r +-12.0 -12.0 12.0 Thal_l +-16.0 2.0 14.0 Ncaud_l +-16.0 0.0 2.0 GP_l +12.0 -10.0 10.0 Thal_r +-34.0 -66.0 -20.0 Cb_FG_l +32.0 -64.0 -18.0 Cb_FG_r \ No newline at end of file diff --git a/junifer/data/VOIs/meta/eMDN_VOIs.txt b/junifer/data/VOIs/meta/eMDN_VOIs.txt new file mode 100644 index 000000000..ece04c248 --- /dev/null +++ b/junifer/data/VOIs/meta/eMDN_VOIs.txt @@ -0,0 +1,17 @@ +-46.0 6.0 30.0 IFG_l +50.0 12.0 28.0 IFG_r +-32.0 20.0 2.0 aIns_l +36.0 22.0 0.0 aIns_r +-4.0 14.0 44.0 SMA_l +6.0 18.0 46.0 SMA_r +-32.0 -52.0 46.0 IPS_l +32.0 -58.0 48.0 IPS_r +44.0 36.0 20.0 MFG_r +-28.0 -4.0 52.0 dPMC_l +-44.0 32.0 22.0 MFG_l +32.0 0.0 52.0 dPMC_r +-20.0 6.0 4.0 Put_l +10.0 -12.0 8.0 Thal_r +-46.0 -60.0 -10.0 ITG_l +22.0 6.0 4.0 Put_r +-10.0 -16.0 6.0 Thal_l \ No newline at end of file diff --git a/junifer/data/VOIs/meta/eSAD_VOIs.txt b/junifer/data/VOIs/meta/eSAD_VOIs.txt new file mode 100644 index 000000000..005cdba17 --- /dev/null +++ b/junifer/data/VOIs/meta/eSAD_VOIs.txt @@ -0,0 +1,12 @@ +0.0 38.0 10.0 ACC +-24.0 -10.0 -20.0 AmyHipp_L +24.0 -8.0 -22.0 AmyHipp_R +-2.0 -52.0 26.0 PrC +-2.0 32.0 -8.0 SGC +-46.0 -66.0 18.0 TPJ_L +50.0 -60.0 18.0 TPJ_R +-2.0 52.0 14.0 dmPFC +-6.0 10.0 -8.0 vBG_L +6.0 10.0 -8.0 vBG_R +-2.0 50.0 -10.0 vmPFC +-54.0 -10.0 -20.0 aMTS/aMTG diff --git a/junifer/data/VOIs/meta/extDMN_VOIs.txt b/junifer/data/VOIs/meta/extDMN_VOIs.txt new file mode 100644 index 000000000..f6539daad --- /dev/null +++ b/junifer/data/VOIs/meta/extDMN_VOIs.txt @@ -0,0 +1,16 @@ +0 -53 6 PCC +0 52 -6 MPFC +-48 -62 36 lAG +46 -62 32 rAG +-24 -22 -20 lHF +24 -22 -20 rHF +10 -22 42 Middlecingulate +-48 -20 38 lIPG +0 -48 -30 cerebellum +34 -80 -34 rCerebellum +56 30 8 rdlPFC +-42 -82 10 lateraloccipital +-54 24 10 rdrPFC +22 34 54 RSFG +-50 14 -40 lTempP +-38 14 54 leftmiddlefrontalgyrus(BA6) diff --git a/junifer/data/coordinates.py b/junifer/data/coordinates.py new file mode 100644 index 000000000..d8c64eee5 --- /dev/null +++ b/junifer/data/coordinates.py @@ -0,0 +1,150 @@ +"""Provide functions for list of coordinates.""" + +# Authors: Federico Raimondo +# License: AGPL + +from pathlib import Path +from typing import Dict, List, Union, Optional, Tuple +import typing + +import pandas as pd +import numpy as np +from numpy.typing import ArrayLike + +from ..utils.logging import logger, raise_error + + +# Path to the VOIs +_vois_path = Path(__file__).parent / "VOIs" + +# Path to the metadata of the VOIs +_vois_meta_path = _vois_path / "meta" + +""" +A dictionary containing all supported coordinates and their respective file or +data. + +The built-in coordinates are files that are shipped with the package in the +data/VOIs directory. The user can also register their own coordinates, which +will be stored as numpy arrays in the dictionary. +""" +_available_coordinates: Dict[ + str, Union[Path, Dict[str, Union[ArrayLike, List[str]]]] +] = { + "CogAC": _vois_meta_path / "CogAC_VOIs.txt", + "CogAR": _vois_meta_path / "CogAR_VOIs.txt", + "DMNBuckner": _vois_meta_path / "DMNBuckner_VOIs.txt", + "eMDN": _vois_meta_path / "eMDN_VOIs.txt", + "Empathy": _vois_meta_path / "Empathy_VOIs.txt", + "eSAD": _vois_meta_path / "eSAD_VOIs.txt", + "extDMN": _vois_meta_path / "extDMN_VOIs.txt", + "Motor": _vois_meta_path / "Motor_VOIs.txt", + "MultiTask": _vois_meta_path / "MultiTask_VOIs.txt", + "PhysioStress": _vois_meta_path / "PhysioStress_VOIs.txt", + "Rew": _vois_meta_path / "Rew_VOIs.txt", + "Somatosensory": _vois_meta_path / "Somatosensory_VOIs.txt", + "ToM": _vois_meta_path / "ToM_VOIs.txt", + "VigAtt": _vois_meta_path / "VigAtt_VOIs.txt", + "WM": _vois_meta_path / "WM_VOIs.txt", +} + + +def register_coordinates( + name: str, + coordinates: ArrayLike, + voi_names: List[str], + overwrite: Optional[bool] = False, +) -> None: + """Register coordinates. + + Parameters + ---------- + name : str + The name of the coordinates. + coordinates : numpy.ndarray + The coordinates. + voi_names : list of str + The names of the VOIs. + overwrite : bool, optional + If True, overwrite an existing list of coordinates with the same name. + Does not apply to built-in coordinates (default False). + + """ + if name in _available_coordinates: + if isinstance(_available_coordinates[name], Path): + raise_error( + f"Coordinates {name} already registered as built-in " + "coordinates." + ) + if overwrite is True: + logger.info(f"Overwriting coordinates {name}") + else: + raise_error( + f"Coordinates {name} already registered. " + "Set `overwrite=True` to update its value." + ) + + if not isinstance(coordinates, np.ndarray): + raise_error( + f"Coordinates must be a numpy.ndarray, not {type(coordinates)}." + ) + if coordinates.ndim != 2: + raise_error( + f"Coordinates must be a 2D array, not {coordinates.ndim}D." + ) + if coordinates.shape[1] != 3: + raise_error( + f"Each coordinate must have 3 values, not {coordinates.shape[1]} " + ) + if len(voi_names) != coordinates.shape[0]: + raise_error( + f"Length of voi_names ({len(voi_names)}) does not match the " + f"number of coordinates ({coordinates.shape[0]})." + ) + _available_coordinates[name] = { + "coords": coordinates, + "voi_names": voi_names, + } + + +def list_coordinates() -> List[str]: + """List all the available coordinates lists (VOIs). + + Returns + ------- + list of str + A list with all available coordinates names. + + """ + return sorted(_available_coordinates.keys()) + + +def load_coordinates(name: str) -> Tuple[ArrayLike, List[str]]: + """Load coordinates. + + Parameters + ---------- + name : str + The name of the coordinates. + + Returns + ------- + numpy.ndarray + The coordinates. + list of str + The names of the VOIs. + + """ + if name not in _available_coordinates: + raise_error(f"Coordinates {name} not found.") + t_coord = _available_coordinates[name] + if isinstance(t_coord, Path): + df_coords = pd.read_csv(t_coord, sep="\t", header=None) + coords = df_coords.iloc[:, [0, 1, 2]].to_numpy() + names = [x for x in df_coords.iloc[:, [3]].values[:, 0]] + else: + coords = t_coord["coords"] + coords = typing.cast(ArrayLike, coords) + names = t_coord["voi_names"] + names = typing.cast(List[str], names) + return coords, names diff --git a/junifer/data/tests/test_coordinates.py b/junifer/data/tests/test_coordinates.py new file mode 100644 index 000000000..e263707e0 --- /dev/null +++ b/junifer/data/tests/test_coordinates.py @@ -0,0 +1,102 @@ +"""Provide tests for coordinates.""" + +# Authors: Federico Raimondo +# License: AGPL + +import pytest + +import numpy as np +from numpy.testing import assert_array_equal + +from junifer.data.coordinates import ( + list_coordinates, + load_coordinates, + register_coordinates, +) + + +def test_register_coordinates_built_in_check() -> None: + """Test coordinates registration check for built-in atlas.""" + with pytest.raises(ValueError, match=r"built-in"): + register_coordinates( + name="DMNBuckner", + coordinates=np.zeros(2), + voi_names=["1", "2"], + overwrite=True, + ) + + +def test_register_coordinates_overwrite() -> None: + """Test coordinates registration check for overwriting.""" + register_coordinates( + name="MyList", + coordinates=np.zeros((2, 3)), + voi_names=["roi1", "roi2"], + overwrite=True, + ) + with pytest.raises(ValueError, match=r"already registered"): + register_coordinates( + name="MyList", + coordinates=np.ones((2, 3)), + voi_names=["roi2", "roi3"], + ) + + register_coordinates( + name="MyList", + coordinates=np.ones((2, 3)), + voi_names=["roi2", "roi3"], + overwrite=True, + ) + + coord, names = load_coordinates("MyList") + assert_array_equal(coord, np.ones((2, 3))) + assert names == ["roi2", "roi3"] + + +def test_register_coordinates_valid_input() -> None: + """Test coordinates registration check for valid input.""" + with pytest.raises(ValueError, match=r"numpy.ndarray"): + register_coordinates( + name="MyList", + coordinates=[1, 2], + voi_names=["roi1", "roi2"], + overwrite=True, + ) + with pytest.raises(ValueError, match=r"2D array"): + register_coordinates( + name="MyList", + coordinates=np.zeros((2, 3, 4)), + voi_names=["roi1", "roi2"], + overwrite=True, + ) + + with pytest.raises(ValueError, match=r"3 values"): + register_coordinates( + name="MyList", + coordinates=np.zeros((2, 4)), + voi_names=["roi1", "roi2"], + overwrite=True, + ) + with pytest.raises(ValueError, match=r"voi_names"): + register_coordinates( + name="MyList", + coordinates=np.zeros((2, 3)), + voi_names=["roi1", "roi2", "roi3"], + overwrite=True, + ) + + +def test_list_coordinates() -> None: + """Test listing of available coordinates.""" + available_coordinates = list_coordinates() + assert "DMNBuckner" in available_coordinates + assert "MultiTask" in available_coordinates + assert "VigAtt" in available_coordinates + assert "WM" in available_coordinates + + +def test_load_coordinates() -> None: + """Test loading coordinates from file.""" + coord, names = load_coordinates("DMNBuckner") + assert coord.shape == (6, 3) # type: ignore + assert names == ["PCC", "MPFC", "lAG", "rAG", "lHF", "rHF"]