junifer 0.0.5.dev242__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.
Files changed (279) hide show
  1. junifer/__init__.py +2 -31
  2. junifer/__init__.pyi +37 -0
  3. junifer/_version.py +9 -4
  4. junifer/api/__init__.py +3 -5
  5. junifer/api/__init__.pyi +4 -0
  6. junifer/api/decorators.py +14 -19
  7. junifer/api/functions.py +165 -109
  8. junifer/api/py.typed +0 -0
  9. junifer/api/queue_context/__init__.py +2 -4
  10. junifer/api/queue_context/__init__.pyi +5 -0
  11. junifer/api/queue_context/gnu_parallel_local_adapter.py +22 -6
  12. junifer/api/queue_context/htcondor_adapter.py +23 -6
  13. junifer/api/queue_context/py.typed +0 -0
  14. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +3 -3
  15. junifer/api/queue_context/tests/test_htcondor_adapter.py +3 -3
  16. junifer/api/tests/test_functions.py +168 -74
  17. junifer/cli/__init__.py +24 -0
  18. junifer/cli/__init__.pyi +3 -0
  19. junifer/{api → cli}/cli.py +141 -125
  20. junifer/cli/parser.py +235 -0
  21. junifer/cli/py.typed +0 -0
  22. junifer/{api → cli}/tests/test_cli.py +8 -8
  23. junifer/{api/tests/test_api_utils.py → cli/tests/test_cli_utils.py} +5 -4
  24. junifer/{api → cli}/tests/test_parser.py +2 -2
  25. junifer/{api → cli}/utils.py +6 -16
  26. junifer/configs/juseless/__init__.py +2 -2
  27. junifer/configs/juseless/__init__.pyi +3 -0
  28. junifer/configs/juseless/datagrabbers/__init__.py +2 -12
  29. junifer/configs/juseless/datagrabbers/__init__.pyi +13 -0
  30. junifer/configs/juseless/datagrabbers/ixi_vbm.py +2 -2
  31. junifer/configs/juseless/datagrabbers/py.typed +0 -0
  32. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -2
  33. junifer/configs/juseless/datagrabbers/ucla.py +4 -4
  34. junifer/configs/juseless/py.typed +0 -0
  35. junifer/conftest.py +25 -0
  36. junifer/data/__init__.py +2 -42
  37. junifer/data/__init__.pyi +29 -0
  38. junifer/data/_dispatch.py +248 -0
  39. junifer/data/coordinates/__init__.py +9 -0
  40. junifer/data/coordinates/__init__.pyi +5 -0
  41. junifer/data/coordinates/_ants_coordinates_warper.py +104 -0
  42. junifer/data/coordinates/_coordinates.py +385 -0
  43. junifer/data/coordinates/_fsl_coordinates_warper.py +81 -0
  44. junifer/data/{tests → coordinates/tests}/test_coordinates.py +26 -33
  45. junifer/data/masks/__init__.py +9 -0
  46. junifer/data/masks/__init__.pyi +6 -0
  47. junifer/data/masks/_ants_mask_warper.py +177 -0
  48. junifer/data/masks/_fsl_mask_warper.py +106 -0
  49. junifer/data/masks/_masks.py +802 -0
  50. junifer/data/{tests → masks/tests}/test_masks.py +67 -63
  51. junifer/data/parcellations/__init__.py +9 -0
  52. junifer/data/parcellations/__init__.pyi +6 -0
  53. junifer/data/parcellations/_ants_parcellation_warper.py +166 -0
  54. junifer/data/parcellations/_fsl_parcellation_warper.py +89 -0
  55. junifer/data/parcellations/_parcellations.py +1388 -0
  56. junifer/data/{tests → parcellations/tests}/test_parcellations.py +165 -295
  57. junifer/data/pipeline_data_registry_base.py +76 -0
  58. junifer/data/py.typed +0 -0
  59. junifer/data/template_spaces.py +44 -79
  60. junifer/data/tests/test_data_utils.py +1 -2
  61. junifer/data/tests/test_template_spaces.py +8 -4
  62. junifer/data/utils.py +109 -4
  63. junifer/datagrabber/__init__.py +2 -26
  64. junifer/datagrabber/__init__.pyi +27 -0
  65. junifer/datagrabber/aomic/__init__.py +2 -4
  66. junifer/datagrabber/aomic/__init__.pyi +5 -0
  67. junifer/datagrabber/aomic/id1000.py +81 -52
  68. junifer/datagrabber/aomic/piop1.py +83 -55
  69. junifer/datagrabber/aomic/piop2.py +85 -56
  70. junifer/datagrabber/aomic/py.typed +0 -0
  71. junifer/datagrabber/aomic/tests/test_id1000.py +19 -12
  72. junifer/datagrabber/aomic/tests/test_piop1.py +52 -18
  73. junifer/datagrabber/aomic/tests/test_piop2.py +50 -17
  74. junifer/datagrabber/base.py +22 -18
  75. junifer/datagrabber/datalad_base.py +71 -34
  76. junifer/datagrabber/dmcc13_benchmark.py +31 -18
  77. junifer/datagrabber/hcp1200/__init__.py +2 -3
  78. junifer/datagrabber/hcp1200/__init__.pyi +4 -0
  79. junifer/datagrabber/hcp1200/datalad_hcp1200.py +3 -3
  80. junifer/datagrabber/hcp1200/hcp1200.py +26 -15
  81. junifer/datagrabber/hcp1200/py.typed +0 -0
  82. junifer/datagrabber/hcp1200/tests/test_hcp1200.py +8 -2
  83. junifer/datagrabber/multiple.py +14 -9
  84. junifer/datagrabber/pattern.py +132 -96
  85. junifer/datagrabber/pattern_validation_mixin.py +206 -94
  86. junifer/datagrabber/py.typed +0 -0
  87. junifer/datagrabber/tests/test_datalad_base.py +27 -12
  88. junifer/datagrabber/tests/test_dmcc13_benchmark.py +28 -11
  89. junifer/datagrabber/tests/test_multiple.py +48 -2
  90. junifer/datagrabber/tests/test_pattern_datalad.py +1 -1
  91. junifer/datagrabber/tests/test_pattern_validation_mixin.py +6 -6
  92. junifer/datareader/__init__.py +2 -2
  93. junifer/datareader/__init__.pyi +3 -0
  94. junifer/datareader/default.py +6 -6
  95. junifer/datareader/py.typed +0 -0
  96. junifer/external/nilearn/__init__.py +2 -3
  97. junifer/external/nilearn/__init__.pyi +4 -0
  98. junifer/external/nilearn/junifer_connectivity_measure.py +25 -17
  99. junifer/external/nilearn/junifer_nifti_spheres_masker.py +4 -4
  100. junifer/external/nilearn/py.typed +0 -0
  101. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +17 -16
  102. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +2 -3
  103. junifer/markers/__init__.py +2 -38
  104. junifer/markers/__init__.pyi +37 -0
  105. junifer/markers/base.py +11 -14
  106. junifer/markers/brainprint.py +12 -14
  107. junifer/markers/complexity/__init__.py +2 -18
  108. junifer/markers/complexity/__init__.pyi +17 -0
  109. junifer/markers/complexity/complexity_base.py +9 -11
  110. junifer/markers/complexity/hurst_exponent.py +7 -7
  111. junifer/markers/complexity/multiscale_entropy_auc.py +7 -7
  112. junifer/markers/complexity/perm_entropy.py +7 -7
  113. junifer/markers/complexity/py.typed +0 -0
  114. junifer/markers/complexity/range_entropy.py +7 -7
  115. junifer/markers/complexity/range_entropy_auc.py +7 -7
  116. junifer/markers/complexity/sample_entropy.py +7 -7
  117. junifer/markers/complexity/tests/test_complexity_base.py +1 -1
  118. junifer/markers/complexity/tests/test_hurst_exponent.py +5 -5
  119. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +5 -5
  120. junifer/markers/complexity/tests/test_perm_entropy.py +5 -5
  121. junifer/markers/complexity/tests/test_range_entropy.py +5 -5
  122. junifer/markers/complexity/tests/test_range_entropy_auc.py +5 -5
  123. junifer/markers/complexity/tests/test_sample_entropy.py +5 -5
  124. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +5 -5
  125. junifer/markers/complexity/weighted_perm_entropy.py +7 -7
  126. junifer/markers/ets_rss.py +12 -11
  127. junifer/markers/falff/__init__.py +2 -3
  128. junifer/markers/falff/__init__.pyi +4 -0
  129. junifer/markers/falff/_afni_falff.py +38 -45
  130. junifer/markers/falff/_junifer_falff.py +16 -19
  131. junifer/markers/falff/falff_base.py +7 -11
  132. junifer/markers/falff/falff_parcels.py +9 -9
  133. junifer/markers/falff/falff_spheres.py +8 -8
  134. junifer/markers/falff/py.typed +0 -0
  135. junifer/markers/falff/tests/test_falff_spheres.py +3 -1
  136. junifer/markers/functional_connectivity/__init__.py +2 -12
  137. junifer/markers/functional_connectivity/__init__.pyi +13 -0
  138. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +9 -8
  139. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +8 -8
  140. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +7 -7
  141. junifer/markers/functional_connectivity/functional_connectivity_base.py +13 -12
  142. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +8 -8
  143. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +7 -7
  144. junifer/markers/functional_connectivity/py.typed +0 -0
  145. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +1 -2
  146. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +1 -2
  147. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +6 -6
  148. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +5 -5
  149. junifer/markers/parcel_aggregation.py +22 -17
  150. junifer/markers/py.typed +0 -0
  151. junifer/markers/reho/__init__.py +2 -3
  152. junifer/markers/reho/__init__.pyi +4 -0
  153. junifer/markers/reho/_afni_reho.py +29 -35
  154. junifer/markers/reho/_junifer_reho.py +13 -14
  155. junifer/markers/reho/py.typed +0 -0
  156. junifer/markers/reho/reho_base.py +7 -11
  157. junifer/markers/reho/reho_parcels.py +10 -10
  158. junifer/markers/reho/reho_spheres.py +9 -9
  159. junifer/markers/sphere_aggregation.py +22 -17
  160. junifer/markers/temporal_snr/__init__.py +2 -3
  161. junifer/markers/temporal_snr/__init__.pyi +4 -0
  162. junifer/markers/temporal_snr/py.typed +0 -0
  163. junifer/markers/temporal_snr/temporal_snr_base.py +11 -10
  164. junifer/markers/temporal_snr/temporal_snr_parcels.py +8 -8
  165. junifer/markers/temporal_snr/temporal_snr_spheres.py +7 -7
  166. junifer/markers/tests/test_ets_rss.py +3 -3
  167. junifer/markers/tests/test_parcel_aggregation.py +24 -24
  168. junifer/markers/tests/test_sphere_aggregation.py +6 -6
  169. junifer/markers/utils.py +3 -3
  170. junifer/onthefly/__init__.py +2 -1
  171. junifer/onthefly/_brainprint.py +138 -0
  172. junifer/onthefly/read_transform.py +5 -8
  173. junifer/pipeline/__init__.py +2 -10
  174. junifer/pipeline/__init__.pyi +13 -0
  175. junifer/{markers/collection.py → pipeline/marker_collection.py} +8 -14
  176. junifer/pipeline/pipeline_component_registry.py +294 -0
  177. junifer/pipeline/pipeline_step_mixin.py +15 -11
  178. junifer/pipeline/py.typed +0 -0
  179. junifer/{markers/tests/test_collection.py → pipeline/tests/test_marker_collection.py} +2 -3
  180. junifer/pipeline/tests/test_pipeline_component_registry.py +200 -0
  181. junifer/pipeline/tests/test_pipeline_step_mixin.py +36 -37
  182. junifer/pipeline/tests/test_update_meta_mixin.py +4 -4
  183. junifer/pipeline/tests/test_workdir_manager.py +43 -0
  184. junifer/pipeline/update_meta_mixin.py +21 -17
  185. junifer/pipeline/utils.py +6 -6
  186. junifer/pipeline/workdir_manager.py +19 -5
  187. junifer/preprocess/__init__.py +2 -10
  188. junifer/preprocess/__init__.pyi +11 -0
  189. junifer/preprocess/base.py +10 -10
  190. junifer/preprocess/confounds/__init__.py +2 -2
  191. junifer/preprocess/confounds/__init__.pyi +3 -0
  192. junifer/preprocess/confounds/fmriprep_confound_remover.py +243 -64
  193. junifer/preprocess/confounds/py.typed +0 -0
  194. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +121 -14
  195. junifer/preprocess/py.typed +0 -0
  196. junifer/preprocess/smoothing/__init__.py +2 -2
  197. junifer/preprocess/smoothing/__init__.pyi +3 -0
  198. junifer/preprocess/smoothing/_afni_smoothing.py +40 -40
  199. junifer/preprocess/smoothing/_fsl_smoothing.py +22 -32
  200. junifer/preprocess/smoothing/_nilearn_smoothing.py +35 -14
  201. junifer/preprocess/smoothing/py.typed +0 -0
  202. junifer/preprocess/smoothing/smoothing.py +11 -13
  203. junifer/preprocess/warping/__init__.py +2 -2
  204. junifer/preprocess/warping/__init__.pyi +3 -0
  205. junifer/preprocess/warping/_ants_warper.py +136 -32
  206. junifer/preprocess/warping/_fsl_warper.py +73 -22
  207. junifer/preprocess/warping/py.typed +0 -0
  208. junifer/preprocess/warping/space_warper.py +39 -11
  209. junifer/preprocess/warping/tests/test_space_warper.py +5 -9
  210. junifer/py.typed +0 -0
  211. junifer/stats.py +5 -5
  212. junifer/storage/__init__.py +2 -10
  213. junifer/storage/__init__.pyi +11 -0
  214. junifer/storage/base.py +47 -13
  215. junifer/storage/hdf5.py +95 -33
  216. junifer/storage/pandas_base.py +12 -11
  217. junifer/storage/py.typed +0 -0
  218. junifer/storage/sqlite.py +11 -11
  219. junifer/storage/tests/test_hdf5.py +86 -4
  220. junifer/storage/tests/test_sqlite.py +2 -2
  221. junifer/storage/tests/test_storage_base.py +5 -2
  222. junifer/storage/tests/test_utils.py +33 -7
  223. junifer/storage/utils.py +95 -9
  224. junifer/testing/__init__.py +2 -3
  225. junifer/testing/__init__.pyi +4 -0
  226. junifer/testing/datagrabbers.py +10 -11
  227. junifer/testing/py.typed +0 -0
  228. junifer/testing/registry.py +4 -7
  229. junifer/testing/tests/test_testing_registry.py +9 -17
  230. junifer/tests/test_stats.py +2 -2
  231. junifer/typing/__init__.py +9 -0
  232. junifer/typing/__init__.pyi +31 -0
  233. junifer/typing/_typing.py +68 -0
  234. junifer/utils/__init__.py +2 -12
  235. junifer/utils/__init__.pyi +18 -0
  236. junifer/utils/_config.py +110 -0
  237. junifer/utils/_yaml.py +16 -0
  238. junifer/utils/helpers.py +6 -6
  239. junifer/utils/logging.py +117 -8
  240. junifer/utils/py.typed +0 -0
  241. junifer/{pipeline → utils}/singleton.py +19 -14
  242. junifer/utils/tests/test_config.py +59 -0
  243. {junifer-0.0.5.dev242.dist-info → junifer-0.0.6.dist-info}/METADATA +43 -38
  244. junifer-0.0.6.dist-info/RECORD +350 -0
  245. {junifer-0.0.5.dev242.dist-info → junifer-0.0.6.dist-info}/WHEEL +1 -1
  246. junifer-0.0.6.dist-info/entry_points.txt +2 -0
  247. junifer/api/parser.py +0 -118
  248. junifer/data/coordinates.py +0 -408
  249. junifer/data/masks.py +0 -670
  250. junifer/data/parcellations.py +0 -1828
  251. junifer/pipeline/registry.py +0 -177
  252. junifer/pipeline/tests/test_registry.py +0 -150
  253. junifer-0.0.5.dev242.dist-info/RECORD +0 -275
  254. junifer-0.0.5.dev242.dist-info/entry_points.txt +0 -2
  255. /junifer/{api → cli}/tests/data/gmd_mean.yaml +0 -0
  256. /junifer/{api → cli}/tests/data/gmd_mean_htcondor.yaml +0 -0
  257. /junifer/{api → cli}/tests/data/partly_cloudy_agg_mean_tian.yml +0 -0
  258. /junifer/data/{VOIs → coordinates/VOIs}/meta/AutobiographicalMemory_VOIs.txt +0 -0
  259. /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAC_VOIs.txt +0 -0
  260. /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAR_VOIs.txt +0 -0
  261. /junifer/data/{VOIs → coordinates/VOIs}/meta/DMNBuckner_VOIs.txt +0 -0
  262. /junifer/data/{VOIs → coordinates/VOIs}/meta/Dosenbach2010_MNI_VOIs.txt +0 -0
  263. /junifer/data/{VOIs → coordinates/VOIs}/meta/Empathy_VOIs.txt +0 -0
  264. /junifer/data/{VOIs → coordinates/VOIs}/meta/Motor_VOIs.txt +0 -0
  265. /junifer/data/{VOIs → coordinates/VOIs}/meta/MultiTask_VOIs.txt +0 -0
  266. /junifer/data/{VOIs → coordinates/VOIs}/meta/PhysioStress_VOIs.txt +0 -0
  267. /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2011_MNI_VOIs.txt +0 -0
  268. /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2013_MNI_VOIs.tsv +0 -0
  269. /junifer/data/{VOIs → coordinates/VOIs}/meta/Rew_VOIs.txt +0 -0
  270. /junifer/data/{VOIs → coordinates/VOIs}/meta/Somatosensory_VOIs.txt +0 -0
  271. /junifer/data/{VOIs → coordinates/VOIs}/meta/ToM_VOIs.txt +0 -0
  272. /junifer/data/{VOIs → coordinates/VOIs}/meta/VigAtt_VOIs.txt +0 -0
  273. /junifer/data/{VOIs → coordinates/VOIs}/meta/WM_VOIs.txt +0 -0
  274. /junifer/data/{VOIs → coordinates/VOIs}/meta/eMDN_VOIs.txt +0 -0
  275. /junifer/data/{VOIs → coordinates/VOIs}/meta/eSAD_VOIs.txt +0 -0
  276. /junifer/data/{VOIs → coordinates/VOIs}/meta/extDMN_VOIs.txt +0 -0
  277. {junifer-0.0.5.dev242.dist-info → junifer-0.0.6.dist-info/licenses}/AUTHORS.rst +0 -0
  278. {junifer-0.0.5.dev242.dist-info → junifer-0.0.6.dist-info/licenses}/LICENSE.md +0 -0
  279. {junifer-0.0.5.dev242.dist-info → junifer-0.0.6.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,6 @@
6
6
 
7
7
  from copy import deepcopy
8
8
  from pathlib import Path
9
- from typing import Dict, Tuple
10
9
 
11
10
  import h5py
12
11
  import numpy as np
@@ -827,6 +826,70 @@ def test_store_timeseries(tmp_path: Path) -> None:
827
826
  assert_array_equal(read_df.values, data)
828
827
 
829
828
 
829
+ def test_store_timeseries2d(tmp_path: Path) -> None:
830
+ """Test 2D timeseries store.
831
+
832
+ Parameters
833
+ ----------
834
+ tmp_path : pathlib.Path
835
+ The path to the test directory.
836
+
837
+ """
838
+ uri = tmp_path / "test_store_timeseries_2d.hdf5"
839
+ storage = HDF5FeatureStorage(uri=uri)
840
+ # Metadata to store
841
+ element = {"subject": "test"}
842
+ meta = {
843
+ "element": element,
844
+ "dependencies": ["numpy"],
845
+ "marker": {"name": "fc"},
846
+ "type": "BOLD",
847
+ }
848
+ # Process the metadata
849
+ meta_md5, meta_to_store, element_to_store = process_meta(meta)
850
+ # Store metadata
851
+ storage.store_metadata(
852
+ meta_md5=meta_md5, element=element_to_store, meta=meta_to_store
853
+ )
854
+
855
+ # Data to store
856
+ data = np.array(
857
+ [[10, 11, 12], [20, 21, 22], [30, 31, 32], [40, 41, 42], [50, 51, 52]]
858
+ )
859
+ data = np.c_[[data + (i * 100) for i in range(4)]] # Generate timeseries
860
+
861
+ col_names = ["roi1", "roi2", "roi3"]
862
+ row_names = ["ev1", "ev2", "ev3", "ev4", "ev5"]
863
+
864
+ # Store 2D timeseries
865
+ storage.store_timeseries_2d(
866
+ meta_md5=meta_md5,
867
+ element=element_to_store,
868
+ data=data,
869
+ col_names=col_names,
870
+ row_names=row_names,
871
+ )
872
+
873
+ # Read into dataframe
874
+ read_data = storage.read(feature_md5=meta_md5)
875
+ # Check if data are equal
876
+ assert_array_equal(read_data["data"][0], data)
877
+ assert read_data["column_headers"] == col_names
878
+ assert read_data["row_headers"], row_names
879
+
880
+ read_df = storage.read_df(feature_md5=meta_md5)
881
+ flatted_names = [f"{row}~{col}" for row in row_names for col in col_names]
882
+
883
+ expected_flat_data = np.array(
884
+ [10, 11, 12, 20, 21, 22, 30, 31, 32, 40, 41, 42, 50, 51, 52]
885
+ )
886
+ expected_flat_data = np.c_[
887
+ [expected_flat_data + (i * 100) for i in range(4)]
888
+ ] # Generate timeseries
889
+ assert_array_equal(read_df.values, expected_flat_data)
890
+ assert read_df.columns.to_list() == flatted_names
891
+
892
+
830
893
  def test_store_scalar_table(tmp_path: Path) -> None:
831
894
  """Test scalar table store.
832
895
 
@@ -858,7 +921,7 @@ def test_store_scalar_table(tmp_path: Path) -> None:
858
921
  col_names = ["roi1", "roi2"]
859
922
  row_names = ["ev1", "ev2", "ev3"]
860
923
 
861
- # Store timeseries
924
+ # Store scalar table
862
925
  storage.store_scalar_table(
863
926
  meta_md5=meta_md5,
864
927
  element=element_to_store,
@@ -874,7 +937,7 @@ def test_store_scalar_table(tmp_path: Path) -> None:
874
937
  assert_array_equal(read_df.values, data)
875
938
 
876
939
 
877
- def _create_data_to_store(n_elements: int, kind: str) -> Tuple[str, Dict]:
940
+ def _create_data_to_store(n_elements: int, kind: str) -> tuple[str, dict]:
878
941
  """Create data to store.
879
942
 
880
943
  Parameters
@@ -911,6 +974,12 @@ def _create_data_to_store(n_elements: int, kind: str) -> Tuple[str, Dict]:
911
974
  "data": np.arange(20).reshape(2, 10),
912
975
  "col_names": [f"col-{i}" for i in range(10)],
913
976
  }
977
+ elif kind in "timeseries_2d":
978
+ data_to_store = {
979
+ "data": np.arange(120).reshape(6, 5, 4),
980
+ "row_names": [f"row-{i}" for i in range(5)],
981
+ "col_names": [f"col-{i}" for i in range(4)],
982
+ }
914
983
  elif kind in "scalar_table":
915
984
  data_to_store = {
916
985
  "data": np.arange(50).reshape(5, 10),
@@ -962,6 +1031,7 @@ def _create_data_to_store(n_elements: int, kind: str) -> Tuple[str, Dict]:
962
1031
  (10, 5, "matrix"),
963
1032
  (10, 5, "timeseries"),
964
1033
  (10, 5, "scalar_table"),
1034
+ (10, 5, "timeseries_2d"),
965
1035
  ],
966
1036
  )
967
1037
  def test_multi_output_store_and_collect(
@@ -983,7 +1053,9 @@ def test_multi_output_store_and_collect(
983
1053
  """
984
1054
  uri = tmp_path / "test_multi_output_store_and_collect.hdf5"
985
1055
  storage = HDF5FeatureStorage(
986
- uri=uri, single_output=False, chunk_size=chunk_size
1056
+ uri=uri,
1057
+ single_output=False,
1058
+ chunk_size=chunk_size,
987
1059
  )
988
1060
 
989
1061
  meta_md5, all_data = _create_data_to_store(n_elements, kind)
@@ -1014,6 +1086,12 @@ def test_multi_output_store_and_collect(
1014
1086
  element=t_data["element"],
1015
1087
  **t_data["data"],
1016
1088
  )
1089
+ elif kind == "timeseries_2d":
1090
+ storage.store_timeseries_2d(
1091
+ meta_md5=meta_md5,
1092
+ element=t_data["element"],
1093
+ **t_data["data"],
1094
+ )
1017
1095
  elif kind == "scalar_table":
1018
1096
  storage.store_scalar_table(
1019
1097
  meta_md5=meta_md5,
@@ -1053,6 +1131,10 @@ def test_multi_output_store_and_collect(
1053
1131
  data_size = np.sum([x["data"]["data"].shape[0] for x in all_data])
1054
1132
  assert len(all_df) == data_size
1055
1133
  idx_names = [x for x in all_df.index.names if x != "timepoint"]
1134
+ elif kind == "timeseries_2d":
1135
+ data_size = np.sum([x["data"]["data"].shape[0] for x in all_data])
1136
+ assert len(all_df) == data_size
1137
+ idx_names = [x for x in all_df.index.names if x != "timepoint"]
1056
1138
  elif kind == "scalar_table":
1057
1139
  data_size = np.sum([x["data"]["data"].shape[0] for x in all_data])
1058
1140
  assert len(all_df) == data_size
@@ -5,7 +5,7 @@
5
5
  # License: AGPL
6
6
 
7
7
  from pathlib import Path
8
- from typing import List, Union
8
+ from typing import Union
9
9
 
10
10
  import numpy as np
11
11
  import pandas as pd
@@ -56,7 +56,7 @@ df_ignore = pd.DataFrame(
56
56
 
57
57
 
58
58
  def _read_sql(
59
- table_name: str, uri: str, index_col: Union[str, List[str]]
59
+ table_name: str, uri: str, index_col: Union[str, list[str]]
60
60
  ) -> pd.DataFrame:
61
61
  """Read database table into a pandas DataFrame.
62
62
 
@@ -25,7 +25,7 @@ def test_BaseFeatureStorage() -> None:
25
25
  """Implement concrete class."""
26
26
 
27
27
  def __init__(self, uri, single_output=True):
28
- storage_types = ["matrix", "vector", "timeseries"]
28
+ storage_types = ["matrix", "vector", "timeseries", "timeseries_2d"]
29
29
  super().__init__(
30
30
  uri=uri,
31
31
  storage_types=storage_types,
@@ -33,7 +33,7 @@ def test_BaseFeatureStorage() -> None:
33
33
  )
34
34
 
35
35
  def get_valid_inputs(self):
36
- return ["matrix", "vector", "timeseries"]
36
+ return ["matrix", "vector", "timeseries", "timeseries_2d"]
37
37
 
38
38
  def list_features(self):
39
39
  super().list_features()
@@ -97,6 +97,9 @@ def test_BaseFeatureStorage() -> None:
97
97
  with pytest.raises(NotImplementedError):
98
98
  st.store(kind="timeseries", meta=meta)
99
99
 
100
+ with pytest.raises(NotImplementedError):
101
+ st.store(kind="timeseries_2d", meta=meta)
102
+
100
103
  with pytest.raises(NotImplementedError):
101
104
  st.store(kind="vector", meta=meta)
102
105
 
@@ -4,7 +4,8 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
- from typing import Dict, Iterable, List, Tuple, Union
7
+ from collections.abc import Iterable
8
+ from typing import Union
8
9
 
9
10
  import numpy as np
10
11
  import pytest
@@ -16,6 +17,7 @@ from junifer.storage.utils import (
16
17
  matrix_to_vector,
17
18
  process_meta,
18
19
  store_matrix_checks,
20
+ timeseries2d_to_vector,
19
21
  )
20
22
 
21
23
 
@@ -198,7 +200,7 @@ def test_process_meta_invalid_metadata_key() -> None:
198
200
  ),
199
201
  ],
200
202
  )
201
- def test_process_meta_element(meta: Dict, elements: List[str]) -> None:
203
+ def test_process_meta_element(meta: dict, elements: list[str]) -> None:
202
204
  """Test metadata element after processing.
203
205
 
204
206
  Parameters
@@ -215,7 +217,7 @@ def test_process_meta_element(meta: Dict, elements: List[str]) -> None:
215
217
  assert "A" in processed_meta
216
218
  assert "B" in processed_meta
217
219
  assert "element" not in processed_meta
218
- assert isinstance(processed_meta["dependencies"], Dict)
220
+ assert isinstance(processed_meta["dependencies"], dict)
219
221
  assert all(
220
222
  x in processed_meta["dependencies"] for x in meta["dependencies"]
221
223
  )
@@ -232,7 +234,7 @@ def test_process_meta_element(meta: Dict, elements: List[str]) -> None:
232
234
  ({"subject": 1, "session": 2}, "element_1_2_"),
233
235
  ],
234
236
  )
235
- def test_element_to_prefix(element: Dict, prefix: str) -> None:
237
+ def test_element_to_prefix(element: dict, prefix: str) -> None:
236
238
  """Test converting element to prefix (for file naming).
237
239
 
238
240
  Parameters
@@ -320,7 +322,7 @@ def test_element_to_prefix_invalid_type() -> None:
320
322
  ],
321
323
  )
322
324
  def test_store_matrix_checks(
323
- params: Dict[str, Union[str, bool, Tuple[int, int], int]], err_msg: str
325
+ params: dict[str, Union[str, bool, tuple[int, int], int]], err_msg: str
324
326
  ) -> None:
325
327
  """Test matrix storing parameter checks.
326
328
 
@@ -401,9 +403,9 @@ def test_store_matrix_checks(
401
403
  ],
402
404
  )
403
405
  def test_matrix_to_vector(
404
- params: Dict[str, Union[np.ndarray, Iterable[str], str, bool]],
406
+ params: dict[str, Union[np.ndarray, Iterable[str], str, bool]],
405
407
  expected_data: np.ndarray,
406
- expected_columns: List[str],
408
+ expected_columns: list[str],
407
409
  ) -> None:
408
410
  """Test matrix to vector.
409
411
 
@@ -420,3 +422,27 @@ def test_matrix_to_vector(
420
422
  data, columns = matrix_to_vector(**params) # type: ignore
421
423
  assert_array_equal(data, expected_data)
422
424
  assert columns == expected_columns
425
+
426
+
427
+ def test_timeseries2d_to_vector() -> None:
428
+ """Test timeseries2d to vector."""
429
+ data = np.array(
430
+ [[10, 11, 12], [20, 21, 22], [30, 31, 32], [40, 41, 42], [50, 51, 52]]
431
+ )
432
+ data = np.c_[[data + (i * 100) for i in range(4)]] # Generate timeseries
433
+ col_names = ["c0", "c1", "c2"]
434
+ row_names = ["r0", "r1", "r2", "r3", "r4"]
435
+ flat_data, columns = timeseries2d_to_vector(
436
+ data=data,
437
+ col_names=col_names,
438
+ row_names=row_names,
439
+ )
440
+
441
+ expected_flat_data = np.array(
442
+ [10, 11, 12, 20, 21, 22, 30, 31, 32, 40, 41, 42, 50, 51, 52]
443
+ )
444
+ expected_flat_data = np.c_[
445
+ [expected_flat_data + (i * 100) for i in range(4)]
446
+ ] # Generate timeseries
447
+ assert_array_equal(flat_data, expected_flat_data)
448
+ assert columns == [f"{r}~{c}" for r in row_names for c in col_names]
junifer/storage/utils.py CHANGED
@@ -6,8 +6,8 @@
6
6
 
7
7
  import hashlib
8
8
  import json
9
+ from collections.abc import Iterable
9
10
  from importlib.metadata import PackageNotFoundError, version
10
- from typing import Dict, Iterable, List, Tuple
11
11
 
12
12
  import numpy as np
13
13
 
@@ -15,11 +15,11 @@ from ..utils.logging import logger, raise_error
15
15
 
16
16
 
17
17
  __all__ = [
18
+ "element_to_prefix",
18
19
  "get_dependency_version",
20
+ "matrix_to_vector",
19
21
  "process_meta",
20
- "element_to_prefix",
21
22
  "store_matrix_checks",
22
- "matrix_to_vector",
23
23
  ]
24
24
 
25
25
 
@@ -50,7 +50,7 @@ def get_dependency_version(dependency: str) -> str:
50
50
  return dep_version
51
51
 
52
52
 
53
- def _meta_hash(meta: Dict) -> str:
53
+ def _meta_hash(meta: dict) -> str:
54
54
  """Compute the MD5 hash of the metadata.
55
55
 
56
56
  Parameters
@@ -78,7 +78,7 @@ def _meta_hash(meta: Dict) -> str:
78
78
  return meta_md5
79
79
 
80
80
 
81
- def process_meta(meta: Dict) -> Tuple[str, Dict, Dict]:
81
+ def process_meta(meta: dict) -> tuple[str, dict, dict]:
82
82
  """Process the metadata for storage.
83
83
 
84
84
  It removes the key "element" and adds the "_element_keys" with the keys
@@ -109,7 +109,7 @@ def process_meta(meta: Dict) -> Tuple[str, Dict, Dict]:
109
109
  # Copy the metadata
110
110
  t_meta = meta.copy()
111
111
  # Remove key "element"
112
- element: Dict = t_meta.pop("element", None)
112
+ element: dict = t_meta.pop("element", None)
113
113
  if element is None:
114
114
  raise_error(msg="`meta` must contain the key 'element'")
115
115
  if "marker" not in t_meta:
@@ -128,7 +128,7 @@ def process_meta(meta: Dict) -> Tuple[str, Dict, Dict]:
128
128
  return md5_hash, t_meta, element
129
129
 
130
130
 
131
- def element_to_prefix(element: Dict) -> str:
131
+ def element_to_prefix(element: dict) -> str:
132
132
  """Convert the element metadata to prefix.
133
133
 
134
134
  Parameters
@@ -156,7 +156,7 @@ def element_to_prefix(element: Dict) -> str:
156
156
  def store_matrix_checks(
157
157
  matrix_kind: str,
158
158
  diagonal: bool,
159
- data_shape: Tuple[int, int],
159
+ data_shape: tuple[int, int],
160
160
  row_names_len: int,
161
161
  col_names_len: int,
162
162
  ) -> None:
@@ -181,6 +181,15 @@ def store_matrix_checks(
181
181
  col_names_len : int
182
182
  The length of column labels.
183
183
 
184
+ Raises
185
+ ------
186
+ ValueError
187
+ If the matrix kind is invalid
188
+ If the diagonal is False and the matrix kind is "full"
189
+ If the matrix kind is "triu" or "tril" and the matrix is not square
190
+ If the number of row names does not match the number of rows
191
+ If the number of column names does not match the number of columns
192
+
184
193
  """
185
194
  # Matrix kind validation
186
195
  if matrix_kind not in ("triu", "tril", "full"):
@@ -212,13 +221,58 @@ def store_matrix_checks(
212
221
  )
213
222
 
214
223
 
224
+ def store_timeseries_2d_checks(
225
+ data_shape: tuple[int, int, int],
226
+ row_names_len: int,
227
+ col_names_len: int,
228
+ ) -> None:
229
+ """Run parameter checks for store_timeseries_2d() methods.
230
+
231
+ Parameters
232
+ ----------
233
+ data_shape : tuple of int and int
234
+ The shape of the matrix data to store.
235
+ row_names_len : int
236
+ The length of row labels.
237
+ col_names_len : int
238
+ The length of column labels.
239
+
240
+ Raises
241
+ ------
242
+ ValueError
243
+ If the data is not a 3D array (timepoints, rows, columns)
244
+ If the number of row names does not match the number of rows
245
+ If the number of column names does not match the number of columns
246
+
247
+ """
248
+ # data validation
249
+ if len(data_shape) != 3:
250
+ raise_error(
251
+ msg="Data must be a 3D array",
252
+ klass=ValueError,
253
+ )
254
+
255
+ # Row label validation
256
+ if row_names_len != data_shape[1]: # type: ignore
257
+ raise_error(
258
+ msg="Number of row names does not match number of rows",
259
+ klass=ValueError,
260
+ )
261
+ # Column label validation
262
+ if col_names_len != data_shape[2]: # type: ignore
263
+ raise_error(
264
+ msg="Number of column names does not match number of columns",
265
+ klass=ValueError,
266
+ )
267
+
268
+
215
269
  def matrix_to_vector(
216
270
  data: np.ndarray,
217
271
  col_names: Iterable[str],
218
272
  row_names: Iterable[str],
219
273
  matrix_kind: str,
220
274
  diagonal: bool,
221
- ) -> Tuple[np.ndarray, List[str]]:
275
+ ) -> tuple[np.ndarray, list[str]]:
222
276
  """Convert matrix to vector based on parameters.
223
277
 
224
278
  Parameters
@@ -268,3 +322,35 @@ def matrix_to_vector(
268
322
  ]
269
323
 
270
324
  return flat_data, columns
325
+
326
+
327
+ def timeseries2d_to_vector(
328
+ data: np.ndarray,
329
+ col_names: Iterable[str],
330
+ row_names: Iterable[str],
331
+ ) -> tuple[np.ndarray, list[str]]:
332
+ """Convert matrix to vector based on parameters.
333
+
334
+ Parameters
335
+ ----------
336
+ data : 2D / 3D numpy.ndarray
337
+ The matrix / tensor data to store / read.
338
+ col_names : list or tuple of str
339
+ The column labels.
340
+ row_names : list or tuple of str
341
+ The row labels.
342
+
343
+ Returns
344
+ -------
345
+ 2D numpy.ndarray
346
+ The vector / matrix data.
347
+ list of str
348
+ The column labels.
349
+
350
+ """
351
+ # Reshape data to 2D
352
+ flat_data = data.reshape(data.shape[0], -1)
353
+ # Generate flat 1D row X column names
354
+ columns = [f"{r}~{c}" for r in row_names for c in col_names]
355
+
356
+ return flat_data, columns
@@ -4,8 +4,7 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
- from . import datagrabbers
8
- from .utils import get_testing_data
7
+ import lazy_loader as lazy
9
8
 
10
9
 
11
- __all__ = ["datagrabbers", "get_testing_data"]
10
+ __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
@@ -0,0 +1,4 @@
1
+ __all__ = ["datagrabbers", "registry", "get_testing_data"]
2
+
3
+ from . import datagrabbers, registry
4
+ from .utils import get_testing_data
@@ -6,7 +6,6 @@
6
6
 
7
7
  import tempfile
8
8
  from pathlib import Path
9
- from typing import Dict, List
10
9
 
11
10
  import nibabel as nib
12
11
  from nilearn import datasets, image
@@ -16,8 +15,8 @@ from ..datagrabber.base import BaseDataGrabber
16
15
 
17
16
  __all__ = [
18
17
  "OasisVBMTestingDataGrabber",
19
- "SPMAuditoryTestingDataGrabber",
20
18
  "PartlyCloudyTestingDataGrabber",
19
+ "SPMAuditoryTestingDataGrabber",
21
20
  ]
22
21
 
23
22
 
@@ -35,7 +34,7 @@ class OasisVBMTestingDataGrabber(BaseDataGrabber):
35
34
  types = ["VBM_GM"]
36
35
  super().__init__(types=types, datadir=datadir)
37
36
 
38
- def get_element_keys(self) -> List[str]:
37
+ def get_element_keys(self) -> list[str]:
39
38
  """Get element keys.
40
39
 
41
40
  Returns
@@ -46,7 +45,7 @@ class OasisVBMTestingDataGrabber(BaseDataGrabber):
46
45
  """
47
46
  return ["subject"]
48
47
 
49
- def get_item(self, subject: str) -> Dict[str, Dict]:
48
+ def get_item(self, subject: str) -> dict[str, dict]:
50
49
  """Implement indexing support.
51
50
 
52
51
  Parameters
@@ -80,7 +79,7 @@ class OasisVBMTestingDataGrabber(BaseDataGrabber):
80
79
  self._dataset = datasets.fetch_oasis_vbm(n_subjects=10)
81
80
  return self
82
81
 
83
- def get_elements(self) -> List[str]:
82
+ def get_elements(self) -> list[str]:
84
83
  """Get elements.
85
84
 
86
85
  Returns
@@ -106,7 +105,7 @@ class SPMAuditoryTestingDataGrabber(BaseDataGrabber):
106
105
  types = ["BOLD", "T1w"] # TODO: Check that they are T1w
107
106
  super().__init__(types=types, datadir=datadir)
108
107
 
109
- def get_element_keys(self) -> List[str]:
108
+ def get_element_keys(self) -> list[str]:
110
109
  """Get element keys.
111
110
 
112
111
  Returns
@@ -117,7 +116,7 @@ class SPMAuditoryTestingDataGrabber(BaseDataGrabber):
117
116
  """
118
117
  return ["subject"]
119
118
 
120
- def get_elements(self) -> List[str]:
119
+ def get_elements(self) -> list[str]:
121
120
  """Get elements.
122
121
 
123
122
  Returns
@@ -128,7 +127,7 @@ class SPMAuditoryTestingDataGrabber(BaseDataGrabber):
128
127
  """
129
128
  return [f"sub{x:03d}" for x in list(range(1, 11))]
130
129
 
131
- def get_item(self, subject: str) -> Dict[str, Dict]:
130
+ def get_item(self, subject: str) -> dict[str, dict]:
132
131
  """Implement indexing support.
133
132
 
134
133
  Parameters
@@ -208,7 +207,7 @@ class PartlyCloudyTestingDataGrabber(BaseDataGrabber):
208
207
  )
209
208
  return self
210
209
 
211
- def get_element_keys(self) -> List[str]:
210
+ def get_element_keys(self) -> list[str]:
212
211
  """Get element keys.
213
212
 
214
213
  Returns
@@ -219,7 +218,7 @@ class PartlyCloudyTestingDataGrabber(BaseDataGrabber):
219
218
  """
220
219
  return ["subject"]
221
220
 
222
- def get_elements(self) -> List[str]:
221
+ def get_elements(self) -> list[str]:
223
222
  """Get elements.
224
223
 
225
224
  Returns
@@ -230,7 +229,7 @@ class PartlyCloudyTestingDataGrabber(BaseDataGrabber):
230
229
  """
231
230
  return [f"sub-{x:02d}" for x in list(range(1, 11))]
232
231
 
233
- def get_item(self, subject: str) -> Dict[str, Dict]:
232
+ def get_item(self, subject: str) -> dict[str, dict]:
234
233
  """Implement indexing support.
235
234
 
236
235
  Parameters
File without changes
@@ -4,7 +4,7 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
- from ..pipeline.registry import register
7
+ from ..pipeline import PipelineComponentRegistry
8
8
  from .datagrabbers import (
9
9
  OasisVBMTestingDataGrabber,
10
10
  PartlyCloudyTestingDataGrabber,
@@ -13,20 +13,17 @@ from .datagrabbers import (
13
13
 
14
14
 
15
15
  # Register testing DataGrabbers
16
- register(
16
+ PipelineComponentRegistry().register(
17
17
  step="datagrabber",
18
- name="OasisVBMTestingDataGrabber",
19
18
  klass=OasisVBMTestingDataGrabber,
20
19
  )
21
20
 
22
- register(
21
+ PipelineComponentRegistry().register(
23
22
  step="datagrabber",
24
- name="SPMAuditoryTestingDataGrabber",
25
23
  klass=SPMAuditoryTestingDataGrabber,
26
24
  )
27
25
 
28
- register(
26
+ PipelineComponentRegistry().register(
29
27
  step="datagrabber",
30
- name="PartlyCloudyTestingDataGrabber",
31
28
  klass=PartlyCloudyTestingDataGrabber,
32
29
  )
@@ -1,24 +1,16 @@
1
1
  """Provide tests for testing registry."""
2
2
 
3
- import importlib
3
+ # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
+ # Synchon Mandal <s.mandal@fz-juelich.de>
5
+ # License: AGPL
4
6
 
5
- from junifer.pipeline.registry import get_step_names
7
+ from junifer.pipeline import PipelineComponentRegistry
6
8
 
7
9
 
8
10
  def test_testing_registry() -> None:
9
11
  """Test testing registry."""
10
- import junifer
11
-
12
- importlib.reload(junifer.pipeline.registry)
13
- importlib.reload(junifer)
14
-
15
- assert "OasisVBMTestingDataGrabber" not in get_step_names("datagrabber")
16
- assert "SPMAuditoryTestingDataGrabber" not in get_step_names("datagrabber")
17
- assert "PartlyCloudyTestingDataGrabber" not in get_step_names(
18
- "datagrabber"
19
- )
20
- importlib.reload(junifer.testing.registry) # type: ignore
21
-
22
- assert "OasisVBMTestingDataGrabber" in get_step_names("datagrabber")
23
- assert "SPMAuditoryTestingDataGrabber" in get_step_names("datagrabber")
24
- assert "PartlyCloudyTestingDataGrabber" in get_step_names("datagrabber")
12
+ assert {
13
+ "OasisVBMTestingDataGrabber",
14
+ "SPMAuditoryTestingDataGrabber",
15
+ "PartlyCloudyTestingDataGrabber",
16
+ }.issubset(set(PipelineComponentRegistry().step_components("datagrabber")))
@@ -3,7 +3,7 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
- from typing import Dict, Optional
6
+ from typing import Optional
7
7
 
8
8
  import numpy as np
9
9
  import pytest
@@ -25,7 +25,7 @@ from junifer.stats import count, get_aggfunc_by_name, select, winsorized_mean
25
25
  ("mode", {"keepdims": True}),
26
26
  ],
27
27
  )
28
- def test_get_aggfunc_by_name(name: str, params: Optional[Dict]) -> None:
28
+ def test_get_aggfunc_by_name(name: str, params: Optional[dict]) -> None:
29
29
  """Test aggregation function retrieval by name.
30
30
 
31
31
  Parameters
@@ -0,0 +1,9 @@
1
+ """Type hints for internal and external use."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ import lazy_loader as lazy
7
+
8
+
9
+ __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)