junifer 0.0.3.dev186__py3-none-any.whl → 0.0.4__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 (178) hide show
  1. junifer/_version.py +14 -2
  2. junifer/api/cli.py +162 -17
  3. junifer/api/functions.py +87 -419
  4. junifer/api/parser.py +24 -0
  5. junifer/api/queue_context/__init__.py +8 -0
  6. junifer/api/queue_context/gnu_parallel_local_adapter.py +258 -0
  7. junifer/api/queue_context/htcondor_adapter.py +365 -0
  8. junifer/api/queue_context/queue_context_adapter.py +60 -0
  9. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +192 -0
  10. junifer/api/queue_context/tests/test_htcondor_adapter.py +257 -0
  11. junifer/api/res/afni/run_afni_docker.sh +6 -6
  12. junifer/api/res/ants/ResampleImage +3 -0
  13. junifer/api/res/ants/antsApplyTransforms +3 -0
  14. junifer/api/res/ants/antsApplyTransformsToPoints +3 -0
  15. junifer/api/res/ants/run_ants_docker.sh +39 -0
  16. junifer/api/res/fsl/applywarp +3 -0
  17. junifer/api/res/fsl/flirt +3 -0
  18. junifer/api/res/fsl/img2imgcoord +3 -0
  19. junifer/api/res/fsl/run_fsl_docker.sh +39 -0
  20. junifer/api/res/fsl/std2imgcoord +3 -0
  21. junifer/api/res/run_conda.sh +4 -4
  22. junifer/api/res/run_venv.sh +22 -0
  23. junifer/api/tests/data/partly_cloudy_agg_mean_tian.yml +16 -0
  24. junifer/api/tests/test_api_utils.py +21 -3
  25. junifer/api/tests/test_cli.py +232 -9
  26. junifer/api/tests/test_functions.py +211 -439
  27. junifer/api/tests/test_parser.py +1 -1
  28. junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +6 -1
  29. junifer/configs/juseless/datagrabbers/camcan_vbm.py +6 -1
  30. junifer/configs/juseless/datagrabbers/ixi_vbm.py +6 -1
  31. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +8 -8
  32. junifer/configs/juseless/datagrabbers/ucla.py +44 -26
  33. junifer/configs/juseless/datagrabbers/ukb_vbm.py +6 -1
  34. junifer/data/VOIs/meta/AutobiographicalMemory_VOIs.txt +23 -0
  35. junifer/data/VOIs/meta/Power2013_MNI_VOIs.tsv +264 -0
  36. junifer/data/__init__.py +4 -0
  37. junifer/data/coordinates.py +298 -31
  38. junifer/data/masks.py +360 -28
  39. junifer/data/parcellations.py +621 -188
  40. junifer/data/template_spaces.py +190 -0
  41. junifer/data/tests/test_coordinates.py +34 -3
  42. junifer/data/tests/test_data_utils.py +1 -0
  43. junifer/data/tests/test_masks.py +202 -86
  44. junifer/data/tests/test_parcellations.py +266 -55
  45. junifer/data/tests/test_template_spaces.py +104 -0
  46. junifer/data/utils.py +4 -2
  47. junifer/datagrabber/__init__.py +1 -0
  48. junifer/datagrabber/aomic/id1000.py +111 -70
  49. junifer/datagrabber/aomic/piop1.py +116 -53
  50. junifer/datagrabber/aomic/piop2.py +116 -53
  51. junifer/datagrabber/aomic/tests/test_id1000.py +27 -27
  52. junifer/datagrabber/aomic/tests/test_piop1.py +27 -27
  53. junifer/datagrabber/aomic/tests/test_piop2.py +27 -27
  54. junifer/datagrabber/base.py +62 -10
  55. junifer/datagrabber/datalad_base.py +0 -2
  56. junifer/datagrabber/dmcc13_benchmark.py +372 -0
  57. junifer/datagrabber/hcp1200/datalad_hcp1200.py +5 -0
  58. junifer/datagrabber/hcp1200/hcp1200.py +30 -13
  59. junifer/datagrabber/pattern.py +133 -27
  60. junifer/datagrabber/pattern_datalad.py +111 -13
  61. junifer/datagrabber/tests/test_base.py +57 -6
  62. junifer/datagrabber/tests/test_datagrabber_utils.py +204 -76
  63. junifer/datagrabber/tests/test_datalad_base.py +0 -6
  64. junifer/datagrabber/tests/test_dmcc13_benchmark.py +256 -0
  65. junifer/datagrabber/tests/test_multiple.py +43 -10
  66. junifer/datagrabber/tests/test_pattern.py +125 -178
  67. junifer/datagrabber/tests/test_pattern_datalad.py +44 -25
  68. junifer/datagrabber/utils.py +151 -16
  69. junifer/datareader/default.py +36 -10
  70. junifer/external/nilearn/junifer_nifti_spheres_masker.py +6 -0
  71. junifer/markers/base.py +25 -16
  72. junifer/markers/collection.py +35 -16
  73. junifer/markers/complexity/__init__.py +27 -0
  74. junifer/markers/complexity/complexity_base.py +149 -0
  75. junifer/markers/complexity/hurst_exponent.py +136 -0
  76. junifer/markers/complexity/multiscale_entropy_auc.py +140 -0
  77. junifer/markers/complexity/perm_entropy.py +132 -0
  78. junifer/markers/complexity/range_entropy.py +136 -0
  79. junifer/markers/complexity/range_entropy_auc.py +145 -0
  80. junifer/markers/complexity/sample_entropy.py +134 -0
  81. junifer/markers/complexity/tests/test_complexity_base.py +19 -0
  82. junifer/markers/complexity/tests/test_hurst_exponent.py +69 -0
  83. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +68 -0
  84. junifer/markers/complexity/tests/test_perm_entropy.py +68 -0
  85. junifer/markers/complexity/tests/test_range_entropy.py +69 -0
  86. junifer/markers/complexity/tests/test_range_entropy_auc.py +69 -0
  87. junifer/markers/complexity/tests/test_sample_entropy.py +68 -0
  88. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +68 -0
  89. junifer/markers/complexity/weighted_perm_entropy.py +133 -0
  90. junifer/markers/falff/_afni_falff.py +153 -0
  91. junifer/markers/falff/_junifer_falff.py +142 -0
  92. junifer/markers/falff/falff_base.py +91 -84
  93. junifer/markers/falff/falff_parcels.py +61 -45
  94. junifer/markers/falff/falff_spheres.py +64 -48
  95. junifer/markers/falff/tests/test_falff_parcels.py +89 -121
  96. junifer/markers/falff/tests/test_falff_spheres.py +92 -127
  97. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +1 -0
  98. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +1 -0
  99. junifer/markers/functional_connectivity/functional_connectivity_base.py +1 -0
  100. junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +46 -44
  101. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +34 -39
  102. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +40 -52
  103. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +62 -70
  104. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +99 -85
  105. junifer/markers/parcel_aggregation.py +60 -38
  106. junifer/markers/reho/_afni_reho.py +192 -0
  107. junifer/markers/reho/_junifer_reho.py +281 -0
  108. junifer/markers/reho/reho_base.py +69 -34
  109. junifer/markers/reho/reho_parcels.py +26 -16
  110. junifer/markers/reho/reho_spheres.py +23 -9
  111. junifer/markers/reho/tests/test_reho_parcels.py +93 -92
  112. junifer/markers/reho/tests/test_reho_spheres.py +88 -86
  113. junifer/markers/sphere_aggregation.py +54 -9
  114. junifer/markers/temporal_snr/temporal_snr_base.py +1 -0
  115. junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +38 -37
  116. junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +34 -38
  117. junifer/markers/tests/test_collection.py +43 -42
  118. junifer/markers/tests/test_ets_rss.py +29 -37
  119. junifer/markers/tests/test_parcel_aggregation.py +587 -468
  120. junifer/markers/tests/test_sphere_aggregation.py +209 -157
  121. junifer/markers/utils.py +2 -40
  122. junifer/onthefly/read_transform.py +13 -6
  123. junifer/pipeline/__init__.py +1 -0
  124. junifer/pipeline/pipeline_step_mixin.py +105 -41
  125. junifer/pipeline/registry.py +17 -0
  126. junifer/pipeline/singleton.py +45 -0
  127. junifer/pipeline/tests/test_pipeline_step_mixin.py +139 -51
  128. junifer/pipeline/tests/test_update_meta_mixin.py +1 -0
  129. junifer/pipeline/tests/test_workdir_manager.py +104 -0
  130. junifer/pipeline/update_meta_mixin.py +8 -2
  131. junifer/pipeline/utils.py +154 -15
  132. junifer/pipeline/workdir_manager.py +246 -0
  133. junifer/preprocess/__init__.py +3 -0
  134. junifer/preprocess/ants/__init__.py +4 -0
  135. junifer/preprocess/ants/ants_apply_transforms_warper.py +185 -0
  136. junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py +56 -0
  137. junifer/preprocess/base.py +96 -69
  138. junifer/preprocess/bold_warper.py +265 -0
  139. junifer/preprocess/confounds/fmriprep_confound_remover.py +91 -134
  140. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +106 -111
  141. junifer/preprocess/fsl/__init__.py +4 -0
  142. junifer/preprocess/fsl/apply_warper.py +179 -0
  143. junifer/preprocess/fsl/tests/test_apply_warper.py +45 -0
  144. junifer/preprocess/tests/test_bold_warper.py +159 -0
  145. junifer/preprocess/tests/test_preprocess_base.py +6 -6
  146. junifer/preprocess/warping/__init__.py +6 -0
  147. junifer/preprocess/warping/_ants_warper.py +167 -0
  148. junifer/preprocess/warping/_fsl_warper.py +109 -0
  149. junifer/preprocess/warping/space_warper.py +213 -0
  150. junifer/preprocess/warping/tests/test_space_warper.py +198 -0
  151. junifer/stats.py +18 -4
  152. junifer/storage/base.py +9 -1
  153. junifer/storage/hdf5.py +8 -3
  154. junifer/storage/pandas_base.py +2 -1
  155. junifer/storage/sqlite.py +1 -0
  156. junifer/storage/tests/test_hdf5.py +2 -1
  157. junifer/storage/tests/test_sqlite.py +8 -8
  158. junifer/storage/tests/test_utils.py +6 -6
  159. junifer/storage/utils.py +1 -0
  160. junifer/testing/datagrabbers.py +11 -7
  161. junifer/testing/utils.py +1 -0
  162. junifer/tests/test_stats.py +2 -0
  163. junifer/utils/__init__.py +1 -0
  164. junifer/utils/helpers.py +53 -0
  165. junifer/utils/logging.py +14 -3
  166. junifer/utils/tests/test_helpers.py +35 -0
  167. {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/METADATA +59 -28
  168. junifer-0.0.4.dist-info/RECORD +257 -0
  169. {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/WHEEL +1 -1
  170. junifer/markers/falff/falff_estimator.py +0 -334
  171. junifer/markers/falff/tests/test_falff_estimator.py +0 -238
  172. junifer/markers/reho/reho_estimator.py +0 -515
  173. junifer/markers/reho/tests/test_reho_estimator.py +0 -260
  174. junifer-0.0.3.dev186.dist-info/RECORD +0 -199
  175. {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/AUTHORS.rst +0 -0
  176. {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/LICENSE.md +0 -0
  177. {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/entry_points.txt +0 -0
  178. {junifer-0.0.3.dev186.dist-info → junifer-0.0.4.dist-info}/top_level.txt +0 -0
@@ -5,9 +5,8 @@
5
5
  # Synchon Mandal <s.mandal@fz-juelich.de>
6
6
  # License: AGPL
7
7
 
8
- import typing
8
+ from typing import List
9
9
 
10
- import nibabel as nib
11
10
  import numpy as np
12
11
  import pandas as pd
13
12
  import pytest
@@ -40,39 +39,62 @@ def test_fMRIPrepConfoundRemover_init() -> None:
40
39
  fMRIPrepConfoundRemover(strategy={"motion": "wrong"})
41
40
 
42
41
 
43
- def test_fMRIPrepConfoundRemover_validate_input() -> None:
44
- """Test fMRIPrepConfoundRemover validate_input."""
45
- confound_remover = fMRIPrepConfoundRemover()
42
+ @pytest.mark.parametrize(
43
+ "input_",
44
+ [
45
+ ["T1w"],
46
+ ["BOLD"],
47
+ ["T1w", "BOLD"],
48
+ ],
49
+ )
50
+ def test_fMRIPrepConfoundRemover_validate_input_errors(
51
+ input_: List[str],
52
+ ) -> None:
53
+ """Test errors for fMRIPrepConfoundRemover validate_input.
46
54
 
47
- # Input is valid when both BOLD and BOLD_confounds are present
55
+ Parameters
56
+ ----------
57
+ input_ : list of str
58
+ The input data types.
48
59
 
49
- input = ["T1w"]
50
- with pytest.raises(ValueError, match="not have the required data"):
51
- confound_remover.validate_input(input)
60
+ """
61
+ confound_remover = fMRIPrepConfoundRemover()
52
62
 
53
- input = ["BOLD"]
54
63
  with pytest.raises(ValueError, match="not have the required data"):
55
- confound_remover.validate_input(input)
64
+ confound_remover.validate_input(input_)
56
65
 
57
- input = ["BOLD", "T1w"]
58
- with pytest.raises(ValueError, match="not have the required data"):
59
- confound_remover.validate_input(input)
60
66
 
61
- input = ["BOLD", "T1w", "BOLD_confounds"]
62
- confound_remover.validate_input(input)
67
+ @pytest.mark.parametrize(
68
+ "input_",
69
+ [
70
+ ["BOLD", "BOLD_confounds"],
71
+ ["T1w", "BOLD", "BOLD_confounds"],
72
+ ],
73
+ )
74
+ def test_fMRIPrepConfoundRemover_validate_input(input_: List[str]) -> None:
75
+ """Test fMRIPrepConfoundRemover validate_input.
76
+
77
+ Parameters
78
+ ----------
79
+ input_ : list of str
80
+ The input data types.
81
+
82
+ """
83
+ confound_remover = fMRIPrepConfoundRemover()
84
+ confound_remover.validate_input(input_)
85
+
86
+
87
+ def test_fMRIPrepConfoundRemover_get_valid_inputs() -> None:
88
+ """Test fMRIPrepConfoundRemover get_valid_inputs."""
89
+ confound_remover = fMRIPrepConfoundRemover()
90
+ assert confound_remover.get_valid_inputs() == ["BOLD"]
63
91
 
64
92
 
65
93
  def test_fMRIPrepConfoundRemover_get_output_type() -> None:
66
- """Test fMRIPrepConfoundRemover validate_input."""
94
+ """Test fMRIPrepConfoundRemover get_output_type."""
67
95
  confound_remover = fMRIPrepConfoundRemover()
68
- inputs = [
69
- ["BOLD", "T1w", "BOLD_confounds"],
70
- ["BOLD", "VBM_GM", "BOLD_confounds"],
71
- ["BOLD", "BOLD_confounds"],
72
- ]
73
96
  # Confound remover works in place
74
- for input in inputs:
75
- assert confound_remover.get_output_type(input) == input
97
+ assert confound_remover.get_output_type("BOLD") == "BOLD"
76
98
 
77
99
 
78
100
  def test_fMRIPrepConfoundRemover__map_adhoc_to_fmriprep() -> None:
@@ -259,7 +281,7 @@ def test_fMRIPrepConfoundRemover__pick_confounds_adhoc() -> None:
259
281
  assert set(out.columns) == set(fmriprep_all_vars)
260
282
 
261
283
 
262
- def test_FMRIPRepConfoundRemover__pick_confounds_fmriprep() -> None:
284
+ def test_fMRIPRepConfoundRemover__pick_confounds_fmriprep() -> None:
263
285
  """Test fMRIPrepConfoundRemover pick confounds on fmriprep confounds."""
264
286
  confound_remover = fMRIPrepConfoundRemover(
265
287
  strategy={"wm_csf": "full"}, spike=0.2
@@ -292,7 +314,7 @@ def test_FMRIPRepConfoundRemover__pick_confounds_fmriprep() -> None:
292
314
  assert_frame_equal(out1, out2)
293
315
 
294
316
 
295
- def test_FMRIPRepConfoundRemover__pick_confounds_fmriprep_compute() -> None:
317
+ def test_fMRIPRepConfoundRemover__pick_confounds_fmriprep_compute() -> None:
296
318
  """Test if fmriprep returns the same derivatives/power2 as we compute."""
297
319
 
298
320
  confound_remover = fMRIPrepConfoundRemover(strategy={"wm_csf": "full"})
@@ -326,78 +348,80 @@ def test_FMRIPRepConfoundRemover__pick_confounds_fmriprep_compute() -> None:
326
348
  def test_fMRIPrepConfoundRemover__validate_data() -> None:
327
349
  """Test fMRIPrepConfoundRemover validate data."""
328
350
  confound_remover = fMRIPrepConfoundRemover(strategy={"wm_csf": "full"})
329
- reader = DefaultDataReader()
351
+
330
352
  with OasisVBMTestingDataGrabber() as dg:
331
- input = dg["sub-01"]
332
- input = reader.fit_transform(input)
333
- new_input = input["VBM_GM"]
353
+ element_data = DefaultDataReader().fit_transform(dg["sub-01"])
354
+ vbm = element_data["VBM_GM"]
334
355
  with pytest.raises(
335
356
  DimensionError, match="incompatible dimensionality"
336
357
  ):
337
- confound_remover._validate_data(new_input, None)
358
+ confound_remover._validate_data(vbm, None)
338
359
 
339
360
  with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
340
- input = dg["sub-01"]
341
- input = reader.fit_transform(input)
342
- new_input = input["BOLD"]
361
+ element_data = DefaultDataReader().fit_transform(dg["sub-01"])
362
+ bold = element_data["BOLD"]
343
363
 
344
364
  with pytest.raises(ValueError, match="No extra input"):
345
- confound_remover._validate_data(new_input, None)
346
- with pytest.raises(ValueError, match="No BOLD_confounds provided"):
347
- confound_remover._validate_data(new_input, {})
365
+ confound_remover._validate_data(bold, None)
366
+ with pytest.raises(
367
+ ValueError, match="`BOLD_confounds` data type not provided"
368
+ ):
369
+ confound_remover._validate_data(bold, {})
348
370
  with pytest.raises(
349
- ValueError, match="No BOLD_confounds data provided"
371
+ ValueError, match="`BOLD_confounds.data` not provided"
350
372
  ):
351
- confound_remover._validate_data(new_input, {"BOLD_confounds": {}})
373
+ confound_remover._validate_data(bold, {"BOLD_confounds": {}})
352
374
 
353
375
  extra_input = {
354
376
  "BOLD_confounds": {"data": "wrong"},
355
377
  }
356
- msg = "must be a pandas dataframe"
378
+ msg = "must be a `pandas.DataFrame`"
357
379
  with pytest.raises(ValueError, match=msg):
358
- confound_remover._validate_data(new_input, extra_input)
380
+ confound_remover._validate_data(bold, extra_input)
359
381
 
360
382
  extra_input = {"BOLD_confounds": {"data": pd.DataFrame()}}
361
383
  with pytest.raises(ValueError, match="Image time series and"):
362
- confound_remover._validate_data(new_input, extra_input)
384
+ confound_remover._validate_data(bold, extra_input)
363
385
 
364
386
  extra_input = {
365
- "BOLD_confounds": {"data": input["BOLD_confounds"]["data"]}
387
+ "BOLD_confounds": {"data": element_data["BOLD_confounds"]["data"]}
366
388
  }
367
- with pytest.raises(ValueError, match="format must be specified"):
368
- confound_remover._validate_data(new_input, extra_input)
389
+ with pytest.raises(
390
+ ValueError, match="`BOLD_confounds.format` not provided"
391
+ ):
392
+ confound_remover._validate_data(bold, extra_input)
369
393
 
370
394
  extra_input = {
371
395
  "BOLD_confounds": {
372
- "data": input["BOLD_confounds"]["data"],
396
+ "data": element_data["BOLD_confounds"]["data"],
373
397
  "format": "wrong",
374
398
  }
375
399
  }
376
- with pytest.raises(ValueError, match="Invalid confounds format wrong"):
377
- confound_remover._validate_data(new_input, extra_input)
400
+ with pytest.raises(ValueError, match="Invalid confounds format"):
401
+ confound_remover._validate_data(bold, extra_input)
378
402
 
379
403
  extra_input = {
380
404
  "BOLD_confounds": {
381
- "data": input["BOLD_confounds"]["data"],
405
+ "data": element_data["BOLD_confounds"]["data"],
382
406
  "format": "adhoc",
383
407
  }
384
408
  }
385
- with pytest.raises(ValueError, match="variables names mappings"):
386
- confound_remover._validate_data(new_input, extra_input)
409
+ with pytest.raises(ValueError, match="need to be set"):
410
+ confound_remover._validate_data(bold, extra_input)
387
411
 
388
412
  extra_input = {
389
413
  "BOLD_confounds": {
390
- "data": input["BOLD_confounds"]["data"],
414
+ "data": element_data["BOLD_confounds"]["data"],
391
415
  "format": "adhoc",
392
416
  "mappings": {},
393
417
  }
394
418
  }
395
- with pytest.raises(ValueError, match="mappings to fmriprep"):
396
- confound_remover._validate_data(new_input, extra_input)
419
+ with pytest.raises(ValueError, match="need to be set"):
420
+ confound_remover._validate_data(bold, extra_input)
397
421
 
398
422
  extra_input = {
399
423
  "BOLD_confounds": {
400
- "data": input["BOLD_confounds"]["data"],
424
+ "data": element_data["BOLD_confounds"]["data"],
401
425
  "format": "adhoc",
402
426
  "mappings": {
403
427
  "fmriprep": {
@@ -409,11 +433,11 @@ def test_fMRIPrepConfoundRemover__validate_data() -> None:
409
433
  }
410
434
  }
411
435
  with pytest.raises(ValueError, match=r"names: \['wrong'\]"):
412
- confound_remover._validate_data(new_input, extra_input)
436
+ confound_remover._validate_data(bold, extra_input)
413
437
 
414
438
  extra_input = {
415
439
  "BOLD_confounds": {
416
- "data": input["BOLD_confounds"]["data"],
440
+ "data": element_data["BOLD_confounds"]["data"],
417
441
  "format": "adhoc",
418
442
  "mappings": {
419
443
  "fmriprep": {
@@ -425,11 +449,11 @@ def test_fMRIPrepConfoundRemover__validate_data() -> None:
425
449
  }
426
450
  }
427
451
  with pytest.raises(ValueError, match=r"Missing columns: \['wrong'\]"):
428
- confound_remover._validate_data(new_input, extra_input)
452
+ confound_remover._validate_data(bold, extra_input)
429
453
 
430
454
  extra_input = {
431
455
  "BOLD_confounds": {
432
- "data": input["BOLD_confounds"]["data"],
456
+ "data": element_data["BOLD_confounds"]["data"],
433
457
  "format": "adhoc",
434
458
  "mappings": {
435
459
  "fmriprep": {
@@ -440,51 +464,25 @@ def test_fMRIPrepConfoundRemover__validate_data() -> None:
440
464
  },
441
465
  }
442
466
  }
443
- confound_remover._validate_data(new_input, extra_input)
444
-
445
-
446
- def test_fMRIPrepConfoundRemover__remove_confounds() -> None:
447
- """Test fMRIPrepConfoundRemover remove confounds."""
448
- confound_remover = fMRIPrepConfoundRemover(
449
- strategy={"wm_csf": "full"}, spike=0.2
450
- )
451
- reader = DefaultDataReader()
452
- with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
453
- input = dg["sub-01"]
454
- input = reader.fit_transform(input)
455
- raw_bold = input["BOLD"]["data"]
456
- extra_input = {k: v for k, v in input.items() if k != "BOLD"}
457
- clean_bold = confound_remover._remove_confounds(
458
- input=input["BOLD"], extra_input=extra_input
459
- )
460
- clean_bold = typing.cast(nib.Nifti1Image, clean_bold)
461
- # TODO: Find a better way to test functionality here
462
- assert (
463
- clean_bold.header.get_zooms() # type: ignore
464
- == raw_bold.header.get_zooms() # type: ignore
465
- )
466
- assert clean_bold.get_fdata().shape == raw_bold.get_fdata().shape
467
- # TODO: Test confound remover with mask, needs #79 to be implemented
467
+ confound_remover._validate_data(bold, extra_input)
468
468
 
469
469
 
470
470
  def test_fMRIPrepConfoundRemover_preprocess() -> None:
471
471
  """Test fMRIPrepConfoundRemover with all confounds present."""
472
-
473
- # need reader for the data
474
- reader = DefaultDataReader()
475
472
  # All strategies full, no spike
476
473
  confound_remover = fMRIPrepConfoundRemover()
477
474
 
478
475
  with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
479
- input = dg["sub-01"]
480
- input = reader.fit_transform(input)
481
- orig_bold = input["BOLD"]["data"].get_fdata().copy()
482
- pre_input = input["BOLD"]
483
- pre_extra_input = {"BOLD_confounds": input["BOLD_confounds"]}
484
- key, output = confound_remover.preprocess(pre_input, pre_extra_input)
476
+ element_data = DefaultDataReader().fit_transform(dg["sub-01"])
477
+ orig_bold = element_data["BOLD"]["data"].get_fdata().copy()
478
+ pre_input = element_data["BOLD"]
479
+ pre_extra_input = {"BOLD_confounds": element_data["BOLD_confounds"]}
480
+ output, _ = confound_remover.preprocess(pre_input, pre_extra_input)
485
481
  trans_bold = output["data"].get_fdata()
486
482
  # Transformation is in place
487
- assert_array_equal(trans_bold, input["BOLD"]["data"].get_fdata())
483
+ assert_array_equal(
484
+ trans_bold, element_data["BOLD"]["data"].get_fdata()
485
+ )
488
486
 
489
487
  # Data should have the same shape
490
488
  assert orig_bold.shape == trans_bold.shape
@@ -493,25 +491,22 @@ def test_fMRIPrepConfoundRemover_preprocess() -> None:
493
491
  assert_raises(
494
492
  AssertionError, assert_array_equal, orig_bold, trans_bold
495
493
  )
496
- assert key == "BOLD"
497
494
 
498
495
 
499
496
  def test_fMRIPrepConfoundRemover_fit_transform() -> None:
500
497
  """Test fMRIPrepConfoundRemover with all confounds present."""
501
-
502
- # need reader for the data
503
- reader = DefaultDataReader()
504
498
  # All strategies full, no spike
505
499
  confound_remover = fMRIPrepConfoundRemover()
506
500
 
507
501
  with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
508
- input = dg["sub-01"]
509
- input = reader.fit_transform(input)
510
- orig_bold = input["BOLD"]["data"].get_fdata().copy()
511
- output = confound_remover.fit_transform(input)
502
+ element_data = DefaultDataReader().fit_transform(dg["sub-01"])
503
+ orig_bold = element_data["BOLD"]["data"].get_fdata().copy()
504
+ output = confound_remover.fit_transform(element_data)
512
505
  trans_bold = output["BOLD"]["data"].get_fdata()
513
506
  # Transformation is in place
514
- assert_array_equal(trans_bold, input["BOLD"]["data"].get_fdata())
507
+ assert_array_equal(
508
+ trans_bold, element_data["BOLD"]["data"].get_fdata()
509
+ )
515
510
 
516
511
  # Data should have the same shape
517
512
  assert orig_bold.shape == trans_bold.shape
@@ -535,6 +530,8 @@ def test_fMRIPrepConfoundRemover_fit_transform() -> None:
535
530
  assert t_meta["t_r"] is None
536
531
  assert t_meta["masks"] is None
537
532
 
533
+ assert "BOLD_mask" not in output
534
+
538
535
  assert "dependencies" in output["BOLD"]["meta"]
539
536
  dependencies = output["BOLD"]["meta"]["dependencies"]
540
537
  assert dependencies == {"numpy", "nilearn"}
@@ -542,22 +539,20 @@ def test_fMRIPrepConfoundRemover_fit_transform() -> None:
542
539
 
543
540
  def test_fMRIPrepConfoundRemover_fit_transform_masks() -> None:
544
541
  """Test fMRIPrepConfoundRemover with all confounds present."""
545
-
546
- # need reader for the data
547
- reader = DefaultDataReader()
548
542
  # All strategies full, no spike
549
543
  confound_remover = fMRIPrepConfoundRemover(
550
544
  masks={"compute_brain_mask": {"threshold": 0.2}}
551
545
  )
552
546
 
553
547
  with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
554
- input = dg["sub-01"]
555
- input = reader.fit_transform(input)
556
- orig_bold = input["BOLD"]["data"].get_fdata().copy()
557
- output = confound_remover.fit_transform(input)
548
+ element_data = DefaultDataReader().fit_transform(dg["sub-01"])
549
+ orig_bold = element_data["BOLD"]["data"].get_fdata().copy()
550
+ output = confound_remover.fit_transform(element_data)
558
551
  trans_bold = output["BOLD"]["data"].get_fdata()
559
552
  # Transformation is in place
560
- assert_array_equal(trans_bold, input["BOLD"]["data"].get_fdata())
553
+ assert_array_equal(
554
+ trans_bold, element_data["BOLD"]["data"].get_fdata()
555
+ )
561
556
 
562
557
  # Data should have the same shape
563
558
  assert orig_bold.shape == trans_bold.shape
@@ -0,0 +1,4 @@
1
+ """Provide imports for fsl sub-package."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
@@ -0,0 +1,179 @@
1
+ """Provide class for warping via FSL FLIRT."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from pathlib import Path
7
+ from typing import (
8
+ TYPE_CHECKING,
9
+ Any,
10
+ ClassVar,
11
+ Dict,
12
+ List,
13
+ Optional,
14
+ Tuple,
15
+ Union,
16
+ )
17
+
18
+ import nibabel as nib
19
+ import numpy as np
20
+
21
+ from ...pipeline import WorkDirManager
22
+ from ...utils import logger, raise_error, run_ext_cmd
23
+
24
+
25
+ if TYPE_CHECKING:
26
+ from nibabel import Nifti1Image
27
+
28
+
29
+ class _ApplyWarper:
30
+ """Class for warping NIfTI images via FSL FLIRT.
31
+
32
+ Wraps FSL FLIRT ``applywarp``.
33
+
34
+ Parameters
35
+ ----------
36
+ reference : str
37
+ The data type to use as reference for warping.
38
+ on : str
39
+ The data type to use for warping.
40
+
41
+ Raises
42
+ ------
43
+ ValueError
44
+ If a list was passed for ``on``.
45
+
46
+ """
47
+
48
+ _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
49
+ {
50
+ "name": "fsl",
51
+ "commands": ["flirt", "applywarp"],
52
+ },
53
+ ]
54
+
55
+ def __init__(self, reference: str, on: str) -> None:
56
+ """Initialize the class."""
57
+ self.ref = reference
58
+ # Check only single data type is passed
59
+ if isinstance(on, list):
60
+ raise_error("Can only work on single data type, list was passed.")
61
+ self.on = on
62
+
63
+ def _run_applywarp(
64
+ self,
65
+ input_data: Dict,
66
+ ref_path: Path,
67
+ warp_path: Path,
68
+ ) -> Tuple["Nifti1Image", Path]:
69
+ """Run ``applywarp``.
70
+
71
+ Parameters
72
+ ----------
73
+ input_data : dict
74
+ The input data.
75
+ ref_path : pathlib.Path
76
+ The path to the reference file.
77
+ warp_path : pathlib.Path
78
+ The path to the warp file.
79
+
80
+ Returns
81
+ -------
82
+ Niimg-like object
83
+ The warped input image.
84
+ pathlib.Path
85
+ The path to the resampled reference image.
86
+
87
+ """
88
+ # Get the min of the voxel sizes from input and use it as the
89
+ # resolution
90
+ resolution = np.min(input_data["data"].header.get_zooms()[:3])
91
+
92
+ # Create element-specific tempdir for storing post-warping assets
93
+ tempdir = WorkDirManager().get_element_tempdir(prefix="applywarp")
94
+
95
+ # Create a tempfile for resampled reference output
96
+ flirt_out_path = tempdir / "reference_resampled.nii.gz"
97
+ # Set flirt command
98
+ flirt_cmd = [
99
+ "flirt",
100
+ "-interp spline",
101
+ f"-in {ref_path.resolve()}",
102
+ f"-ref {ref_path.resolve()}",
103
+ f"-applyisoxfm {resolution}",
104
+ f"-out {flirt_out_path.resolve()}",
105
+ ]
106
+ # Call flirt
107
+ run_ext_cmd(name="flirt", cmd=flirt_cmd)
108
+
109
+ # Create a tempfile for warped output
110
+ applywarp_out_path = tempdir / "input_warped.nii.gz"
111
+ # Set applywarp command
112
+ applywarp_cmd = [
113
+ "applywarp",
114
+ "--interp=spline",
115
+ f"-i {input_data['path'].resolve()}",
116
+ f"-r {flirt_out_path.resolve()}", # use resampled reference
117
+ f"-w {warp_path.resolve()}",
118
+ f"-o {applywarp_out_path.resolve()}",
119
+ ]
120
+ # Call applywarp
121
+ run_ext_cmd(name="applywarp", cmd=applywarp_cmd)
122
+
123
+ # Load nifti
124
+ output_img = nib.load(applywarp_out_path)
125
+
126
+ return output_img, flirt_out_path # type: ignore
127
+
128
+ def preprocess(
129
+ self,
130
+ input: Dict[str, Any],
131
+ extra_input: Optional[Dict[str, Any]] = None,
132
+ ) -> Tuple[str, Dict[str, Any]]:
133
+ """Preprocess.
134
+
135
+ Parameters
136
+ ----------
137
+ input : dict
138
+ A single input from the Junifer Data object in which to preprocess.
139
+ extra_input : dict, optional
140
+ The other fields in the Junifer Data object. Must include the
141
+ ``Warp`` and ``ref`` value's keys.
142
+
143
+ Returns
144
+ -------
145
+ str
146
+ The key to store the output in the Junifer Data object.
147
+ dict
148
+ The computed result as dictionary. This will be stored in the
149
+ Junifer Data object under the key ``data`` of the data type.
150
+
151
+ Raises
152
+ ------
153
+ ValueError
154
+ If ``extra_input`` is None.
155
+
156
+ """
157
+ logger.debug("Warping via FSL using ApplyWarper")
158
+ # Check for extra inputs
159
+ if extra_input is None:
160
+ raise_error(
161
+ f"No extra input provided, requires `Warp` and `{self.ref}` "
162
+ "data types in particular."
163
+ )
164
+ # Retrieve data type info to warp
165
+ to_warp_input = input
166
+ # Retrieve data type info to use as reference
167
+ ref_input = extra_input[self.ref]
168
+ # Retrieve Warp data
169
+ warp = extra_input["Warp"]
170
+ # Replace original data with warped data and add resampled reference
171
+ # path
172
+ input["data"], input["reference_path"] = self._run_applywarp(
173
+ input_data=to_warp_input,
174
+ ref_path=ref_input["path"],
175
+ warp_path=warp["path"],
176
+ )
177
+ # Use reference input's space as warped input's space
178
+ input["space"] = ref_input["space"]
179
+ return self.on, input
@@ -0,0 +1,45 @@
1
+ """Provide tests for ApplyWarper."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ import socket
7
+
8
+ import pytest
9
+
10
+ from junifer.datagrabber import DataladHCP1200
11
+ from junifer.datareader import DefaultDataReader
12
+ from junifer.pipeline.utils import _check_fsl
13
+ from junifer.preprocess.fsl.apply_warper import _ApplyWarper
14
+
15
+
16
+ def test_ApplyWarper_init() -> None:
17
+ """Test ApplyWarper init."""
18
+ apply_warper = _ApplyWarper(reference="T1w", on="BOLD")
19
+ assert apply_warper.ref == "T1w"
20
+ assert apply_warper.on == "BOLD"
21
+
22
+
23
+ @pytest.mark.skipif(_check_fsl() is False, reason="requires FSL to be in PATH")
24
+ @pytest.mark.skipif(
25
+ socket.gethostname() != "juseless",
26
+ reason="only for juseless",
27
+ )
28
+ def test_ApplyWarper_preprocess() -> None:
29
+ """Test ApplyWarper preprocess."""
30
+ with DataladHCP1200(
31
+ tasks=["REST1"],
32
+ phase_encodings=["LR"],
33
+ ica_fix=True,
34
+ ) as dg:
35
+ # Read data
36
+ element_data = DefaultDataReader().fit_transform(
37
+ dg[("100206", "REST1", "LR")]
38
+ )
39
+ # Preprocess data
40
+ data_type, data = _ApplyWarper(reference="T1w", on="BOLD").preprocess(
41
+ input=element_data["BOLD"],
42
+ extra_input=element_data,
43
+ )
44
+ assert isinstance(data_type, str)
45
+ assert isinstance(data, dict)