junifer 0.0.4.dev831__py3-none-any.whl → 0.0.5__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 (206) hide show
  1. junifer/__init__.py +17 -0
  2. junifer/_version.py +2 -2
  3. junifer/api/__init__.py +4 -1
  4. junifer/api/cli.py +91 -1
  5. junifer/api/decorators.py +9 -0
  6. junifer/api/functions.py +56 -10
  7. junifer/api/parser.py +3 -0
  8. junifer/api/queue_context/__init__.py +4 -1
  9. junifer/api/queue_context/gnu_parallel_local_adapter.py +16 -6
  10. junifer/api/queue_context/htcondor_adapter.py +16 -5
  11. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +41 -12
  12. junifer/api/queue_context/tests/test_htcondor_adapter.py +48 -15
  13. junifer/api/res/afni/run_afni_docker.sh +1 -1
  14. junifer/api/res/ants/run_ants_docker.sh +1 -1
  15. junifer/api/res/freesurfer/mri_binarize +3 -0
  16. junifer/api/res/freesurfer/mri_mc +3 -0
  17. junifer/api/res/freesurfer/mri_pretess +3 -0
  18. junifer/api/res/freesurfer/mris_convert +3 -0
  19. junifer/api/res/freesurfer/run_freesurfer_docker.sh +61 -0
  20. junifer/api/res/fsl/run_fsl_docker.sh +1 -1
  21. junifer/api/res/{run_conda.sh → run_conda.bash} +1 -1
  22. junifer/api/res/run_conda.zsh +23 -0
  23. junifer/api/res/run_venv.bash +22 -0
  24. junifer/api/res/{run_venv.sh → run_venv.zsh} +1 -1
  25. junifer/api/tests/test_api_utils.py +4 -2
  26. junifer/api/tests/test_cli.py +83 -0
  27. junifer/api/tests/test_functions.py +27 -2
  28. junifer/configs/__init__.py +1 -1
  29. junifer/configs/juseless/__init__.py +4 -1
  30. junifer/configs/juseless/datagrabbers/__init__.py +10 -1
  31. junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +4 -3
  32. junifer/configs/juseless/datagrabbers/camcan_vbm.py +3 -0
  33. junifer/configs/juseless/datagrabbers/ixi_vbm.py +4 -3
  34. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +1 -3
  35. junifer/configs/juseless/datagrabbers/ucla.py +12 -9
  36. junifer/configs/juseless/datagrabbers/ukb_vbm.py +3 -0
  37. junifer/data/__init__.py +21 -1
  38. junifer/data/coordinates.py +10 -19
  39. junifer/data/masks/ukb/UKB_15K_GM_template.nii.gz +0 -0
  40. junifer/data/masks.py +58 -87
  41. junifer/data/parcellations.py +14 -3
  42. junifer/data/template_spaces.py +4 -1
  43. junifer/data/tests/test_masks.py +26 -37
  44. junifer/data/utils.py +3 -0
  45. junifer/datagrabber/__init__.py +18 -1
  46. junifer/datagrabber/aomic/__init__.py +3 -0
  47. junifer/datagrabber/aomic/id1000.py +70 -37
  48. junifer/datagrabber/aomic/piop1.py +69 -36
  49. junifer/datagrabber/aomic/piop2.py +71 -38
  50. junifer/datagrabber/aomic/tests/test_id1000.py +44 -100
  51. junifer/datagrabber/aomic/tests/test_piop1.py +65 -108
  52. junifer/datagrabber/aomic/tests/test_piop2.py +45 -102
  53. junifer/datagrabber/base.py +13 -6
  54. junifer/datagrabber/datalad_base.py +13 -1
  55. junifer/datagrabber/dmcc13_benchmark.py +36 -53
  56. junifer/datagrabber/hcp1200/__init__.py +3 -0
  57. junifer/datagrabber/hcp1200/datalad_hcp1200.py +3 -0
  58. junifer/datagrabber/hcp1200/hcp1200.py +4 -1
  59. junifer/datagrabber/multiple.py +45 -6
  60. junifer/datagrabber/pattern.py +170 -62
  61. junifer/datagrabber/pattern_datalad.py +25 -12
  62. junifer/datagrabber/pattern_validation_mixin.py +388 -0
  63. junifer/datagrabber/tests/test_datalad_base.py +4 -4
  64. junifer/datagrabber/tests/test_dmcc13_benchmark.py +46 -19
  65. junifer/datagrabber/tests/test_multiple.py +161 -84
  66. junifer/datagrabber/tests/test_pattern.py +45 -0
  67. junifer/datagrabber/tests/test_pattern_datalad.py +4 -4
  68. junifer/datagrabber/tests/test_pattern_validation_mixin.py +249 -0
  69. junifer/datareader/__init__.py +4 -1
  70. junifer/datareader/default.py +95 -43
  71. junifer/external/BrainPrint/brainprint/__init__.py +4 -0
  72. junifer/external/BrainPrint/brainprint/_version.py +3 -0
  73. junifer/external/BrainPrint/brainprint/asymmetry.py +91 -0
  74. junifer/external/BrainPrint/brainprint/brainprint.py +441 -0
  75. junifer/external/BrainPrint/brainprint/surfaces.py +258 -0
  76. junifer/external/BrainPrint/brainprint/utils/__init__.py +1 -0
  77. junifer/external/BrainPrint/brainprint/utils/_config.py +112 -0
  78. junifer/external/BrainPrint/brainprint/utils/utils.py +188 -0
  79. junifer/external/__init__.py +1 -1
  80. junifer/external/nilearn/__init__.py +5 -1
  81. junifer/external/nilearn/junifer_connectivity_measure.py +483 -0
  82. junifer/external/nilearn/junifer_nifti_spheres_masker.py +23 -9
  83. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +1089 -0
  84. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +76 -1
  85. junifer/markers/__init__.py +23 -1
  86. junifer/markers/base.py +68 -28
  87. junifer/markers/brainprint.py +459 -0
  88. junifer/markers/collection.py +10 -2
  89. junifer/markers/complexity/__init__.py +10 -0
  90. junifer/markers/complexity/complexity_base.py +26 -43
  91. junifer/markers/complexity/hurst_exponent.py +3 -0
  92. junifer/markers/complexity/multiscale_entropy_auc.py +3 -0
  93. junifer/markers/complexity/perm_entropy.py +3 -0
  94. junifer/markers/complexity/range_entropy.py +3 -0
  95. junifer/markers/complexity/range_entropy_auc.py +3 -0
  96. junifer/markers/complexity/sample_entropy.py +3 -0
  97. junifer/markers/complexity/tests/test_hurst_exponent.py +11 -3
  98. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +11 -3
  99. junifer/markers/complexity/tests/test_perm_entropy.py +11 -3
  100. junifer/markers/complexity/tests/test_range_entropy.py +11 -3
  101. junifer/markers/complexity/tests/test_range_entropy_auc.py +11 -3
  102. junifer/markers/complexity/tests/test_sample_entropy.py +11 -3
  103. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +11 -3
  104. junifer/markers/complexity/weighted_perm_entropy.py +3 -0
  105. junifer/markers/ets_rss.py +27 -42
  106. junifer/markers/falff/__init__.py +3 -0
  107. junifer/markers/falff/_afni_falff.py +5 -2
  108. junifer/markers/falff/_junifer_falff.py +3 -0
  109. junifer/markers/falff/falff_base.py +20 -46
  110. junifer/markers/falff/falff_parcels.py +56 -27
  111. junifer/markers/falff/falff_spheres.py +60 -29
  112. junifer/markers/falff/tests/test_falff_parcels.py +39 -23
  113. junifer/markers/falff/tests/test_falff_spheres.py +39 -23
  114. junifer/markers/functional_connectivity/__init__.py +9 -0
  115. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +63 -60
  116. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +45 -32
  117. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +49 -36
  118. junifer/markers/functional_connectivity/functional_connectivity_base.py +71 -70
  119. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +34 -25
  120. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +40 -30
  121. junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +11 -7
  122. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +27 -7
  123. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +28 -12
  124. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +35 -11
  125. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +36 -62
  126. junifer/markers/parcel_aggregation.py +47 -61
  127. junifer/markers/reho/__init__.py +3 -0
  128. junifer/markers/reho/_afni_reho.py +5 -2
  129. junifer/markers/reho/_junifer_reho.py +4 -1
  130. junifer/markers/reho/reho_base.py +8 -27
  131. junifer/markers/reho/reho_parcels.py +28 -17
  132. junifer/markers/reho/reho_spheres.py +27 -18
  133. junifer/markers/reho/tests/test_reho_parcels.py +8 -3
  134. junifer/markers/reho/tests/test_reho_spheres.py +8 -3
  135. junifer/markers/sphere_aggregation.py +43 -59
  136. junifer/markers/temporal_snr/__init__.py +3 -0
  137. junifer/markers/temporal_snr/temporal_snr_base.py +23 -32
  138. junifer/markers/temporal_snr/temporal_snr_parcels.py +9 -6
  139. junifer/markers/temporal_snr/temporal_snr_spheres.py +9 -6
  140. junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +6 -3
  141. junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +6 -3
  142. junifer/markers/tests/test_brainprint.py +58 -0
  143. junifer/markers/tests/test_collection.py +9 -8
  144. junifer/markers/tests/test_ets_rss.py +15 -9
  145. junifer/markers/tests/test_markers_base.py +17 -18
  146. junifer/markers/tests/test_parcel_aggregation.py +93 -32
  147. junifer/markers/tests/test_sphere_aggregation.py +72 -19
  148. junifer/onthefly/__init__.py +4 -1
  149. junifer/onthefly/read_transform.py +3 -0
  150. junifer/pipeline/__init__.py +9 -1
  151. junifer/pipeline/pipeline_step_mixin.py +21 -4
  152. junifer/pipeline/registry.py +3 -0
  153. junifer/pipeline/singleton.py +3 -0
  154. junifer/pipeline/tests/test_registry.py +1 -1
  155. junifer/pipeline/update_meta_mixin.py +3 -0
  156. junifer/pipeline/utils.py +67 -1
  157. junifer/pipeline/workdir_manager.py +3 -0
  158. junifer/preprocess/__init__.py +10 -2
  159. junifer/preprocess/base.py +6 -3
  160. junifer/preprocess/confounds/__init__.py +3 -0
  161. junifer/preprocess/confounds/fmriprep_confound_remover.py +47 -60
  162. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +72 -113
  163. junifer/preprocess/smoothing/__init__.py +9 -0
  164. junifer/preprocess/smoothing/_afni_smoothing.py +119 -0
  165. junifer/preprocess/smoothing/_fsl_smoothing.py +116 -0
  166. junifer/preprocess/smoothing/_nilearn_smoothing.py +69 -0
  167. junifer/preprocess/smoothing/smoothing.py +174 -0
  168. junifer/preprocess/smoothing/tests/test_smoothing.py +94 -0
  169. junifer/preprocess/warping/__init__.py +3 -0
  170. junifer/preprocess/warping/_ants_warper.py +3 -0
  171. junifer/preprocess/warping/_fsl_warper.py +3 -0
  172. junifer/stats.py +4 -1
  173. junifer/storage/__init__.py +9 -1
  174. junifer/storage/base.py +40 -1
  175. junifer/storage/hdf5.py +71 -9
  176. junifer/storage/pandas_base.py +3 -0
  177. junifer/storage/sqlite.py +3 -0
  178. junifer/storage/tests/test_hdf5.py +82 -10
  179. junifer/storage/utils.py +9 -0
  180. junifer/testing/__init__.py +4 -1
  181. junifer/testing/datagrabbers.py +13 -6
  182. junifer/testing/tests/test_partlycloudytesting_datagrabber.py +7 -7
  183. junifer/testing/utils.py +3 -0
  184. junifer/utils/__init__.py +13 -2
  185. junifer/utils/fs.py +3 -0
  186. junifer/utils/helpers.py +32 -1
  187. junifer/utils/logging.py +33 -4
  188. junifer/utils/tests/test_logging.py +8 -0
  189. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/METADATA +17 -16
  190. junifer-0.0.5.dist-info/RECORD +275 -0
  191. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/WHEEL +1 -1
  192. junifer/datagrabber/tests/test_datagrabber_utils.py +0 -218
  193. junifer/datagrabber/utils.py +0 -230
  194. junifer/preprocess/ants/__init__.py +0 -4
  195. junifer/preprocess/ants/ants_apply_transforms_warper.py +0 -185
  196. junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py +0 -56
  197. junifer/preprocess/bold_warper.py +0 -265
  198. junifer/preprocess/fsl/__init__.py +0 -4
  199. junifer/preprocess/fsl/apply_warper.py +0 -179
  200. junifer/preprocess/fsl/tests/test_apply_warper.py +0 -45
  201. junifer/preprocess/tests/test_bold_warper.py +0 -159
  202. junifer-0.0.4.dev831.dist-info/RECORD +0 -257
  203. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/AUTHORS.rst +0 -0
  204. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/LICENSE.md +0 -0
  205. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/entry_points.txt +0 -0
  206. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/top_level.txt +0 -0
@@ -20,6 +20,7 @@ from junifer.testing import get_testing_data
20
20
  from junifer.testing.datagrabbers import (
21
21
  OasisVBMTestingDataGrabber,
22
22
  PartlyCloudyTestingDataGrabber,
23
+ SPMAuditoryTestingDataGrabber,
23
24
  )
24
25
 
25
26
 
@@ -42,35 +43,10 @@ def test_fMRIPrepConfoundRemover_init() -> None:
42
43
  @pytest.mark.parametrize(
43
44
  "input_",
44
45
  [
45
- ["T1w"],
46
46
  ["BOLD"],
47
47
  ["T1w", "BOLD"],
48
48
  ],
49
49
  )
50
- def test_fMRIPrepConfoundRemover_validate_input_errors(
51
- input_: List[str],
52
- ) -> None:
53
- """Test errors for fMRIPrepConfoundRemover validate_input.
54
-
55
- Parameters
56
- ----------
57
- input_ : list of str
58
- The input data types.
59
-
60
- """
61
- confound_remover = fMRIPrepConfoundRemover()
62
-
63
- with pytest.raises(ValueError, match="not have the required data"):
64
- confound_remover.validate_input(input_)
65
-
66
-
67
- @pytest.mark.parametrize(
68
- "input_",
69
- [
70
- ["BOLD", "BOLD_confounds"],
71
- ["T1w", "BOLD", "BOLD_confounds"],
72
- ],
73
- )
74
50
  def test_fMRIPrepConfoundRemover_validate_input(input_: List[str]) -> None:
75
51
  """Test fMRIPrepConfoundRemover validate_input.
76
52
 
@@ -302,13 +278,13 @@ def test_fMRIPRepConfoundRemover__pick_confounds_fmriprep() -> None:
302
278
  with PartlyCloudyTestingDataGrabber() as dg:
303
279
  input = dg["sub-01"]
304
280
  input = reader.fit_transform(input)
305
- out1 = confound_remover._pick_confounds(input["BOLD_confounds"])
281
+ out1 = confound_remover._pick_confounds(input["BOLD"]["confounds"])
306
282
  assert set(out1.columns) == {*fmriprep_all_vars, "spike"}
307
283
 
308
284
  with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
309
285
  input = dg["sub-01"]
310
286
  input = reader.fit_transform(input)
311
- out2 = confound_remover._pick_confounds(input["BOLD_confounds"])
287
+ out2 = confound_remover._pick_confounds(input["BOLD"]["confounds"])
312
288
  assert set(out2.columns) == {*fmriprep_all_vars, "spike"}
313
289
 
314
290
  assert_frame_equal(out1, out2)
@@ -348,123 +324,106 @@ def test_fMRIPRepConfoundRemover__pick_confounds_fmriprep_compute() -> None:
348
324
  def test_fMRIPrepConfoundRemover__validate_data() -> None:
349
325
  """Test fMRIPrepConfoundRemover validate data."""
350
326
  confound_remover = fMRIPrepConfoundRemover(strategy={"wm_csf": "full"})
351
-
327
+ # Check correct data type
352
328
  with OasisVBMTestingDataGrabber() as dg:
353
329
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
354
330
  vbm = element_data["VBM_GM"]
355
331
  with pytest.raises(
356
332
  DimensionError, match="incompatible dimensionality"
357
333
  ):
358
- confound_remover._validate_data(vbm, None)
359
-
360
- with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
334
+ confound_remover._validate_data(vbm)
335
+ # Check missing nested type in correct data type
336
+ with SPMAuditoryTestingDataGrabber() as dg:
361
337
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
362
338
  bold = element_data["BOLD"]
363
-
364
- with pytest.raises(ValueError, match="No extra input"):
365
- confound_remover._validate_data(bold, None)
339
+ # Test confound type
366
340
  with pytest.raises(
367
- ValueError, match="`BOLD_confounds` data type not provided"
341
+ ValueError, match="`BOLD.confounds` data type not provided"
368
342
  ):
369
- confound_remover._validate_data(bold, {})
343
+ confound_remover._validate_data(bold)
344
+ # Test confound data
345
+ bold["confounds"] = {}
370
346
  with pytest.raises(
371
- ValueError, match="`BOLD_confounds.data` not provided"
347
+ ValueError, match="`BOLD.confounds.data` not provided"
372
348
  ):
373
- confound_remover._validate_data(bold, {"BOLD_confounds": {}})
374
-
375
- extra_input = {
376
- "BOLD_confounds": {"data": "wrong"},
377
- }
378
- msg = "must be a `pandas.DataFrame`"
379
- with pytest.raises(ValueError, match=msg):
380
- confound_remover._validate_data(bold, extra_input)
381
-
382
- extra_input = {"BOLD_confounds": {"data": pd.DataFrame()}}
349
+ confound_remover._validate_data(bold)
350
+ # Test confound data is valid type
351
+ bold["confounds"] = {"data": None}
352
+ with pytest.raises(ValueError, match="must be a `pandas.DataFrame`"):
353
+ confound_remover._validate_data(bold)
354
+ # Test confound data dimension mismatch with BOLD
355
+ bold["confounds"] = {"data": pd.DataFrame()}
383
356
  with pytest.raises(ValueError, match="Image time series and"):
384
- confound_remover._validate_data(bold, extra_input)
385
-
386
- extra_input = {
387
- "BOLD_confounds": {"data": element_data["BOLD_confounds"]["data"]}
357
+ confound_remover._validate_data(bold)
358
+ # Check nested type variations
359
+ with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
360
+ element_data = DefaultDataReader().fit_transform(dg["sub-01"])
361
+ # Test format
362
+ modified_bold = {
363
+ "data": element_data["BOLD"]["data"],
364
+ "confounds": {
365
+ "data": element_data["BOLD"]["confounds"]["data"],
366
+ "format": "adhoc",
367
+ },
388
368
  }
369
+ # Test incorrect format
370
+ modified_bold["confounds"].update({"format": "wrong"})
371
+ with pytest.raises(ValueError, match="Invalid confounds format"):
372
+ confound_remover._validate_data(modified_bold)
373
+ # Test missing mappings for adhoc
374
+ modified_bold["confounds"].update({"format": "adhoc"})
389
375
  with pytest.raises(
390
- ValueError, match="`BOLD_confounds.format` not provided"
376
+ ValueError, match="`BOLD.confounds.mappings` need to be set"
391
377
  ):
392
- confound_remover._validate_data(bold, extra_input)
393
-
394
- extra_input = {
395
- "BOLD_confounds": {
396
- "data": element_data["BOLD_confounds"]["data"],
397
- "format": "wrong",
398
- }
399
- }
400
- with pytest.raises(ValueError, match="Invalid confounds format"):
401
- confound_remover._validate_data(bold, extra_input)
402
-
403
- extra_input = {
404
- "BOLD_confounds": {
405
- "data": element_data["BOLD_confounds"]["data"],
406
- "format": "adhoc",
407
- }
408
- }
409
- with pytest.raises(ValueError, match="need to be set"):
410
- confound_remover._validate_data(bold, extra_input)
411
-
412
- extra_input = {
413
- "BOLD_confounds": {
414
- "data": element_data["BOLD_confounds"]["data"],
415
- "format": "adhoc",
416
- "mappings": {},
417
- }
418
- }
419
- with pytest.raises(ValueError, match="need to be set"):
420
- confound_remover._validate_data(bold, extra_input)
421
-
422
- extra_input = {
423
- "BOLD_confounds": {
424
- "data": element_data["BOLD_confounds"]["data"],
425
- "format": "adhoc",
378
+ confound_remover._validate_data(modified_bold)
379
+ # Test missing fmriprep mappings for adhoc
380
+ modified_bold["confounds"].update({"mappings": {}})
381
+ with pytest.raises(
382
+ ValueError,
383
+ match="`BOLD.confounds.mappings.fmriprep` need to be set",
384
+ ):
385
+ confound_remover._validate_data(modified_bold)
386
+ # Test incorrect fmriprep mappings for adhoc
387
+ modified_bold["confounds"].update(
388
+ {
426
389
  "mappings": {
427
390
  "fmriprep": {
428
391
  "rot_x": "wrong",
429
392
  "rot_y": "rot_z",
430
393
  "rot_z": "rot_y",
431
- }
432
- },
394
+ },
395
+ }
433
396
  }
434
- }
397
+ )
435
398
  with pytest.raises(ValueError, match=r"names: \['wrong'\]"):
436
- confound_remover._validate_data(bold, extra_input)
437
-
438
- extra_input = {
439
- "BOLD_confounds": {
440
- "data": element_data["BOLD_confounds"]["data"],
441
- "format": "adhoc",
399
+ confound_remover._validate_data(modified_bold)
400
+ # Test missing fmriprep mappings for adhoc
401
+ modified_bold["confounds"].update(
402
+ {
442
403
  "mappings": {
443
404
  "fmriprep": {
444
405
  "wrong": "rot_x",
445
406
  "rot_y": "rot_z",
446
407
  "rot_z": "rot_y",
447
- }
448
- },
408
+ },
409
+ }
449
410
  }
450
- }
411
+ )
451
412
  with pytest.raises(ValueError, match=r"Missing columns: \['wrong'\]"):
452
- confound_remover._validate_data(bold, extra_input)
453
-
454
- extra_input = {
455
- "BOLD_confounds": {
456
- "data": element_data["BOLD_confounds"]["data"],
457
- "format": "adhoc",
413
+ confound_remover._validate_data(modified_bold)
414
+ # Test correct adhoc format
415
+ modified_bold["confounds"].update(
416
+ {
458
417
  "mappings": {
459
418
  "fmriprep": {
460
419
  "rot_x": "rot_x",
461
420
  "rot_y": "rot_z",
462
421
  "rot_z": "rot_y",
463
- }
464
- },
422
+ },
423
+ }
465
424
  }
466
- }
467
- confound_remover._validate_data(bold, extra_input)
425
+ )
426
+ confound_remover._validate_data(modified_bold)
468
427
 
469
428
 
470
429
  def test_fMRIPrepConfoundRemover_preprocess() -> None:
@@ -476,7 +435,9 @@ def test_fMRIPrepConfoundRemover_preprocess() -> None:
476
435
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
477
436
  orig_bold = element_data["BOLD"]["data"].get_fdata().copy()
478
437
  pre_input = element_data["BOLD"]
479
- pre_extra_input = {"BOLD_confounds": element_data["BOLD_confounds"]}
438
+ pre_extra_input = {
439
+ "BOLD": {"confounds": element_data["BOLD"]["confounds"]}
440
+ }
480
441
  output, _ = confound_remover.preprocess(pre_input, pre_extra_input)
481
442
  trans_bold = output["data"].get_fdata()
482
443
  # Transformation is in place
@@ -530,7 +491,7 @@ def test_fMRIPrepConfoundRemover_fit_transform() -> None:
530
491
  assert t_meta["t_r"] is None
531
492
  assert t_meta["masks"] is None
532
493
 
533
- assert "BOLD_mask" not in output
494
+ assert "mask" not in output["BOLD"]
534
495
 
535
496
  assert "dependencies" in output["BOLD"]["meta"]
536
497
  dependencies = output["BOLD"]["meta"]["dependencies"]
@@ -582,9 +543,7 @@ def test_fMRIPrepConfoundRemover_fit_transform_masks() -> None:
582
543
  assert "threshold" in t_meta["masks"]["compute_brain_mask"]
583
544
  assert t_meta["masks"]["compute_brain_mask"]["threshold"] == 0.2
584
545
 
585
- assert "BOLD_mask" in output
586
- assert "mask_item" in output["BOLD"]
587
- assert output["BOLD"]["mask_item"] == "BOLD_mask"
546
+ assert "mask" in output["BOLD"]
588
547
 
589
548
  assert "dependencies" in output["BOLD"]["meta"]
590
549
  dependencies = output["BOLD"]["meta"]["dependencies"]
@@ -0,0 +1,9 @@
1
+ """Provide imports for smoothing sub-package."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from .smoothing import Smoothing
7
+
8
+
9
+ __all__ = ["Smoothing"]
@@ -0,0 +1,119 @@
1
+ """Provide class for smoothing via AFNI."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from typing import (
7
+ TYPE_CHECKING,
8
+ ClassVar,
9
+ Dict,
10
+ List,
11
+ Set,
12
+ Union,
13
+ )
14
+
15
+ import nibabel as nib
16
+
17
+ from ...pipeline import WorkDirManager
18
+ from ...utils import logger, run_ext_cmd
19
+
20
+
21
+ if TYPE_CHECKING:
22
+ from nibabel import Nifti1Image
23
+
24
+
25
+ __all__ = ["AFNISmoothing"]
26
+
27
+
28
+ class AFNISmoothing:
29
+ """Class for smoothing via AFNI.
30
+
31
+ This class uses AFNI's 3dBlurToFWHM.
32
+
33
+ """
34
+
35
+ _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
36
+ {
37
+ "name": "afni",
38
+ "commands": ["3dBlurToFWHM"],
39
+ },
40
+ ]
41
+
42
+ _DEPENDENCIES: ClassVar[Set[str]] = {"nibabel"}
43
+
44
+ def preprocess(
45
+ self,
46
+ data: "Nifti1Image",
47
+ fwhm: Union[int, float],
48
+ ) -> "Nifti1Image":
49
+ """Preprocess using AFNI.
50
+
51
+ Parameters
52
+ ----------
53
+ data : Niimg-like object
54
+ Image(s) to preprocess.
55
+ fwhm : int or float
56
+ Smooth until the value. AFNI estimates the smoothing and then
57
+ applies smoothing to reach ``fwhm``.
58
+
59
+ Returns
60
+ -------
61
+ Niimg-like object
62
+ The preprocessed image(s).
63
+
64
+ Notes
65
+ -----
66
+ For more information on ``3dBlurToFWHM``, check:
67
+ https://afni.nimh.nih.gov/pub/dist/doc/program_help/3dBlurToFWHM.html
68
+
69
+ As the process also depends on the conversion of AFNI files to NIfTI
70
+ via AFNI's ``3dAFNItoNIFTI``, the help for that can be found at:
71
+ https://afni.nimh.nih.gov/pub/dist/doc/program_help/3dAFNItoNIFTI.html
72
+
73
+ """
74
+ logger.info("Smoothing using AFNI")
75
+
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)
82
+
83
+ # Set 3dBlurToFWHM command
84
+ blur_out_path_prefix = tempdir / "blur"
85
+ blur_cmd = [
86
+ "3dBlurToFWHM",
87
+ f"-input {nifti_in_file_path.resolve()}",
88
+ f"-prefix {blur_out_path_prefix.resolve()}",
89
+ "-automask",
90
+ f"-FWHM {fwhm}",
91
+ ]
92
+ # Call 3dBlurToFWHM
93
+ run_ext_cmd(name="3dBlurToFWHM", cmd=blur_cmd)
94
+
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
+ )
101
+ # Convert afni to nifti
102
+ blur_afni_to_nifti_out_path = (
103
+ element_tempdir / "output.nii" # needs to be .nii
104
+ )
105
+ convert_cmd = [
106
+ "3dAFNItoNIFTI",
107
+ f"-prefix {blur_afni_to_nifti_out_path.resolve()}",
108
+ f"{blur_out_path_prefix}+orig.BRIK",
109
+ ]
110
+ # Call 3dAFNItoNIFTI
111
+ run_ext_cmd(name="3dAFNItoNIFTI", cmd=convert_cmd)
112
+
113
+ # Load nifti
114
+ output_data = nib.load(blur_afni_to_nifti_out_path)
115
+
116
+ # Delete tempdir
117
+ WorkDirManager().delete_tempdir(tempdir)
118
+
119
+ return output_data # type: ignore
@@ -0,0 +1,116 @@
1
+ """Provide class for smoothing via FSL."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from typing import (
7
+ TYPE_CHECKING,
8
+ ClassVar,
9
+ Dict,
10
+ List,
11
+ Set,
12
+ Union,
13
+ )
14
+
15
+ import nibabel as nib
16
+
17
+ from ...pipeline import WorkDirManager
18
+ from ...utils import logger, run_ext_cmd
19
+
20
+
21
+ if TYPE_CHECKING:
22
+ from nibabel import Nifti1Image
23
+
24
+
25
+ __all__ = ["FSLSmoothing"]
26
+
27
+
28
+ class FSLSmoothing:
29
+ """Class for smoothing via FSL.
30
+
31
+ This class uses FSL's susan.
32
+
33
+ """
34
+
35
+ _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
36
+ {
37
+ "name": "fsl",
38
+ "commands": ["susan"],
39
+ },
40
+ ]
41
+
42
+ _DEPENDENCIES: ClassVar[Set[str]] = {"nibabel"}
43
+
44
+ def preprocess(
45
+ self,
46
+ data: "Nifti1Image",
47
+ brightness_threshold: float,
48
+ fwhm: float,
49
+ ) -> "Nifti1Image":
50
+ """Preprocess using FSL.
51
+
52
+ Parameters
53
+ ----------
54
+ data : Niimg-like object
55
+ Image(s) to preprocess.
56
+ brightness_threshold : float
57
+ Threshold to discriminate between noise and the underlying image.
58
+ The value should be set greater than the noise level and less than
59
+ the contrast of the underlying image.
60
+ fwhm : float
61
+ Spatial extent of smoothing.
62
+
63
+ Returns
64
+ -------
65
+ Niimg-like object
66
+ The preprocessed image(s).
67
+
68
+ Notes
69
+ -----
70
+ For more information on ``SUSAN``, check [1]_
71
+
72
+ References
73
+ ----------
74
+ .. [1] Smith, S.M. and Brady, J.M. (1997).
75
+ SUSAN - a new approach to low level image processing.
76
+ International Journal of Computer Vision, Volume 23(1),
77
+ Pages 45-78.
78
+
79
+ """
80
+ logger.info("Smoothing using FSL")
81
+
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
+ # Create element-scoped tempdir so that the output is
90
+ # available later as nibabel stores file path reference for
91
+ # loading on computation
92
+ element_tempdir = WorkDirManager().get_element_tempdir(
93
+ prefix="fsl_susan"
94
+ )
95
+ susan_out_path = element_tempdir / "output.nii.gz"
96
+ # Set susan command
97
+ susan_cmd = [
98
+ "susan",
99
+ f"{nifti_in_file_path.resolve()}",
100
+ f"{brightness_threshold}",
101
+ f"{fwhm}",
102
+ "3", # dimension
103
+ "1", # use median when no neighbourhood is found
104
+ "0", # use input image to find USAN
105
+ f"{susan_out_path.resolve()}",
106
+ ]
107
+ # Call susan
108
+ run_ext_cmd(name="susan", cmd=susan_cmd)
109
+
110
+ # Load nifti
111
+ output_data = nib.load(susan_out_path)
112
+
113
+ # Delete tempdir
114
+ WorkDirManager().delete_tempdir(tempdir)
115
+
116
+ return output_data # type: ignore
@@ -0,0 +1,69 @@
1
+ """Provide class for smoothing via nilearn."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from typing import (
7
+ TYPE_CHECKING,
8
+ ClassVar,
9
+ Literal,
10
+ Set,
11
+ Union,
12
+ )
13
+
14
+ from nilearn import image as nimg
15
+ from numpy.typing import ArrayLike
16
+
17
+ from ...utils import logger
18
+
19
+
20
+ if TYPE_CHECKING:
21
+ from nibabel import Nifti1Image
22
+
23
+
24
+ __all__ = ["NilearnSmoothing"]
25
+
26
+
27
+ class NilearnSmoothing:
28
+ """Class for smoothing via nilearn.
29
+
30
+ This class uses :func:`nilearn.image.smooth_img` to smooth image(s).
31
+
32
+ """
33
+
34
+ _DEPENDENCIES: ClassVar[Set[str]] = {"nilearn"}
35
+
36
+ def preprocess(
37
+ self,
38
+ data: "Nifti1Image",
39
+ fwhm: Union[int, float, ArrayLike, Literal["fast"], None],
40
+ ) -> "Nifti1Image":
41
+ """Preprocess using nilearn.
42
+
43
+ Parameters
44
+ ----------
45
+ data : Niimg-like object
46
+ Image(s) to preprocess.
47
+ fwhm : scalar, ``numpy.ndarray``, tuple or list of scalar, "fast" or \
48
+ None
49
+ Smoothing strength, as a full-width at half maximum, in
50
+ millimeters:
51
+
52
+ * If nonzero scalar, width is identical in all 3 directions.
53
+ * If ``numpy.ndarray``, tuple, or list, it must have 3 elements,
54
+ giving the FWHM along each axis. If any of the elements is 0 or
55
+ None, smoothing is not performed along that axis.
56
+ * If ``"fast"``, a fast smoothing will be performed with a filter
57
+ ``[0.2, 1, 0.2]`` in each direction and a normalisation to
58
+ preserve the local average value.
59
+ * If None, no filtering is performed (useful when just removal of
60
+ non-finite values is needed).
61
+
62
+ Returns
63
+ -------
64
+ Niimg-like object
65
+ The preprocessed image(s).
66
+
67
+ """
68
+ logger.info("Smoothing using nilearn")
69
+ return nimg.smooth_img(imgs=data, fwhm=fwhm) # type: ignore