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
@@ -0,0 +1,159 @@
1
+ """Provide tests for BOLDWarper."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ import socket
7
+ from typing import TYPE_CHECKING, Tuple
8
+
9
+ import pytest
10
+ from numpy.testing import assert_array_equal, assert_raises
11
+
12
+ from junifer.datagrabber import DataladHCP1200, DMCC13Benchmark
13
+ from junifer.datareader import DefaultDataReader
14
+ from junifer.pipeline.utils import _check_ants, _check_fsl
15
+ from junifer.preprocess import BOLDWarper
16
+
17
+
18
+ if TYPE_CHECKING:
19
+ from junifer.datagrabber import BaseDataGrabber
20
+
21
+
22
+ def test_BOLDWarper_init() -> None:
23
+ """Test BOLDWarper init."""
24
+ bold_warper = BOLDWarper(using="ants", reference="T1w")
25
+ assert bold_warper._on == ["BOLD"]
26
+
27
+
28
+ def test_BOLDWarper_get_valid_inputs() -> None:
29
+ """Test BOLDWarper get_valid_inputs."""
30
+ bold_warper = BOLDWarper(using="ants", reference="T1w")
31
+ assert bold_warper.get_valid_inputs() == ["BOLD"]
32
+
33
+
34
+ def test_BOLDWarper_get_output_type() -> None:
35
+ """Test BOLDWarper get_output_type."""
36
+ bold_warper = BOLDWarper(using="ants", reference="T1w")
37
+ assert bold_warper.get_output_type("BOLD") == "BOLD"
38
+
39
+
40
+ @pytest.mark.parametrize(
41
+ "datagrabber, element",
42
+ [
43
+ [
44
+ DMCC13Benchmark(
45
+ types=["BOLD", "T1w", "Warp"],
46
+ sessions=["ses-wave1bas"],
47
+ tasks=["Rest"],
48
+ phase_encodings=["AP"],
49
+ runs=["1"],
50
+ native_t1w=True,
51
+ ),
52
+ ("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1"),
53
+ ],
54
+ [
55
+ DataladHCP1200(
56
+ tasks=["REST1"],
57
+ phase_encodings=["LR"],
58
+ ica_fix=True,
59
+ ),
60
+ ("100206", "REST1", "LR"),
61
+ ],
62
+ ],
63
+ )
64
+ @pytest.mark.skipif(_check_fsl() is False, reason="requires FSL to be in PATH")
65
+ @pytest.mark.skipif(
66
+ _check_ants() is False, reason="requires ANTs to be in PATH"
67
+ )
68
+ @pytest.mark.skipif(
69
+ socket.gethostname() != "juseless",
70
+ reason="only for juseless",
71
+ )
72
+ def test_BOLDWarper_preprocess_to_native(
73
+ datagrabber: "BaseDataGrabber", element: Tuple[str, ...]
74
+ ) -> None:
75
+ """Test BOLDWarper preprocess.
76
+
77
+ Parameters
78
+ ----------
79
+ datagrabber : DataGrabber-like object
80
+ The parametrized DataGrabber objects.
81
+ element : tuple of str
82
+ The parametrized elements.
83
+
84
+ """
85
+ with datagrabber as dg:
86
+ # Read data
87
+ element_data = DefaultDataReader().fit_transform(dg[element])
88
+ # Preprocess data
89
+ data, _ = BOLDWarper(reference="T1w").preprocess(
90
+ input=element_data["BOLD"],
91
+ extra_input=element_data,
92
+ )
93
+ assert isinstance(data, dict)
94
+
95
+
96
+ @pytest.mark.parametrize(
97
+ "datagrabber, element, space",
98
+ [
99
+ [
100
+ DMCC13Benchmark(
101
+ types=["BOLD"],
102
+ sessions=["ses-wave1bas"],
103
+ tasks=["Rest"],
104
+ phase_encodings=["AP"],
105
+ runs=["1"],
106
+ native_t1w=False,
107
+ ),
108
+ ("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1"),
109
+ "MNI152NLin2009aAsym",
110
+ ],
111
+ [
112
+ DMCC13Benchmark(
113
+ types=["BOLD"],
114
+ sessions=["ses-wave1bas"],
115
+ tasks=["Rest"],
116
+ phase_encodings=["AP"],
117
+ runs=["1"],
118
+ native_t1w=False,
119
+ ),
120
+ ("sub-f9057kp", "ses-wave1bas", "Rest", "AP", "1"),
121
+ "MNI152NLin6Asym",
122
+ ],
123
+ ],
124
+ )
125
+ @pytest.mark.skipif(
126
+ _check_ants() is False, reason="requires ANTs to be in PATH"
127
+ )
128
+ @pytest.mark.skipif(
129
+ socket.gethostname() != "juseless",
130
+ reason="only for juseless",
131
+ )
132
+ def test_BOLDWarper_preprocess_to_multi_mni(
133
+ datagrabber: "BaseDataGrabber", element: Tuple[str, ...], space: str
134
+ ) -> None:
135
+ """Test BOLDWarper preprocess.
136
+
137
+ Parameters
138
+ ----------
139
+ datagrabber : DataGrabber-like object
140
+ The parametrized DataGrabber objects.
141
+ element : tuple of str
142
+ The parametrized elements.
143
+ space : str
144
+ The parametrized template space to transform to.
145
+
146
+ """
147
+ with datagrabber as dg:
148
+ # Read data
149
+ element_data = DefaultDataReader().fit_transform(dg[element])
150
+ pre_xfm_data = element_data["BOLD"]["data"].get_fdata().copy()
151
+ # Preprocess data
152
+ data, _ = BOLDWarper(reference=space).preprocess(
153
+ input=element_data["BOLD"],
154
+ extra_input=element_data,
155
+ )
156
+ assert isinstance(data, dict)
157
+ assert data["space"] == space
158
+ with assert_raises(AssertionError):
159
+ assert_array_equal(pre_xfm_data, data["data"])
@@ -27,12 +27,12 @@ def test_base_preprocessor_subclassing() -> None:
27
27
  def get_valid_inputs(self):
28
28
  return ["BOLD", "T1w"]
29
29
 
30
- def get_output_type(self, input):
31
- return ["timeseries"]
30
+ def get_output_type(self, input_type):
31
+ return input_type
32
32
 
33
- def preprocess(self, input, extra_input):
34
- input["data"] = f"mofidied_{input['data']}"
35
- return "BOLD", input
33
+ def preprocess(self, input, extra_input=None):
34
+ input["data"] = f"modified_{input['data']}"
35
+ return input, extra_input
36
36
 
37
37
  with pytest.raises(ValueError, match=r"cannot be computed on \['T2w'\]"):
38
38
  MyBasePreprocessor(on=["BOLD", "T2w"])
@@ -70,7 +70,7 @@ def test_base_preprocessor_subclassing() -> None:
70
70
  # Check output
71
71
  assert "BOLD" in output
72
72
  assert "data" in output["BOLD"]
73
- assert output["BOLD"]["data"] == "mofidied_data"
73
+ assert output["BOLD"]["data"] == "modified_data"
74
74
  assert "path" in output["BOLD"]
75
75
  assert "meta" in output["BOLD"]
76
76
 
@@ -0,0 +1,6 @@
1
+ """Provide imports for warping sub-package."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from .space_warper import SpaceWarper
@@ -0,0 +1,167 @@
1
+ """Provide class for space warping via ANTs antsApplyTransforms."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from typing import (
7
+ Any,
8
+ ClassVar,
9
+ Dict,
10
+ List,
11
+ Set,
12
+ Union,
13
+ )
14
+
15
+ import nibabel as nib
16
+ import numpy as np
17
+
18
+ from ...data import get_template, get_xfm
19
+ from ...pipeline import WorkDirManager
20
+ from ...utils import logger, run_ext_cmd
21
+
22
+
23
+ class ANTsWarper:
24
+ """Class for space warping via ANTs antsApplyTransforms.
25
+
26
+ This class uses ANTs' ``ResampleImage`` for resampling (if required) and
27
+ ``antsApplyTransforms`` for transformation.
28
+
29
+ """
30
+
31
+ _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
32
+ {
33
+ "name": "ants",
34
+ "commands": ["ResampleImage", "antsApplyTransforms"],
35
+ },
36
+ ]
37
+
38
+ _DEPENDENCIES: ClassVar[Set[str]] = {"numpy", "nibabel"}
39
+
40
+ def preprocess(
41
+ self,
42
+ input: Dict[str, Any],
43
+ extra_input: Dict[str, Any],
44
+ reference: str,
45
+ ) -> Dict[str, Any]:
46
+ """Preprocess using ANTs.
47
+
48
+ Parameters
49
+ ----------
50
+ input : dict
51
+ A single input from the Junifer Data object in which to preprocess.
52
+ extra_input : dict
53
+ The other fields in the Junifer Data object. Should have ``T1w``
54
+ and ``Warp`` data types.
55
+ reference : str
56
+ The data type or template space to use as reference for warping.
57
+
58
+ Returns
59
+ -------
60
+ dict
61
+ The ``input`` dictionary with modified ``data`` and ``space`` key
62
+ values and new ``reference_path`` key whose value points to the
63
+ reference file used for warping.
64
+
65
+ """
66
+ # Create element-specific tempdir for storing post-warping assets
67
+ element_tempdir = WorkDirManager().get_element_tempdir(
68
+ prefix="ants_warper"
69
+ )
70
+
71
+ # Native space warping
72
+ if reference == "T1w":
73
+ logger.debug("Using ANTs for space warping")
74
+
75
+ # Get the min of the voxel sizes from input and use it as the
76
+ # resolution
77
+ resolution = np.min(input["data"].header.get_zooms()[:3])
78
+
79
+ # Create a tempfile for resampled reference output
80
+ resample_image_out_path = (
81
+ element_tempdir / "resampled_reference.nii.gz"
82
+ )
83
+ # Set ResampleImage command
84
+ resample_image_cmd = [
85
+ "ResampleImage",
86
+ "3", # image dimension
87
+ f"{extra_input['T1w']['path'].resolve()}",
88
+ f"{resample_image_out_path.resolve()}",
89
+ f"{resolution}x{resolution}x{resolution}",
90
+ "0", # option for spacing and not size
91
+ "3 3", # Lanczos windowed sinc
92
+ ]
93
+ # Call ResampleImage
94
+ run_ext_cmd(name="ResampleImage", cmd=resample_image_cmd)
95
+
96
+ # Create a tempfile for warped output
97
+ apply_transforms_out_path = element_tempdir / "output.nii.gz"
98
+ # Set antsApplyTransforms command
99
+ apply_transforms_cmd = [
100
+ "antsApplyTransforms",
101
+ "-d 3",
102
+ "-e 3",
103
+ "-n LanczosWindowedSinc",
104
+ f"-i {input['path'].resolve()}",
105
+ # use resampled reference
106
+ f"-r {resample_image_out_path.resolve()}",
107
+ f"-t {extra_input['Warp']['path'].resolve()}",
108
+ f"-o {apply_transforms_out_path.resolve()}",
109
+ ]
110
+ # Call antsApplyTransforms
111
+ run_ext_cmd(name="antsApplyTransforms", cmd=apply_transforms_cmd)
112
+
113
+ # Load nifti
114
+ input["data"] = nib.load(apply_transforms_out_path)
115
+ # Save resampled reference path
116
+ input["reference_path"] = resample_image_out_path
117
+ # Use reference input's space as warped input's space
118
+ input["space"] = extra_input["T1w"]["space"]
119
+
120
+ # Template space warping
121
+ else:
122
+ logger.debug(
123
+ f"Using ANTs to warp data from {input['space']} to {reference}"
124
+ )
125
+
126
+ # Get xfm file
127
+ xfm_file_path = get_xfm(src=input["space"], dst=reference)
128
+ # Get template space image
129
+ template_space_img = get_template(
130
+ space=reference,
131
+ target_data=input,
132
+ extra_input=None,
133
+ )
134
+
135
+ # Create component-scoped tempdir
136
+ tempdir = WorkDirManager().get_tempdir(prefix="ants_warper")
137
+ # Save template
138
+ template_space_img_path = tempdir / f"{reference}_T1w.nii.gz"
139
+ nib.save(template_space_img, template_space_img_path)
140
+
141
+ # Create a tempfile for warped output
142
+ warped_output_path = element_tempdir / (
143
+ f"data_warped_from_{input['space']}_to_" f"{reference}.nii.gz"
144
+ )
145
+
146
+ # Set antsApplyTransforms command
147
+ apply_transforms_cmd = [
148
+ "antsApplyTransforms",
149
+ "-d 3",
150
+ "-e 3",
151
+ "-n LanczosWindowedSinc",
152
+ f"-i {input['path'].resolve()}",
153
+ f"-r {template_space_img_path.resolve()}",
154
+ f"-t {xfm_file_path.resolve()}",
155
+ f"-o {warped_output_path.resolve()}",
156
+ ]
157
+ # Call antsApplyTransforms
158
+ run_ext_cmd(name="antsApplyTransforms", cmd=apply_transforms_cmd)
159
+
160
+ # Delete tempdir
161
+ WorkDirManager().delete_tempdir(tempdir)
162
+
163
+ # Modify target data
164
+ input["data"] = nib.load(warped_output_path)
165
+ input["space"] = reference
166
+
167
+ return input
@@ -0,0 +1,109 @@
1
+ """Provide class for space warping via FSL FLIRT."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from typing import (
7
+ Any,
8
+ ClassVar,
9
+ Dict,
10
+ List,
11
+ Set,
12
+ Union,
13
+ )
14
+
15
+ import nibabel as nib
16
+ import numpy as np
17
+
18
+ from ...pipeline import WorkDirManager
19
+ from ...utils import logger, run_ext_cmd
20
+
21
+
22
+ class FSLWarper:
23
+ """Class for space warping via FSL FLIRT.
24
+
25
+ This class uses FSL FLIRT's ``flirt`` for resampling and ``applywarp`` for
26
+ transformation.
27
+
28
+ """
29
+
30
+ _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
31
+ {
32
+ "name": "fsl",
33
+ "commands": ["flirt", "applywarp"],
34
+ },
35
+ ]
36
+
37
+ _DEPENDENCIES: ClassVar[Set[str]] = {"numpy", "nibabel"}
38
+
39
+ def preprocess(
40
+ self,
41
+ input: Dict[str, Any],
42
+ extra_input: Dict[str, Any],
43
+ ) -> Dict[str, Any]:
44
+ """Preprocess using FSL.
45
+
46
+ Parameters
47
+ ----------
48
+ input : dict
49
+ A single input from the Junifer Data object in which to preprocess.
50
+ extra_input : dict
51
+ The other fields in the Junifer Data object. Should have ``T1w``
52
+ and ``Warp`` data types.
53
+
54
+ Returns
55
+ -------
56
+ dict
57
+ The ``input`` dictionary with modified ``data`` and ``space`` key
58
+ values and new ``reference_path`` key whose value points to the
59
+ reference file used for warping.
60
+
61
+ """
62
+ logger.debug("Using FSL for space warping")
63
+
64
+ # Get the min of the voxel sizes from input and use it as the
65
+ # resolution
66
+ resolution = np.min(input["data"].header.get_zooms()[:3])
67
+
68
+ # Create element-specific tempdir for storing post-warping assets
69
+ element_tempdir = WorkDirManager().get_element_tempdir(
70
+ prefix="fsl_warper"
71
+ )
72
+
73
+ # Create a tempfile for resampled reference output
74
+ flirt_out_path = element_tempdir / "resampled_reference.nii.gz"
75
+ # Set flirt command
76
+ flirt_cmd = [
77
+ "flirt",
78
+ "-interp spline",
79
+ f"-in {extra_input['T1w']['path'].resolve()}",
80
+ f"-ref {extra_input['T1w']['path'].resolve()}",
81
+ f"-applyisoxfm {resolution}",
82
+ f"-out {flirt_out_path.resolve()}",
83
+ ]
84
+ # Call flirt
85
+ run_ext_cmd(name="flirt", cmd=flirt_cmd)
86
+
87
+ # Create a tempfile for warped output
88
+ applywarp_out_path = element_tempdir / "output.nii.gz"
89
+ # Set applywarp command
90
+ applywarp_cmd = [
91
+ "applywarp",
92
+ "--interp=spline",
93
+ f"-i {input['path'].resolve()}",
94
+ f"-r {flirt_out_path.resolve()}", # use resampled reference
95
+ f"-w {extra_input['Warp']['path'].resolve()}",
96
+ f"-o {applywarp_out_path.resolve()}",
97
+ ]
98
+ # Call applywarp
99
+ run_ext_cmd(name="applywarp", cmd=applywarp_cmd)
100
+
101
+ # Load nifti
102
+ input["data"] = nib.load(applywarp_out_path)
103
+ # Save resampled reference path
104
+ input["reference_path"] = flirt_out_path
105
+
106
+ # Use reference input's space as warped input's space
107
+ input["space"] = extra_input["T1w"]["space"]
108
+
109
+ return input
@@ -0,0 +1,213 @@
1
+ """Provide class for warping data to other template spaces."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type, Union
7
+
8
+ from templateflow import api as tflow
9
+
10
+ from ...api.decorators import register_preprocessor
11
+ from ...utils import logger, raise_error
12
+ from ..base import BasePreprocessor
13
+ from ._ants_warper import ANTsWarper
14
+ from ._fsl_warper import FSLWarper
15
+
16
+
17
+ __all__ = ["SpaceWarper"]
18
+
19
+
20
+ @register_preprocessor
21
+ class SpaceWarper(BasePreprocessor):
22
+ """Class for warping data to other template spaces.
23
+
24
+ Parameters
25
+ ----------
26
+ using : {"fsl", "ants"}
27
+ Implementation to use for warping:
28
+
29
+ * "fsl" : Use FSL's ``applywarp``
30
+ * "ants" : Use ANTs' ``antsApplyTransforms``
31
+
32
+ reference : str
33
+ The data type to use as reference for warping, can be either a data
34
+ type like ``"T1w"`` or a template space like ``"MNI152NLin2009cAsym"``.
35
+ Use ``"T1w"`` for native space warping and named templates for
36
+ template space warping.
37
+ on : {"T1w", "T2w", "BOLD", "VBM_GM", "VBM_WM", "VBM_CSF", "fALFF", \
38
+ "GCOR", "LCOR"} or list of the options
39
+ The data type to warp.
40
+
41
+ Raises
42
+ ------
43
+ ValueError
44
+ If ``using`` is invalid or
45
+ if ``reference`` is invalid.
46
+
47
+ """
48
+
49
+ _CONDITIONAL_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, Type]]]] = [
50
+ {
51
+ "using": "fsl",
52
+ "depends_on": FSLWarper,
53
+ },
54
+ {
55
+ "using": "ants",
56
+ "depends_on": ANTsWarper,
57
+ },
58
+ ]
59
+
60
+ def __init__(
61
+ self, using: str, reference: str, on: Union[List[str], str]
62
+ ) -> None:
63
+ """Initialize the class."""
64
+ # Validate `using` parameter
65
+ valid_using = [dep["using"] for dep in self._CONDITIONAL_DEPENDENCIES]
66
+ if using not in valid_using:
67
+ raise_error(
68
+ f"Invalid value for `using`, should be one of: {valid_using}"
69
+ )
70
+ self.using = using
71
+ self.reference = reference
72
+ # Set required data types based on reference and
73
+ # initialize superclass
74
+ if self.reference == "T1w":
75
+ required_data_types = [self.reference, "Warp"]
76
+ # Listify on
77
+ if not isinstance(on, list):
78
+ on = [on]
79
+ # Extend required data types
80
+ required_data_types.extend(on)
81
+
82
+ super().__init__(
83
+ on=on,
84
+ required_data_types=required_data_types,
85
+ )
86
+ elif self.reference in tflow.templates():
87
+ super().__init__(on=on)
88
+ else:
89
+ raise_error(f"Unknown reference: {self.reference}")
90
+
91
+ def get_valid_inputs(self) -> List[str]:
92
+ """Get valid data types for input.
93
+
94
+ Returns
95
+ -------
96
+ list of str
97
+ The list of data types that can be used as input for this
98
+ preprocessor.
99
+
100
+ """
101
+ return [
102
+ "T1w",
103
+ "T2w",
104
+ "BOLD",
105
+ "VBM_GM",
106
+ "VBM_WM",
107
+ "VBM_CSF",
108
+ "fALFF",
109
+ "GCOR",
110
+ "LCOR",
111
+ ]
112
+
113
+ def get_output_type(self, input_type: str) -> str:
114
+ """Get output type.
115
+
116
+ Parameters
117
+ ----------
118
+ input_type : str
119
+ The data type input to the preprocessor.
120
+
121
+ Returns
122
+ -------
123
+ str
124
+ The data type output by the preprocessor.
125
+
126
+ """
127
+ # Does not add any new keys
128
+ return input_type
129
+
130
+ def preprocess(
131
+ self,
132
+ input: Dict[str, Any],
133
+ extra_input: Optional[Dict[str, Any]] = None,
134
+ ) -> Tuple[Dict[str, Any], Optional[Dict[str, Dict[str, Any]]]]:
135
+ """Preprocess.
136
+
137
+ Parameters
138
+ ----------
139
+ input : dict
140
+ The input from the Junifer Data object.
141
+ extra_input : dict, optional
142
+ The other fields in the Junifer Data object.
143
+
144
+ Returns
145
+ -------
146
+ dict
147
+ The computed result as dictionary.
148
+ None
149
+ Extra "helper" data types as dictionary to add to the Junifer Data
150
+ object.
151
+
152
+ Raises
153
+ ------
154
+ ValueError
155
+ If ``extra_input`` is None when transforming to native space
156
+ i.e., using ``"T1w"`` as reference.
157
+ RuntimeError
158
+ If the data is in the correct space and does not require
159
+ warping or
160
+ if FSL is used for template space warping.
161
+
162
+ """
163
+ logger.info(f"Warping to {self.reference} space using SpaceWarper")
164
+ # Transform to native space
165
+ if self.using in ["fsl", "ants"] and self.reference == "T1w":
166
+ # Check for extra inputs
167
+ if extra_input is None:
168
+ raise_error(
169
+ "No extra input provided, requires `Warp` and "
170
+ f"`{self.reference}` data types in particular."
171
+ )
172
+ # Conditional preprocessor
173
+ if self.using == "fsl":
174
+ input = FSLWarper().preprocess(
175
+ input=input,
176
+ extra_input=extra_input,
177
+ )
178
+ elif self.using == "ants":
179
+ input = ANTsWarper().preprocess(
180
+ input=input,
181
+ extra_input=extra_input,
182
+ reference=self.reference,
183
+ )
184
+ # Transform to template space with ANTs possible
185
+ elif self.using == "ants" and self.reference != "T1w":
186
+ # Check pre-requirements for space manipulation
187
+ if self.reference == input["space"]:
188
+ raise_error(
189
+ (
190
+ f"The target data is in {self.reference} space "
191
+ "and thus warping will not be performed, hence you "
192
+ "should remove the SpaceWarper from the preprocess "
193
+ "step."
194
+ ),
195
+ klass=RuntimeError,
196
+ )
197
+
198
+ input = ANTsWarper().preprocess(
199
+ input=input,
200
+ extra_input={},
201
+ reference=self.reference,
202
+ )
203
+ # Transform to template space with FSL not possible
204
+ elif self.using == "fsl" and self.reference != "T1w":
205
+ raise_error(
206
+ (
207
+ f"Warping to {self.reference} space not possible with "
208
+ "FSL, use ANTs instead."
209
+ ),
210
+ klass=RuntimeError,
211
+ )
212
+
213
+ return input, None