junifer 0.0.5.dev240__py3-none-any.whl → 0.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- junifer/__init__.py +2 -31
- junifer/__init__.pyi +37 -0
- junifer/_version.py +9 -4
- junifer/api/__init__.py +3 -5
- junifer/api/__init__.pyi +4 -0
- junifer/api/decorators.py +14 -19
- junifer/api/functions.py +165 -109
- junifer/api/py.typed +0 -0
- junifer/api/queue_context/__init__.py +2 -4
- junifer/api/queue_context/__init__.pyi +5 -0
- junifer/api/queue_context/gnu_parallel_local_adapter.py +22 -6
- junifer/api/queue_context/htcondor_adapter.py +23 -6
- junifer/api/queue_context/py.typed +0 -0
- junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +3 -3
- junifer/api/queue_context/tests/test_htcondor_adapter.py +3 -3
- junifer/api/tests/test_functions.py +168 -74
- junifer/cli/__init__.py +24 -0
- junifer/cli/__init__.pyi +3 -0
- junifer/{api → cli}/cli.py +141 -125
- junifer/cli/parser.py +235 -0
- junifer/cli/py.typed +0 -0
- junifer/{api → cli}/tests/test_cli.py +8 -8
- junifer/{api/tests/test_api_utils.py → cli/tests/test_cli_utils.py} +5 -4
- junifer/{api → cli}/tests/test_parser.py +2 -2
- junifer/{api → cli}/utils.py +6 -16
- junifer/configs/juseless/__init__.py +2 -2
- junifer/configs/juseless/__init__.pyi +3 -0
- junifer/configs/juseless/datagrabbers/__init__.py +2 -12
- junifer/configs/juseless/datagrabbers/__init__.pyi +13 -0
- junifer/configs/juseless/datagrabbers/ixi_vbm.py +2 -2
- junifer/configs/juseless/datagrabbers/py.typed +0 -0
- junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -2
- junifer/configs/juseless/datagrabbers/ucla.py +4 -4
- junifer/configs/juseless/py.typed +0 -0
- junifer/conftest.py +25 -0
- junifer/data/__init__.py +2 -42
- junifer/data/__init__.pyi +29 -0
- junifer/data/_dispatch.py +248 -0
- junifer/data/coordinates/__init__.py +9 -0
- junifer/data/coordinates/__init__.pyi +5 -0
- junifer/data/coordinates/_ants_coordinates_warper.py +104 -0
- junifer/data/coordinates/_coordinates.py +385 -0
- junifer/data/coordinates/_fsl_coordinates_warper.py +81 -0
- junifer/data/{tests → coordinates/tests}/test_coordinates.py +26 -33
- junifer/data/masks/__init__.py +9 -0
- junifer/data/masks/__init__.pyi +6 -0
- junifer/data/masks/_ants_mask_warper.py +177 -0
- junifer/data/masks/_fsl_mask_warper.py +106 -0
- junifer/data/masks/_masks.py +802 -0
- junifer/data/{tests → masks/tests}/test_masks.py +67 -63
- junifer/data/parcellations/__init__.py +9 -0
- junifer/data/parcellations/__init__.pyi +6 -0
- junifer/data/parcellations/_ants_parcellation_warper.py +166 -0
- junifer/data/parcellations/_fsl_parcellation_warper.py +89 -0
- junifer/data/parcellations/_parcellations.py +1388 -0
- junifer/data/{tests → parcellations/tests}/test_parcellations.py +165 -295
- junifer/data/pipeline_data_registry_base.py +76 -0
- junifer/data/py.typed +0 -0
- junifer/data/template_spaces.py +44 -79
- junifer/data/tests/test_data_utils.py +1 -2
- junifer/data/tests/test_template_spaces.py +8 -4
- junifer/data/utils.py +109 -4
- junifer/datagrabber/__init__.py +2 -26
- junifer/datagrabber/__init__.pyi +27 -0
- junifer/datagrabber/aomic/__init__.py +2 -4
- junifer/datagrabber/aomic/__init__.pyi +5 -0
- junifer/datagrabber/aomic/id1000.py +81 -52
- junifer/datagrabber/aomic/piop1.py +83 -55
- junifer/datagrabber/aomic/piop2.py +85 -56
- junifer/datagrabber/aomic/py.typed +0 -0
- junifer/datagrabber/aomic/tests/test_id1000.py +19 -12
- junifer/datagrabber/aomic/tests/test_piop1.py +52 -18
- junifer/datagrabber/aomic/tests/test_piop2.py +50 -17
- junifer/datagrabber/base.py +22 -18
- junifer/datagrabber/datalad_base.py +71 -34
- junifer/datagrabber/dmcc13_benchmark.py +31 -18
- junifer/datagrabber/hcp1200/__init__.py +2 -3
- junifer/datagrabber/hcp1200/__init__.pyi +4 -0
- junifer/datagrabber/hcp1200/datalad_hcp1200.py +3 -3
- junifer/datagrabber/hcp1200/hcp1200.py +26 -15
- junifer/datagrabber/hcp1200/py.typed +0 -0
- junifer/datagrabber/hcp1200/tests/test_hcp1200.py +8 -2
- junifer/datagrabber/multiple.py +14 -9
- junifer/datagrabber/pattern.py +132 -96
- junifer/datagrabber/pattern_validation_mixin.py +206 -94
- junifer/datagrabber/py.typed +0 -0
- junifer/datagrabber/tests/test_datalad_base.py +27 -12
- junifer/datagrabber/tests/test_dmcc13_benchmark.py +28 -11
- junifer/datagrabber/tests/test_multiple.py +48 -2
- junifer/datagrabber/tests/test_pattern_datalad.py +1 -1
- junifer/datagrabber/tests/test_pattern_validation_mixin.py +6 -6
- junifer/datareader/__init__.py +2 -2
- junifer/datareader/__init__.pyi +3 -0
- junifer/datareader/default.py +6 -6
- junifer/datareader/py.typed +0 -0
- junifer/external/nilearn/__init__.py +2 -3
- junifer/external/nilearn/__init__.pyi +4 -0
- junifer/external/nilearn/junifer_connectivity_measure.py +25 -17
- junifer/external/nilearn/junifer_nifti_spheres_masker.py +4 -4
- junifer/external/nilearn/py.typed +0 -0
- junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +17 -16
- junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +2 -3
- junifer/markers/__init__.py +2 -38
- junifer/markers/__init__.pyi +37 -0
- junifer/markers/base.py +11 -14
- junifer/markers/brainprint.py +12 -14
- junifer/markers/complexity/__init__.py +2 -18
- junifer/markers/complexity/__init__.pyi +17 -0
- junifer/markers/complexity/complexity_base.py +9 -11
- junifer/markers/complexity/hurst_exponent.py +7 -7
- junifer/markers/complexity/multiscale_entropy_auc.py +7 -7
- junifer/markers/complexity/perm_entropy.py +7 -7
- junifer/markers/complexity/py.typed +0 -0
- junifer/markers/complexity/range_entropy.py +7 -7
- junifer/markers/complexity/range_entropy_auc.py +7 -7
- junifer/markers/complexity/sample_entropy.py +7 -7
- junifer/markers/complexity/tests/test_complexity_base.py +1 -1
- junifer/markers/complexity/tests/test_hurst_exponent.py +5 -5
- junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +5 -5
- junifer/markers/complexity/tests/test_perm_entropy.py +5 -5
- junifer/markers/complexity/tests/test_range_entropy.py +5 -5
- junifer/markers/complexity/tests/test_range_entropy_auc.py +5 -5
- junifer/markers/complexity/tests/test_sample_entropy.py +5 -5
- junifer/markers/complexity/tests/test_weighted_perm_entropy.py +5 -5
- junifer/markers/complexity/weighted_perm_entropy.py +7 -7
- junifer/markers/ets_rss.py +12 -11
- junifer/markers/falff/__init__.py +2 -3
- junifer/markers/falff/__init__.pyi +4 -0
- junifer/markers/falff/_afni_falff.py +38 -45
- junifer/markers/falff/_junifer_falff.py +16 -19
- junifer/markers/falff/falff_base.py +7 -11
- junifer/markers/falff/falff_parcels.py +9 -9
- junifer/markers/falff/falff_spheres.py +8 -8
- junifer/markers/falff/py.typed +0 -0
- junifer/markers/falff/tests/test_falff_spheres.py +3 -1
- junifer/markers/functional_connectivity/__init__.py +2 -12
- junifer/markers/functional_connectivity/__init__.pyi +13 -0
- junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +9 -8
- junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +8 -8
- junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +7 -7
- junifer/markers/functional_connectivity/functional_connectivity_base.py +13 -12
- junifer/markers/functional_connectivity/functional_connectivity_parcels.py +8 -8
- junifer/markers/functional_connectivity/functional_connectivity_spheres.py +7 -7
- junifer/markers/functional_connectivity/py.typed +0 -0
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +1 -2
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +1 -2
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +6 -6
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +5 -5
- junifer/markers/parcel_aggregation.py +22 -17
- junifer/markers/py.typed +0 -0
- junifer/markers/reho/__init__.py +2 -3
- junifer/markers/reho/__init__.pyi +4 -0
- junifer/markers/reho/_afni_reho.py +29 -35
- junifer/markers/reho/_junifer_reho.py +13 -14
- junifer/markers/reho/py.typed +0 -0
- junifer/markers/reho/reho_base.py +7 -11
- junifer/markers/reho/reho_parcels.py +10 -10
- junifer/markers/reho/reho_spheres.py +9 -9
- junifer/markers/sphere_aggregation.py +22 -17
- junifer/markers/temporal_snr/__init__.py +2 -3
- junifer/markers/temporal_snr/__init__.pyi +4 -0
- junifer/markers/temporal_snr/py.typed +0 -0
- junifer/markers/temporal_snr/temporal_snr_base.py +11 -10
- junifer/markers/temporal_snr/temporal_snr_parcels.py +8 -8
- junifer/markers/temporal_snr/temporal_snr_spheres.py +7 -7
- junifer/markers/tests/test_ets_rss.py +3 -3
- junifer/markers/tests/test_parcel_aggregation.py +24 -24
- junifer/markers/tests/test_sphere_aggregation.py +6 -6
- junifer/markers/utils.py +3 -3
- junifer/onthefly/__init__.py +2 -1
- junifer/onthefly/_brainprint.py +138 -0
- junifer/onthefly/read_transform.py +5 -8
- junifer/pipeline/__init__.py +2 -10
- junifer/pipeline/__init__.pyi +13 -0
- junifer/{markers/collection.py → pipeline/marker_collection.py} +8 -14
- junifer/pipeline/pipeline_component_registry.py +294 -0
- junifer/pipeline/pipeline_step_mixin.py +15 -11
- junifer/pipeline/py.typed +0 -0
- junifer/{markers/tests/test_collection.py → pipeline/tests/test_marker_collection.py} +2 -3
- junifer/pipeline/tests/test_pipeline_component_registry.py +200 -0
- junifer/pipeline/tests/test_pipeline_step_mixin.py +36 -37
- junifer/pipeline/tests/test_update_meta_mixin.py +4 -4
- junifer/pipeline/tests/test_workdir_manager.py +43 -0
- junifer/pipeline/update_meta_mixin.py +21 -17
- junifer/pipeline/utils.py +6 -6
- junifer/pipeline/workdir_manager.py +19 -5
- junifer/preprocess/__init__.py +2 -10
- junifer/preprocess/__init__.pyi +11 -0
- junifer/preprocess/base.py +10 -10
- junifer/preprocess/confounds/__init__.py +2 -2
- junifer/preprocess/confounds/__init__.pyi +3 -0
- junifer/preprocess/confounds/fmriprep_confound_remover.py +243 -64
- junifer/preprocess/confounds/py.typed +0 -0
- junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +121 -14
- junifer/preprocess/py.typed +0 -0
- junifer/preprocess/smoothing/__init__.py +2 -2
- junifer/preprocess/smoothing/__init__.pyi +3 -0
- junifer/preprocess/smoothing/_afni_smoothing.py +40 -40
- junifer/preprocess/smoothing/_fsl_smoothing.py +22 -32
- junifer/preprocess/smoothing/_nilearn_smoothing.py +35 -14
- junifer/preprocess/smoothing/py.typed +0 -0
- junifer/preprocess/smoothing/smoothing.py +11 -13
- junifer/preprocess/warping/__init__.py +2 -2
- junifer/preprocess/warping/__init__.pyi +3 -0
- junifer/preprocess/warping/_ants_warper.py +136 -32
- junifer/preprocess/warping/_fsl_warper.py +73 -22
- junifer/preprocess/warping/py.typed +0 -0
- junifer/preprocess/warping/space_warper.py +39 -11
- junifer/preprocess/warping/tests/test_space_warper.py +5 -9
- junifer/py.typed +0 -0
- junifer/stats.py +5 -5
- junifer/storage/__init__.py +2 -10
- junifer/storage/__init__.pyi +11 -0
- junifer/storage/base.py +47 -13
- junifer/storage/hdf5.py +95 -33
- junifer/storage/pandas_base.py +12 -11
- junifer/storage/py.typed +0 -0
- junifer/storage/sqlite.py +11 -11
- junifer/storage/tests/test_hdf5.py +86 -4
- junifer/storage/tests/test_sqlite.py +2 -2
- junifer/storage/tests/test_storage_base.py +5 -2
- junifer/storage/tests/test_utils.py +33 -7
- junifer/storage/utils.py +95 -9
- junifer/testing/__init__.py +2 -3
- junifer/testing/__init__.pyi +4 -0
- junifer/testing/datagrabbers.py +10 -11
- junifer/testing/py.typed +0 -0
- junifer/testing/registry.py +4 -7
- junifer/testing/tests/test_testing_registry.py +9 -17
- junifer/tests/test_stats.py +2 -2
- junifer/typing/__init__.py +9 -0
- junifer/typing/__init__.pyi +31 -0
- junifer/typing/_typing.py +68 -0
- junifer/utils/__init__.py +2 -12
- junifer/utils/__init__.pyi +18 -0
- junifer/utils/_config.py +110 -0
- junifer/utils/_yaml.py +16 -0
- junifer/utils/helpers.py +6 -6
- junifer/utils/logging.py +117 -8
- junifer/utils/py.typed +0 -0
- junifer/{pipeline → utils}/singleton.py +19 -14
- junifer/utils/tests/test_config.py +59 -0
- {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/METADATA +43 -38
- junifer-0.0.6.dist-info/RECORD +350 -0
- {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/WHEEL +1 -1
- junifer-0.0.6.dist-info/entry_points.txt +2 -0
- junifer/api/parser.py +0 -118
- junifer/data/coordinates.py +0 -408
- junifer/data/masks.py +0 -670
- junifer/data/parcellations.py +0 -1828
- junifer/pipeline/registry.py +0 -177
- junifer/pipeline/tests/test_registry.py +0 -150
- junifer-0.0.5.dev240.dist-info/RECORD +0 -275
- junifer-0.0.5.dev240.dist-info/entry_points.txt +0 -2
- /junifer/{api → cli}/tests/data/gmd_mean.yaml +0 -0
- /junifer/{api → cli}/tests/data/gmd_mean_htcondor.yaml +0 -0
- /junifer/{api → cli}/tests/data/partly_cloudy_agg_mean_tian.yml +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/AutobiographicalMemory_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAC_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAR_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/DMNBuckner_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Dosenbach2010_MNI_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Empathy_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Motor_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/MultiTask_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/PhysioStress_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2011_MNI_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2013_MNI_VOIs.tsv +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Rew_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/Somatosensory_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/ToM_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/VigAtt_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/WM_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/eMDN_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/eSAD_VOIs.txt +0 -0
- /junifer/data/{VOIs → coordinates/VOIs}/meta/extDMN_VOIs.txt +0 -0
- {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info/licenses}/AUTHORS.rst +0 -0
- {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info/licenses}/LICENSE.md +0 -0
- {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,385 @@
|
|
1
|
+
"""Provide a class for centralized coordinates data registry."""
|
2
|
+
|
3
|
+
# Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
|
4
|
+
# Synchon Mandal <s.mandal@fz-juelich.de>
|
5
|
+
# License: AGPL
|
6
|
+
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Any, Optional
|
9
|
+
|
10
|
+
import numpy as np
|
11
|
+
import pandas as pd
|
12
|
+
from junifer_data import get
|
13
|
+
from numpy.typing import ArrayLike
|
14
|
+
|
15
|
+
from ...utils import logger, raise_error
|
16
|
+
from ...utils.singleton import Singleton
|
17
|
+
from ..pipeline_data_registry_base import BasePipelineDataRegistry
|
18
|
+
from ..utils import JUNIFER_DATA_PARAMS, get_dataset_path, get_native_warper
|
19
|
+
from ._ants_coordinates_warper import ANTsCoordinatesWarper
|
20
|
+
from ._fsl_coordinates_warper import FSLCoordinatesWarper
|
21
|
+
|
22
|
+
|
23
|
+
__all__ = ["CoordinatesRegistry"]
|
24
|
+
|
25
|
+
|
26
|
+
class CoordinatesRegistry(BasePipelineDataRegistry, metaclass=Singleton):
|
27
|
+
"""Class for coordinates data registry.
|
28
|
+
|
29
|
+
This class is a singleton and is used for managing available coordinates
|
30
|
+
data in a centralized manner.
|
31
|
+
|
32
|
+
"""
|
33
|
+
|
34
|
+
def __init__(self) -> None:
|
35
|
+
"""Initialize the class."""
|
36
|
+
super().__init__()
|
37
|
+
# Each entry in registry is a dictionary that must contain at least
|
38
|
+
# the following keys:
|
39
|
+
# * 'space': the coordinates' space (e.g., 'MNI')
|
40
|
+
# The built-in coordinates are files that are shipped with the
|
41
|
+
# junifer-data dataset. The user can also register their own
|
42
|
+
# coordinates, which will be stored as numpy arrays in the dictionary.
|
43
|
+
# Make built-in and external dictionaries for validation later
|
44
|
+
self._builtin = {}
|
45
|
+
self._external = {}
|
46
|
+
|
47
|
+
self._builtin.update(
|
48
|
+
{
|
49
|
+
"CogAC": {
|
50
|
+
"file_path_suffix": "CogAC/CogAC_VOIs.txt",
|
51
|
+
"space": "MNI",
|
52
|
+
},
|
53
|
+
"CogAR": {
|
54
|
+
"file_path_suffix": "CogAR/CogAR_VOIs.txt",
|
55
|
+
"space": "MNI",
|
56
|
+
},
|
57
|
+
"DMNBuckner": {
|
58
|
+
"file_path_suffix": "DMNBuckner/DMNBuckner_VOIs.txt",
|
59
|
+
"space": "MNI",
|
60
|
+
},
|
61
|
+
"eMDN": {
|
62
|
+
"file_path_suffix": "eMDN/eMDN_VOIs.txt",
|
63
|
+
"space": "MNI",
|
64
|
+
},
|
65
|
+
"Empathy": {
|
66
|
+
"file_path_suffix": "Empathy/Empathy_VOIs.txt",
|
67
|
+
"space": "MNI",
|
68
|
+
},
|
69
|
+
"eSAD": {
|
70
|
+
"file_path_suffix": "eSAD/eSAD_VOIs.txt",
|
71
|
+
"space": "MNI",
|
72
|
+
},
|
73
|
+
"extDMN": {
|
74
|
+
"file_path_suffix": "extDMN/extDMN_VOIs.txt",
|
75
|
+
"space": "MNI",
|
76
|
+
},
|
77
|
+
"Motor": {
|
78
|
+
"file_path_suffix": "Motor/Motor_VOIs.txt",
|
79
|
+
"space": "MNI",
|
80
|
+
},
|
81
|
+
"MultiTask": {
|
82
|
+
"file_path_suffix": "MultiTask/MultiTask_VOIs.txt",
|
83
|
+
"space": "MNI",
|
84
|
+
},
|
85
|
+
"PhysioStress": {
|
86
|
+
"file_path_suffix": "PhysioStress/PhysioStress_VOIs.txt",
|
87
|
+
"space": "MNI",
|
88
|
+
},
|
89
|
+
"Rew": {
|
90
|
+
"file_path_suffix": "Rew/Rew_VOIs.txt",
|
91
|
+
"space": "MNI",
|
92
|
+
},
|
93
|
+
"Somatosensory": {
|
94
|
+
"file_path_suffix": "Somatosensory/Somatosensory_VOIs.txt",
|
95
|
+
"space": "MNI",
|
96
|
+
},
|
97
|
+
"ToM": {
|
98
|
+
"file_path_suffix": "ToM/ToM_VOIs.txt",
|
99
|
+
"space": "MNI",
|
100
|
+
},
|
101
|
+
"VigAtt": {
|
102
|
+
"file_path_suffix": "VigAtt/VigAtt_VOIs.txt",
|
103
|
+
"space": "MNI",
|
104
|
+
},
|
105
|
+
"WM": {
|
106
|
+
"file_path_suffix": "WM/WM_VOIs.txt",
|
107
|
+
"space": "MNI",
|
108
|
+
},
|
109
|
+
"Power2011": {
|
110
|
+
"file_path_suffix": "Power/Power2011_MNI_VOIs.txt",
|
111
|
+
"space": "MNI",
|
112
|
+
},
|
113
|
+
"Dosenbach": {
|
114
|
+
"file_path_suffix": "Dosenbach/Dosenbach2010_MNI_VOIs.txt",
|
115
|
+
"space": "MNI",
|
116
|
+
},
|
117
|
+
"AutobiographicalMemory": {
|
118
|
+
"file_path_suffix": (
|
119
|
+
"AutobiographicalMemory/AutobiographicalMemory_VOIs.txt"
|
120
|
+
),
|
121
|
+
"space": "MNI",
|
122
|
+
},
|
123
|
+
"Seitzman2018": {
|
124
|
+
"file_path_suffix": "Seitzman/Seitzman2018_MNI_VOIs.txt",
|
125
|
+
"space": "MNI",
|
126
|
+
},
|
127
|
+
}
|
128
|
+
)
|
129
|
+
|
130
|
+
# Update registry with built-in ones
|
131
|
+
self._registry.update(self._builtin)
|
132
|
+
|
133
|
+
def register(
|
134
|
+
self,
|
135
|
+
name: str,
|
136
|
+
coordinates: ArrayLike,
|
137
|
+
voi_names: list[str],
|
138
|
+
space: str,
|
139
|
+
overwrite: Optional[bool] = False,
|
140
|
+
) -> None:
|
141
|
+
"""Register a custom user coordinates.
|
142
|
+
|
143
|
+
Parameters
|
144
|
+
----------
|
145
|
+
name : str
|
146
|
+
The name of the coordinates.
|
147
|
+
coordinates : numpy.ndarray
|
148
|
+
The coordinates. This should be a 2-dimensional array with three
|
149
|
+
columns. Each row corresponds to a volume-of-interest (VOI) and
|
150
|
+
each column corresponds to a spatial dimension (i.e. x, y, and
|
151
|
+
z-coordinates).
|
152
|
+
voi_names : list of str
|
153
|
+
The names of the VOIs.
|
154
|
+
space : str
|
155
|
+
The space of the coordinates, e.g., "MNI".
|
156
|
+
overwrite : bool, optional
|
157
|
+
If True, overwrite an existing list of coordinates with the same
|
158
|
+
name. Does not apply to built-in coordinates (default False).
|
159
|
+
|
160
|
+
Raises
|
161
|
+
------
|
162
|
+
ValueError
|
163
|
+
If the coordinates ``name`` is a built-in coordinates or
|
164
|
+
if the coordinates ``name`` is already registered and
|
165
|
+
``overwrite=False`` or
|
166
|
+
if the ``coordinates`` is not a 2D array or
|
167
|
+
if coordinate value does not have 3 components or
|
168
|
+
if the ``voi_names`` shape does not match the
|
169
|
+
``coordinates`` shape.
|
170
|
+
TypeError
|
171
|
+
If ``coordinates`` is not a ``numpy.ndarray``.
|
172
|
+
|
173
|
+
"""
|
174
|
+
# Check for attempt of overwriting built-in coordinates
|
175
|
+
if name in self._builtin:
|
176
|
+
raise_error(
|
177
|
+
f"Coordinates: {name} already registered as built-in "
|
178
|
+
"coordinates."
|
179
|
+
)
|
180
|
+
# Check for attempt of overwriting external coordinates
|
181
|
+
if name in self._external:
|
182
|
+
if overwrite:
|
183
|
+
logger.info(f"Overwriting coordinates: {name}")
|
184
|
+
else:
|
185
|
+
raise_error(
|
186
|
+
f"Coordinates: {name} already registered. "
|
187
|
+
"Set `overwrite=True` to update its value."
|
188
|
+
)
|
189
|
+
# Further checks
|
190
|
+
if not isinstance(coordinates, np.ndarray):
|
191
|
+
raise_error(
|
192
|
+
"Coordinates must be a `numpy.ndarray`, "
|
193
|
+
f"not {type(coordinates)}.",
|
194
|
+
klass=TypeError,
|
195
|
+
)
|
196
|
+
if coordinates.ndim != 2:
|
197
|
+
raise_error(
|
198
|
+
f"Coordinates must be a 2D array, not {coordinates.ndim}D."
|
199
|
+
)
|
200
|
+
if coordinates.shape[1] != 3:
|
201
|
+
raise_error(
|
202
|
+
"Each coordinate must have 3 values, "
|
203
|
+
f"not {coordinates.shape[1]}"
|
204
|
+
)
|
205
|
+
if len(voi_names) != coordinates.shape[0]:
|
206
|
+
raise_error(
|
207
|
+
f"Length of `voi_names` ({len(voi_names)}) does not match the "
|
208
|
+
f"number of `coordinates` ({coordinates.shape[0]})."
|
209
|
+
)
|
210
|
+
# Registration
|
211
|
+
logger.info(f"Registering coordinates: {name}")
|
212
|
+
# Add coordinates info
|
213
|
+
self._external[name] = {
|
214
|
+
"coords": coordinates,
|
215
|
+
"voi_names": voi_names,
|
216
|
+
"space": space,
|
217
|
+
}
|
218
|
+
# Update registry
|
219
|
+
self._registry[name] = {
|
220
|
+
"coords": coordinates,
|
221
|
+
"voi_names": voi_names,
|
222
|
+
"space": space,
|
223
|
+
}
|
224
|
+
|
225
|
+
def deregister(self, name: str) -> None:
|
226
|
+
"""De-register a custom user coordinates.
|
227
|
+
|
228
|
+
Parameters
|
229
|
+
----------
|
230
|
+
name : str
|
231
|
+
The name of the coordinates.
|
232
|
+
|
233
|
+
"""
|
234
|
+
logger.info(f"De-registering coordinates: {name}")
|
235
|
+
# Remove coordinates info
|
236
|
+
_ = self._external.pop(name)
|
237
|
+
# Update registry
|
238
|
+
_ = self._registry.pop(name)
|
239
|
+
|
240
|
+
def load(self, name: str) -> tuple[ArrayLike, list[str], str]:
|
241
|
+
"""Load coordinates.
|
242
|
+
|
243
|
+
Parameters
|
244
|
+
----------
|
245
|
+
name : str
|
246
|
+
The name of the coordinates.
|
247
|
+
|
248
|
+
Returns
|
249
|
+
-------
|
250
|
+
numpy.ndarray
|
251
|
+
The coordinates.
|
252
|
+
list of str
|
253
|
+
The names of the VOIs.
|
254
|
+
str
|
255
|
+
The space of the coordinates.
|
256
|
+
|
257
|
+
Raises
|
258
|
+
------
|
259
|
+
ValueError
|
260
|
+
If ``name`` is invalid.
|
261
|
+
RuntimeError
|
262
|
+
If there is a problem fetching the coordinates file.
|
263
|
+
|
264
|
+
"""
|
265
|
+
# Check for valid coordinates name
|
266
|
+
if name not in self._registry:
|
267
|
+
raise_error(
|
268
|
+
f"Coordinates: {name} not found. "
|
269
|
+
f"Valid options are: {self.list}"
|
270
|
+
)
|
271
|
+
# Load coordinates info
|
272
|
+
t_coord = self._registry[name]
|
273
|
+
|
274
|
+
# Load data for in-built ones
|
275
|
+
if t_coord.get("file_path_suffix") is not None:
|
276
|
+
# Set file path to retrieve
|
277
|
+
coords_file_path = Path(
|
278
|
+
f"coordinates/{t_coord['file_path_suffix']}"
|
279
|
+
)
|
280
|
+
logger.debug(f"Loading coordinates: `{name}`")
|
281
|
+
# Load via pandas
|
282
|
+
df_coords = pd.read_csv(
|
283
|
+
get(
|
284
|
+
file_path=coords_file_path,
|
285
|
+
dataset_path=get_dataset_path(),
|
286
|
+
**JUNIFER_DATA_PARAMS,
|
287
|
+
),
|
288
|
+
sep="\t",
|
289
|
+
header=None,
|
290
|
+
)
|
291
|
+
# Convert dataframe to numpy ndarray
|
292
|
+
coords = df_coords.iloc[:, [0, 1, 2]].to_numpy()
|
293
|
+
# Get label names
|
294
|
+
names = list(df_coords.iloc[:, [3]].values[:, 0])
|
295
|
+
# Load data for external ones
|
296
|
+
else:
|
297
|
+
coords = t_coord["coords"]
|
298
|
+
names = t_coord["voi_names"]
|
299
|
+
|
300
|
+
return coords, names, t_coord["space"]
|
301
|
+
|
302
|
+
def get(
|
303
|
+
self,
|
304
|
+
coords: str,
|
305
|
+
target_data: dict[str, Any],
|
306
|
+
extra_input: Optional[dict[str, Any]] = None,
|
307
|
+
) -> tuple[ArrayLike, list[str]]:
|
308
|
+
"""Get coordinates, tailored for the target data.
|
309
|
+
|
310
|
+
Parameters
|
311
|
+
----------
|
312
|
+
coords : str
|
313
|
+
The name of the coordinates.
|
314
|
+
target_data : dict
|
315
|
+
The corresponding item of the data object to which the coordinates
|
316
|
+
will be applied.
|
317
|
+
extra_input : dict, optional
|
318
|
+
The other fields in the data object. Useful for accessing other
|
319
|
+
data kinds that needs to be used in the computation of coordinates
|
320
|
+
(default None).
|
321
|
+
|
322
|
+
Returns
|
323
|
+
-------
|
324
|
+
numpy.ndarray
|
325
|
+
The coordinates.
|
326
|
+
list of str
|
327
|
+
The names of the VOIs.
|
328
|
+
|
329
|
+
Raises
|
330
|
+
------
|
331
|
+
RuntimeError
|
332
|
+
If warping specification required for warping using ANTs, is not
|
333
|
+
found.
|
334
|
+
ValueError
|
335
|
+
If ``extra_input`` is None when ``target_data``'s space is native.
|
336
|
+
|
337
|
+
"""
|
338
|
+
# Load the coordinates
|
339
|
+
seeds, labels, _ = self.load(name=coords)
|
340
|
+
|
341
|
+
# Transform coordinate if target data is native
|
342
|
+
if target_data["space"] == "native":
|
343
|
+
# Check for extra inputs
|
344
|
+
if extra_input is None:
|
345
|
+
raise_error(
|
346
|
+
"No extra input provided, requires `Warp` and `T1w` "
|
347
|
+
"data types in particular for transformation to "
|
348
|
+
f"{target_data['space']} space for further computation."
|
349
|
+
)
|
350
|
+
|
351
|
+
# Get native space warper spec
|
352
|
+
warper_spec = get_native_warper(
|
353
|
+
target_data=target_data,
|
354
|
+
other_data=extra_input,
|
355
|
+
)
|
356
|
+
# Conditional for warping tool implementation
|
357
|
+
if warper_spec["warper"] == "fsl":
|
358
|
+
seeds = FSLCoordinatesWarper().warp(
|
359
|
+
seeds=seeds,
|
360
|
+
target_data=target_data,
|
361
|
+
warp_data=warper_spec,
|
362
|
+
)
|
363
|
+
elif warper_spec["warper"] == "ants":
|
364
|
+
# Requires the inverse warp
|
365
|
+
inverse_warper_spec = get_native_warper(
|
366
|
+
target_data=target_data,
|
367
|
+
other_data=extra_input,
|
368
|
+
inverse=True,
|
369
|
+
)
|
370
|
+
# Check warper
|
371
|
+
if inverse_warper_spec["warper"] != "ants":
|
372
|
+
raise_error(
|
373
|
+
klass=RuntimeError,
|
374
|
+
msg=(
|
375
|
+
"Warping specification mismatch for native space "
|
376
|
+
"warping of coordinates using ANTs."
|
377
|
+
),
|
378
|
+
)
|
379
|
+
seeds = ANTsCoordinatesWarper().warp(
|
380
|
+
seeds=seeds,
|
381
|
+
target_data=target_data,
|
382
|
+
warp_data=inverse_warper_spec,
|
383
|
+
)
|
384
|
+
|
385
|
+
return seeds, labels
|
@@ -0,0 +1,81 @@
|
|
1
|
+
"""Provide class for coordinates space warping via FSL FLIRT."""
|
2
|
+
|
3
|
+
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
4
|
+
# License: AGPL
|
5
|
+
|
6
|
+
from typing import Any
|
7
|
+
|
8
|
+
import numpy as np
|
9
|
+
from numpy.typing import ArrayLike
|
10
|
+
|
11
|
+
from ...pipeline import WorkDirManager
|
12
|
+
from ...utils import logger, run_ext_cmd
|
13
|
+
|
14
|
+
|
15
|
+
__all__ = ["FSLCoordinatesWarper"]
|
16
|
+
|
17
|
+
|
18
|
+
class FSLCoordinatesWarper:
|
19
|
+
"""Class for coordinates space warping via FSL FLIRT.
|
20
|
+
|
21
|
+
This class uses FSL FLIRT's ``img2imgcoord`` for transformation.
|
22
|
+
|
23
|
+
"""
|
24
|
+
|
25
|
+
def warp(
|
26
|
+
self,
|
27
|
+
seeds: ArrayLike,
|
28
|
+
target_data: dict[str, Any],
|
29
|
+
warp_data: dict[str, Any],
|
30
|
+
) -> ArrayLike:
|
31
|
+
"""Warp ``seeds`` to correct space.
|
32
|
+
|
33
|
+
Parameters
|
34
|
+
----------
|
35
|
+
seeds : array-like
|
36
|
+
The coordinates to transform.
|
37
|
+
target_data : dict
|
38
|
+
The corresponding item of the data object to which the coordinates
|
39
|
+
will be applied.
|
40
|
+
warp_data : dict
|
41
|
+
The warp data item of the data object.
|
42
|
+
|
43
|
+
Returns
|
44
|
+
-------
|
45
|
+
numpy.ndarray
|
46
|
+
The transformed coordinates.
|
47
|
+
|
48
|
+
"""
|
49
|
+
logger.debug("Using FSL for coordinates transformation")
|
50
|
+
|
51
|
+
# Create element-specific tempdir for storing post-warping assets
|
52
|
+
element_tempdir = WorkDirManager().get_element_tempdir(
|
53
|
+
prefix="fsl_coordinates_warper"
|
54
|
+
)
|
55
|
+
|
56
|
+
# Save existing coordinates to a tempfile
|
57
|
+
pretransform_coordinates_path = (
|
58
|
+
element_tempdir / "pretransform_coordinates.txt"
|
59
|
+
)
|
60
|
+
np.savetxt(pretransform_coordinates_path, seeds)
|
61
|
+
|
62
|
+
# Create a tempfile for transformed coordinates output
|
63
|
+
transformed_coords_path = (
|
64
|
+
element_tempdir / "coordinates_transformed.txt"
|
65
|
+
)
|
66
|
+
# Set img2imgcoord command
|
67
|
+
img2imgcoord_cmd = [
|
68
|
+
"cat",
|
69
|
+
f"{pretransform_coordinates_path.resolve()}",
|
70
|
+
"| img2imgcoord -mm",
|
71
|
+
f"-src {target_data['path'].resolve()}",
|
72
|
+
f"-dest {target_data['reference']['path'].resolve()}",
|
73
|
+
f"-warp {warp_data['path'].resolve()}",
|
74
|
+
f"> {transformed_coords_path.resolve()};",
|
75
|
+
f"sed -i 1d {transformed_coords_path.resolve()}",
|
76
|
+
]
|
77
|
+
# Call img2imgcoord
|
78
|
+
run_ext_cmd(name="img2imgcoord", cmd=img2imgcoord_cmd)
|
79
|
+
|
80
|
+
# Load coordinates
|
81
|
+
return np.loadtxt(transformed_coords_path)
|
@@ -1,52 +1,47 @@
|
|
1
1
|
"""Provide tests for coordinates."""
|
2
2
|
|
3
3
|
# Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
|
4
|
+
# Synchon Mandal <s.mandal@fz-juelich.de>
|
4
5
|
# License: AGPL
|
5
6
|
|
6
7
|
import numpy as np
|
7
8
|
import pytest
|
8
9
|
from numpy.testing import assert_array_equal
|
9
10
|
|
10
|
-
from junifer.data
|
11
|
-
get_coordinates,
|
12
|
-
list_coordinates,
|
13
|
-
load_coordinates,
|
14
|
-
register_coordinates,
|
15
|
-
)
|
11
|
+
from junifer.data import CoordinatesRegistry
|
16
12
|
from junifer.datareader import DefaultDataReader
|
17
13
|
from junifer.testing.datagrabbers import OasisVBMTestingDataGrabber
|
18
14
|
|
19
15
|
|
20
|
-
def
|
16
|
+
def test_register_built_in_check() -> None:
|
21
17
|
"""Test coordinates registration check for built-in coordinates."""
|
22
18
|
with pytest.raises(ValueError, match=r"built-in"):
|
23
|
-
|
19
|
+
CoordinatesRegistry().register(
|
24
20
|
name="DMNBuckner",
|
25
21
|
coordinates=np.zeros(2),
|
26
22
|
voi_names=["1", "2"],
|
27
23
|
space="MNI",
|
28
|
-
overwrite=True,
|
29
24
|
)
|
30
25
|
|
31
26
|
|
32
|
-
def
|
27
|
+
def test_register_overwrite() -> None:
|
33
28
|
"""Test coordinates registration check for overwriting."""
|
34
|
-
|
29
|
+
CoordinatesRegistry().register(
|
35
30
|
name="MyList",
|
36
31
|
coordinates=np.zeros((2, 3)),
|
37
32
|
voi_names=["roi1", "roi2"],
|
38
33
|
space="MNI",
|
39
|
-
overwrite=True,
|
40
34
|
)
|
41
35
|
with pytest.raises(ValueError, match=r"already registered"):
|
42
|
-
|
36
|
+
CoordinatesRegistry().register(
|
43
37
|
name="MyList",
|
44
38
|
coordinates=np.ones((2, 3)),
|
45
39
|
voi_names=["roi2", "roi3"],
|
46
40
|
space="MNI",
|
41
|
+
overwrite=False,
|
47
42
|
)
|
48
43
|
|
49
|
-
|
44
|
+
CoordinatesRegistry().register(
|
50
45
|
name="MyList",
|
51
46
|
coordinates=np.ones((2, 3)),
|
52
47
|
voi_names=["roi2", "roi3"],
|
@@ -54,16 +49,16 @@ def test_register_coordinates_overwrite() -> None:
|
|
54
49
|
overwrite=True,
|
55
50
|
)
|
56
51
|
|
57
|
-
coord, names, space =
|
52
|
+
coord, names, space = CoordinatesRegistry().load("MyList")
|
58
53
|
assert_array_equal(coord, np.ones((2, 3)))
|
59
54
|
assert names == ["roi2", "roi3"]
|
60
55
|
assert space == "MNI"
|
61
56
|
|
62
57
|
|
63
|
-
def
|
58
|
+
def test_register_valid_input() -> None:
|
64
59
|
"""Test coordinates registration check for valid input."""
|
65
60
|
with pytest.raises(TypeError, match=r"numpy.ndarray"):
|
66
|
-
|
61
|
+
CoordinatesRegistry().register(
|
67
62
|
name="MyList",
|
68
63
|
coordinates=[1, 2],
|
69
64
|
voi_names=["roi1", "roi2"],
|
@@ -71,7 +66,7 @@ def test_register_coordinates_valid_input() -> None:
|
|
71
66
|
overwrite=True,
|
72
67
|
)
|
73
68
|
with pytest.raises(ValueError, match=r"2D array"):
|
74
|
-
|
69
|
+
CoordinatesRegistry().register(
|
75
70
|
name="MyList",
|
76
71
|
coordinates=np.zeros((2, 3, 4)),
|
77
72
|
voi_names=["roi1", "roi2"],
|
@@ -80,7 +75,7 @@ def test_register_coordinates_valid_input() -> None:
|
|
80
75
|
)
|
81
76
|
|
82
77
|
with pytest.raises(ValueError, match=r"3 values"):
|
83
|
-
|
78
|
+
CoordinatesRegistry().register(
|
84
79
|
name="MyList",
|
85
80
|
coordinates=np.zeros((2, 4)),
|
86
81
|
voi_names=["roi1", "roi2"],
|
@@ -88,7 +83,7 @@ def test_register_coordinates_valid_input() -> None:
|
|
88
83
|
overwrite=True,
|
89
84
|
)
|
90
85
|
with pytest.raises(ValueError, match=r"voi_names"):
|
91
|
-
|
86
|
+
CoordinatesRegistry().register(
|
92
87
|
name="MyList",
|
93
88
|
coordinates=np.zeros((2, 3)),
|
94
89
|
voi_names=["roi1", "roi2", "roi3"],
|
@@ -97,30 +92,28 @@ def test_register_coordinates_valid_input() -> None:
|
|
97
92
|
)
|
98
93
|
|
99
94
|
|
100
|
-
def
|
95
|
+
def test_list() -> None:
|
101
96
|
"""Test listing of available coordinates."""
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
assert "VigAtt" in available_coordinates
|
106
|
-
assert "WM" in available_coordinates
|
97
|
+
assert {"DMNBuckner", "MultiTask", "VigAtt", "WM"}.issubset(
|
98
|
+
set(CoordinatesRegistry().list)
|
99
|
+
)
|
107
100
|
|
108
101
|
|
109
|
-
def
|
102
|
+
def test_load() -> None:
|
110
103
|
"""Test loading coordinates from file."""
|
111
|
-
coord, names, space =
|
104
|
+
coord, names, space = CoordinatesRegistry().load("DMNBuckner")
|
112
105
|
assert coord.shape == (6, 3) # type: ignore
|
113
106
|
assert names == ["PCC", "MPFC", "lAG", "rAG", "lHF", "rHF"]
|
114
107
|
assert space == "MNI"
|
115
108
|
|
116
109
|
|
117
|
-
def
|
110
|
+
def test_load_nonexisting() -> None:
|
118
111
|
"""Test loading coordinates that not exist."""
|
119
112
|
with pytest.raises(ValueError, match=r"not found"):
|
120
|
-
|
113
|
+
CoordinatesRegistry().load("NonExisting")
|
121
114
|
|
122
115
|
|
123
|
-
def
|
116
|
+
def test_get() -> None:
|
124
117
|
"""Test tailored coordinates fetch."""
|
125
118
|
reader = DefaultDataReader()
|
126
119
|
with OasisVBMTestingDataGrabber() as dg:
|
@@ -128,11 +121,11 @@ def test_get_coordinates() -> None:
|
|
128
121
|
element_data = reader.fit_transform(element)
|
129
122
|
vbm_gm = element_data["VBM_GM"]
|
130
123
|
# Get tailored coordinates
|
131
|
-
tailored_coords, tailored_labels =
|
124
|
+
tailored_coords, tailored_labels = CoordinatesRegistry().get(
|
132
125
|
coords="DMNBuckner", target_data=vbm_gm
|
133
126
|
)
|
134
127
|
# Get raw coordinates
|
135
|
-
raw_coords, raw_labels, _ =
|
128
|
+
raw_coords, raw_labels, _ = CoordinatesRegistry().load("DMNBuckner")
|
136
129
|
# Both tailored and raw should be same for now
|
137
130
|
assert_array_equal(tailored_coords, raw_coords)
|
138
131
|
assert tailored_labels == raw_labels
|