junifer 0.0.5__py3-none-any.whl → 0.0.5.dev24__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 (194) hide show
  1. junifer/__init__.py +0 -17
  2. junifer/_version.py +2 -2
  3. junifer/api/__init__.py +1 -4
  4. junifer/api/cli.py +1 -91
  5. junifer/api/decorators.py +0 -9
  6. junifer/api/functions.py +10 -56
  7. junifer/api/parser.py +0 -3
  8. junifer/api/queue_context/__init__.py +1 -4
  9. junifer/api/res/afni/run_afni_docker.sh +1 -1
  10. junifer/api/res/ants/run_ants_docker.sh +1 -1
  11. junifer/api/res/fsl/run_fsl_docker.sh +1 -1
  12. junifer/api/tests/test_api_utils.py +2 -4
  13. junifer/api/tests/test_cli.py +0 -83
  14. junifer/api/tests/test_functions.py +2 -27
  15. junifer/configs/__init__.py +1 -1
  16. junifer/configs/juseless/__init__.py +1 -4
  17. junifer/configs/juseless/datagrabbers/__init__.py +1 -10
  18. junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +0 -3
  19. junifer/configs/juseless/datagrabbers/camcan_vbm.py +0 -3
  20. junifer/configs/juseless/datagrabbers/ixi_vbm.py +0 -3
  21. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +3 -1
  22. junifer/configs/juseless/datagrabbers/ucla.py +9 -12
  23. junifer/configs/juseless/datagrabbers/ukb_vbm.py +0 -3
  24. junifer/data/__init__.py +1 -21
  25. junifer/data/coordinates.py +19 -10
  26. junifer/data/masks.py +87 -58
  27. junifer/data/parcellations.py +3 -14
  28. junifer/data/template_spaces.py +1 -4
  29. junifer/data/tests/test_masks.py +37 -26
  30. junifer/data/utils.py +0 -3
  31. junifer/datagrabber/__init__.py +1 -18
  32. junifer/datagrabber/aomic/__init__.py +0 -3
  33. junifer/datagrabber/aomic/id1000.py +37 -70
  34. junifer/datagrabber/aomic/piop1.py +36 -69
  35. junifer/datagrabber/aomic/piop2.py +38 -71
  36. junifer/datagrabber/aomic/tests/test_id1000.py +99 -44
  37. junifer/datagrabber/aomic/tests/test_piop1.py +108 -65
  38. junifer/datagrabber/aomic/tests/test_piop2.py +102 -45
  39. junifer/datagrabber/base.py +6 -13
  40. junifer/datagrabber/datalad_base.py +1 -13
  41. junifer/datagrabber/dmcc13_benchmark.py +53 -36
  42. junifer/datagrabber/hcp1200/__init__.py +0 -3
  43. junifer/datagrabber/hcp1200/datalad_hcp1200.py +0 -3
  44. junifer/datagrabber/hcp1200/hcp1200.py +1 -4
  45. junifer/datagrabber/multiple.py +6 -45
  46. junifer/datagrabber/pattern.py +62 -170
  47. junifer/datagrabber/pattern_datalad.py +12 -25
  48. junifer/datagrabber/tests/test_datagrabber_utils.py +218 -0
  49. junifer/datagrabber/tests/test_datalad_base.py +4 -4
  50. junifer/datagrabber/tests/test_dmcc13_benchmark.py +19 -46
  51. junifer/datagrabber/tests/test_multiple.py +84 -161
  52. junifer/datagrabber/tests/test_pattern.py +0 -45
  53. junifer/datagrabber/tests/test_pattern_datalad.py +4 -4
  54. junifer/datagrabber/utils.py +230 -0
  55. junifer/datareader/__init__.py +1 -4
  56. junifer/datareader/default.py +43 -95
  57. junifer/external/__init__.py +1 -1
  58. junifer/external/nilearn/__init__.py +1 -5
  59. junifer/external/nilearn/junifer_nifti_spheres_masker.py +9 -23
  60. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +1 -76
  61. junifer/markers/__init__.py +1 -23
  62. junifer/markers/base.py +28 -68
  63. junifer/markers/collection.py +2 -10
  64. junifer/markers/complexity/__init__.py +0 -10
  65. junifer/markers/complexity/complexity_base.py +43 -26
  66. junifer/markers/complexity/hurst_exponent.py +0 -3
  67. junifer/markers/complexity/multiscale_entropy_auc.py +0 -3
  68. junifer/markers/complexity/perm_entropy.py +0 -3
  69. junifer/markers/complexity/range_entropy.py +0 -3
  70. junifer/markers/complexity/range_entropy_auc.py +0 -3
  71. junifer/markers/complexity/sample_entropy.py +0 -3
  72. junifer/markers/complexity/tests/test_hurst_exponent.py +3 -11
  73. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +3 -11
  74. junifer/markers/complexity/tests/test_perm_entropy.py +3 -11
  75. junifer/markers/complexity/tests/test_range_entropy.py +3 -11
  76. junifer/markers/complexity/tests/test_range_entropy_auc.py +3 -11
  77. junifer/markers/complexity/tests/test_sample_entropy.py +3 -11
  78. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +3 -11
  79. junifer/markers/complexity/weighted_perm_entropy.py +0 -3
  80. junifer/markers/ets_rss.py +42 -27
  81. junifer/markers/falff/__init__.py +0 -3
  82. junifer/markers/falff/_afni_falff.py +2 -5
  83. junifer/markers/falff/_junifer_falff.py +0 -3
  84. junifer/markers/falff/falff_base.py +46 -20
  85. junifer/markers/falff/falff_parcels.py +27 -56
  86. junifer/markers/falff/falff_spheres.py +29 -60
  87. junifer/markers/falff/tests/test_falff_parcels.py +23 -39
  88. junifer/markers/falff/tests/test_falff_spheres.py +23 -39
  89. junifer/markers/functional_connectivity/__init__.py +0 -9
  90. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +60 -63
  91. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +32 -45
  92. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +36 -49
  93. junifer/markers/functional_connectivity/functional_connectivity_base.py +70 -71
  94. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +25 -34
  95. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +30 -40
  96. junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +7 -11
  97. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +7 -27
  98. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +12 -28
  99. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +11 -35
  100. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +62 -36
  101. junifer/markers/parcel_aggregation.py +61 -47
  102. junifer/markers/reho/__init__.py +0 -3
  103. junifer/markers/reho/_afni_reho.py +2 -5
  104. junifer/markers/reho/_junifer_reho.py +1 -4
  105. junifer/markers/reho/reho_base.py +27 -8
  106. junifer/markers/reho/reho_parcels.py +17 -28
  107. junifer/markers/reho/reho_spheres.py +18 -27
  108. junifer/markers/reho/tests/test_reho_parcels.py +3 -8
  109. junifer/markers/reho/tests/test_reho_spheres.py +3 -8
  110. junifer/markers/sphere_aggregation.py +59 -43
  111. junifer/markers/temporal_snr/__init__.py +0 -3
  112. junifer/markers/temporal_snr/temporal_snr_base.py +32 -23
  113. junifer/markers/temporal_snr/temporal_snr_parcels.py +6 -9
  114. junifer/markers/temporal_snr/temporal_snr_spheres.py +6 -9
  115. junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +3 -6
  116. junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +3 -6
  117. junifer/markers/tests/test_collection.py +8 -9
  118. junifer/markers/tests/test_ets_rss.py +9 -15
  119. junifer/markers/tests/test_markers_base.py +18 -17
  120. junifer/markers/tests/test_parcel_aggregation.py +32 -93
  121. junifer/markers/tests/test_sphere_aggregation.py +19 -72
  122. junifer/onthefly/__init__.py +1 -4
  123. junifer/onthefly/read_transform.py +0 -3
  124. junifer/pipeline/__init__.py +1 -9
  125. junifer/pipeline/pipeline_step_mixin.py +4 -21
  126. junifer/pipeline/registry.py +0 -3
  127. junifer/pipeline/singleton.py +0 -3
  128. junifer/pipeline/tests/test_registry.py +1 -1
  129. junifer/pipeline/update_meta_mixin.py +0 -3
  130. junifer/pipeline/utils.py +1 -67
  131. junifer/pipeline/workdir_manager.py +0 -3
  132. junifer/preprocess/__init__.py +2 -9
  133. junifer/preprocess/ants/__init__.py +4 -0
  134. junifer/preprocess/ants/ants_apply_transforms_warper.py +185 -0
  135. junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py +56 -0
  136. junifer/preprocess/base.py +3 -6
  137. junifer/preprocess/bold_warper.py +265 -0
  138. junifer/preprocess/confounds/__init__.py +0 -3
  139. junifer/preprocess/confounds/fmriprep_confound_remover.py +60 -47
  140. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +113 -72
  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/smoothing/__init__.py +0 -3
  145. junifer/preprocess/smoothing/_afni_smoothing.py +1 -1
  146. junifer/preprocess/tests/test_bold_warper.py +159 -0
  147. junifer/preprocess/warping/__init__.py +0 -3
  148. junifer/preprocess/warping/_ants_warper.py +0 -3
  149. junifer/preprocess/warping/_fsl_warper.py +0 -3
  150. junifer/stats.py +1 -4
  151. junifer/storage/__init__.py +1 -9
  152. junifer/storage/base.py +1 -40
  153. junifer/storage/hdf5.py +9 -71
  154. junifer/storage/pandas_base.py +0 -3
  155. junifer/storage/sqlite.py +0 -3
  156. junifer/storage/tests/test_hdf5.py +10 -82
  157. junifer/storage/utils.py +0 -9
  158. junifer/testing/__init__.py +1 -4
  159. junifer/testing/datagrabbers.py +6 -13
  160. junifer/testing/tests/test_partlycloudytesting_datagrabber.py +7 -7
  161. junifer/testing/utils.py +0 -3
  162. junifer/utils/__init__.py +2 -13
  163. junifer/utils/fs.py +0 -3
  164. junifer/utils/helpers.py +1 -32
  165. junifer/utils/logging.py +4 -33
  166. junifer/utils/tests/test_logging.py +0 -8
  167. {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/METADATA +16 -17
  168. junifer-0.0.5.dev24.dist-info/RECORD +265 -0
  169. {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/WHEEL +1 -1
  170. junifer/api/res/freesurfer/mri_binarize +0 -3
  171. junifer/api/res/freesurfer/mri_mc +0 -3
  172. junifer/api/res/freesurfer/mri_pretess +0 -3
  173. junifer/api/res/freesurfer/mris_convert +0 -3
  174. junifer/api/res/freesurfer/run_freesurfer_docker.sh +0 -61
  175. junifer/data/masks/ukb/UKB_15K_GM_template.nii.gz +0 -0
  176. junifer/datagrabber/pattern_validation_mixin.py +0 -388
  177. junifer/datagrabber/tests/test_pattern_validation_mixin.py +0 -249
  178. junifer/external/BrainPrint/brainprint/__init__.py +0 -4
  179. junifer/external/BrainPrint/brainprint/_version.py +0 -3
  180. junifer/external/BrainPrint/brainprint/asymmetry.py +0 -91
  181. junifer/external/BrainPrint/brainprint/brainprint.py +0 -441
  182. junifer/external/BrainPrint/brainprint/surfaces.py +0 -258
  183. junifer/external/BrainPrint/brainprint/utils/__init__.py +0 -1
  184. junifer/external/BrainPrint/brainprint/utils/_config.py +0 -112
  185. junifer/external/BrainPrint/brainprint/utils/utils.py +0 -188
  186. junifer/external/nilearn/junifer_connectivity_measure.py +0 -483
  187. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +0 -1089
  188. junifer/markers/brainprint.py +0 -459
  189. junifer/markers/tests/test_brainprint.py +0 -58
  190. junifer-0.0.5.dist-info/RECORD +0 -275
  191. {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/AUTHORS.rst +0 -0
  192. {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/LICENSE.md +0 -0
  193. {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/entry_points.txt +0 -0
  194. {junifer-0.0.5.dist-info → junifer-0.0.5.dev24.dist-info}/top_level.txt +0 -0
@@ -27,9 +27,6 @@ from ...utils import logger, raise_error
27
27
  from ..base import BasePreprocessor
28
28
 
29
29
 
30
- __all__ = ["fMRIPrepConfoundRemover"]
31
-
32
-
33
30
  FMRIPREP_BASICS = {
34
31
  "motion": [
35
32
  "trans_x",
@@ -206,7 +203,9 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
206
203
  "include it in the future",
207
204
  klass=ValueError,
208
205
  )
209
- super().__init__(on="BOLD", required_data_types=["BOLD"])
206
+ super().__init__(
207
+ on="BOLD", required_data_types=["BOLD", "BOLD_confounds"]
208
+ )
210
209
 
211
210
  def get_valid_inputs(self) -> List[str]:
212
211
  """Get valid data types for input.
@@ -362,7 +361,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
362
361
  Parameters
363
362
  ----------
364
363
  input : dict
365
- Dictionary containing the ``BOLD.confounds`` value from the
364
+ Dictionary containing the ``BOLD_confounds`` value from the
366
365
  Junifer Data object.
367
366
 
368
367
  Returns
@@ -371,6 +370,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
371
370
  Dataframe containing the relevant confounds.
372
371
 
373
372
  """
373
+
374
374
  confounds_format = input["format"]
375
375
  if confounds_format == "adhoc":
376
376
  self._map_adhoc_to_fmriprep(input)
@@ -416,42 +416,50 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
416
416
  def _validate_data(
417
417
  self,
418
418
  input: Dict[str, Any],
419
+ extra_input: Optional[Dict[str, Any]] = None,
419
420
  ) -> None:
420
421
  """Validate input data.
421
422
 
422
423
  Parameters
423
424
  ----------
424
425
  input : dict
425
- Dictionary containing the ``BOLD`` data from the
426
+ Dictionary containing the ``BOLD`` value from the
426
427
  Junifer Data object.
428
+ extra_input : dict, optional
429
+ Dictionary containing the rest of the Junifer Data object. Must
430
+ include the ``BOLD_confounds`` key.
427
431
 
428
432
  Raises
429
433
  ------
430
434
  ValueError
431
- If ``"confounds"`` is not found in ``input`` or
432
- if ``"data"`` key is not found in ``"input.confounds"`` or
433
- if ``"input.confounds.data"`` is not pandas.DataFrame or
435
+ If ``extra_input`` is None or
436
+ if ``"BOLD_confounds"`` is not found in ``extra_input`` or
437
+ if ``"data"`` key is not found in ``"BOLD_confounds"`` or
438
+ if ``"data"`` is not pandas.DataFrame or
434
439
  if image time series and confounds have different lengths or
435
- if ``format = "adhoc"`` and ``"mappings"`` key is not found or
436
- ``"fmriprep"`` key is not found in ``"mappings"`` or
437
- ``"fmriprep"`` has incorrect fMRIPrep mappings or required
438
- fMRIPrep mappings are not found or
439
- if invalid confounds format is found.
440
+ if ``"format"`` is not found in ``"BOLD_confounds"`` or
441
+ if ``format = "adhoc"`` and ``"mappings"`` key or ``"fmriprep"``
442
+ key or correct fMRIPrep mappings or required fMRIPrep mappings are
443
+ not found or if invalid confounds format is found.
440
444
 
441
445
  """
442
446
  # BOLD must be 4D niimg
443
447
  check_niimg_4d(input["data"])
444
- # Check for confound data
445
- if "confounds" not in input:
446
- raise_error("`BOLD.confounds` data type not provided")
447
- if "data" not in input["confounds"]:
448
- raise_error("`BOLD.confounds.data` not provided")
449
- # Confounds must be a pandas.DataFrame;
450
- # if extension is unknown, will not be read, which will give None
451
- if not isinstance(input["confounds"]["data"], pd.DataFrame):
452
- raise_error("`BOLD.confounds.data` must be a `pandas.DataFrame`")
453
-
454
- confound_df = input["confounds"]["data"]
448
+ # Check for extra inputs
449
+ if extra_input is None:
450
+ raise_error(
451
+ "No extra input provided, requires `BOLD_confounds` data type "
452
+ "in particular"
453
+ )
454
+ if "BOLD_confounds" not in extra_input:
455
+ raise_error("`BOLD_confounds` data type not provided")
456
+ if "data" not in extra_input["BOLD_confounds"]:
457
+ raise_error("`BOLD_confounds.data` not provided")
458
+ # Confounds must be a pandas.DataFrame
459
+ if not isinstance(extra_input["BOLD_confounds"]["data"], pd.DataFrame):
460
+ raise_error("`BOLD_confounds.data` must be a `pandas.DataFrame`")
461
+
462
+ confound_df = extra_input["BOLD_confounds"]["data"]
455
463
  bold_img = input["data"]
456
464
  if bold_img.get_fdata().shape[3] != len(confound_df):
457
465
  raise_error(
@@ -461,19 +469,23 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
461
469
  )
462
470
 
463
471
  # Check format
464
- t_format = input["confounds"]["format"]
472
+ if "format" not in extra_input["BOLD_confounds"]:
473
+ raise_error("`BOLD_confounds.format` not provided")
474
+ t_format = extra_input["BOLD_confounds"]["format"]
465
475
  if t_format == "adhoc":
466
- if "mappings" not in input["confounds"]:
476
+ if "mappings" not in extra_input["BOLD_confounds"]:
467
477
  raise_error(
468
- "`BOLD.confounds.mappings` need to be set when "
469
- "`BOLD.confounds.format == 'adhoc'`"
478
+ "`BOLD_confounds.mappings` need to be set when "
479
+ "`BOLD_confounds.format == 'adhoc'`"
470
480
  )
471
- if "fmriprep" not in input["confounds"]["mappings"]:
481
+ if "fmriprep" not in extra_input["BOLD_confounds"]["mappings"]:
472
482
  raise_error(
473
- "`BOLD.confounds.mappings.fmriprep` need to be set when "
474
- "`BOLD.confounds.format == 'adhoc'`"
483
+ "`BOLD_confounds.mappings.fmriprep` need to be set when "
484
+ "`BOLD_confounds.format == 'adhoc'`"
475
485
  )
476
- fmriprep_mappings = input["confounds"]["mappings"]["fmriprep"]
486
+ fmriprep_mappings = extra_input["BOLD_confounds"]["mappings"][
487
+ "fmriprep"
488
+ ]
477
489
  wrong_names = [
478
490
  x
479
491
  for x in fmriprep_mappings.values()
@@ -513,22 +525,22 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
513
525
  input : dict
514
526
  A single input from the Junifer Data object to preprocess.
515
527
  extra_input : dict, optional
516
- The other fields in the Junifer Data object.
528
+ The other fields in the Junifer Data object. Must include the
529
+ ``BOLD_confounds`` key.
517
530
 
518
531
  Returns
519
532
  -------
520
533
  dict
521
- The computed result as dictionary. If `self.masks` is not None,
522
- then the target data computed mask is updated for further steps.
523
- None
524
- Extra "helper" data types as dictionary to add to the Junifer Data
525
- object.
534
+ The computed result as dictionary.
535
+ dict or None
536
+ If `self.masks` is not None, then the target data computed mask is
537
+ returned else None.
526
538
 
527
539
  """
528
540
  # Validate data
529
- self._validate_data(input)
541
+ self._validate_data(input, extra_input)
530
542
  # Pick confounds
531
- confounds_df = self._pick_confounds(input["confounds"]) # type: ignore
543
+ confounds_df = self._pick_confounds(extra_input["BOLD_confounds"]) # type: ignore
532
544
  # Get BOLD data
533
545
  bold_img = input["data"]
534
546
  # Set t_r
@@ -541,6 +553,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
541
553
  )
542
554
  # Set mask data
543
555
  mask_img = None
556
+ bold_mask_dict = None
544
557
  if self.masks is not None:
545
558
  logger.debug(f"Masking with {self.masks}")
546
559
  mask_img = get_mask(
@@ -548,15 +561,15 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
548
561
  )
549
562
  # Return the BOLD mask and link it to the BOLD data type dict;
550
563
  # this allows to use "inherit" down the pipeline
551
- logger.debug("Setting `BOLD.mask`")
552
- input.update(
553
- {
554
- "mask": {
564
+ if extra_input is not None:
565
+ logger.debug("Setting `BOLD.mask_item`")
566
+ input["mask_item"] = "BOLD_mask"
567
+ bold_mask_dict = {
568
+ "BOLD_mask": {
555
569
  "data": mask_img,
556
570
  "space": input["space"],
557
571
  }
558
572
  }
559
- )
560
573
  # Clean image
561
574
  logger.info("Cleaning image using nilearn")
562
575
  logger.debug(f"\tdetrend: {self.detrend}")
@@ -574,4 +587,4 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
574
587
  mask_img=mask_img,
575
588
  )
576
589
 
577
- return input, None
590
+ return input, bold_mask_dict
@@ -20,7 +20,6 @@ from junifer.testing import get_testing_data
20
20
  from junifer.testing.datagrabbers import (
21
21
  OasisVBMTestingDataGrabber,
22
22
  PartlyCloudyTestingDataGrabber,
23
- SPMAuditoryTestingDataGrabber,
24
23
  )
25
24
 
26
25
 
@@ -43,10 +42,35 @@ def test_fMRIPrepConfoundRemover_init() -> None:
43
42
  @pytest.mark.parametrize(
44
43
  "input_",
45
44
  [
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
+ )
50
74
  def test_fMRIPrepConfoundRemover_validate_input(input_: List[str]) -> None:
51
75
  """Test fMRIPrepConfoundRemover validate_input.
52
76
 
@@ -278,13 +302,13 @@ def test_fMRIPRepConfoundRemover__pick_confounds_fmriprep() -> None:
278
302
  with PartlyCloudyTestingDataGrabber() as dg:
279
303
  input = dg["sub-01"]
280
304
  input = reader.fit_transform(input)
281
- out1 = confound_remover._pick_confounds(input["BOLD"]["confounds"])
305
+ out1 = confound_remover._pick_confounds(input["BOLD_confounds"])
282
306
  assert set(out1.columns) == {*fmriprep_all_vars, "spike"}
283
307
 
284
308
  with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
285
309
  input = dg["sub-01"]
286
310
  input = reader.fit_transform(input)
287
- out2 = confound_remover._pick_confounds(input["BOLD"]["confounds"])
311
+ out2 = confound_remover._pick_confounds(input["BOLD_confounds"])
288
312
  assert set(out2.columns) == {*fmriprep_all_vars, "spike"}
289
313
 
290
314
  assert_frame_equal(out1, out2)
@@ -324,106 +348,123 @@ def test_fMRIPRepConfoundRemover__pick_confounds_fmriprep_compute() -> None:
324
348
  def test_fMRIPrepConfoundRemover__validate_data() -> None:
325
349
  """Test fMRIPrepConfoundRemover validate data."""
326
350
  confound_remover = fMRIPrepConfoundRemover(strategy={"wm_csf": "full"})
327
- # Check correct data type
351
+
328
352
  with OasisVBMTestingDataGrabber() as dg:
329
353
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
330
354
  vbm = element_data["VBM_GM"]
331
355
  with pytest.raises(
332
356
  DimensionError, match="incompatible dimensionality"
333
357
  ):
334
- confound_remover._validate_data(vbm)
335
- # Check missing nested type in correct data type
336
- with SPMAuditoryTestingDataGrabber() as dg:
358
+ confound_remover._validate_data(vbm, None)
359
+
360
+ with PartlyCloudyTestingDataGrabber(reduce_confounds=False) as dg:
337
361
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
338
362
  bold = element_data["BOLD"]
339
- # Test confound type
363
+
364
+ with pytest.raises(ValueError, match="No extra input"):
365
+ confound_remover._validate_data(bold, None)
340
366
  with pytest.raises(
341
- ValueError, match="`BOLD.confounds` data type not provided"
367
+ ValueError, match="`BOLD_confounds` data type not provided"
342
368
  ):
343
- confound_remover._validate_data(bold)
344
- # Test confound data
345
- bold["confounds"] = {}
369
+ confound_remover._validate_data(bold, {})
346
370
  with pytest.raises(
347
- ValueError, match="`BOLD.confounds.data` not provided"
371
+ ValueError, match="`BOLD_confounds.data` not provided"
348
372
  ):
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()}
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()}}
356
383
  with pytest.raises(ValueError, match="Image time series and"):
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
- },
384
+ confound_remover._validate_data(bold, extra_input)
385
+
386
+ extra_input = {
387
+ "BOLD_confounds": {"data": element_data["BOLD_confounds"]["data"]}
368
388
  }
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"})
375
389
  with pytest.raises(
376
- ValueError, match="`BOLD.confounds.mappings` need to be set"
390
+ ValueError, match="`BOLD_confounds.format` not provided"
377
391
  ):
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
- {
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",
389
426
  "mappings": {
390
427
  "fmriprep": {
391
428
  "rot_x": "wrong",
392
429
  "rot_y": "rot_z",
393
430
  "rot_z": "rot_y",
394
- },
395
- }
431
+ }
432
+ },
396
433
  }
397
- )
434
+ }
398
435
  with pytest.raises(ValueError, match=r"names: \['wrong'\]"):
399
- confound_remover._validate_data(modified_bold)
400
- # Test missing fmriprep mappings for adhoc
401
- modified_bold["confounds"].update(
402
- {
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",
403
442
  "mappings": {
404
443
  "fmriprep": {
405
444
  "wrong": "rot_x",
406
445
  "rot_y": "rot_z",
407
446
  "rot_z": "rot_y",
408
- },
409
- }
447
+ }
448
+ },
410
449
  }
411
- )
450
+ }
412
451
  with pytest.raises(ValueError, match=r"Missing columns: \['wrong'\]"):
413
- confound_remover._validate_data(modified_bold)
414
- # Test correct adhoc format
415
- modified_bold["confounds"].update(
416
- {
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",
417
458
  "mappings": {
418
459
  "fmriprep": {
419
460
  "rot_x": "rot_x",
420
461
  "rot_y": "rot_z",
421
462
  "rot_z": "rot_y",
422
- },
423
- }
463
+ }
464
+ },
424
465
  }
425
- )
426
- confound_remover._validate_data(modified_bold)
466
+ }
467
+ confound_remover._validate_data(bold, extra_input)
427
468
 
428
469
 
429
470
  def test_fMRIPrepConfoundRemover_preprocess() -> None:
@@ -435,9 +476,7 @@ def test_fMRIPrepConfoundRemover_preprocess() -> None:
435
476
  element_data = DefaultDataReader().fit_transform(dg["sub-01"])
436
477
  orig_bold = element_data["BOLD"]["data"].get_fdata().copy()
437
478
  pre_input = element_data["BOLD"]
438
- pre_extra_input = {
439
- "BOLD": {"confounds": element_data["BOLD"]["confounds"]}
440
- }
479
+ pre_extra_input = {"BOLD_confounds": element_data["BOLD_confounds"]}
441
480
  output, _ = confound_remover.preprocess(pre_input, pre_extra_input)
442
481
  trans_bold = output["data"].get_fdata()
443
482
  # Transformation is in place
@@ -491,7 +530,7 @@ def test_fMRIPrepConfoundRemover_fit_transform() -> None:
491
530
  assert t_meta["t_r"] is None
492
531
  assert t_meta["masks"] is None
493
532
 
494
- assert "mask" not in output["BOLD"]
533
+ assert "BOLD_mask" not in output
495
534
 
496
535
  assert "dependencies" in output["BOLD"]["meta"]
497
536
  dependencies = output["BOLD"]["meta"]["dependencies"]
@@ -543,7 +582,9 @@ def test_fMRIPrepConfoundRemover_fit_transform_masks() -> None:
543
582
  assert "threshold" in t_meta["masks"]["compute_brain_mask"]
544
583
  assert t_meta["masks"]["compute_brain_mask"]["threshold"] == 0.2
545
584
 
546
- assert "mask" in output["BOLD"]
585
+ assert "BOLD_mask" in output
586
+ assert "mask_item" in output["BOLD"]
587
+ assert output["BOLD"]["mask_item"] == "BOLD_mask"
547
588
 
548
589
  assert "dependencies" in output["BOLD"]["meta"]
549
590
  dependencies = output["BOLD"]["meta"]["dependencies"]
@@ -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