junifer 0.0.7.dev332__py3-none-any.whl → 0.0.8__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 (293) hide show
  1. junifer/_version.py +9 -19
  2. junifer/api/__init__.pyi +17 -2
  3. junifer/api/functions.py +222 -39
  4. junifer/api/queue_context/__init__.pyi +11 -3
  5. junifer/api/queue_context/gnu_parallel_local_adapter.py +81 -120
  6. junifer/api/queue_context/htcondor_adapter.py +118 -181
  7. junifer/api/queue_context/queue_context_adapter.py +51 -7
  8. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +10 -35
  9. junifer/api/queue_context/tests/test_htcondor_adapter.py +13 -50
  10. junifer/api/res/fsl/run_fsl_docker.sh +4 -0
  11. junifer/api/tests/test_functions.py +863 -36
  12. junifer/cli/__init__.py +1 -7
  13. junifer/cli/cli.py +41 -22
  14. junifer/cli/parser.py +23 -147
  15. junifer/cli/tests/data/gmd_mean.yaml +6 -6
  16. junifer/cli/tests/data/gmd_mean_htcondor.yaml +6 -5
  17. junifer/cli/tests/data/partly_cloudy_agg_mean_tian.yml +1 -0
  18. junifer/cli/tests/test_cli.py +120 -4
  19. junifer/cli/tests/test_cli_utils.py +23 -6
  20. junifer/cli/utils.py +14 -9
  21. junifer/configs/__init__.py +5 -0
  22. junifer/configs/__init__.pyi +0 -0
  23. junifer/configs/juseless/datagrabbers/__init__.pyi +4 -2
  24. junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +18 -24
  25. junifer/configs/juseless/datagrabbers/camcan_vbm.py +20 -27
  26. junifer/configs/juseless/datagrabbers/ixi_vbm.py +44 -52
  27. junifer/configs/juseless/datagrabbers/tests/test_ixi_vbm.py +0 -7
  28. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -19
  29. junifer/configs/juseless/datagrabbers/ucla.py +111 -104
  30. junifer/configs/juseless/datagrabbers/ukb_vbm.py +19 -25
  31. junifer/configs/py.typed +0 -0
  32. junifer/conftest.py +63 -24
  33. junifer/data/_dispatch.py +9 -19
  34. junifer/data/coordinates/_ants_coordinates_warper.py +4 -1
  35. junifer/data/coordinates/_coordinates.py +51 -6
  36. junifer/data/coordinates/_fsl_coordinates_warper.py +4 -1
  37. junifer/data/coordinates/tests/test_coordinates.py +15 -3
  38. junifer/data/coordinates/tests/test_coordinates_names.py +37 -0
  39. junifer/data/maps/_ants_maps_warper.py +18 -12
  40. junifer/data/maps/_fsl_maps_warper.py +7 -3
  41. junifer/data/maps/_maps.py +12 -8
  42. junifer/data/maps/tests/test_maps.py +13 -21
  43. junifer/data/masks/_ants_mask_warper.py +18 -12
  44. junifer/data/masks/_fsl_mask_warper.py +7 -3
  45. junifer/data/masks/_masks.py +15 -12
  46. junifer/data/masks/tests/test_masks.py +20 -17
  47. junifer/data/parcellations/_ants_parcellation_warper.py +18 -12
  48. junifer/data/parcellations/_fsl_parcellation_warper.py +7 -5
  49. junifer/data/parcellations/_parcellations.py +125 -91
  50. junifer/data/parcellations/tests/test_parcellations.py +122 -1
  51. junifer/data/template_spaces.py +117 -50
  52. junifer/data/tests/test_template_spaces.py +10 -7
  53. junifer/data/utils.py +10 -8
  54. junifer/datagrabber/__init__.pyi +38 -5
  55. junifer/datagrabber/aomic/__init__.pyi +8 -1
  56. junifer/datagrabber/aomic/_types.py +22 -0
  57. junifer/datagrabber/aomic/id1000.py +193 -183
  58. junifer/datagrabber/aomic/piop1.py +215 -259
  59. junifer/datagrabber/aomic/piop2.py +211 -244
  60. junifer/datagrabber/aomic/tests/test_id1000.py +70 -53
  61. junifer/datagrabber/aomic/tests/test_piop1.py +98 -102
  62. junifer/datagrabber/aomic/tests/test_piop2.py +88 -85
  63. junifer/datagrabber/base.py +253 -79
  64. junifer/datagrabber/datalad_base.py +127 -76
  65. junifer/datagrabber/dmcc13_benchmark.py +204 -289
  66. junifer/datagrabber/hcp1200/__init__.pyi +7 -2
  67. junifer/datagrabber/hcp1200/datalad_hcp1200.py +61 -40
  68. junifer/datagrabber/hcp1200/hcp1200.py +151 -164
  69. junifer/datagrabber/hcp1200/tests/test_hcp1200.py +116 -128
  70. junifer/datagrabber/multiple.py +183 -63
  71. junifer/datagrabber/pattern.py +358 -227
  72. junifer/datagrabber/pattern_datalad.py +84 -124
  73. junifer/datagrabber/pattern_validation_mixin.py +22 -50
  74. junifer/datagrabber/tests/test_base.py +17 -4
  75. junifer/datagrabber/tests/test_datalad_base.py +20 -27
  76. junifer/datagrabber/tests/test_dmcc13_benchmark.py +110 -122
  77. junifer/datagrabber/tests/test_multiple.py +293 -31
  78. junifer/datagrabber/tests/test_pattern.py +577 -18
  79. junifer/datagrabber/tests/test_pattern_datalad.py +177 -107
  80. junifer/datagrabber/tests/test_pattern_validation_mixin.py +9 -51
  81. junifer/datareader/default.py +8 -5
  82. junifer/datareader/tests/test_default_reader.py +1 -1
  83. junifer/external/BrainPrint/.codecov.yml +2 -0
  84. junifer/external/BrainPrint/.codespellignore +2 -0
  85. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/bug-report.md +45 -0
  86. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/documentation.md +12 -0
  87. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/feature-request.md +24 -0
  88. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/questions-help-support.md +28 -0
  89. junifer/external/BrainPrint/.github/dependabot.yml +6 -0
  90. junifer/external/BrainPrint/.github/workflows/build.yml +53 -0
  91. junifer/external/BrainPrint/.github/workflows/code-style.yml +44 -0
  92. junifer/external/BrainPrint/.github/workflows/doc.yml +65 -0
  93. junifer/external/BrainPrint/.github/workflows/publish.yml +31 -0
  94. junifer/external/BrainPrint/.github/workflows/pytest.yml +54 -0
  95. junifer/external/BrainPrint/.gitignore +123 -0
  96. junifer/external/BrainPrint/LICENSE +21 -0
  97. junifer/external/BrainPrint/README.md +108 -0
  98. junifer/external/BrainPrint/brainprint/cli/__init__.py +11 -0
  99. junifer/external/BrainPrint/brainprint/cli/help_text.py +169 -0
  100. junifer/external/BrainPrint/brainprint/cli/parser.py +150 -0
  101. junifer/external/BrainPrint/brainprint/cli/utils.py +14 -0
  102. junifer/external/BrainPrint/brainprint/commands/__init__.py +0 -0
  103. junifer/external/BrainPrint/brainprint/commands/sys_info.py +18 -0
  104. junifer/external/BrainPrint/brainprint/utils/tests/__init__.py +0 -0
  105. junifer/external/BrainPrint/brainprint/utils/tests/test_config.py +33 -0
  106. junifer/external/BrainPrint/doc/Makefile +20 -0
  107. junifer/external/BrainPrint/doc/_static/.gitkeep +0 -0
  108. junifer/external/BrainPrint/doc/_templates/autosummary/class.rst +10 -0
  109. junifer/external/BrainPrint/doc/_templates/autosummary/function.rst +8 -0
  110. junifer/external/BrainPrint/doc/_templates/autosummary/module.rst +6 -0
  111. junifer/external/BrainPrint/doc/api/index.rst +11 -0
  112. junifer/external/BrainPrint/doc/conf.py +272 -0
  113. junifer/external/BrainPrint/doc/index.rst +140 -0
  114. junifer/external/BrainPrint/doc/links.inc +36 -0
  115. junifer/external/BrainPrint/doc/make.bat +35 -0
  116. junifer/external/BrainPrint/doc/references.bib +23 -0
  117. junifer/external/BrainPrint/pyproject.toml +170 -0
  118. junifer/external/BrainPrint/setup.py +3 -0
  119. junifer/external/__init__.py +5 -0
  120. junifer/external/__init__.pyi +0 -0
  121. junifer/external/nilearn/junifer_connectivity_measure.py +95 -39
  122. junifer/external/nilearn/junifer_nifti_spheres_masker.py +226 -67
  123. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +39 -27
  124. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +231 -21
  125. junifer/external/py.typed +0 -0
  126. junifer/markers/__init__.pyi +6 -3
  127. junifer/markers/base.py +126 -76
  128. junifer/markers/brainprint.py +30 -39
  129. junifer/markers/complexity/__init__.py +20 -3
  130. junifer/markers/complexity/complexity_base.py +40 -41
  131. junifer/markers/complexity/hurst_exponent.py +22 -35
  132. junifer/markers/complexity/multiscale_entropy_auc.py +53 -41
  133. junifer/markers/complexity/perm_entropy.py +22 -36
  134. junifer/markers/complexity/range_entropy.py +22 -35
  135. junifer/markers/complexity/range_entropy_auc.py +27 -36
  136. junifer/markers/complexity/sample_entropy.py +22 -36
  137. junifer/markers/complexity/tests/test_complexity_registry.py +48 -0
  138. junifer/markers/complexity/tests/test_hurst_exponent.py +47 -36
  139. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +50 -36
  140. junifer/markers/complexity/tests/test_perm_entropy.py +48 -36
  141. junifer/markers/complexity/tests/test_range_entropy.py +47 -36
  142. junifer/markers/complexity/tests/test_range_entropy_auc.py +47 -36
  143. junifer/markers/complexity/tests/test_sample_entropy.py +48 -36
  144. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +50 -36
  145. junifer/markers/complexity/weighted_perm_entropy.py +22 -36
  146. junifer/markers/ets_rss.py +35 -31
  147. junifer/markers/falff/__init__.pyi +7 -1
  148. junifer/markers/falff/_afni_falff.py +22 -9
  149. junifer/markers/falff/_junifer_falff.py +47 -31
  150. junifer/markers/falff/falff_base.py +80 -60
  151. junifer/markers/falff/falff_maps.py +17 -41
  152. junifer/markers/falff/falff_parcels.py +26 -51
  153. junifer/markers/falff/falff_spheres.py +34 -61
  154. junifer/markers/falff/tests/test_falff_maps.py +20 -23
  155. junifer/markers/falff/tests/test_falff_mask.py +99 -0
  156. junifer/markers/falff/tests/test_falff_parcels.py +14 -10
  157. junifer/markers/falff/tests/test_falff_spheres.py +14 -10
  158. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +32 -34
  159. junifer/markers/functional_connectivity/edge_functional_connectivity_maps.py +12 -25
  160. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +19 -34
  161. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +24 -44
  162. junifer/markers/functional_connectivity/functional_connectivity_base.py +50 -40
  163. junifer/markers/functional_connectivity/functional_connectivity_maps.py +12 -25
  164. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +19 -34
  165. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +24 -45
  166. junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +21 -22
  167. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_maps.py +8 -7
  168. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +8 -4
  169. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +8 -4
  170. junifer/markers/functional_connectivity/tests/test_functional_connectivity_maps.py +8 -7
  171. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +9 -5
  172. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +8 -12
  173. junifer/markers/maps_aggregation.py +77 -55
  174. junifer/markers/parcel_aggregation.py +91 -70
  175. junifer/markers/reho/__init__.pyi +7 -1
  176. junifer/markers/reho/_afni_reho.py +23 -14
  177. junifer/markers/reho/_junifer_reho.py +112 -123
  178. junifer/markers/reho/reho_base.py +115 -37
  179. junifer/markers/reho/reho_maps.py +18 -32
  180. junifer/markers/reho/reho_parcels.py +26 -41
  181. junifer/markers/reho/reho_spheres.py +33 -54
  182. junifer/markers/reho/tests/test_junifer_reho.py +178 -0
  183. junifer/markers/reho/tests/test_reho_maps.py +27 -25
  184. junifer/markers/reho/tests/test_reho_parcels.py +28 -22
  185. junifer/markers/reho/tests/test_reho_spheres.py +16 -12
  186. junifer/markers/sphere_aggregation.py +88 -72
  187. junifer/markers/temporal_snr/temporal_snr_base.py +32 -30
  188. junifer/markers/temporal_snr/temporal_snr_maps.py +9 -18
  189. junifer/markers/temporal_snr/temporal_snr_parcels.py +19 -27
  190. junifer/markers/temporal_snr/temporal_snr_spheres.py +27 -41
  191. junifer/markers/temporal_snr/tests/test_temporal_snr_base.py +2 -2
  192. junifer/markers/temporal_snr/tests/test_temporal_snr_maps.py +17 -22
  193. junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +11 -3
  194. junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +9 -9
  195. junifer/markers/tests/test_brainprint.py +10 -9
  196. junifer/markers/tests/test_ets_rss.py +7 -3
  197. junifer/markers/tests/test_lost_regions.py +279 -0
  198. junifer/markers/tests/test_maps_aggregation.py +67 -114
  199. junifer/markers/tests/test_marker_utils.py +40 -1
  200. junifer/markers/tests/test_markers_base.py +275 -6
  201. junifer/markers/tests/test_parcel_aggregation.py +91 -53
  202. junifer/markers/tests/test_sphere_aggregation.py +53 -47
  203. junifer/markers/utils.py +267 -5
  204. junifer/onthefly/__init__.py +2 -3
  205. junifer/onthefly/__init__.pyi +4 -0
  206. junifer/onthefly/_brainprint.py +3 -5
  207. junifer/onthefly/py.typed +0 -0
  208. junifer/onthefly/read_transform.py +11 -9
  209. junifer/onthefly/tests/test_read_transform.py +7 -7
  210. junifer/pipeline/__init__.pyi +4 -2
  211. junifer/pipeline/marker_collection.py +14 -10
  212. junifer/pipeline/pipeline_component_registry.py +25 -6
  213. junifer/pipeline/pipeline_step_mixin.py +5 -12
  214. junifer/pipeline/tests/test_data_object_dumper.py +10 -10
  215. junifer/pipeline/tests/test_marker_collection.py +145 -168
  216. junifer/pipeline/tests/test_pipeline_component_registry.py +52 -8
  217. junifer/pipeline/tests/test_pipeline_step_mixin.py +12 -26
  218. junifer/pipeline/tests/test_update_meta_mixin.py +4 -5
  219. junifer/pipeline/tests/test_workdir_manager.py +39 -0
  220. junifer/pipeline/update_meta_mixin.py +13 -10
  221. junifer/pipeline/utils.py +22 -17
  222. junifer/pipeline/workdir_manager.py +41 -32
  223. junifer/preprocess/__init__.pyi +9 -4
  224. junifer/preprocess/_temporal_filter.py +34 -33
  225. junifer/preprocess/_temporal_slicer.py +17 -30
  226. junifer/preprocess/base.py +99 -55
  227. junifer/preprocess/confounds/__init__.pyi +10 -2
  228. junifer/preprocess/confounds/fmriprep_confound_remover.py +87 -109
  229. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +64 -45
  230. junifer/preprocess/smoothing/__init__.pyi +2 -2
  231. junifer/preprocess/smoothing/_afni_smoothing.py +8 -7
  232. junifer/preprocess/smoothing/_fsl_smoothing.py +6 -4
  233. junifer/preprocess/smoothing/_nilearn_smoothing.py +7 -5
  234. junifer/preprocess/smoothing/smoothing.py +59 -40
  235. junifer/preprocess/smoothing/tests/test_smoothing.py +28 -8
  236. junifer/preprocess/tests/test_preprocess_base.py +17 -9
  237. junifer/preprocess/tests/test_temporal_filter.py +37 -16
  238. junifer/preprocess/tests/test_temporal_slicer.py +12 -12
  239. junifer/preprocess/warping/__init__.pyi +2 -2
  240. junifer/preprocess/warping/_ants_warper.py +56 -40
  241. junifer/preprocess/warping/_fsl_warper.py +49 -36
  242. junifer/preprocess/warping/_utils.py +106 -0
  243. junifer/preprocess/warping/space_warper.py +99 -98
  244. junifer/preprocess/warping/tests/test_space_warper.py +46 -41
  245. junifer/preprocess/warping/tests/test_warping_utils.py +163 -0
  246. junifer/stats.py +46 -34
  247. junifer/storage/__init__.pyi +8 -4
  248. junifer/storage/base.py +129 -64
  249. junifer/storage/hdf5.py +307 -199
  250. junifer/storage/pandas_base.py +46 -24
  251. junifer/storage/sqlite.py +99 -62
  252. junifer/storage/tests/data/collected_h5io_fork.hdf5 +0 -0
  253. junifer/storage/tests/test_hdf5.py +147 -12
  254. junifer/storage/tests/test_repeated_storage.py +293 -0
  255. junifer/storage/tests/test_sqlite.py +9 -20
  256. junifer/storage/tests/test_storage_base.py +18 -21
  257. junifer/storage/tests/test_utils.py +83 -22
  258. junifer/storage/utils.py +58 -38
  259. junifer/testing/__init__.pyi +2 -2
  260. junifer/testing/datagrabbers.py +480 -44
  261. junifer/testing/registry.py +12 -0
  262. junifer/testing/tests/test_adhdtesting_datagrabber.py +24 -0
  263. junifer/testing/tests/test_oasisvmbtesting_datagrabber.py +5 -0
  264. junifer/testing/tests/test_partlycloudytesting_datagrabber.py +52 -0
  265. junifer/testing/tests/test_spmauditory_datagrabber.py +16 -0
  266. junifer/testing/tests/test_testing_registry.py +31 -0
  267. junifer/testing/tests/test_testing_utils.py +35 -0
  268. junifer/testing/tests/test_uclacnpvbmtesting_datagrabber.py +81 -0
  269. junifer/testing/utils.py +33 -1
  270. junifer/tests/test_stats.py +1 -5
  271. junifer/typing/__init__.pyi +2 -0
  272. junifer/typing/_typing.py +34 -19
  273. junifer/typing/py.typed +0 -0
  274. junifer/utils/__init__.pyi +10 -1
  275. junifer/utils/_config.py +1 -2
  276. junifer/utils/helpers.py +68 -2
  277. junifer/utils/logging.py +31 -7
  278. junifer/utils/tests/test_helpers.py +36 -1
  279. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/METADATA +43 -35
  280. junifer-0.0.8.dist-info/RECORD +402 -0
  281. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/WHEEL +1 -1
  282. junifer/cli/tests/test_parser.py +0 -280
  283. junifer/external/h5io/h5io/__init__.py +0 -15
  284. junifer/external/h5io/h5io/_h5io.py +0 -767
  285. junifer/external/h5io/h5io/_version.py +0 -1
  286. junifer/external/h5io/h5io/chunked_array.py +0 -94
  287. junifer/external/h5io/h5io/chunked_list.py +0 -65
  288. junifer/markers/complexity/__init__.pyi +0 -17
  289. junifer-0.0.7.dev332.dist-info/RECORD +0 -353
  290. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/entry_points.txt +0 -0
  291. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/AUTHORS.rst +0 -0
  292. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/LICENSE.md +0 -0
  293. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/top_level.txt +0 -0
junifer/stats.py CHANGED
@@ -4,37 +4,63 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
- from typing import Any, Callable, Optional
7
+ from collections.abc import Callable
8
+ from enum import StrEnum
9
+ from typing import Any
8
10
 
9
11
  import numpy as np
12
+ import structlog
13
+ from pydantic import validate_call
10
14
  from scipy.stats import mode, trim_mean
11
15
  from scipy.stats.mstats import winsorize
12
16
 
13
- from .utils import logger, raise_error
17
+ from .utils import raise_error
14
18
 
15
19
 
16
- __all__ = ["count", "get_aggfunc_by_name", "select", "winsorized_mean"]
20
+ __all__ = [
21
+ "AggFunc",
22
+ "count",
23
+ "get_aggfunc_by_name",
24
+ "select",
25
+ "winsorized_mean",
26
+ ]
17
27
 
28
+ _log = structlog.get_logger("junifer")
29
+ logger = _log.bind(pkg="stats")
18
30
 
31
+
32
+ class AggFunc(StrEnum):
33
+ """Accepted aggregation function names.
34
+
35
+ * ``mean`` -> :func:`numpy.mean`
36
+ * ``winsorized_mean`` -> :func:`scipy.stats.mstats.winsorize`
37
+ * ``trim_mean`` -> :func:`scipy.stats.trim_mean`
38
+ * ``mode`` -> :func:`scipy.stats.mode`
39
+ * ``std`` -> :func:`numpy.std`
40
+ * ``count`` -> :func:`.count`
41
+ * ``select`` -> :func:`.select`
42
+
43
+ """
44
+
45
+ Mean = "mean"
46
+ WinsorizedMean = "winsorized_mean"
47
+ TrimMean = "trim_mean"
48
+ Mode = "mode"
49
+ Std = "std"
50
+ Count = "count"
51
+ Select = "select"
52
+
53
+
54
+ @validate_call
19
55
  def get_aggfunc_by_name(
20
- name: str, func_params: Optional[dict[str, Any]] = None
56
+ name: AggFunc, func_params: dict[str, Any] | None = None
21
57
  ) -> Callable:
22
58
  """Get an aggregation function by its name.
23
59
 
24
60
  Parameters
25
61
  ----------
26
- name : str
27
- Name to identify the function. Currently supported names and
28
- corresponding functions are:
29
-
30
- * ``mean`` -> :func:`numpy.mean`
31
- * ``winsorized_mean`` -> :func:`scipy.stats.mstats.winsorize`
32
- * ``trim_mean`` -> :func:`scipy.stats.trim_mean`
33
- * ``mode`` -> :func:`scipy.stats.mode`
34
- * ``std`` -> :func:`numpy.std`
35
- * ``count`` -> :func:`.count`
36
- * ``select`` -> :func:`.select`
37
-
62
+ name : :enum:`.AggFunc`
63
+ Aggregation function name.
38
64
  func_params : dict, optional
39
65
  Parameters to pass to the function.
40
66
  E.g. for ``winsorized_mean``: ``func_params = {'limits': [0.1, 0.1]}``
@@ -48,16 +74,6 @@ def get_aggfunc_by_name(
48
74
  """
49
75
  from functools import partial # local import to avoid sphinx error
50
76
 
51
- # check validity of names
52
- _valid_func_names = {
53
- "winsorized_mean",
54
- "mean",
55
- "std",
56
- "trim_mean",
57
- "count",
58
- "select",
59
- "mode",
60
- }
61
77
  if func_params is None:
62
78
  func_params = {}
63
79
  # apply functions
@@ -101,11 +117,7 @@ def get_aggfunc_by_name(
101
117
  func = partial(select, **func_params)
102
118
  elif name == "mode":
103
119
  func = partial(mode, **func_params)
104
- else:
105
- raise_error(
106
- f"Function {name} unknown. Please provide any of "
107
- f"{_valid_func_names}"
108
- )
120
+
109
121
  return func
110
122
 
111
123
 
@@ -134,7 +146,7 @@ def count(data: np.ndarray, axis: int = 0) -> np.ndarray:
134
146
 
135
147
 
136
148
  def winsorized_mean(
137
- data: np.ndarray, axis: Optional[int] = None, **win_params
149
+ data: np.ndarray, axis: int | None = None, **win_params
138
150
  ) -> np.ndarray:
139
151
  """Compute a winsorized mean by chaining winsorization and mean.
140
152
 
@@ -169,8 +181,8 @@ def winsorized_mean(
169
181
  def select(
170
182
  data: np.ndarray,
171
183
  axis: int = 0,
172
- pick: Optional[list[int]] = None,
173
- drop: Optional[list[int]] = None,
184
+ pick: list[int] | None = None,
185
+ drop: list[int] | None = None,
174
186
  ) -> np.ndarray:
175
187
  """Select a subset of the data.
176
188
 
@@ -1,11 +1,15 @@
1
1
  __all__ = [
2
2
  "BaseFeatureStorage",
3
+ "MatrixKind",
4
+ "StorageType",
5
+ "HDF5FeatureStorage",
3
6
  "PandasBaseFeatureStorage",
4
7
  "SQLiteFeatureStorage",
5
- "HDF5FeatureStorage",
8
+ "Upsert",
9
+ "logger",
6
10
  ]
7
11
 
8
- from .base import BaseFeatureStorage
9
- from .pandas_base import PandasBaseFeatureStorage
10
- from .sqlite import SQLiteFeatureStorage
12
+ from .base import BaseFeatureStorage, MatrixKind, StorageType, logger
11
13
  from .hdf5 import HDF5FeatureStorage
14
+ from .pandas_base import PandasBaseFeatureStorage
15
+ from .sqlite import SQLiteFeatureStorage, Upsert
junifer/storage/base.py CHANGED
@@ -6,20 +6,44 @@
6
6
 
7
7
  from abc import ABC, abstractmethod
8
8
  from collections.abc import Sequence
9
+ from enum import StrEnum
9
10
  from pathlib import Path
10
- from typing import Any, ClassVar, Optional, Union
11
+ from typing import Any, ClassVar
11
12
 
12
13
  import numpy as np
13
14
  import pandas as pd
15
+ import structlog
16
+ from pydantic import BaseModel, ConfigDict
14
17
 
15
- from ..utils import logger, raise_error
16
- from .utils import process_meta
18
+ from ..utils import raise_error
17
19
 
18
20
 
19
- __all__ = ["BaseFeatureStorage"]
21
+ __all__ = ["BaseFeatureStorage", "MatrixKind", "StorageType"]
20
22
 
21
23
 
22
- class BaseFeatureStorage(ABC):
24
+ class MatrixKind(StrEnum):
25
+ """Accepted matrix kind value."""
26
+
27
+ UpperTriangle = "triu"
28
+ LowerTriangle = "tril"
29
+ Full = "full"
30
+
31
+
32
+ _log = structlog.get_logger("junifer")
33
+ logger = _log.bind(pkg="storage", step="storage")
34
+
35
+
36
+ class StorageType(StrEnum):
37
+ """Accepted storage type."""
38
+
39
+ Vector = "vector"
40
+ Matrix = "matrix"
41
+ Timeseries = "timeseries"
42
+ Timeseries2D = "timeseries_2d"
43
+ ScalarTable = "scalar_table"
44
+
45
+
46
+ class BaseFeatureStorage(BaseModel, ABC):
23
47
  """Abstract base class for feature storage.
24
48
 
25
49
  For every storage, one needs to provide a concrete
@@ -27,7 +51,7 @@ class BaseFeatureStorage(ABC):
27
51
 
28
52
  Parameters
29
53
  ----------
30
- uri : str or pathlib.Path
54
+ uri : pathlib.Path
31
55
  The path to the storage.
32
56
  single_output : bool, optional
33
57
  Whether to have single output (default True).
@@ -35,27 +59,26 @@ class BaseFeatureStorage(ABC):
35
59
  Raises
36
60
  ------
37
61
  AttributeError
38
- If the storage does not have `_STORAGE_TYPES` attribute.
62
+ If the storage does not have ``_STORAGE_TYPES`` attribute.
39
63
 
40
64
  """
41
65
 
42
- _STORAGE_TYPES: ClassVar[Sequence[str]]
66
+ _STORAGE_TYPES: ClassVar[Sequence[StorageType]]
43
67
 
44
- def __init__(
45
- self,
46
- uri: Union[str, Path],
47
- single_output: bool = True,
48
- ) -> None:
68
+ model_config = ConfigDict(
69
+ extra="forbid", frozen=True, use_enum_values=True
70
+ )
71
+
72
+ uri: Path
73
+ single_output: bool = True
74
+
75
+ def model_post_init(self, context: Any): # noqa: D102
49
76
  # Check for missing storage types attribute
50
77
  if not hasattr(self, "_STORAGE_TYPES"):
51
78
  raise_error(
52
79
  msg="Missing `_STORAGE_TYPES` for the storage",
53
80
  klass=AttributeError,
54
81
  )
55
- # Convert str to Path
56
- if not isinstance(uri, Path):
57
- uri = Path(uri)
58
- self.uri = uri
59
82
  # Create parent directories if not present
60
83
  if not self.uri.parent.exists():
61
84
  logger.info(
@@ -63,21 +86,12 @@ class BaseFeatureStorage(ABC):
63
86
  "does not exist, creating now"
64
87
  )
65
88
  self.uri.parent.mkdir(parents=True, exist_ok=True)
66
- self.single_output = single_output
67
-
68
- def get_valid_inputs(self) -> list[str]:
69
- """Get valid storage types for input.
70
-
71
- Returns
72
- -------
73
- list of str
74
- The list of storage types that can be used as input for this
75
- storage.
76
-
77
- """
78
- return list(self._STORAGE_TYPES)
89
+ logger.info(
90
+ f"Parameters: {self.model_dump(mode='json')}",
91
+ component=self.__class__.__name__,
92
+ )
79
93
 
80
- def validate(self, input_: list[str]) -> None:
94
+ def validate_input(self, input_: list[str]) -> None:
81
95
  """Validate the input to the pipeline step.
82
96
 
83
97
  Parameters
@@ -119,11 +133,9 @@ class BaseFeatureStorage(ABC):
119
133
  @abstractmethod
120
134
  def read(
121
135
  self,
122
- feature_name: Optional[str] = None,
123
- feature_md5: Optional[str] = None,
124
- ) -> dict[
125
- str, Union[str, list[Union[int, str, dict[str, str]]], np.ndarray]
126
- ]:
136
+ feature_name: str | None = None,
137
+ feature_md5: str | None = None,
138
+ ) -> dict[str, str | list[int | str | dict[str, str]] | np.ndarray]:
127
139
  """Read stored feature.
128
140
 
129
141
  Parameters
@@ -147,8 +159,8 @@ class BaseFeatureStorage(ABC):
147
159
  @abstractmethod
148
160
  def read_df(
149
161
  self,
150
- feature_name: Optional[str] = None,
151
- feature_md5: Optional[str] = None,
162
+ feature_name: str | None = None,
163
+ feature_md5: str | None = None,
152
164
  ) -> pd.DataFrame:
153
165
  """Read feature into a pandas DataFrame.
154
166
 
@@ -171,7 +183,13 @@ class BaseFeatureStorage(ABC):
171
183
  ) # pragma: no cover
172
184
 
173
185
  @abstractmethod
174
- def store_metadata(self, meta_md5: str, element: dict, meta: dict) -> None:
186
+ def store_metadata(
187
+ self,
188
+ meta_md5: str,
189
+ element: dict,
190
+ meta: dict,
191
+ processed_element: dict | None = None,
192
+ ) -> None:
175
193
  """Store metadata.
176
194
 
177
195
  Parameters
@@ -182,6 +200,11 @@ class BaseFeatureStorage(ABC):
182
200
  The element as a dictionary.
183
201
  meta : dict
184
202
  The metadata as a dictionary.
203
+ processed_element : dict or None, optional
204
+ The element being processed when the data was computed, with all
205
+ the keys of the element, which can be used to choose where to
206
+ store the data (e.g., the file of each element being processed).
207
+ If None, ``element`` is used (default None).
185
208
 
186
209
  """
187
210
  raise_error(
@@ -189,12 +212,12 @@ class BaseFeatureStorage(ABC):
189
212
  klass=NotImplementedError,
190
213
  ) # pragma: no cover
191
214
 
192
- def store(self, kind: str, **kwargs) -> None:
215
+ def store(self, kind: StorageType, **kwargs) -> None:
193
216
  """Store extracted features data.
194
217
 
195
218
  Parameters
196
219
  ----------
197
- kind : {"matrix", "timeseries", "vector", "scalar_table"}
220
+ kind : :enum:`.StorageType`
198
221
  The storage kind.
199
222
  **kwargs
200
223
  The keyword arguments.
@@ -212,11 +235,24 @@ class BaseFeatureStorage(ABC):
212
235
  msg=f"I don't know how to store {kind}.",
213
236
  klass=ValueError,
214
237
  )
215
- # Process and store metadata
238
+ # Imported here to avoid circular import
239
+ from .utils import process_meta
240
+
241
+ # Process and store metadata: the data is stored for the element with
242
+ # the keys that it depends on, and the element being processed is
243
+ # passed as ``processed_element`` (e.g., to choose the file to store
244
+ # the data of each element being processed)
216
245
  t_meta = kwargs.pop("meta")
246
+ processed_element = t_meta.get("element")
217
247
  meta_md5, t_meta, t_element = process_meta(t_meta)
218
- self.store_metadata(meta_md5=meta_md5, element=t_element, meta=t_meta)
248
+ self.store_metadata(
249
+ meta_md5=meta_md5,
250
+ element=t_element,
251
+ meta=t_meta,
252
+ processed_element=processed_element,
253
+ )
219
254
  # Store data
255
+ kwargs["processed_element"] = processed_element
220
256
  if kind == "matrix":
221
257
  self.store_matrix(meta_md5=meta_md5, element=t_element, **kwargs)
222
258
  elif kind == "timeseries":
@@ -239,10 +275,11 @@ class BaseFeatureStorage(ABC):
239
275
  meta_md5: str,
240
276
  element: dict,
241
277
  data: np.ndarray,
242
- col_names: Optional[Sequence[str]] = None,
243
- row_names: Optional[Sequence[str]] = None,
244
- matrix_kind: str = "full",
278
+ col_names: Sequence[str] | None = None,
279
+ row_names: Sequence[str] | None = None,
280
+ matrix_kind: MatrixKind = MatrixKind.Full,
245
281
  diagonal: bool = True,
282
+ processed_element: dict | None = None,
246
283
  ) -> None:
247
284
  """Store matrix.
248
285
 
@@ -258,17 +295,17 @@ class BaseFeatureStorage(ABC):
258
295
  The column labels (default None).
259
296
  row_names : list-like of str, optional
260
297
  The row labels (default None).
261
- matrix_kind : str, optional
262
- The kind of matrix:
263
-
264
- * ``triu`` : store upper triangular only
265
- * ``tril`` : store lower triangular
266
- * ``full`` : full matrix
267
-
268
- (default "full").
298
+ matrix_kind : :enum:`.MatrixKind`, optional
299
+ The matrix kind (default ``MatrixKind.Full``).
269
300
  diagonal : bool, optional
270
- Whether to store the diagonal. If ``matrix_kind = full``, setting
271
- this to False will raise an error (default True).
301
+ Whether to store the diagonal. If ``matrix_kind=MatrixKind.Full``,
302
+ setting this to False will raise an error (default True).
303
+
304
+ processed_element : dict or None, optional
305
+ The element being processed when the data was computed, with all
306
+ the keys of the element, which can be used to choose where to
307
+ store the data (e.g., the file of each element being processed).
308
+ If None, ``element`` is used (default None).
272
309
 
273
310
  """
274
311
  raise_error(
@@ -280,8 +317,9 @@ class BaseFeatureStorage(ABC):
280
317
  self,
281
318
  meta_md5: str,
282
319
  element: dict,
283
- data: Union[np.ndarray, list],
284
- col_names: Optional[Sequence[str]] = None,
320
+ data: np.ndarray | list,
321
+ col_names: Sequence[str] | None = None,
322
+ processed_element: dict | None = None,
285
323
  ) -> None:
286
324
  """Store vector.
287
325
 
@@ -296,6 +334,12 @@ class BaseFeatureStorage(ABC):
296
334
  col_names : list-like of str, optional
297
335
  The column labels (default None).
298
336
 
337
+ processed_element : dict or None, optional
338
+ The element being processed when the data was computed, with all
339
+ the keys of the element, which can be used to choose where to
340
+ store the data (e.g., the file of each element being processed).
341
+ If None, ``element`` is used (default None).
342
+
299
343
  """
300
344
  raise_error(
301
345
  msg="Concrete classes need to implement store_vector().",
@@ -307,7 +351,8 @@ class BaseFeatureStorage(ABC):
307
351
  meta_md5: str,
308
352
  element: dict,
309
353
  data: np.ndarray,
310
- col_names: Optional[Sequence[str]] = None,
354
+ col_names: Sequence[str] | None = None,
355
+ processed_element: dict | None = None,
311
356
  ) -> None:
312
357
  """Store timeseries.
313
358
 
@@ -322,6 +367,12 @@ class BaseFeatureStorage(ABC):
322
367
  col_names : list-like of str, optional
323
368
  The column labels (default None).
324
369
 
370
+ processed_element : dict or None, optional
371
+ The element being processed when the data was computed, with all
372
+ the keys of the element, which can be used to choose where to
373
+ store the data (e.g., the file of each element being processed).
374
+ If None, ``element`` is used (default None).
375
+
325
376
  """
326
377
  raise_error(
327
378
  msg="Concrete classes need to implement store_timeseries().",
@@ -333,8 +384,9 @@ class BaseFeatureStorage(ABC):
333
384
  meta_md5: str,
334
385
  element: dict,
335
386
  data: np.ndarray,
336
- col_names: Optional[Sequence[str]] = None,
337
- row_names: Optional[Sequence[str]] = None,
387
+ col_names: Sequence[str] | None = None,
388
+ row_names: Sequence[str] | None = None,
389
+ processed_element: dict | None = None,
338
390
  ) -> None:
339
391
  """Store 2D timeseries.
340
392
 
@@ -351,6 +403,12 @@ class BaseFeatureStorage(ABC):
351
403
  row_names : list-like of str, optional
352
404
  The row labels (default None).
353
405
 
406
+ processed_element : dict or None, optional
407
+ The element being processed when the data was computed, with all
408
+ the keys of the element, which can be used to choose where to
409
+ store the data (e.g., the file of each element being processed).
410
+ If None, ``element`` is used (default None).
411
+
354
412
  """
355
413
  raise_error(
356
414
  msg="Concrete classes need to implement store_timeseries_2d().",
@@ -362,9 +420,10 @@ class BaseFeatureStorage(ABC):
362
420
  meta_md5: str,
363
421
  element: dict,
364
422
  data: np.ndarray,
365
- col_names: Optional[Sequence[str]] = None,
366
- row_names: Optional[Sequence[str]] = None,
367
- row_header_col_name: Optional[str] = "feature",
423
+ col_names: Sequence[str] | None = None,
424
+ row_names: Sequence[str] | None = None,
425
+ row_header_col_name: str | None = "feature",
426
+ processed_element: dict | None = None,
368
427
  ) -> None:
369
428
  """Store table with scalar values.
370
429
 
@@ -383,6 +442,12 @@ class BaseFeatureStorage(ABC):
383
442
  row_header_col_name : str, optional
384
443
  The column name for the row header column (default "feature").
385
444
 
445
+ processed_element : dict or None, optional
446
+ The element being processed when the data was computed, with all
447
+ the keys of the element, which can be used to choose where to
448
+ store the data (e.g., the file of each element being processed).
449
+ If None, ``element`` is used (default None).
450
+
386
451
  """
387
452
  raise_error(
388
453
  msg="Concrete classes need to implement store_scalar_table().",