junifer 0.0.5.dev240__py3-none-any.whl → 0.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. junifer/__init__.py +2 -31
  2. junifer/__init__.pyi +37 -0
  3. junifer/_version.py +9 -4
  4. junifer/api/__init__.py +3 -5
  5. junifer/api/__init__.pyi +4 -0
  6. junifer/api/decorators.py +14 -19
  7. junifer/api/functions.py +165 -109
  8. junifer/api/py.typed +0 -0
  9. junifer/api/queue_context/__init__.py +2 -4
  10. junifer/api/queue_context/__init__.pyi +5 -0
  11. junifer/api/queue_context/gnu_parallel_local_adapter.py +22 -6
  12. junifer/api/queue_context/htcondor_adapter.py +23 -6
  13. junifer/api/queue_context/py.typed +0 -0
  14. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +3 -3
  15. junifer/api/queue_context/tests/test_htcondor_adapter.py +3 -3
  16. junifer/api/tests/test_functions.py +168 -74
  17. junifer/cli/__init__.py +24 -0
  18. junifer/cli/__init__.pyi +3 -0
  19. junifer/{api → cli}/cli.py +141 -125
  20. junifer/cli/parser.py +235 -0
  21. junifer/cli/py.typed +0 -0
  22. junifer/{api → cli}/tests/test_cli.py +8 -8
  23. junifer/{api/tests/test_api_utils.py → cli/tests/test_cli_utils.py} +5 -4
  24. junifer/{api → cli}/tests/test_parser.py +2 -2
  25. junifer/{api → cli}/utils.py +6 -16
  26. junifer/configs/juseless/__init__.py +2 -2
  27. junifer/configs/juseless/__init__.pyi +3 -0
  28. junifer/configs/juseless/datagrabbers/__init__.py +2 -12
  29. junifer/configs/juseless/datagrabbers/__init__.pyi +13 -0
  30. junifer/configs/juseless/datagrabbers/ixi_vbm.py +2 -2
  31. junifer/configs/juseless/datagrabbers/py.typed +0 -0
  32. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -2
  33. junifer/configs/juseless/datagrabbers/ucla.py +4 -4
  34. junifer/configs/juseless/py.typed +0 -0
  35. junifer/conftest.py +25 -0
  36. junifer/data/__init__.py +2 -42
  37. junifer/data/__init__.pyi +29 -0
  38. junifer/data/_dispatch.py +248 -0
  39. junifer/data/coordinates/__init__.py +9 -0
  40. junifer/data/coordinates/__init__.pyi +5 -0
  41. junifer/data/coordinates/_ants_coordinates_warper.py +104 -0
  42. junifer/data/coordinates/_coordinates.py +385 -0
  43. junifer/data/coordinates/_fsl_coordinates_warper.py +81 -0
  44. junifer/data/{tests → coordinates/tests}/test_coordinates.py +26 -33
  45. junifer/data/masks/__init__.py +9 -0
  46. junifer/data/masks/__init__.pyi +6 -0
  47. junifer/data/masks/_ants_mask_warper.py +177 -0
  48. junifer/data/masks/_fsl_mask_warper.py +106 -0
  49. junifer/data/masks/_masks.py +802 -0
  50. junifer/data/{tests → masks/tests}/test_masks.py +67 -63
  51. junifer/data/parcellations/__init__.py +9 -0
  52. junifer/data/parcellations/__init__.pyi +6 -0
  53. junifer/data/parcellations/_ants_parcellation_warper.py +166 -0
  54. junifer/data/parcellations/_fsl_parcellation_warper.py +89 -0
  55. junifer/data/parcellations/_parcellations.py +1388 -0
  56. junifer/data/{tests → parcellations/tests}/test_parcellations.py +165 -295
  57. junifer/data/pipeline_data_registry_base.py +76 -0
  58. junifer/data/py.typed +0 -0
  59. junifer/data/template_spaces.py +44 -79
  60. junifer/data/tests/test_data_utils.py +1 -2
  61. junifer/data/tests/test_template_spaces.py +8 -4
  62. junifer/data/utils.py +109 -4
  63. junifer/datagrabber/__init__.py +2 -26
  64. junifer/datagrabber/__init__.pyi +27 -0
  65. junifer/datagrabber/aomic/__init__.py +2 -4
  66. junifer/datagrabber/aomic/__init__.pyi +5 -0
  67. junifer/datagrabber/aomic/id1000.py +81 -52
  68. junifer/datagrabber/aomic/piop1.py +83 -55
  69. junifer/datagrabber/aomic/piop2.py +85 -56
  70. junifer/datagrabber/aomic/py.typed +0 -0
  71. junifer/datagrabber/aomic/tests/test_id1000.py +19 -12
  72. junifer/datagrabber/aomic/tests/test_piop1.py +52 -18
  73. junifer/datagrabber/aomic/tests/test_piop2.py +50 -17
  74. junifer/datagrabber/base.py +22 -18
  75. junifer/datagrabber/datalad_base.py +71 -34
  76. junifer/datagrabber/dmcc13_benchmark.py +31 -18
  77. junifer/datagrabber/hcp1200/__init__.py +2 -3
  78. junifer/datagrabber/hcp1200/__init__.pyi +4 -0
  79. junifer/datagrabber/hcp1200/datalad_hcp1200.py +3 -3
  80. junifer/datagrabber/hcp1200/hcp1200.py +26 -15
  81. junifer/datagrabber/hcp1200/py.typed +0 -0
  82. junifer/datagrabber/hcp1200/tests/test_hcp1200.py +8 -2
  83. junifer/datagrabber/multiple.py +14 -9
  84. junifer/datagrabber/pattern.py +132 -96
  85. junifer/datagrabber/pattern_validation_mixin.py +206 -94
  86. junifer/datagrabber/py.typed +0 -0
  87. junifer/datagrabber/tests/test_datalad_base.py +27 -12
  88. junifer/datagrabber/tests/test_dmcc13_benchmark.py +28 -11
  89. junifer/datagrabber/tests/test_multiple.py +48 -2
  90. junifer/datagrabber/tests/test_pattern_datalad.py +1 -1
  91. junifer/datagrabber/tests/test_pattern_validation_mixin.py +6 -6
  92. junifer/datareader/__init__.py +2 -2
  93. junifer/datareader/__init__.pyi +3 -0
  94. junifer/datareader/default.py +6 -6
  95. junifer/datareader/py.typed +0 -0
  96. junifer/external/nilearn/__init__.py +2 -3
  97. junifer/external/nilearn/__init__.pyi +4 -0
  98. junifer/external/nilearn/junifer_connectivity_measure.py +25 -17
  99. junifer/external/nilearn/junifer_nifti_spheres_masker.py +4 -4
  100. junifer/external/nilearn/py.typed +0 -0
  101. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +17 -16
  102. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +2 -3
  103. junifer/markers/__init__.py +2 -38
  104. junifer/markers/__init__.pyi +37 -0
  105. junifer/markers/base.py +11 -14
  106. junifer/markers/brainprint.py +12 -14
  107. junifer/markers/complexity/__init__.py +2 -18
  108. junifer/markers/complexity/__init__.pyi +17 -0
  109. junifer/markers/complexity/complexity_base.py +9 -11
  110. junifer/markers/complexity/hurst_exponent.py +7 -7
  111. junifer/markers/complexity/multiscale_entropy_auc.py +7 -7
  112. junifer/markers/complexity/perm_entropy.py +7 -7
  113. junifer/markers/complexity/py.typed +0 -0
  114. junifer/markers/complexity/range_entropy.py +7 -7
  115. junifer/markers/complexity/range_entropy_auc.py +7 -7
  116. junifer/markers/complexity/sample_entropy.py +7 -7
  117. junifer/markers/complexity/tests/test_complexity_base.py +1 -1
  118. junifer/markers/complexity/tests/test_hurst_exponent.py +5 -5
  119. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +5 -5
  120. junifer/markers/complexity/tests/test_perm_entropy.py +5 -5
  121. junifer/markers/complexity/tests/test_range_entropy.py +5 -5
  122. junifer/markers/complexity/tests/test_range_entropy_auc.py +5 -5
  123. junifer/markers/complexity/tests/test_sample_entropy.py +5 -5
  124. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +5 -5
  125. junifer/markers/complexity/weighted_perm_entropy.py +7 -7
  126. junifer/markers/ets_rss.py +12 -11
  127. junifer/markers/falff/__init__.py +2 -3
  128. junifer/markers/falff/__init__.pyi +4 -0
  129. junifer/markers/falff/_afni_falff.py +38 -45
  130. junifer/markers/falff/_junifer_falff.py +16 -19
  131. junifer/markers/falff/falff_base.py +7 -11
  132. junifer/markers/falff/falff_parcels.py +9 -9
  133. junifer/markers/falff/falff_spheres.py +8 -8
  134. junifer/markers/falff/py.typed +0 -0
  135. junifer/markers/falff/tests/test_falff_spheres.py +3 -1
  136. junifer/markers/functional_connectivity/__init__.py +2 -12
  137. junifer/markers/functional_connectivity/__init__.pyi +13 -0
  138. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +9 -8
  139. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +8 -8
  140. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +7 -7
  141. junifer/markers/functional_connectivity/functional_connectivity_base.py +13 -12
  142. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +8 -8
  143. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +7 -7
  144. junifer/markers/functional_connectivity/py.typed +0 -0
  145. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +1 -2
  146. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +1 -2
  147. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +6 -6
  148. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +5 -5
  149. junifer/markers/parcel_aggregation.py +22 -17
  150. junifer/markers/py.typed +0 -0
  151. junifer/markers/reho/__init__.py +2 -3
  152. junifer/markers/reho/__init__.pyi +4 -0
  153. junifer/markers/reho/_afni_reho.py +29 -35
  154. junifer/markers/reho/_junifer_reho.py +13 -14
  155. junifer/markers/reho/py.typed +0 -0
  156. junifer/markers/reho/reho_base.py +7 -11
  157. junifer/markers/reho/reho_parcels.py +10 -10
  158. junifer/markers/reho/reho_spheres.py +9 -9
  159. junifer/markers/sphere_aggregation.py +22 -17
  160. junifer/markers/temporal_snr/__init__.py +2 -3
  161. junifer/markers/temporal_snr/__init__.pyi +4 -0
  162. junifer/markers/temporal_snr/py.typed +0 -0
  163. junifer/markers/temporal_snr/temporal_snr_base.py +11 -10
  164. junifer/markers/temporal_snr/temporal_snr_parcels.py +8 -8
  165. junifer/markers/temporal_snr/temporal_snr_spheres.py +7 -7
  166. junifer/markers/tests/test_ets_rss.py +3 -3
  167. junifer/markers/tests/test_parcel_aggregation.py +24 -24
  168. junifer/markers/tests/test_sphere_aggregation.py +6 -6
  169. junifer/markers/utils.py +3 -3
  170. junifer/onthefly/__init__.py +2 -1
  171. junifer/onthefly/_brainprint.py +138 -0
  172. junifer/onthefly/read_transform.py +5 -8
  173. junifer/pipeline/__init__.py +2 -10
  174. junifer/pipeline/__init__.pyi +13 -0
  175. junifer/{markers/collection.py → pipeline/marker_collection.py} +8 -14
  176. junifer/pipeline/pipeline_component_registry.py +294 -0
  177. junifer/pipeline/pipeline_step_mixin.py +15 -11
  178. junifer/pipeline/py.typed +0 -0
  179. junifer/{markers/tests/test_collection.py → pipeline/tests/test_marker_collection.py} +2 -3
  180. junifer/pipeline/tests/test_pipeline_component_registry.py +200 -0
  181. junifer/pipeline/tests/test_pipeline_step_mixin.py +36 -37
  182. junifer/pipeline/tests/test_update_meta_mixin.py +4 -4
  183. junifer/pipeline/tests/test_workdir_manager.py +43 -0
  184. junifer/pipeline/update_meta_mixin.py +21 -17
  185. junifer/pipeline/utils.py +6 -6
  186. junifer/pipeline/workdir_manager.py +19 -5
  187. junifer/preprocess/__init__.py +2 -10
  188. junifer/preprocess/__init__.pyi +11 -0
  189. junifer/preprocess/base.py +10 -10
  190. junifer/preprocess/confounds/__init__.py +2 -2
  191. junifer/preprocess/confounds/__init__.pyi +3 -0
  192. junifer/preprocess/confounds/fmriprep_confound_remover.py +243 -64
  193. junifer/preprocess/confounds/py.typed +0 -0
  194. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +121 -14
  195. junifer/preprocess/py.typed +0 -0
  196. junifer/preprocess/smoothing/__init__.py +2 -2
  197. junifer/preprocess/smoothing/__init__.pyi +3 -0
  198. junifer/preprocess/smoothing/_afni_smoothing.py +40 -40
  199. junifer/preprocess/smoothing/_fsl_smoothing.py +22 -32
  200. junifer/preprocess/smoothing/_nilearn_smoothing.py +35 -14
  201. junifer/preprocess/smoothing/py.typed +0 -0
  202. junifer/preprocess/smoothing/smoothing.py +11 -13
  203. junifer/preprocess/warping/__init__.py +2 -2
  204. junifer/preprocess/warping/__init__.pyi +3 -0
  205. junifer/preprocess/warping/_ants_warper.py +136 -32
  206. junifer/preprocess/warping/_fsl_warper.py +73 -22
  207. junifer/preprocess/warping/py.typed +0 -0
  208. junifer/preprocess/warping/space_warper.py +39 -11
  209. junifer/preprocess/warping/tests/test_space_warper.py +5 -9
  210. junifer/py.typed +0 -0
  211. junifer/stats.py +5 -5
  212. junifer/storage/__init__.py +2 -10
  213. junifer/storage/__init__.pyi +11 -0
  214. junifer/storage/base.py +47 -13
  215. junifer/storage/hdf5.py +95 -33
  216. junifer/storage/pandas_base.py +12 -11
  217. junifer/storage/py.typed +0 -0
  218. junifer/storage/sqlite.py +11 -11
  219. junifer/storage/tests/test_hdf5.py +86 -4
  220. junifer/storage/tests/test_sqlite.py +2 -2
  221. junifer/storage/tests/test_storage_base.py +5 -2
  222. junifer/storage/tests/test_utils.py +33 -7
  223. junifer/storage/utils.py +95 -9
  224. junifer/testing/__init__.py +2 -3
  225. junifer/testing/__init__.pyi +4 -0
  226. junifer/testing/datagrabbers.py +10 -11
  227. junifer/testing/py.typed +0 -0
  228. junifer/testing/registry.py +4 -7
  229. junifer/testing/tests/test_testing_registry.py +9 -17
  230. junifer/tests/test_stats.py +2 -2
  231. junifer/typing/__init__.py +9 -0
  232. junifer/typing/__init__.pyi +31 -0
  233. junifer/typing/_typing.py +68 -0
  234. junifer/utils/__init__.py +2 -12
  235. junifer/utils/__init__.pyi +18 -0
  236. junifer/utils/_config.py +110 -0
  237. junifer/utils/_yaml.py +16 -0
  238. junifer/utils/helpers.py +6 -6
  239. junifer/utils/logging.py +117 -8
  240. junifer/utils/py.typed +0 -0
  241. junifer/{pipeline → utils}/singleton.py +19 -14
  242. junifer/utils/tests/test_config.py +59 -0
  243. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/METADATA +43 -38
  244. junifer-0.0.6.dist-info/RECORD +350 -0
  245. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/WHEEL +1 -1
  246. junifer-0.0.6.dist-info/entry_points.txt +2 -0
  247. junifer/api/parser.py +0 -118
  248. junifer/data/coordinates.py +0 -408
  249. junifer/data/masks.py +0 -670
  250. junifer/data/parcellations.py +0 -1828
  251. junifer/pipeline/registry.py +0 -177
  252. junifer/pipeline/tests/test_registry.py +0 -150
  253. junifer-0.0.5.dev240.dist-info/RECORD +0 -275
  254. junifer-0.0.5.dev240.dist-info/entry_points.txt +0 -2
  255. /junifer/{api → cli}/tests/data/gmd_mean.yaml +0 -0
  256. /junifer/{api → cli}/tests/data/gmd_mean_htcondor.yaml +0 -0
  257. /junifer/{api → cli}/tests/data/partly_cloudy_agg_mean_tian.yml +0 -0
  258. /junifer/data/{VOIs → coordinates/VOIs}/meta/AutobiographicalMemory_VOIs.txt +0 -0
  259. /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAC_VOIs.txt +0 -0
  260. /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAR_VOIs.txt +0 -0
  261. /junifer/data/{VOIs → coordinates/VOIs}/meta/DMNBuckner_VOIs.txt +0 -0
  262. /junifer/data/{VOIs → coordinates/VOIs}/meta/Dosenbach2010_MNI_VOIs.txt +0 -0
  263. /junifer/data/{VOIs → coordinates/VOIs}/meta/Empathy_VOIs.txt +0 -0
  264. /junifer/data/{VOIs → coordinates/VOIs}/meta/Motor_VOIs.txt +0 -0
  265. /junifer/data/{VOIs → coordinates/VOIs}/meta/MultiTask_VOIs.txt +0 -0
  266. /junifer/data/{VOIs → coordinates/VOIs}/meta/PhysioStress_VOIs.txt +0 -0
  267. /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2011_MNI_VOIs.txt +0 -0
  268. /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2013_MNI_VOIs.tsv +0 -0
  269. /junifer/data/{VOIs → coordinates/VOIs}/meta/Rew_VOIs.txt +0 -0
  270. /junifer/data/{VOIs → coordinates/VOIs}/meta/Somatosensory_VOIs.txt +0 -0
  271. /junifer/data/{VOIs → coordinates/VOIs}/meta/ToM_VOIs.txt +0 -0
  272. /junifer/data/{VOIs → coordinates/VOIs}/meta/VigAtt_VOIs.txt +0 -0
  273. /junifer/data/{VOIs → coordinates/VOIs}/meta/WM_VOIs.txt +0 -0
  274. /junifer/data/{VOIs → coordinates/VOIs}/meta/eMDN_VOIs.txt +0 -0
  275. /junifer/data/{VOIs → coordinates/VOIs}/meta/eSAD_VOIs.txt +0 -0
  276. /junifer/data/{VOIs → coordinates/VOIs}/meta/extDMN_VOIs.txt +0 -0
  277. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info/licenses}/AUTHORS.rst +0 -0
  278. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info/licenses}/LICENSE.md +0 -0
  279. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/top_level.txt +0 -0
@@ -5,12 +5,12 @@
5
5
  # Synchon Mandal <s.mandal@fz-juelich.de>
6
6
  # License: AGPL
7
7
 
8
- from typing import List
9
8
 
10
9
  import numpy as np
11
10
  import pandas as pd
12
11
  import pytest
13
12
  from nilearn._utils.exceptions import DimensionError
13
+ from nilearn.interfaces.fmriprep.load_confounds_utils import prepare_output
14
14
  from numpy.testing import assert_array_equal, assert_raises
15
15
  from pandas.testing import assert_frame_equal
16
16
 
@@ -47,7 +47,7 @@ def test_fMRIPrepConfoundRemover_init() -> None:
47
47
  ["T1w", "BOLD"],
48
48
  ],
49
49
  )
50
- def test_fMRIPrepConfoundRemover_validate_input(input_: List[str]) -> None:
50
+ def test_fMRIPrepConfoundRemover_validate_input(input_: list[str]) -> None:
51
51
  """Test fMRIPrepConfoundRemover validate_input.
52
52
 
53
53
  Parameters
@@ -212,7 +212,7 @@ def test_fMRIPrepConfoundRemover__process_fmriprep_spec() -> None:
212
212
  )
213
213
 
214
214
  msg = r"Missing basic confounds: \['white_matter'\]"
215
- with pytest.raises(ValueError, match=msg):
215
+ with pytest.raises(RuntimeError, match=msg):
216
216
  confound_remover._process_fmriprep_spec({"data": confounds_df})
217
217
 
218
218
  var_names = ["csf", "white_matter"]
@@ -221,7 +221,7 @@ def test_fMRIPrepConfoundRemover__process_fmriprep_spec() -> None:
221
221
  )
222
222
 
223
223
  msg = r"Missing framewise_displacement"
224
- with pytest.raises(ValueError, match=msg):
224
+ with pytest.raises(RuntimeError, match=msg):
225
225
  confound_remover._process_fmriprep_spec({"data": confounds_df})
226
226
 
227
227
 
@@ -321,6 +321,32 @@ def test_fMRIPRepConfoundRemover__pick_confounds_fmriprep_compute() -> None:
321
321
  assert_frame_equal(out_junifer, out_fmriprep)
322
322
 
323
323
 
324
+ @pytest.mark.parametrize(
325
+ "preprocessor",
326
+ [
327
+ fMRIPrepConfoundRemover(
328
+ std_dvars_threshold=1.5,
329
+ ),
330
+ fMRIPrepConfoundRemover(
331
+ fd_threshold=0.5,
332
+ ),
333
+ ],
334
+ )
335
+ def test_fMRIPrepConfoundRemover__get_scrub_regressors_errors(
336
+ preprocessor: type,
337
+ ) -> None:
338
+ """Test fMRIPrepConfoundRemover scrub regressors errors.
339
+
340
+ Parameters
341
+ ----------
342
+ preprocessor : object
343
+ The parametrized preprocessor.
344
+
345
+ """
346
+ with pytest.raises(RuntimeError, match="Invalid confounds file."):
347
+ preprocessor._get_scrub_regressors(pd.DataFrame({"a": [1, 2]}))
348
+
349
+
324
350
  def test_fMRIPrepConfoundRemover__validate_data() -> None:
325
351
  """Test fMRIPrepConfoundRemover validate data."""
326
352
  confound_remover = fMRIPrepConfoundRemover(strategy={"wm_csf": "full"})
@@ -461,22 +487,32 @@ def test_fMRIPrepConfoundRemover_fit_transform() -> None:
461
487
 
462
488
  with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
463
489
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
464
- orig_bold = element_data["BOLD"]["data"].get_fdata().copy()
490
+ # Get original data
491
+ input_img = element_data["BOLD"]["data"]
492
+ input_bold = input_img.get_fdata().copy()
493
+ input_tr = input_img.header.get_zooms()[3]
494
+ # Fit-transform
465
495
  output = confound_remover.fit_transform(element_data)
466
- trans_bold = output["BOLD"]["data"].get_fdata()
496
+ output_img = output["BOLD"]["data"]
497
+ output_bold = output_img.get_fdata()
498
+ output_tr = output_img.header.get_zooms()[3]
499
+
467
500
  # Transformation is in place
468
501
  assert_array_equal(
469
- trans_bold, element_data["BOLD"]["data"].get_fdata()
502
+ output_bold, element_data["BOLD"]["data"].get_fdata()
470
503
  )
471
504
 
472
505
  # Data should have the same shape
473
- assert orig_bold.shape == trans_bold.shape
506
+ assert input_bold.shape == output_bold.shape
474
507
 
475
508
  # but be different
476
509
  assert_raises(
477
- AssertionError, assert_array_equal, orig_bold, trans_bold
510
+ AssertionError, assert_array_equal, input_bold, output_bold
478
511
  )
479
512
 
513
+ # Check t_r
514
+ assert input_tr == output_tr
515
+
480
516
  assert "meta" in output["BOLD"]
481
517
  assert "preprocess" in output["BOLD"]["meta"]
482
518
  t_meta = output["BOLD"]["meta"]["preprocess"]
@@ -507,22 +543,32 @@ def test_fMRIPrepConfoundRemover_fit_transform_masks() -> None:
507
543
 
508
544
  with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
509
545
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
510
- orig_bold = element_data["BOLD"]["data"].get_fdata().copy()
546
+ # Get original data
547
+ input_img = element_data["BOLD"]["data"]
548
+ input_bold = input_img.get_fdata().copy()
549
+ input_tr = input_img.header.get_zooms()[3]
550
+ # Fit-transform
511
551
  output = confound_remover.fit_transform(element_data)
512
- trans_bold = output["BOLD"]["data"].get_fdata()
552
+ output_img = output["BOLD"]["data"]
553
+ output_bold = output_img.get_fdata()
554
+ output_tr = output_img.header.get_zooms()[3]
555
+
513
556
  # Transformation is in place
514
557
  assert_array_equal(
515
- trans_bold, element_data["BOLD"]["data"].get_fdata()
558
+ output_bold, element_data["BOLD"]["data"].get_fdata()
516
559
  )
517
560
 
518
561
  # Data should have the same shape
519
- assert orig_bold.shape == trans_bold.shape
562
+ assert input_bold.shape == output_bold.shape
520
563
 
521
564
  # but be different
522
565
  assert_raises(
523
- AssertionError, assert_array_equal, orig_bold, trans_bold
566
+ AssertionError, assert_array_equal, input_bold, output_bold
524
567
  )
525
568
 
569
+ # Check t_r
570
+ assert input_tr == output_tr
571
+
526
572
  assert "meta" in output["BOLD"]
527
573
  assert "preprocess" in output["BOLD"]["meta"]
528
574
  t_meta = output["BOLD"]["meta"]["preprocess"]
@@ -548,3 +594,64 @@ def test_fMRIPrepConfoundRemover_fit_transform_masks() -> None:
548
594
  assert "dependencies" in output["BOLD"]["meta"]
549
595
  dependencies = output["BOLD"]["meta"]["dependencies"]
550
596
  assert dependencies == {"numpy", "nilearn"}
597
+
598
+
599
+ def test_fMRIPrepConfoundRemover_scrubbing() -> None:
600
+ """Test fMRIPrepConfoundRemover with scrubbing."""
601
+ confound_remover = fMRIPrepConfoundRemover(
602
+ strategy={
603
+ "motion": "full",
604
+ "wm_csf": "full",
605
+ "global_signal": "full",
606
+ "scrubbing": True,
607
+ },
608
+ )
609
+
610
+ with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
611
+ element_data = DefaultDataReader().fit_transform(dg["sub-01"])
612
+ orig_bold = element_data["BOLD"]["data"].get_fdata().copy()
613
+ orig_confounds = element_data["BOLD"]["confounds"].copy()
614
+ pre_input = element_data["BOLD"]
615
+ pre_extra_input = {
616
+ "BOLD": {"confounds": element_data["BOLD"]["confounds"]}
617
+ }
618
+ output, _ = confound_remover.preprocess(pre_input, pre_extra_input)
619
+ trans_bold = output["data"].get_fdata()
620
+ # Transformation is in place
621
+ assert_array_equal(
622
+ trans_bold, element_data["BOLD"]["data"].get_fdata()
623
+ )
624
+
625
+ # Data should have different shape
626
+ assert_raises(
627
+ AssertionError,
628
+ assert_array_equal,
629
+ orig_bold.shape,
630
+ trans_bold.shape,
631
+ )
632
+ # and be different
633
+ assert_raises(
634
+ AssertionError, assert_array_equal, orig_bold, trans_bold
635
+ )
636
+
637
+ # Check scrubbing process
638
+ # Should be at the start
639
+ confounds_df = confound_remover._pick_confounds(orig_confounds)
640
+ assert confounds_df.shape == (168, 36)
641
+ # Should have 4 motion outliers based on threshold
642
+ motion_outline_regressors = confound_remover._get_scrub_regressors(
643
+ orig_confounds["data"]
644
+ )
645
+ assert motion_outline_regressors.shape == (168, 4)
646
+ # Add regressors to confounds
647
+ concat_confounds_df = pd.concat(
648
+ [confounds_df, motion_outline_regressors], axis=1
649
+ )
650
+ assert concat_confounds_df.shape == (168, 40)
651
+ # Get sample mask and correct confounds
652
+ sample_mask, final_confounds_df = prepare_output(
653
+ confounds=concat_confounds_df, demean=False
654
+ )
655
+ assert not confounds_df.equals(final_confounds_df)
656
+ assert sample_mask.shape == (164,)
657
+ assert not (np.isin([91, 92, 93, 113], sample_mask)).all()
File without changes
@@ -3,7 +3,7 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
- from .smoothing import Smoothing
6
+ import lazy_loader as lazy
7
7
 
8
8
 
9
- __all__ = ["Smoothing"]
9
+ __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
@@ -0,0 +1,3 @@
1
+ __all__ = ["Smoothing"]
2
+
3
+ from .smoothing import Smoothing
@@ -4,24 +4,18 @@
4
4
  # License: AGPL
5
5
 
6
6
  from typing import (
7
- TYPE_CHECKING,
7
+ Any,
8
8
  ClassVar,
9
- Dict,
10
- List,
11
- Set,
12
9
  Union,
13
10
  )
14
11
 
15
12
  import nibabel as nib
16
13
 
17
14
  from ...pipeline import WorkDirManager
15
+ from ...typing import Dependencies, ExternalDependencies
18
16
  from ...utils import logger, run_ext_cmd
19
17
 
20
18
 
21
- if TYPE_CHECKING:
22
- from nibabel import Nifti1Image
23
-
24
-
25
19
  __all__ = ["AFNISmoothing"]
26
20
 
27
21
 
@@ -32,34 +26,34 @@ class AFNISmoothing:
32
26
 
33
27
  """
34
28
 
35
- _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
29
+ _EXT_DEPENDENCIES: ClassVar[ExternalDependencies] = [
36
30
  {
37
31
  "name": "afni",
38
32
  "commands": ["3dBlurToFWHM"],
39
33
  },
40
34
  ]
41
35
 
42
- _DEPENDENCIES: ClassVar[Set[str]] = {"nibabel"}
36
+ _DEPENDENCIES: ClassVar[Dependencies] = {"nibabel"}
43
37
 
44
38
  def preprocess(
45
39
  self,
46
- data: "Nifti1Image",
40
+ input: dict[str, Any],
47
41
  fwhm: Union[int, float],
48
- ) -> "Nifti1Image":
42
+ ) -> dict[str, Any]:
49
43
  """Preprocess using AFNI.
50
44
 
51
45
  Parameters
52
46
  ----------
53
- data : Niimg-like object
54
- Image(s) to preprocess.
47
+ input : dict
48
+ A single input from the Junifer Data object in which to preprocess.
55
49
  fwhm : int or float
56
50
  Smooth until the value. AFNI estimates the smoothing and then
57
51
  applies smoothing to reach ``fwhm``.
58
52
 
59
53
  Returns
60
54
  -------
61
- Niimg-like object
62
- The preprocessed image(s).
55
+ dict
56
+ The ``input`` dictionary with updated values.
63
57
 
64
58
  Notes
65
59
  -----
@@ -73,18 +67,18 @@ class AFNISmoothing:
73
67
  """
74
68
  logger.info("Smoothing using AFNI")
75
69
 
76
- # Create component-scoped tempdir
77
- tempdir = WorkDirManager().get_tempdir(prefix="afni_smoothing")
78
-
79
- # Save target data to a component-scoped tempfile
80
- nifti_in_file_path = tempdir / "input.nii" # needs to be .nii
81
- nib.save(data, nifti_in_file_path)
70
+ # Create element-scoped tempdir so that the output is
71
+ # available later as nibabel stores file path reference for
72
+ # loading on computation
73
+ element_tempdir = WorkDirManager().get_element_tempdir(
74
+ prefix="afni_smoothing"
75
+ )
82
76
 
83
77
  # Set 3dBlurToFWHM command
84
- blur_out_path_prefix = tempdir / "blur"
78
+ blur_out_path_prefix = element_tempdir / "blur"
85
79
  blur_cmd = [
86
80
  "3dBlurToFWHM",
87
- f"-input {nifti_in_file_path.resolve()}",
81
+ f"-input {input['path'].resolve()}",
88
82
  f"-prefix {blur_out_path_prefix.resolve()}",
89
83
  "-automask",
90
84
  f"-FWHM {fwhm}",
@@ -92,28 +86,34 @@ class AFNISmoothing:
92
86
  # Call 3dBlurToFWHM
93
87
  run_ext_cmd(name="3dBlurToFWHM", cmd=blur_cmd)
94
88
 
95
- # Create element-scoped tempdir so that the blurred output is
96
- # available later as nibabel stores file path reference for
97
- # loading on computation
98
- element_tempdir = WorkDirManager().get_element_tempdir(
99
- prefix="afni_blur"
100
- )
89
+ # Read header to get output suffix
90
+ header = input["data"].header
91
+ sform_code = header.get_sform(coded=True)[1]
92
+ if sform_code == 4:
93
+ output_suffix = "tlrc"
94
+ else:
95
+ output_suffix = "orig"
96
+
101
97
  # Convert afni to nifti
102
- blur_afni_to_nifti_out_path = (
103
- element_tempdir / "output.nii" # needs to be .nii
98
+ blur_nifti_out_path = (
99
+ element_tempdir / "smoothed_data.nii" # needs to be .nii
104
100
  )
105
101
  convert_cmd = [
106
102
  "3dAFNItoNIFTI",
107
- f"-prefix {blur_afni_to_nifti_out_path.resolve()}",
108
- f"{blur_out_path_prefix}+orig.BRIK",
103
+ f"-prefix {blur_nifti_out_path.resolve()}",
104
+ f"{blur_out_path_prefix}+{output_suffix}.BRIK",
109
105
  ]
110
106
  # Call 3dAFNItoNIFTI
111
107
  run_ext_cmd(name="3dAFNItoNIFTI", cmd=convert_cmd)
112
108
 
113
- # Load nifti
114
- output_data = nib.load(blur_afni_to_nifti_out_path)
115
-
116
- # Delete tempdir
117
- WorkDirManager().delete_tempdir(tempdir)
109
+ logger.debug("Updating smoothed data")
110
+ input.update(
111
+ {
112
+ # Update path to sync with "data"
113
+ "path": blur_nifti_out_path,
114
+ # Load nifti
115
+ "data": nib.load(blur_nifti_out_path),
116
+ }
117
+ )
118
118
 
119
- return output_data # type: ignore
119
+ return input
@@ -4,24 +4,17 @@
4
4
  # License: AGPL
5
5
 
6
6
  from typing import (
7
- TYPE_CHECKING,
7
+ Any,
8
8
  ClassVar,
9
- Dict,
10
- List,
11
- Set,
12
- Union,
13
9
  )
14
10
 
15
11
  import nibabel as nib
16
12
 
17
13
  from ...pipeline import WorkDirManager
14
+ from ...typing import Dependencies, ExternalDependencies
18
15
  from ...utils import logger, run_ext_cmd
19
16
 
20
17
 
21
- if TYPE_CHECKING:
22
- from nibabel import Nifti1Image
23
-
24
-
25
18
  __all__ = ["FSLSmoothing"]
26
19
 
27
20
 
@@ -32,27 +25,27 @@ class FSLSmoothing:
32
25
 
33
26
  """
34
27
 
35
- _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
28
+ _EXT_DEPENDENCIES: ClassVar[ExternalDependencies] = [
36
29
  {
37
30
  "name": "fsl",
38
31
  "commands": ["susan"],
39
32
  },
40
33
  ]
41
34
 
42
- _DEPENDENCIES: ClassVar[Set[str]] = {"nibabel"}
35
+ _DEPENDENCIES: ClassVar[Dependencies] = {"nibabel"}
43
36
 
44
37
  def preprocess(
45
38
  self,
46
- data: "Nifti1Image",
39
+ input: dict[str, Any],
47
40
  brightness_threshold: float,
48
41
  fwhm: float,
49
- ) -> "Nifti1Image":
42
+ ) -> dict[str, Any]:
50
43
  """Preprocess using FSL.
51
44
 
52
45
  Parameters
53
46
  ----------
54
- data : Niimg-like object
55
- Image(s) to preprocess.
47
+ input : dict
48
+ A single input from the Junifer Data object in which to preprocess.
56
49
  brightness_threshold : float
57
50
  Threshold to discriminate between noise and the underlying image.
58
51
  The value should be set greater than the noise level and less than
@@ -62,8 +55,8 @@ class FSLSmoothing:
62
55
 
63
56
  Returns
64
57
  -------
65
- Niimg-like object
66
- The preprocessed image(s).
58
+ dict
59
+ The ``input`` dictionary with updated values.
67
60
 
68
61
  Notes
69
62
  -----
@@ -79,24 +72,17 @@ class FSLSmoothing:
79
72
  """
80
73
  logger.info("Smoothing using FSL")
81
74
 
82
- # Create component-scoped tempdir
83
- tempdir = WorkDirManager().get_tempdir(prefix="fsl_smoothing")
84
-
85
- # Save target data to a component-scoped tempfile
86
- nifti_in_file_path = tempdir / "input.nii.gz"
87
- nib.save(data, nifti_in_file_path)
88
-
89
75
  # Create element-scoped tempdir so that the output is
90
76
  # available later as nibabel stores file path reference for
91
77
  # loading on computation
92
78
  element_tempdir = WorkDirManager().get_element_tempdir(
93
79
  prefix="fsl_susan"
94
80
  )
95
- susan_out_path = element_tempdir / "output.nii.gz"
81
+ susan_out_path = element_tempdir / "smoothed_data.nii.gz"
96
82
  # Set susan command
97
83
  susan_cmd = [
98
84
  "susan",
99
- f"{nifti_in_file_path.resolve()}",
85
+ f"{input['path'].resolve()}",
100
86
  f"{brightness_threshold}",
101
87
  f"{fwhm}",
102
88
  "3", # dimension
@@ -107,10 +93,14 @@ class FSLSmoothing:
107
93
  # Call susan
108
94
  run_ext_cmd(name="susan", cmd=susan_cmd)
109
95
 
110
- # Load nifti
111
- output_data = nib.load(susan_out_path)
112
-
113
- # Delete tempdir
114
- WorkDirManager().delete_tempdir(tempdir)
96
+ logger.debug("Updating smoothed data")
97
+ input.update(
98
+ {
99
+ # Update path to sync with "data"
100
+ "path": susan_out_path,
101
+ # Load nifti
102
+ "data": nib.load(susan_out_path),
103
+ }
104
+ )
115
105
 
116
- return output_data # type: ignore
106
+ return input
@@ -4,23 +4,21 @@
4
4
  # License: AGPL
5
5
 
6
6
  from typing import (
7
- TYPE_CHECKING,
7
+ Any,
8
8
  ClassVar,
9
9
  Literal,
10
- Set,
11
10
  Union,
12
11
  )
13
12
 
13
+ import nibabel as nib
14
14
  from nilearn import image as nimg
15
15
  from numpy.typing import ArrayLike
16
16
 
17
+ from ...pipeline import WorkDirManager
18
+ from ...typing import Dependencies
17
19
  from ...utils import logger
18
20
 
19
21
 
20
- if TYPE_CHECKING:
21
- from nibabel import Nifti1Image
22
-
23
-
24
22
  __all__ = ["NilearnSmoothing"]
25
23
 
26
24
 
@@ -31,19 +29,19 @@ class NilearnSmoothing:
31
29
 
32
30
  """
33
31
 
34
- _DEPENDENCIES: ClassVar[Set[str]] = {"nilearn"}
32
+ _DEPENDENCIES: ClassVar[Dependencies] = {"nilearn"}
35
33
 
36
34
  def preprocess(
37
35
  self,
38
- data: "Nifti1Image",
36
+ input: dict[str, Any],
39
37
  fwhm: Union[int, float, ArrayLike, Literal["fast"], None],
40
- ) -> "Nifti1Image":
38
+ ) -> dict[str, Any]:
41
39
  """Preprocess using nilearn.
42
40
 
43
41
  Parameters
44
42
  ----------
45
- data : Niimg-like object
46
- Image(s) to preprocess.
43
+ input : dict
44
+ A single input from the Junifer Data object in which to preprocess.
47
45
  fwhm : scalar, ``numpy.ndarray``, tuple or list of scalar, "fast" or \
48
46
  None
49
47
  Smoothing strength, as a full-width at half maximum, in
@@ -61,9 +59,32 @@ class NilearnSmoothing:
61
59
 
62
60
  Returns
63
61
  -------
64
- Niimg-like object
65
- The preprocessed image(s).
62
+ dict
63
+ The ``input`` dictionary with updated values.
66
64
 
67
65
  """
68
66
  logger.info("Smoothing using nilearn")
69
- return nimg.smooth_img(imgs=data, fwhm=fwhm) # type: ignore
67
+
68
+ # Create element-scoped tempdir so that the output is
69
+ # available later as nibabel stores file path reference for
70
+ # loading on computation
71
+ element_tempdir = WorkDirManager().get_element_tempdir(
72
+ prefix="nilearn_smoothing"
73
+ )
74
+
75
+ smoothed_img = nimg.smooth_img(imgs=input["data"], fwhm=fwhm)
76
+
77
+ # Save smoothed output
78
+ smoothed_img_path = element_tempdir / "smoothed_data.nii.gz"
79
+ nib.save(smoothed_img, smoothed_img_path)
80
+
81
+ logger.debug("Updating smoothed data")
82
+ input.update(
83
+ {
84
+ # Update path to sync with "data"
85
+ "path": smoothed_img_path,
86
+ "data": smoothed_img,
87
+ }
88
+ )
89
+
90
+ return input
File without changes
@@ -3,9 +3,10 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
- from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type, Union
6
+ from typing import Any, ClassVar, Optional, Union
7
7
 
8
8
  from ...api.decorators import register_preprocessor
9
+ from ...typing import ConditionalDependencies
9
10
  from ...utils import logger, raise_error
10
11
  from ..base import BasePreprocessor
11
12
  from ._afni_smoothing import AFNISmoothing
@@ -67,7 +68,7 @@ class Smoothing(BasePreprocessor):
67
68
 
68
69
  """
69
70
 
70
- _CONDITIONAL_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, Type]]]] = [
71
+ _CONDITIONAL_DEPENDENCIES: ClassVar[ConditionalDependencies] = [
71
72
  {
72
73
  "using": "nilearn",
73
74
  "depends_on": NilearnSmoothing,
@@ -85,8 +86,8 @@ class Smoothing(BasePreprocessor):
85
86
  def __init__(
86
87
  self,
87
88
  using: str,
88
- on: Union[List[str], str],
89
- smoothing_params: Optional[Dict] = None,
89
+ on: Union[list[str], str],
90
+ smoothing_params: Optional[dict] = None,
90
91
  ) -> None:
91
92
  """Initialize the class."""
92
93
  # Validate `using` parameter
@@ -101,7 +102,7 @@ class Smoothing(BasePreprocessor):
101
102
  )
102
103
  super().__init__(on=on)
103
104
 
104
- def get_valid_inputs(self) -> List[str]:
105
+ def get_valid_inputs(self) -> list[str]:
105
106
  """Get valid data types for input.
106
107
 
107
108
  Returns
@@ -132,9 +133,9 @@ class Smoothing(BasePreprocessor):
132
133
 
133
134
  def preprocess(
134
135
  self,
135
- input: Dict[str, Any],
136
- extra_input: Optional[Dict[str, Any]] = None,
137
- ) -> Tuple[Dict[str, Any], Optional[Dict[str, Dict[str, Any]]]]:
136
+ input: dict[str, Any],
137
+ extra_input: Optional[dict[str, Any]] = None,
138
+ ) -> tuple[dict[str, Any], Optional[dict[str, dict[str, Any]]]]:
138
139
  """Preprocess.
139
140
 
140
141
  Parameters
@@ -163,12 +164,9 @@ class Smoothing(BasePreprocessor):
163
164
  elif self.using == "fsl":
164
165
  preprocessor = FSLSmoothing()
165
166
  # Smooth
166
- output = preprocessor.preprocess( # type: ignore
167
- data=input["data"],
167
+ input = preprocessor.preprocess(
168
+ input=input,
168
169
  **self.smoothing_params,
169
170
  )
170
171
 
171
- # Modify target data
172
- input["data"] = output
173
-
174
172
  return input, None
@@ -3,7 +3,7 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
- from .space_warper import SpaceWarper
6
+ import lazy_loader as lazy
7
7
 
8
8
 
9
- __all__ = ["SpaceWarper"]
9
+ __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
@@ -0,0 +1,3 @@
1
+ __all__ = ["SpaceWarper"]
2
+
3
+ from .space_warper import SpaceWarper