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
@@ -3,11 +3,12 @@ __all__ = [
3
3
  "AssetLoaderDispatcher",
4
4
  "BaseDataDumpAsset",
5
5
  "DataObjectDumper",
6
+ "ExtDep",
7
+ "MarkerCollection",
6
8
  "PipelineComponentRegistry",
7
9
  "PipelineStepMixin",
8
10
  "UpdateMetaMixin",
9
11
  "WorkDirManager",
10
- "MarkerCollection",
11
12
  ]
12
13
 
13
14
  from ._data_object_dumper import (
@@ -16,8 +17,9 @@ from ._data_object_dumper import (
16
17
  BaseDataDumpAsset,
17
18
  DataObjectDumper,
18
19
  )
20
+ from .marker_collection import MarkerCollection
19
21
  from .pipeline_component_registry import PipelineComponentRegistry
20
22
  from .pipeline_step_mixin import PipelineStepMixin
21
23
  from .update_meta_mixin import UpdateMetaMixin
24
+ from .utils import ExtDep
22
25
  from .workdir_manager import WorkDirManager
23
- from .marker_collection import MarkerCollection
@@ -6,16 +6,20 @@
6
6
 
7
7
  from collections import Counter
8
8
  from pathlib import Path
9
- from typing import Optional
9
+
10
+ import structlog
10
11
 
11
12
  from ..datareader import DefaultDataReader
12
13
  from ..pipeline import DataObjectDumper, PipelineStepMixin, WorkDirManager
13
14
  from ..typing import DataGrabberLike, MarkerLike, PreprocessorLike, StorageLike
14
- from ..utils import config, logger, raise_error
15
+ from ..utils import config, raise_error
15
16
 
16
17
 
17
18
  __all__ = ["MarkerCollection"]
18
19
 
20
+ _log = structlog.get_logger("junifer")
21
+ logger = _log.bind(pkg="pipeline")
22
+
19
23
 
20
24
  class MarkerCollection:
21
25
  """Class for marker collection.
@@ -41,9 +45,9 @@ class MarkerCollection:
41
45
  def __init__(
42
46
  self,
43
47
  markers: list[MarkerLike],
44
- datareader: Optional[PipelineStepMixin] = None,
45
- preprocessors: Optional[list[PreprocessorLike]] = None,
46
- storage: Optional[StorageLike] = None,
48
+ datareader: PipelineStepMixin | None = None,
49
+ preprocessors: list[PreprocessorLike] | None = None,
50
+ storage: StorageLike | None = None,
47
51
  ):
48
52
  # Check that the markers have different names
49
53
  marker_names = [m.name for m in markers]
@@ -60,7 +64,7 @@ class MarkerCollection:
60
64
  self._preprocessors = preprocessors
61
65
  self._storage = storage
62
66
 
63
- def fit(self, input: dict[str, dict]) -> Optional[dict]:
67
+ def fit(self, input: dict[str, dict]) -> dict | None:
64
68
  """Fit the pipeline.
65
69
 
66
70
  Parameters
@@ -162,7 +166,7 @@ class MarkerCollection:
162
166
  logger.info(f"DataGrabber output type: {t_data}")
163
167
 
164
168
  logger.info("Validating Data Reader:")
165
- t_data = self._datareader.validate(t_data)
169
+ t_data = self._datareader.validate_component(t_data)
166
170
  logger.info(f"Data Reader output type: {t_data}")
167
171
 
168
172
  if self._preprocessors is not None:
@@ -175,7 +179,7 @@ class MarkerCollection:
175
179
  old_t_data = t_data.copy()
176
180
  logger.info(f"Preprocessor input type: {t_data}")
177
181
  # Validate preprocessor
178
- new_t_data = preprocessor.validate(old_t_data)
182
+ new_t_data = preprocessor.validate_component(old_t_data)
179
183
  # Set new data types
180
184
  t_data = list(set(old_t_data) | set(new_t_data))
181
185
  logger.info(f"Preprocessor output type: {t_data}")
@@ -183,10 +187,10 @@ class MarkerCollection:
183
187
  for marker in self._markers:
184
188
  logger.info(f"Validating Marker: {marker.name}")
185
189
  # Validate marker
186
- m_data = marker.validate(input=t_data)
190
+ m_data = marker.validate_component(input=t_data)
187
191
  logger.info(f"Marker output type: {m_data}")
188
192
  # Check storage for the marker
189
193
  if self._storage is not None:
190
194
  logger.info(f"Validating storage for {marker.name}")
191
195
  # Validate storage
192
- self._storage.validate(input_=m_data)
196
+ self._storage.validate_input(input_=m_data)
@@ -7,15 +7,19 @@
7
7
 
8
8
  import importlib
9
9
  from collections.abc import Mapping
10
- from typing import Optional, Union
10
+
11
+ import structlog
11
12
 
12
13
  from ..typing import DataGrabberLike, MarkerLike, PreprocessorLike, StorageLike
13
- from ..utils import logger, raise_error
14
+ from ..utils import raise_error
14
15
  from ..utils.singleton import Singleton
15
16
 
16
17
 
17
18
  __all__ = ["PipelineComponentRegistry"]
18
19
 
20
+ _log = structlog.get_logger("junifer")
21
+ logger = _log.bind(pkg="pipeline")
22
+
19
23
 
20
24
  class PipelineComponentRegistry(metaclass=Singleton):
21
25
  """Class for pipeline component registry.
@@ -79,24 +83,30 @@ class PipelineComponentRegistry(metaclass=Singleton):
79
83
  "TemporalFilter": "TemporalFilter",
80
84
  },
81
85
  "marker": {
86
+ "ALFFMaps": "ALFFMaps",
82
87
  "ALFFParcels": "ALFFParcels",
83
88
  "ALFFSpheres": "ALFFSpheres",
84
89
  "BaseMarker": "BaseMarker",
85
90
  "BrainPrint": "BrainPrint",
86
91
  "CrossParcellationFC": "CrossParcellationFC",
92
+ "EdgeCentricFCMaps": "EdgeCentricFCMaps",
87
93
  "EdgeCentricFCParcels": "EdgeCentricFCParcels",
88
94
  "EdgeCentricFCSpheres": "EdgeCentricFCSpheres",
95
+ "FunctionalConnectivityMaps": "FunctionalConnectivityMaps",
89
96
  "FunctionalConnectivityParcels": (
90
97
  "FunctionalConnectivityParcels"
91
98
  ),
92
99
  "FunctionalConnectivitySpheres": (
93
100
  "FunctionalConnectivitySpheres"
94
101
  ),
102
+ "MapsAggregation": "MapsAggregation",
95
103
  "ParcelAggregation": "ParcelAggregation",
104
+ "ReHoMaps": "ReHoMaps",
96
105
  "ReHoParcels": "ReHoParcels",
97
106
  "ReHoSpheres": "ReHoSpheres",
98
107
  "RSSETSMarker": "RSSETSMarker",
99
108
  "SphereAggregation": "SphereAggregation",
109
+ "TemporalSNRMaps": "TemporalSNRMaps",
100
110
  "TemporalSNRParcels": "TemporalSNRParcels",
101
111
  "TemporalSNRSpheres": "TemporalSNRSpheres",
102
112
  },
@@ -119,7 +129,7 @@ class PipelineComponentRegistry(metaclass=Singleton):
119
129
  return self._steps
120
130
 
121
131
  @property
122
- def components(self) -> Mapping[str, Mapping[str, Union[str, type]]]:
132
+ def components(self) -> Mapping[str, Mapping[str, str | type]]:
123
133
  """Get registered components for valid pipeline steps."""
124
134
  return self._components
125
135
 
@@ -238,8 +248,8 @@ class PipelineComponentRegistry(metaclass=Singleton):
238
248
  step: str,
239
249
  name: str,
240
250
  baseclass: type,
241
- init_params: Optional[dict] = None,
242
- ) -> Union[DataGrabberLike, PreprocessorLike, MarkerLike, StorageLike]:
251
+ init_params: dict | None = None,
252
+ ) -> DataGrabberLike | PreprocessorLike | MarkerLike | StorageLike:
243
253
  """Build an instance of class registered as ``name``.
244
254
 
245
255
  Parameters
@@ -277,7 +287,16 @@ class PipelineComponentRegistry(metaclass=Singleton):
277
287
  logger.debug(f"\tInit params: {init_params}")
278
288
  try:
279
289
  # Create instance of the class
280
- object_ = klass(**init_params)
290
+ if step == "datagrabber" and name == "MultipleDataGrabber":
291
+ dgs = []
292
+ for d in init_params["datagrabbers"]:
293
+ k = d.pop("kind")
294
+ dg_klass = self.get_class(step=step, name=k)
295
+ dg_obj = dg_klass(**d)
296
+ dgs.append(dg_obj)
297
+ object_ = klass(datagrabbers=dgs)
298
+ else:
299
+ object_ = klass(**init_params)
281
300
  except (ValueError, TypeError) as e:
282
301
  raise_error(
283
302
  msg=f"Failed to create {step} ({name}). Error: {e}",
@@ -4,15 +4,7 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
- import sys
8
-
9
-
10
- if sys.version_info < (3, 11): # pragma: no cover
11
- from importlib_metadata import packages_distributions
12
- else:
13
- from importlib.metadata import packages_distributions
14
-
15
-
7
+ from importlib.metadata import packages_distributions
16
8
  from importlib.util import find_spec
17
9
  from itertools import chain
18
10
  from typing import Any
@@ -75,8 +67,8 @@ class PipelineStepMixin:
75
67
  klass=NotImplementedError,
76
68
  ) # pragma: no cover
77
69
 
78
- def validate(self, input: list[str]) -> list[str]:
79
- """Validate the the pipeline step.
70
+ def validate_component(self, input: list[str]) -> list[str]:
71
+ """Validate the pipeline component.
80
72
 
81
73
  Parameters
82
74
  ----------
@@ -228,5 +220,6 @@ class PipelineStepMixin:
228
220
  The processed output of the pipeline step.
229
221
 
230
222
  """
231
- self.validate(input=list(input.keys()))
223
+ # Needs to be validated if called directly via API
224
+ self.validate_component(input=list(input.keys()))
232
225
  return self._fit_transform(input=input, **kwargs)
@@ -5,7 +5,6 @@
5
5
 
6
6
  import pickle
7
7
  from pathlib import Path
8
- from typing import Union
9
8
 
10
9
  import nibabel
11
10
  import pytest
@@ -23,6 +22,9 @@ from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
23
22
  from junifer.utils import config
24
23
 
25
24
 
25
+ pytestmark = pytest.mark.external
26
+
27
+
26
28
  @pytest.mark.parametrize(
27
29
  "dispatcher, inbuilt_key, ext_key, val",
28
30
  [
@@ -36,9 +38,9 @@ from junifer.utils import config
36
38
  ],
37
39
  )
38
40
  def test_dispatcher_addition_errors(
39
- dispatcher: Union[AssetDumperDispatcher, AssetLoaderDispatcher],
40
- inbuilt_key: Union[str, type],
41
- ext_key: Union[str, type],
41
+ dispatcher: AssetDumperDispatcher | AssetLoaderDispatcher,
42
+ inbuilt_key: str | type,
43
+ ext_key: str | type,
42
44
  val: type,
43
45
  ) -> None:
44
46
  """Test asset dumper / loader addition errors.
@@ -70,9 +72,9 @@ def test_dispatcher_addition_errors(
70
72
  ],
71
73
  )
72
74
  def test_dispatcher_removal_errors(
73
- dispatcher: Union[AssetDumperDispatcher, AssetLoaderDispatcher],
74
- inbuilt_key: Union[str, type],
75
- ext_key: Union[str, type],
75
+ dispatcher: AssetDumperDispatcher | AssetLoaderDispatcher,
76
+ inbuilt_key: str | type,
77
+ ext_key: str | type,
76
78
  ) -> None:
77
79
  """Test asset dumper / loader removal errors.
78
80
 
@@ -157,9 +159,7 @@ def test_data_object_dumper(
157
159
  "wm_csf": "full",
158
160
  },
159
161
  detrend=True,
160
- standardize=True,
161
- low_pass=0.08,
162
- high_pass=0.01,
162
+ standardize="zscore_sample",
163
163
  ),
164
164
  ],
165
165
  markers=[
@@ -6,141 +6,145 @@
6
6
 
7
7
  from pathlib import Path
8
8
 
9
+ import numpy as np
9
10
  import pytest
10
- from numpy.testing import assert_array_equal
11
+ from numpy.testing import assert_allclose, assert_array_equal
11
12
 
12
- from junifer.datareader.default import DefaultDataReader
13
- from junifer.markers import (
14
- FunctionalConnectivityParcels,
15
- ParcelAggregation,
16
- )
13
+ from junifer.datagrabber import DataType
14
+ from junifer.datareader import DefaultDataReader
15
+ from junifer.markers import FunctionalConnectivityParcels, ParcelAggregation
16
+ from junifer.markers.base import BaseMarker
17
17
  from junifer.pipeline import MarkerCollection, PipelineStepMixin
18
18
  from junifer.preprocess import fMRIPrepConfoundRemover
19
- from junifer.storage import SQLiteFeatureStorage
20
- from junifer.testing.datagrabbers import (
21
- PartlyCloudyTestingDataGrabber,
22
- )
19
+ from junifer.storage import HDF5FeatureStorage, StorageType
20
+ from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
23
21
 
24
22
 
25
- def test_marker_collection_incorrect_markers() -> None:
26
- """Test incorrect markers for MarkerCollection."""
27
- wrong_markers = [
28
- ParcelAggregation(
29
- parcellation="Schaefer100x7",
30
- method="mean",
31
- name="gmd_schaefer100x7_mean",
32
- ),
33
- ParcelAggregation(
34
- parcellation="Schaefer100x7",
35
- method="mean",
36
- name="gmd_schaefer100x7_mean",
37
- ),
38
- ]
39
- with pytest.raises(ValueError, match=r"must have different names"):
40
- MarkerCollection(wrong_markers) # type: ignore
23
+ pytestmark = pytest.mark.external
41
24
 
25
+ PARCELLATION = "TianxS2x3TxMNInonlinear2009cAsym"
42
26
 
43
- def test_marker_collection() -> None:
44
- """Test MarkerCollection."""
45
- markers = [
27
+
28
+ def _markers() -> list[ParcelAggregation]:
29
+ """Get markers aggregating the BOLD data in different ways.
30
+
31
+ Returns
32
+ -------
33
+ list of ParcelAggregation
34
+ The markers, named ``tian_mean``, ``tian_std`` and
35
+ ``tian_trim_mean90``.
36
+
37
+ """
38
+ return [
46
39
  ParcelAggregation(
47
- parcellation="TianxS2x3TxMNInonlinear2009cAsym",
48
- method="mean",
49
- name="tian_mean",
40
+ parcellation=PARCELLATION, method="mean", name="tian_mean"
50
41
  ),
51
42
  ParcelAggregation(
52
- parcellation="TianxS2x3TxMNInonlinear2009cAsym",
53
- method="std",
54
- name="tian_std",
43
+ parcellation=PARCELLATION, method="std", name="tian_std"
55
44
  ),
56
45
  ParcelAggregation(
57
- parcellation="TianxS2x3TxMNInonlinear2009cAsym",
46
+ parcellation=PARCELLATION,
58
47
  method="trim_mean",
59
48
  method_params={"proportiontocut": 0.1},
60
49
  name="tian_trim_mean90",
61
50
  ),
62
51
  ]
52
+
53
+
54
+ def _fit(mc: MarkerCollection) -> dict | None:
55
+ """Validate and fit a marker collection on one subject.
56
+
57
+ Parameters
58
+ ----------
59
+ mc : MarkerCollection
60
+ The marker collection.
61
+
62
+ Returns
63
+ -------
64
+ dict or None
65
+ The output of the marker collection.
66
+
67
+ """
68
+ dg = PartlyCloudyTestingDataGrabber(n_timepoints=50)
69
+ mc.validate(dg)
70
+ with dg:
71
+ return mc.fit(dg["sub-01"])
72
+
73
+
74
+ def test_marker_collection_same_names() -> None:
75
+ """Test markers must have different names."""
76
+ markers = [
77
+ ParcelAggregation(parcellation=PARCELLATION, method="mean", name="a"),
78
+ ParcelAggregation(parcellation=PARCELLATION, method="std", name="a"),
79
+ ]
80
+ with pytest.raises(ValueError, match=r"must have different names"):
81
+ MarkerCollection(markers=markers) # type: ignore
82
+
83
+
84
+ def test_marker_collection_defaults() -> None:
85
+ """Test the defaults of MarkerCollection."""
86
+ markers = _markers()
63
87
  mc = MarkerCollection(markers=markers) # type: ignore
64
88
  assert mc._markers == markers
65
89
  assert mc._preprocessors is None
66
90
  assert mc._storage is None
67
91
  assert isinstance(mc._datareader, DefaultDataReader)
68
92
 
69
- # Create testing datagrabber
70
- dg = PartlyCloudyTestingDataGrabber()
71
- mc.validate(dg)
72
93
 
73
- with dg:
74
- input = dg["sub-01"]
75
- out = mc.fit(input)
76
- assert out is not None
77
- assert isinstance(out, dict)
78
- assert len(out) == 3
79
- assert "tian_mean" in out
80
- assert "tian_std" in out
81
- assert "tian_trim_mean90" in out
82
-
83
- for t_marker in markers:
84
- t_name = t_marker.name
85
- assert "BOLD" in out[t_name]
86
- t_bold = out[t_name]["BOLD"]["aggregation"]
87
- assert "data" in t_bold
88
- assert "col_names" in t_bold
89
- assert "meta" in t_bold
90
-
91
- # Test preprocessing
92
- class BypassPreprocessing(PipelineStepMixin):
94
+ def test_marker_collection_fit() -> None:
95
+ """Test MarkerCollection returns the output of each marker."""
96
+ out = _fit(MarkerCollection(markers=_markers())) # type: ignore
97
+ assert out is not None
98
+ assert set(out) == {"tian_mean", "tian_std", "tian_trim_mean90"}
99
+ for name in out:
100
+ aggregation = out[name]["BOLD"]["aggregation"]
101
+ assert {"data", "col_names", "meta"} <= set(aggregation)
102
+
103
+
104
+ def test_marker_collection_preprocessing() -> None:
105
+ """Test MarkerCollection computes the markers on the preprocessed data."""
106
+
107
+ class Unchanged(PipelineStepMixin):
108
+ """Preprocessing that returns the data unchanged."""
109
+
110
+ def validate_input(self, input):
111
+ return input
112
+
93
113
  def fit_transform(self, input):
94
114
  return input
95
115
 
96
- mc2 = MarkerCollection(
97
- markers=markers, # type: ignore
98
- preprocessors=[BypassPreprocessing()], # type: ignore
99
- datareader=DefaultDataReader(),
116
+ out = _fit(MarkerCollection(markers=_markers())) # type: ignore
117
+ out_preprocessed = _fit(
118
+ MarkerCollection(
119
+ markers=_markers(), # type: ignore
120
+ preprocessors=[Unchanged()], # type: ignore
121
+ )
100
122
  )
101
- assert isinstance(mc2._datareader, DefaultDataReader)
102
- with dg:
103
- input = dg["sub-01"]
104
- out2 = mc2.fit(input)
105
- assert out2 is not None
106
- for t_marker in markers:
107
- t_name = t_marker.name
108
- assert_array_equal(
109
- out[t_name]["BOLD"]["aggregation"]["data"],
110
- out2[t_name]["BOLD"]["aggregation"]["data"],
111
- )
112
-
113
-
114
- def test_marker_collection_with_preprocessing() -> None:
115
- """Test MarkerCollection with preprocessing."""
116
- markers = [
117
- FunctionalConnectivityParcels(
118
- parcellation="Schaefer100x17",
119
- agg_method="mean",
120
- name="Schaefer100x17_mean_FC",
121
- ),
122
- FunctionalConnectivityParcels(
123
- parcellation="TianxS2x3TxMNInonlinear2009cAsym",
124
- agg_method="mean",
125
- name="TianxS2x3TxMNInonlinear2009cAsym_mean_FC",
126
- ),
127
- ]
123
+ assert out is not None
124
+ assert out_preprocessed is not None
125
+ for name in out:
126
+ assert_array_equal(
127
+ out[name]["BOLD"]["aggregation"]["data"],
128
+ out_preprocessed[name]["BOLD"]["aggregation"]["data"],
129
+ )
130
+
131
+
132
+ def test_marker_collection_validate_preprocessing() -> None:
133
+ """Test MarkerCollection validates with a confound removal step."""
128
134
  mc = MarkerCollection(
129
- markers=markers, # type: ignore
135
+ markers=[ # type: ignore
136
+ FunctionalConnectivityParcels(
137
+ parcellation=PARCELLATION, agg_method="mean", name="tian_fc"
138
+ ),
139
+ ],
130
140
  preprocessors=[fMRIPrepConfoundRemover()],
131
141
  )
132
- assert mc._markers == markers
133
142
  assert mc._preprocessors is not None
134
- assert mc._storage is None
135
- assert isinstance(mc._datareader, DefaultDataReader)
136
-
137
- # Create testing datagrabber
138
- dg = PartlyCloudyTestingDataGrabber(reduce_confounds=False)
139
- mc.validate(dg)
143
+ mc.validate(PartlyCloudyTestingDataGrabber(reduce_confounds=False))
140
144
 
141
145
 
142
146
  def test_marker_collection_storage(tmp_path: Path) -> None:
143
- """Test marker collection with storage.
147
+ """Test MarkerCollection stores the output of each marker.
144
148
 
145
149
  Parameters
146
150
  ----------
@@ -148,74 +152,47 @@ def test_marker_collection_storage(tmp_path: Path) -> None:
148
152
  The path to the test directory.
149
153
 
150
154
  """
151
- markers = [
152
- ParcelAggregation(
153
- parcellation="TianxS2x3TxMNInonlinear2009cAsym",
154
- method="mean",
155
- name="tian_mean",
156
- ),
157
- ParcelAggregation(
158
- parcellation="TianxS2x3TxMNInonlinear2009cAsym",
159
- method="std",
160
- name="tian_std",
161
- ),
162
- ParcelAggregation(
163
- parcellation="TianxS2x3TxMNInonlinear2009cAsym",
164
- method="trim_mean",
165
- method_params={"proportiontocut": 0.1},
166
- name="tian_trim_mean90",
167
- ),
168
- ]
169
- # Setup datagrabber
170
- dg = PartlyCloudyTestingDataGrabber()
171
- # Setup storage
172
- storage = SQLiteFeatureStorage(
173
- tmp_path / "test_marker_collection_storage.sqlite"
174
- )
175
- mc = MarkerCollection(
176
- markers=markers, # type: ignore
177
- storage=storage,
178
- datareader=DefaultDataReader(),
179
- )
180
- mc.validate(dg)
181
- assert mc._storage is not None
182
- assert mc._storage.uri == storage.uri
183
- with dg:
184
- input = dg["sub-01"]
185
- out = mc.fit(input)
186
- assert out is None
155
+ storage = HDF5FeatureStorage(uri=tmp_path / "features.hdf5")
156
+ mc = MarkerCollection(markers=_markers(), storage=storage) # type: ignore
157
+ assert mc._storage is storage
158
+ # Nothing is returned when storing
159
+ assert _fit(mc) is None
160
+
161
+ # The stored features are the same as the ones returned without storage
162
+ out = _fit(MarkerCollection(markers=_markers())) # type: ignore
163
+ assert out is not None
164
+ features = storage.list_features()
165
+ assert len(features) == len(out)
166
+ for md5, feature in features.items():
167
+ # Features are named "<type>_<marker>_<feature>"
168
+ name = feature["name"].removeprefix("BOLD_")
169
+ name = name.removesuffix("_aggregation")
170
+ aggregation = out[name]["BOLD"]["aggregation"]
171
+ stored = storage.read_df(feature_md5=md5)
172
+ # The storage casts to float32
173
+ assert_allclose(
174
+ stored[aggregation["col_names"]].to_numpy(),
175
+ aggregation["data"],
176
+ rtol=1e-6,
177
+ )
178
+
179
+
180
+ def test_marker_collection_marker_changes_data() -> None:
181
+ """Test markers cannot change the data shared with the other markers."""
182
+
183
+ class ChangingMarker(BaseMarker):
184
+ """Marker that replaces the BOLD data."""
185
+
186
+ _MARKER_INOUT_MAPPINGS = { # noqa: RUF012
187
+ DataType.BOLD: {"feat": StorageType.Vector},
188
+ }
189
+
190
+ def compute(self, input, extra_input=None):
191
+ input["data"] = "changed"
192
+ return {"feat": {"data": np.zeros((1, 1)), "col_names": ["x"]}}
187
193
 
188
- mc2 = MarkerCollection(
189
- markers=markers,
190
- datareader=DefaultDataReader(), # type: ignore
194
+ mc = MarkerCollection(
195
+ markers=[ChangingMarker(name="changing"), *_markers()] # type: ignore
191
196
  )
192
- mc2.validate(dg)
193
- assert mc2._storage is None
194
-
195
- with dg:
196
- input = dg["sub-01"]
197
- out = mc2.fit(input)
198
-
199
- features = storage.list_features()
200
- assert len(features) == 3
201
-
202
- feature_md5 = next(iter(features.keys()))
203
- t_feature = storage.read_df(feature_md5=feature_md5)
204
- fname = "tian_mean"
205
- t_data = out[fname]["BOLD"]["aggregation"]["data"] # type: ignore
206
- cols = out[fname]["BOLD"]["aggregation"]["col_names"] # type: ignore
207
- assert_array_equal(t_feature[cols].values, t_data) # type: ignore
208
-
209
- feature_md5 = list(features.keys())[1]
210
- t_feature = storage.read_df(feature_md5=feature_md5)
211
- fname = "tian_std"
212
- t_data = out[fname]["BOLD"]["aggregation"]["data"] # type: ignore
213
- cols = out[fname]["BOLD"]["aggregation"]["col_names"] # type: ignore
214
- assert_array_equal(t_feature[cols].values, t_data) # type: ignore
215
-
216
- feature_md5 = list(features.keys())[2]
217
- t_feature = storage.read_df(feature_md5=feature_md5)
218
- fname = "tian_trim_mean90"
219
- t_data = out[fname]["BOLD"]["aggregation"]["data"] # type: ignore
220
- cols = out[fname]["BOLD"]["aggregation"]["col_names"] # type: ignore
221
- assert_array_equal(t_feature[cols].values, t_data) # type: ignore
197
+ with pytest.raises(RuntimeError, match=r"changing changed .*BOLD.data"):
198
+ _fit(mc)