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
@@ -7,7 +7,6 @@ from typing import (
7
7
  Any,
8
8
  ClassVar,
9
9
  Literal,
10
- Union,
11
10
  )
12
11
 
13
12
  import nibabel as nib
@@ -16,7 +15,7 @@ from numpy.typing import ArrayLike
16
15
 
17
16
  from ...pipeline import WorkDirManager
18
17
  from ...typing import Dependencies
19
- from ...utils import logger
18
+ from ..base import logger
20
19
 
21
20
 
22
21
  __all__ = ["NilearnSmoothing"]
@@ -34,7 +33,7 @@ class NilearnSmoothing:
34
33
  def preprocess(
35
34
  self,
36
35
  input: dict[str, Any],
37
- fwhm: Union[int, float, ArrayLike, Literal["fast"], None],
36
+ fwhm: int | float | ArrayLike | Literal["fast"] | None,
38
37
  ) -> dict[str, Any]:
39
38
  """Preprocess using nilearn.
40
39
 
@@ -75,7 +74,7 @@ class NilearnSmoothing:
75
74
  smoothed_img = nimg.smooth_img(imgs=input["data"], fwhm=fwhm)
76
75
 
77
76
  # Save smoothed output
78
- smoothed_img_path = element_tempdir / "smoothed_data.nii.gz"
77
+ smoothed_img_path = element_tempdir / "smoothed_data.nii"
79
78
  nib.save(smoothed_img, smoothed_img_path)
80
79
 
81
80
  logger.debug("Updating smoothed data")
@@ -83,7 +82,10 @@ class NilearnSmoothing:
83
82
  {
84
83
  # Update path to sync with "data"
85
84
  "path": smoothed_img_path,
86
- "data": smoothed_img,
85
+ # Update data, loaded from the file to not keep the
86
+ # in-memory image (memory-mapped, as the file is not
87
+ # compressed)
88
+ "data": nib.load(smoothed_img_path),
87
89
  }
88
90
  )
89
91
 
@@ -4,18 +4,44 @@
4
4
  # License: AGPL
5
5
 
6
6
  from collections.abc import Sequence
7
- from typing import Any, ClassVar, Optional, Union
7
+ from enum import StrEnum
8
+ from typing import (
9
+ Annotated,
10
+ Any,
11
+ ClassVar,
12
+ Literal,
13
+ )
14
+
15
+ from pydantic import BeforeValidator
8
16
 
9
17
  from ...api.decorators import register_preprocessor
18
+ from ...datagrabber import DataType
10
19
  from ...typing import ConditionalDependencies
11
- from ...utils import logger, raise_error
12
- from ..base import BasePreprocessor
20
+ from ...utils import ensure_list_or_none
21
+ from ..base import BasePreprocessor, logger
13
22
  from ._afni_smoothing import AFNISmoothing
14
23
  from ._fsl_smoothing import FSLSmoothing
15
24
  from ._nilearn_smoothing import NilearnSmoothing
16
25
 
17
26
 
18
- __all__ = ["Smoothing"]
27
+ __all__ = ["Smoothing", "SmoothingImpl"]
28
+
29
+
30
+ class SmoothingImpl(StrEnum):
31
+ """Accepted smoothing implementations.
32
+
33
+ * ``nilearn`` : :func:`nilearn.image.smooth_img`
34
+ * ``afni`` : AFNI's ``3dBlurToFWHM``
35
+ * ``fsl`` : FSL SUSAN's ``susan``
36
+
37
+ """
38
+
39
+ nilearn = "nilearn"
40
+ afni = "afni"
41
+ fsl = "fsl"
42
+
43
+
44
+ _on = Literal[DataType.T1w, DataType.T2w, DataType.BOLD]
19
45
 
20
46
 
21
47
  @register_preprocessor
@@ -24,18 +50,13 @@ class Smoothing(BasePreprocessor):
24
50
 
25
51
  Parameters
26
52
  ----------
27
- using : {"nilearn", "afni", "fsl"}
28
- Implementation to use for smoothing:
29
-
30
- * "nilearn" : Use :func:`nilearn.image.smooth_img`
31
- * "afni" : Use AFNI's ``3dBlurToFWHM``
32
- * "fsl" : Use FSL SUSAN's ``susan``
33
-
34
- on : {"T1w", "T2w", "BOLD"} or list of the options
35
- The data type to apply smoothing to.
53
+ using : :enum:`.SmoothingImpl`
54
+ on : {``DataType.T1w``, ``DataType.T2w``, ``DataType.BOLD``} or \
55
+ list of them
56
+ The data type(s) to apply smoothing to.
36
57
  smoothing_params : dict, optional
37
58
  Extra parameters for smoothing as a dictionary (default None).
38
- If ``using="nilearn"``, then the valid keys are:
59
+ If ``using=SmoothingImpl.nilearn``, then the valid keys are:
39
60
 
40
61
  * ``fmhw`` : scalar, ``numpy.ndarray``, tuple or list of scalar, \
41
62
  "fast" or None
@@ -52,13 +73,13 @@ class Smoothing(BasePreprocessor):
52
73
  - If None, no filtering is performed (useful when just removal of
53
74
  non-finite values is needed).
54
75
 
55
- else if ``using="afni"``, then the valid keys are:
76
+ else if ``using=SmoothingImpl.afni``, then the valid keys are:
56
77
 
57
78
  * ``fwhm`` : int or float
58
79
  Smooth until the value. AFNI estimates the smoothing and then
59
80
  applies smoothing to reach ``fwhm``.
60
81
 
61
- else if ``using="fsl"``, then the valid keys are:
82
+ else if ``using=SmoothingImpl.fsl``, then the valid keys are:
62
83
 
63
84
  * ``brightness_threshold`` : float
64
85
  Threshold to discriminate between noise and the underlying image.
@@ -71,43 +92,41 @@ class Smoothing(BasePreprocessor):
71
92
 
72
93
  _CONDITIONAL_DEPENDENCIES: ClassVar[ConditionalDependencies] = [
73
94
  {
74
- "using": "nilearn",
75
- "depends_on": NilearnSmoothing,
95
+ "using": SmoothingImpl.nilearn,
96
+ "depends_on": [NilearnSmoothing],
76
97
  },
77
98
  {
78
- "using": "afni",
79
- "depends_on": AFNISmoothing,
99
+ "using": SmoothingImpl.afni,
100
+ "depends_on": [AFNISmoothing],
80
101
  },
81
102
  {
82
- "using": "fsl",
83
- "depends_on": FSLSmoothing,
103
+ "using": SmoothingImpl.fsl,
104
+ "depends_on": [FSLSmoothing],
84
105
  },
85
106
  ]
86
- _VALID_DATA_TYPES: ClassVar[Sequence[str]] = ["T1w", "T2w", "BOLD"]
107
+ _VALID_DATA_TYPES: ClassVar[Sequence[DataType]] = [
108
+ DataType.T1w,
109
+ DataType.T2w,
110
+ DataType.BOLD,
111
+ ]
87
112
 
88
- def __init__(
89
- self,
90
- using: str,
91
- on: Union[list[str], str],
92
- smoothing_params: Optional[dict] = None,
93
- ) -> None:
94
- """Initialize the class."""
95
- # Validate `using` parameter
96
- valid_using = [dep["using"] for dep in self._CONDITIONAL_DEPENDENCIES]
97
- if using not in valid_using:
98
- raise_error(
99
- f"Invalid value for `using`, should be one of: {valid_using}"
100
- )
101
- self.using = using
113
+ using: SmoothingImpl
114
+ on: Annotated[
115
+ _on | list[_on],
116
+ BeforeValidator(ensure_list_or_none),
117
+ ]
118
+ smoothing_params: dict | None = None
119
+
120
+ def validate_preprocessor_params(self) -> None:
121
+ """Run extra logical validation for preprocessor."""
102
122
  self.smoothing_params = (
103
- smoothing_params if smoothing_params is not None else {}
123
+ self.smoothing_params if self.smoothing_params is not None else {}
104
124
  )
105
- super().__init__(on=on)
106
125
 
107
126
  def preprocess(
108
127
  self,
109
128
  input: dict[str, Any],
110
- extra_input: Optional[dict[str, Any]] = None,
129
+ extra_input: dict[str, Any] | None = None,
111
130
  ) -> dict[str, Any]:
112
131
  """Preprocess.
113
132
 
@@ -3,14 +3,20 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
+ from pathlib import Path
7
+
8
+ import nibabel as nib
6
9
  import pytest
7
10
 
8
11
  from junifer.datareader import DefaultDataReader
9
12
  from junifer.pipeline.utils import _check_afni, _check_fsl
10
- from junifer.preprocess import Smoothing
13
+ from junifer.preprocess import Smoothing, SmoothingImpl
11
14
  from junifer.testing.datagrabbers import SPMAuditoryTestingDataGrabber
12
15
 
13
16
 
17
+ pytestmark = pytest.mark.external
18
+
19
+
14
20
  @pytest.mark.parametrize(
15
21
  "data_type",
16
22
  ["T1w", "BOLD"],
@@ -24,17 +30,22 @@ def test_Smoothing_nilearn(data_type: str) -> None:
24
30
  The parametrized data type.
25
31
 
26
32
  """
27
- with SPMAuditoryTestingDataGrabber() as dg:
33
+ with SPMAuditoryTestingDataGrabber(n_timepoints=10) as dg:
28
34
  # Read data
29
35
  element_data = DefaultDataReader().fit_transform(dg["sub001"])
30
36
  # Preprocess data
31
37
  output = Smoothing(
32
- using="nilearn",
38
+ using=SmoothingImpl.nilearn,
33
39
  on=data_type,
34
40
  smoothing_params={"fwhm": "fast"},
35
41
  ).fit_transform(element_data)
36
42
 
37
43
  assert isinstance(output, dict)
44
+ # The data is loaded from the saved file, not kept in memory
45
+ assert nib.is_proxy(output[data_type]["data"].dataobj)
46
+ assert output[data_type]["data"].get_filename() == str(
47
+ output[data_type]["path"]
48
+ )
38
49
 
39
50
 
40
51
  @pytest.mark.parametrize(
@@ -44,26 +55,35 @@ def test_Smoothing_nilearn(data_type: str) -> None:
44
55
  @pytest.mark.skipif(
45
56
  _check_afni() is False, reason="requires AFNI to be in PATH"
46
57
  )
47
- def test_Smoothing_afni(data_type: str) -> None:
58
+ def test_Smoothing_afni(
59
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch, data_type: str
60
+ ) -> None:
48
61
  """Test Smoothing using AFNI.
49
62
 
50
63
  Parameters
51
64
  ----------
65
+ tmp_path : pathlib.Path
66
+ The path to the test directory.
67
+ monkeypatch : pytest.MonkeyPatch
68
+ The pytest.MonkeyPatch object.
52
69
  data_type : str
53
70
  The parametrized data type.
54
71
 
55
72
  """
56
- with SPMAuditoryTestingDataGrabber() as dg:
73
+ # Run in an empty directory, to check that no files are written in it
74
+ monkeypatch.chdir(tmp_path)
75
+ with SPMAuditoryTestingDataGrabber(n_timepoints=10) as dg:
57
76
  # Read data
58
77
  element_data = DefaultDataReader().fit_transform(dg["sub001"])
59
78
  # Preprocess data
60
79
  output = Smoothing(
61
- using="afni",
80
+ using=SmoothingImpl.afni,
62
81
  on=data_type,
63
82
  smoothing_params={"fwhm": 3},
64
83
  ).fit_transform(element_data)
65
84
 
66
85
  assert isinstance(output, dict)
86
+ assert list(tmp_path.iterdir()) == []
67
87
 
68
88
 
69
89
  @pytest.mark.parametrize(
@@ -80,12 +100,12 @@ def test_Smoothing_fsl(data_type: str) -> None:
80
100
  The parametrized data type.
81
101
 
82
102
  """
83
- with SPMAuditoryTestingDataGrabber() as dg:
103
+ with SPMAuditoryTestingDataGrabber(n_timepoints=10) as dg:
84
104
  # Read data
85
105
  element_data = DefaultDataReader().fit_transform(dg["sub001"])
86
106
  # Preprocess data
87
107
  output = Smoothing(
88
- using="fsl",
108
+ using=SmoothingImpl.fsl,
89
109
  on=data_type,
90
110
  smoothing_params={"brightness_threshold": 10.0, "fwhm": 3.0},
91
111
  ).fit_transform(element_data)
@@ -15,7 +15,7 @@ from junifer.preprocess.base import BasePreprocessor
15
15
  def test_base_preprocessor_abstractness() -> None:
16
16
  """Test BasePreprocessor is abstract base class."""
17
17
  with pytest.raises(TypeError, match=r"abstract"):
18
- BasePreprocessor(on=["BOLD"]) # type: ignore
18
+ BasePreprocessor(on="BOLD")
19
19
 
20
20
 
21
21
  def test_base_preprocessor_subclassing() -> None:
@@ -25,9 +25,7 @@ def test_base_preprocessor_subclassing() -> None:
25
25
  class MyBasePreprocessor(BasePreprocessor):
26
26
  _VALID_DATA_TYPES: ClassVar[Sequence[str]] = ["BOLD", "T1w"]
27
27
 
28
- def __init__(self, on):
29
- self.parameter = 1
30
- super().__init__(on=on)
28
+ parameter: int = 1
31
29
 
32
30
  def preprocess(self, input, extra_input=None):
33
31
  input["data"] = f"modified_{input['data']}"
@@ -60,7 +58,7 @@ def test_base_preprocessor_subclassing() -> None:
60
58
  },
61
59
  },
62
60
  }
63
- prep = MyBasePreprocessor(on=["BOLD"])
61
+ prep = MyBasePreprocessor(on="BOLD")
64
62
 
65
63
  with pytest.raises(ValueError, match="not have the required data"):
66
64
  prep.validate_input(["T1w"])
@@ -75,10 +73,20 @@ def test_base_preprocessor_subclassing() -> None:
75
73
 
76
74
  meta = output["BOLD"]["meta"]
77
75
  assert "preprocess" in meta
78
- assert "class" in meta["preprocess"]
79
- assert "MyBasePreprocessor" == meta["preprocess"]["class"]
80
- assert "parameter" in meta["preprocess"]
81
- assert 1 == meta["preprocess"]["parameter"]
76
+ assert len(meta["preprocess"]) == 1
77
+ assert "class" in meta["preprocess"][0]
78
+ assert "MyBasePreprocessor" == meta["preprocess"][0]["class"]
79
+ assert "parameter" in meta["preprocess"][0]
80
+ assert 1 == meta["preprocess"][0]["parameter"]
81
+
82
+ # A second preprocessor is recorded after the first one
83
+ output = MyBasePreprocessor(on="BOLD", parameter=2).fit_transform(
84
+ input=output
85
+ )
86
+ assert [x["parameter"] for x in output["BOLD"]["meta"]["preprocess"]] == [
87
+ 1,
88
+ 2,
89
+ ]
82
90
 
83
91
  assert "T1w" in output
84
92
  assert "data" in output["T1w"]
@@ -3,8 +3,7 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
- from typing import Optional
7
-
6
+ import nibabel as nib
8
7
  import pytest
9
8
 
10
9
  from junifer.datareader import DefaultDataReader
@@ -12,12 +11,15 @@ from junifer.preprocess import TemporalFilter
12
11
  from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
13
12
 
14
13
 
14
+ pytestmark = pytest.mark.external
15
+
16
+
15
17
  @pytest.mark.parametrize(
16
18
  "detrend, standardize, low_pass, high_pass, t_r, masks",
17
19
  (
18
20
  [
19
21
  True,
20
- True,
22
+ "zscore_sample",
21
23
  None,
22
24
  None,
23
25
  None,
@@ -25,7 +27,7 @@ from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
25
27
  ],
26
28
  [
27
29
  False,
28
- True,
30
+ "zscore_sample",
29
31
  0.1,
30
32
  None,
31
33
  None,
@@ -33,15 +35,15 @@ from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
33
35
  ],
34
36
  [
35
37
  True,
36
- False,
38
+ None,
37
39
  None,
38
40
  0.08,
39
41
  None,
40
- "compute_background_mask",
42
+ ["compute_background_mask"],
41
43
  ],
42
44
  [
43
45
  False,
44
- False,
46
+ "psc",
45
47
  None,
46
48
  None,
47
49
  2,
@@ -49,7 +51,7 @@ from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
49
51
  ],
50
52
  [
51
53
  True,
52
- True,
54
+ "zscore_sample",
53
55
  0.1,
54
56
  0.08,
55
57
  2,
@@ -59,11 +61,11 @@ from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
59
61
  )
60
62
  def test_TemporalFilter(
61
63
  detrend: bool,
62
- standardize: bool,
63
- low_pass: Optional[float],
64
- high_pass: Optional[float],
65
- t_r: Optional[float],
66
- masks: Optional[str],
64
+ standardize: str | None,
65
+ low_pass: float | None,
66
+ high_pass: float | None,
67
+ t_r: float | None,
68
+ masks: str | list[str] | None,
67
69
  ) -> None:
68
70
  """Test TemporalFilter.
69
71
 
@@ -71,15 +73,15 @@ def test_TemporalFilter(
71
73
  ----------
72
74
  detrend : bool
73
75
  The parametrized detrending flag.
74
- standardize : bool
75
- The parametrized standardization flag.
76
+ standardize : str or None
77
+ The parametrized standardization strategy.
76
78
  low_pass : float or None
77
79
  The parametrized low pass value.
78
80
  high_pass : float or None
79
81
  The parametrized high pass value.
80
82
  t_r : float or None
81
83
  The parametrized repetition time.
82
- masks : str or None
84
+ masks : str, list of str or None
83
85
  The parametrized mask.
84
86
 
85
87
  """
@@ -97,3 +99,22 @@ def test_TemporalFilter(
97
99
  ).fit_transform(element_data)
98
100
 
99
101
  assert isinstance(output, dict)
102
+ # The data is loaded from the saved file, not kept in memory
103
+ assert nib.is_proxy(output["BOLD"]["data"].dataobj)
104
+ assert output["BOLD"]["data"].get_filename() == str(
105
+ output["BOLD"]["path"]
106
+ )
107
+
108
+
109
+ @pytest.mark.parametrize("standardize", [True, False])
110
+ def test_TemporalFilter_standardize_bool(standardize: bool) -> None:
111
+ """Test TemporalFilter error for boolean standardize.
112
+
113
+ Parameters
114
+ ----------
115
+ standardize : bool
116
+ The parametrized standardization flag.
117
+
118
+ """
119
+ with pytest.raises(ValueError, match="does not accept booleans"):
120
+ TemporalFilter(standardize=standardize)
@@ -4,8 +4,8 @@
4
4
  # License: AGPL
5
5
 
6
6
  from contextlib import AbstractContextManager, nullcontext
7
- from typing import Optional
8
7
 
8
+ import nibabel as nib
9
9
  import pytest
10
10
 
11
11
  from junifer.datareader import DefaultDataReader
@@ -13,6 +13,9 @@ from junifer.preprocess import TemporalSlicer
13
13
  from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
14
14
 
15
15
 
16
+ pytestmark = pytest.mark.external
17
+
18
+
16
19
  @pytest.mark.parametrize(
17
20
  "start, stop, duration, t_r, expected_dim, expect",
18
21
  (
@@ -75,14 +78,6 @@ from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
75
78
  pytest.raises(RuntimeError, match="`stop` should be None"),
76
79
  ],
77
80
  [10.0, None, 30.0, None, 30, nullcontext()],
78
- [
79
- -1.0,
80
- None,
81
- None,
82
- None,
83
- 84,
84
- pytest.raises(ValueError, match="`start` cannot be negative"),
85
- ],
86
81
  [
87
82
  0.0,
88
83
  500.0,
@@ -95,9 +90,9 @@ from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
95
90
  )
96
91
  def test_TemporalSlicer(
97
92
  start: float,
98
- stop: Optional[float],
99
- duration: Optional[float],
100
- t_r: Optional[float],
93
+ stop: float | None,
94
+ duration: float | None,
95
+ t_r: float | None,
101
96
  expected_dim: int,
102
97
  expect: AbstractContextManager,
103
98
  ) -> None:
@@ -134,5 +129,10 @@ def test_TemporalSlicer(
134
129
 
135
130
  # Check image data dim
136
131
  assert output["BOLD"]["data"].shape[3] == expected_dim
132
+ # The data is loaded from the saved file, not kept in memory
133
+ assert nib.is_proxy(output["BOLD"]["data"].dataobj)
134
+ assert output["BOLD"]["data"].get_filename() == str(
135
+ output["BOLD"]["path"]
136
+ )
137
137
  # Check confounds dim
138
138
  assert output["BOLD"]["confounds"]["data"].shape[0] == expected_dim
@@ -1,3 +1,3 @@
1
- __all__ = ["SpaceWarper"]
1
+ __all__ = ["SpaceWarper", "SpaceWarpingImpl"]
2
2
 
3
- from .space_warper import SpaceWarper
3
+ from .space_warper import SpaceWarper, SpaceWarpingImpl