junifer 0.0.4.dev829__py3-none-any.whl → 0.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. junifer/__init__.py +17 -0
  2. junifer/_version.py +2 -2
  3. junifer/api/__init__.py +4 -1
  4. junifer/api/cli.py +91 -1
  5. junifer/api/decorators.py +9 -0
  6. junifer/api/functions.py +56 -10
  7. junifer/api/parser.py +3 -0
  8. junifer/api/queue_context/__init__.py +4 -1
  9. junifer/api/queue_context/gnu_parallel_local_adapter.py +16 -6
  10. junifer/api/queue_context/htcondor_adapter.py +16 -5
  11. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +41 -12
  12. junifer/api/queue_context/tests/test_htcondor_adapter.py +48 -15
  13. junifer/api/res/afni/run_afni_docker.sh +1 -1
  14. junifer/api/res/ants/run_ants_docker.sh +1 -1
  15. junifer/api/res/freesurfer/mri_binarize +3 -0
  16. junifer/api/res/freesurfer/mri_mc +3 -0
  17. junifer/api/res/freesurfer/mri_pretess +3 -0
  18. junifer/api/res/freesurfer/mris_convert +3 -0
  19. junifer/api/res/freesurfer/run_freesurfer_docker.sh +61 -0
  20. junifer/api/res/fsl/run_fsl_docker.sh +1 -1
  21. junifer/api/res/{run_conda.sh → run_conda.bash} +1 -1
  22. junifer/api/res/run_conda.zsh +23 -0
  23. junifer/api/res/run_venv.bash +22 -0
  24. junifer/api/res/{run_venv.sh → run_venv.zsh} +1 -1
  25. junifer/api/tests/test_api_utils.py +4 -2
  26. junifer/api/tests/test_cli.py +83 -0
  27. junifer/api/tests/test_functions.py +27 -2
  28. junifer/configs/__init__.py +1 -1
  29. junifer/configs/juseless/__init__.py +4 -1
  30. junifer/configs/juseless/datagrabbers/__init__.py +10 -1
  31. junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +4 -3
  32. junifer/configs/juseless/datagrabbers/camcan_vbm.py +3 -0
  33. junifer/configs/juseless/datagrabbers/ixi_vbm.py +4 -3
  34. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +1 -3
  35. junifer/configs/juseless/datagrabbers/ucla.py +12 -9
  36. junifer/configs/juseless/datagrabbers/ukb_vbm.py +3 -0
  37. junifer/data/__init__.py +21 -1
  38. junifer/data/coordinates.py +10 -19
  39. junifer/data/masks/ukb/UKB_15K_GM_template.nii.gz +0 -0
  40. junifer/data/masks.py +58 -87
  41. junifer/data/parcellations.py +14 -3
  42. junifer/data/template_spaces.py +4 -1
  43. junifer/data/tests/test_masks.py +26 -37
  44. junifer/data/utils.py +3 -0
  45. junifer/datagrabber/__init__.py +18 -1
  46. junifer/datagrabber/aomic/__init__.py +3 -0
  47. junifer/datagrabber/aomic/id1000.py +70 -37
  48. junifer/datagrabber/aomic/piop1.py +69 -36
  49. junifer/datagrabber/aomic/piop2.py +71 -38
  50. junifer/datagrabber/aomic/tests/test_id1000.py +44 -100
  51. junifer/datagrabber/aomic/tests/test_piop1.py +65 -108
  52. junifer/datagrabber/aomic/tests/test_piop2.py +45 -102
  53. junifer/datagrabber/base.py +13 -6
  54. junifer/datagrabber/datalad_base.py +13 -1
  55. junifer/datagrabber/dmcc13_benchmark.py +36 -53
  56. junifer/datagrabber/hcp1200/__init__.py +3 -0
  57. junifer/datagrabber/hcp1200/datalad_hcp1200.py +3 -0
  58. junifer/datagrabber/hcp1200/hcp1200.py +4 -1
  59. junifer/datagrabber/multiple.py +45 -6
  60. junifer/datagrabber/pattern.py +170 -62
  61. junifer/datagrabber/pattern_datalad.py +25 -12
  62. junifer/datagrabber/pattern_validation_mixin.py +388 -0
  63. junifer/datagrabber/tests/test_datalad_base.py +4 -4
  64. junifer/datagrabber/tests/test_dmcc13_benchmark.py +46 -19
  65. junifer/datagrabber/tests/test_multiple.py +161 -84
  66. junifer/datagrabber/tests/test_pattern.py +45 -0
  67. junifer/datagrabber/tests/test_pattern_datalad.py +4 -4
  68. junifer/datagrabber/tests/test_pattern_validation_mixin.py +249 -0
  69. junifer/datareader/__init__.py +4 -1
  70. junifer/datareader/default.py +95 -43
  71. junifer/external/BrainPrint/brainprint/__init__.py +4 -0
  72. junifer/external/BrainPrint/brainprint/_version.py +3 -0
  73. junifer/external/BrainPrint/brainprint/asymmetry.py +91 -0
  74. junifer/external/BrainPrint/brainprint/brainprint.py +441 -0
  75. junifer/external/BrainPrint/brainprint/surfaces.py +258 -0
  76. junifer/external/BrainPrint/brainprint/utils/__init__.py +1 -0
  77. junifer/external/BrainPrint/brainprint/utils/_config.py +112 -0
  78. junifer/external/BrainPrint/brainprint/utils/utils.py +188 -0
  79. junifer/external/__init__.py +1 -1
  80. junifer/external/nilearn/__init__.py +5 -1
  81. junifer/external/nilearn/junifer_connectivity_measure.py +483 -0
  82. junifer/external/nilearn/junifer_nifti_spheres_masker.py +23 -9
  83. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +1089 -0
  84. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +76 -1
  85. junifer/markers/__init__.py +23 -1
  86. junifer/markers/base.py +68 -28
  87. junifer/markers/brainprint.py +459 -0
  88. junifer/markers/collection.py +10 -2
  89. junifer/markers/complexity/__init__.py +10 -0
  90. junifer/markers/complexity/complexity_base.py +26 -43
  91. junifer/markers/complexity/hurst_exponent.py +3 -0
  92. junifer/markers/complexity/multiscale_entropy_auc.py +3 -0
  93. junifer/markers/complexity/perm_entropy.py +3 -0
  94. junifer/markers/complexity/range_entropy.py +3 -0
  95. junifer/markers/complexity/range_entropy_auc.py +3 -0
  96. junifer/markers/complexity/sample_entropy.py +3 -0
  97. junifer/markers/complexity/tests/test_hurst_exponent.py +11 -3
  98. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +11 -3
  99. junifer/markers/complexity/tests/test_perm_entropy.py +11 -3
  100. junifer/markers/complexity/tests/test_range_entropy.py +11 -3
  101. junifer/markers/complexity/tests/test_range_entropy_auc.py +11 -3
  102. junifer/markers/complexity/tests/test_sample_entropy.py +11 -3
  103. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +11 -3
  104. junifer/markers/complexity/weighted_perm_entropy.py +3 -0
  105. junifer/markers/ets_rss.py +27 -42
  106. junifer/markers/falff/__init__.py +3 -0
  107. junifer/markers/falff/_afni_falff.py +5 -2
  108. junifer/markers/falff/_junifer_falff.py +3 -0
  109. junifer/markers/falff/falff_base.py +20 -46
  110. junifer/markers/falff/falff_parcels.py +56 -27
  111. junifer/markers/falff/falff_spheres.py +60 -29
  112. junifer/markers/falff/tests/test_falff_parcels.py +39 -23
  113. junifer/markers/falff/tests/test_falff_spheres.py +39 -23
  114. junifer/markers/functional_connectivity/__init__.py +9 -0
  115. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +63 -60
  116. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +45 -32
  117. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +49 -36
  118. junifer/markers/functional_connectivity/functional_connectivity_base.py +71 -70
  119. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +34 -25
  120. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +40 -30
  121. junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +11 -7
  122. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +27 -7
  123. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +28 -12
  124. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +35 -11
  125. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +36 -62
  126. junifer/markers/parcel_aggregation.py +47 -61
  127. junifer/markers/reho/__init__.py +3 -0
  128. junifer/markers/reho/_afni_reho.py +5 -2
  129. junifer/markers/reho/_junifer_reho.py +4 -1
  130. junifer/markers/reho/reho_base.py +8 -27
  131. junifer/markers/reho/reho_parcels.py +28 -17
  132. junifer/markers/reho/reho_spheres.py +27 -18
  133. junifer/markers/reho/tests/test_reho_parcels.py +8 -3
  134. junifer/markers/reho/tests/test_reho_spheres.py +8 -3
  135. junifer/markers/sphere_aggregation.py +43 -59
  136. junifer/markers/temporal_snr/__init__.py +3 -0
  137. junifer/markers/temporal_snr/temporal_snr_base.py +23 -32
  138. junifer/markers/temporal_snr/temporal_snr_parcels.py +9 -6
  139. junifer/markers/temporal_snr/temporal_snr_spheres.py +9 -6
  140. junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +6 -3
  141. junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +6 -3
  142. junifer/markers/tests/test_brainprint.py +58 -0
  143. junifer/markers/tests/test_collection.py +9 -8
  144. junifer/markers/tests/test_ets_rss.py +15 -9
  145. junifer/markers/tests/test_markers_base.py +17 -18
  146. junifer/markers/tests/test_parcel_aggregation.py +93 -32
  147. junifer/markers/tests/test_sphere_aggregation.py +72 -19
  148. junifer/onthefly/__init__.py +4 -1
  149. junifer/onthefly/read_transform.py +3 -0
  150. junifer/pipeline/__init__.py +9 -1
  151. junifer/pipeline/pipeline_step_mixin.py +21 -4
  152. junifer/pipeline/registry.py +3 -0
  153. junifer/pipeline/singleton.py +3 -0
  154. junifer/pipeline/tests/test_registry.py +1 -1
  155. junifer/pipeline/update_meta_mixin.py +3 -0
  156. junifer/pipeline/utils.py +67 -1
  157. junifer/pipeline/workdir_manager.py +3 -0
  158. junifer/preprocess/__init__.py +10 -2
  159. junifer/preprocess/base.py +6 -3
  160. junifer/preprocess/confounds/__init__.py +3 -0
  161. junifer/preprocess/confounds/fmriprep_confound_remover.py +47 -60
  162. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +72 -113
  163. junifer/preprocess/smoothing/__init__.py +9 -0
  164. junifer/preprocess/smoothing/_afni_smoothing.py +119 -0
  165. junifer/preprocess/smoothing/_fsl_smoothing.py +116 -0
  166. junifer/preprocess/smoothing/_nilearn_smoothing.py +69 -0
  167. junifer/preprocess/smoothing/smoothing.py +174 -0
  168. junifer/preprocess/smoothing/tests/test_smoothing.py +94 -0
  169. junifer/preprocess/warping/__init__.py +3 -0
  170. junifer/preprocess/warping/_ants_warper.py +3 -0
  171. junifer/preprocess/warping/_fsl_warper.py +3 -0
  172. junifer/stats.py +4 -1
  173. junifer/storage/__init__.py +9 -1
  174. junifer/storage/base.py +40 -1
  175. junifer/storage/hdf5.py +71 -9
  176. junifer/storage/pandas_base.py +3 -0
  177. junifer/storage/sqlite.py +3 -0
  178. junifer/storage/tests/test_hdf5.py +82 -10
  179. junifer/storage/utils.py +9 -0
  180. junifer/testing/__init__.py +4 -1
  181. junifer/testing/datagrabbers.py +13 -6
  182. junifer/testing/tests/test_partlycloudytesting_datagrabber.py +7 -7
  183. junifer/testing/utils.py +3 -0
  184. junifer/utils/__init__.py +13 -2
  185. junifer/utils/fs.py +3 -0
  186. junifer/utils/helpers.py +32 -1
  187. junifer/utils/logging.py +33 -4
  188. junifer/utils/tests/test_logging.py +8 -0
  189. {junifer-0.0.4.dev829.dist-info → junifer-0.0.5.dist-info}/METADATA +17 -16
  190. junifer-0.0.5.dist-info/RECORD +275 -0
  191. {junifer-0.0.4.dev829.dist-info → junifer-0.0.5.dist-info}/WHEEL +1 -1
  192. junifer/datagrabber/tests/test_datagrabber_utils.py +0 -218
  193. junifer/datagrabber/utils.py +0 -230
  194. junifer/preprocess/ants/__init__.py +0 -4
  195. junifer/preprocess/ants/ants_apply_transforms_warper.py +0 -185
  196. junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py +0 -56
  197. junifer/preprocess/bold_warper.py +0 -265
  198. junifer/preprocess/fsl/__init__.py +0 -4
  199. junifer/preprocess/fsl/apply_warper.py +0 -179
  200. junifer/preprocess/fsl/tests/test_apply_warper.py +0 -45
  201. junifer/preprocess/tests/test_bold_warper.py +0 -159
  202. junifer-0.0.4.dev829.dist-info/RECORD +0 -257
  203. {junifer-0.0.4.dev829.dist-info → junifer-0.0.5.dist-info}/AUTHORS.rst +0 -0
  204. {junifer-0.0.4.dev829.dist-info → junifer-0.0.5.dist-info}/LICENSE.md +0 -0
  205. {junifer-0.0.4.dev829.dist-info → junifer-0.0.5.dist-info}/entry_points.txt +0 -0
  206. {junifer-0.0.4.dev829.dist-info → junifer-0.0.5.dist-info}/top_level.txt +0 -0
@@ -25,14 +25,65 @@ COORDS = "DMNBuckner"
25
25
  RADIUS = 8
26
26
 
27
27
 
28
- def test_SphereAggregation_input_output() -> None:
29
- """Test SphereAggregation input and output types."""
30
- marker = SphereAggregation(coords="DMNBuckner", method="mean", on="VBM_GM")
31
- for in_, out_ in [("VBM_GM", "vector"), ("BOLD", "timeseries")]:
32
- assert marker.get_output_type(in_) == out_
28
+ @pytest.mark.parametrize(
29
+ "input_type, storage_type",
30
+ [
31
+ (
32
+ "T1w",
33
+ "vector",
34
+ ),
35
+ (
36
+ "T2w",
37
+ "vector",
38
+ ),
39
+ (
40
+ "BOLD",
41
+ "timeseries",
42
+ ),
43
+ (
44
+ "VBM_GM",
45
+ "vector",
46
+ ),
47
+ (
48
+ "VBM_WM",
49
+ "vector",
50
+ ),
51
+ (
52
+ "VBM_CSF",
53
+ "vector",
54
+ ),
55
+ (
56
+ "fALFF",
57
+ "vector",
58
+ ),
59
+ (
60
+ "GCOR",
61
+ "vector",
62
+ ),
63
+ (
64
+ "LCOR",
65
+ "vector",
66
+ ),
67
+ ],
68
+ )
69
+ def test_SphereAggregation_input_output(
70
+ input_type: str, storage_type: str
71
+ ) -> None:
72
+ """Test SphereAggregation input and output types.
33
73
 
34
- with pytest.raises(ValueError, match="Unknown input"):
35
- marker.get_output_type("unknown")
74
+ Parameters
75
+ ----------
76
+ input_type : str
77
+ The parametrized input type.
78
+ storage_type : str
79
+ The parametrized storage type.
80
+
81
+ """
82
+ assert storage_type == SphereAggregation(
83
+ coords="DMNBuckner",
84
+ method="mean",
85
+ on=input_type,
86
+ ).get_output_type(input_type=input_type, output_feature="aggregation")
36
87
 
37
88
 
38
89
  def test_SphereAggregation_3D() -> None:
@@ -44,8 +95,8 @@ def test_SphereAggregation_3D() -> None:
44
95
  coords=COORDS, method="mean", radius=RADIUS, on="VBM_GM"
45
96
  )
46
97
  sphere_agg_vbm_gm_data = marker.fit_transform(element_data)["VBM_GM"][
47
- "data"
48
- ]
98
+ "aggregation"
99
+ ]["data"]
49
100
 
50
101
  # Compare with nilearn
51
102
  # Load testing coordinates
@@ -76,8 +127,8 @@ def test_SphereAggregation_4D() -> None:
76
127
  coords=COORDS, method="mean", radius=RADIUS, on="BOLD"
77
128
  )
78
129
  sphere_agg_bold_data = marker.fit_transform(element_data)["BOLD"][
79
- "data"
80
- ]
130
+ "aggregation"
131
+ ]["data"]
81
132
 
82
133
  # Compare with nilearn
83
134
  # Load testing coordinates
@@ -120,7 +171,8 @@ def test_SphereAggregation_storage(tmp_path: Path) -> None:
120
171
  marker.fit_transform(input=element_data, storage=storage)
121
172
  features = storage.list_features()
122
173
  assert any(
123
- x["name"] == "VBM_GM_SphereAggregation" for x in features.values()
174
+ x["name"] == "VBM_GM_SphereAggregation_aggregation"
175
+ for x in features.values()
124
176
  )
125
177
 
126
178
  # Store 4D
@@ -135,7 +187,8 @@ def test_SphereAggregation_storage(tmp_path: Path) -> None:
135
187
  marker.fit_transform(input=element_data, storage=storage)
136
188
  features = storage.list_features()
137
189
  assert any(
138
- x["name"] == "BOLD_SphereAggregation" for x in features.values()
190
+ x["name"] == "BOLD_SphereAggregation_aggregation"
191
+ for x in features.values()
139
192
  )
140
193
 
141
194
 
@@ -152,8 +205,8 @@ def test_SphereAggregation_3D_mask() -> None:
152
205
  masks="compute_brain_mask",
153
206
  )
154
207
  sphere_agg_vbm_gm_data = marker.fit_transform(element_data)["VBM_GM"][
155
- "data"
156
- ]
208
+ "aggregation"
209
+ ]["data"]
157
210
 
158
211
  # Compare with nilearn
159
212
  # Load testing coordinates
@@ -195,8 +248,8 @@ def test_SphereAggregation_4D_agg_time() -> None:
195
248
  on="BOLD",
196
249
  )
197
250
  sphere_agg_bold_data = marker.fit_transform(element_data)["BOLD"][
198
- "data"
199
- ]
251
+ "aggregation"
252
+ ]["data"]
200
253
 
201
254
  # Compare with nilearn
202
255
  # Load testing coordinates
@@ -231,8 +284,8 @@ def test_SphereAggregation_4D_agg_time() -> None:
231
284
  on="BOLD",
232
285
  )
233
286
  sphere_agg_bold_data = marker.fit_transform(element_data)["BOLD"][
234
- "data"
235
- ]
287
+ "aggregation"
288
+ ]["data"]
236
289
 
237
290
  assert sphere_agg_bold_data.ndim == 2
238
291
  assert_array_equal(
@@ -1,6 +1,9 @@
1
- """Provide imports for onthefly sub-package."""
1
+ """Utilities for on-the-fly analyses."""
2
2
 
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
6
  from .read_transform import read_transform
7
+
8
+
9
+ __all__ = ["read_transform"]
@@ -15,6 +15,9 @@ if TYPE_CHECKING:
15
15
  from junifer.storage import BaseFeatureStorage
16
16
 
17
17
 
18
+ __all__ = ["read_transform"]
19
+
20
+
18
21
  def read_transform(
19
22
  storage: Type["BaseFeatureStorage"],
20
23
  transform: str,
@@ -1,4 +1,4 @@
1
- """Provide imports for pipeline sub-package."""
1
+ """Pipeline components."""
2
2
 
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
@@ -7,3 +7,11 @@ from . import registry
7
7
  from .pipeline_step_mixin import PipelineStepMixin
8
8
  from .update_meta_mixin import UpdateMetaMixin
9
9
  from .workdir_manager import WorkDirManager
10
+
11
+
12
+ __all__ = [
13
+ "registry",
14
+ "PipelineStepMixin",
15
+ "UpdateMetaMixin",
16
+ "WorkDirManager",
17
+ ]
@@ -4,10 +4,14 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
- try:
8
- from importlib.metadata import packages_distributions
9
- except ImportError: # pragma: no cover
7
+ import sys
8
+
9
+
10
+ if sys.version_info < (3, 11): # pragma: no cover
10
11
  from importlib_metadata import packages_distributions
12
+ else:
13
+ from importlib.metadata import packages_distributions
14
+
11
15
 
12
16
  from importlib.util import find_spec
13
17
  from itertools import chain
@@ -17,6 +21,9 @@ from ..utils import raise_error
17
21
  from .utils import check_ext_dependencies
18
22
 
19
23
 
24
+ __all__ = ["PipelineStepMixin"]
25
+
26
+
20
27
  class PipelineStepMixin:
21
28
  """Mixin class for a pipeline step."""
22
29
 
@@ -203,7 +210,17 @@ class PipelineStepMixin:
203
210
  # Validate input
204
211
  fit_input = self.validate_input(input=input)
205
212
  # Validate output type
206
- outputs = [self.get_output_type(t_input) for t_input in fit_input]
213
+ # Nested output type for marker
214
+ if hasattr(self, "_MARKER_INOUT_MAPPINGS"):
215
+ outputs = list(
216
+ {
217
+ val
218
+ for t_input in fit_input
219
+ for val in self._MARKER_INOUT_MAPPINGS[t_input].values()
220
+ }
221
+ )
222
+ else:
223
+ outputs = [self.get_output_type(t_input) for t_input in fit_input]
207
224
  return outputs
208
225
 
209
226
  def fit_transform(
@@ -16,6 +16,9 @@ if TYPE_CHECKING:
16
16
  from .pipeline_step_mixin import PipelineStepMixin
17
17
 
18
18
 
19
+ __all__ = ["register", "get_step_names", "get_class", "build"]
20
+
21
+
19
22
  # Define valid steps for operation
20
23
  _VALID_STEPS: List[str] = [
21
24
  "datagrabber",
@@ -6,6 +6,9 @@
6
6
  from typing import Any, Dict, Type
7
7
 
8
8
 
9
+ __all__ = ["singleton"]
10
+
11
+
9
12
  def singleton(cls: Type) -> Type:
10
13
  """Make a class singleton.
11
14
 
@@ -101,7 +101,7 @@ def test_get_class():
101
101
  register(step="datagrabber", name="bar", klass=str)
102
102
  # Get class
103
103
  obj = get_class(step="datagrabber", name="bar")
104
- assert obj == str
104
+ assert isinstance(obj, type(str))
105
105
 
106
106
 
107
107
  # TODO: possible parametrization?
@@ -7,6 +7,9 @@
7
7
  from typing import Dict
8
8
 
9
9
 
10
+ __all__ = ["UpdateMetaMixin"]
11
+
12
+
10
13
  class UpdateMetaMixin:
11
14
  """Mixin class for updating meta."""
12
15
 
junifer/pipeline/utils.py CHANGED
@@ -10,6 +10,9 @@ from typing import Any, List, Optional
10
10
  from junifer.utils.logging import raise_error, warn_with_log
11
11
 
12
12
 
13
+ __all__ = ["check_ext_dependencies"]
14
+
15
+
13
16
  def check_ext_dependencies(
14
17
  name: str, optional: bool = False, **kwargs: Any
15
18
  ) -> bool:
@@ -37,7 +40,7 @@ def check_ext_dependencies(
37
40
  If ``name`` is mandatory and is not found.
38
41
 
39
42
  """
40
- valid_ext_dependencies = ("afni", "fsl", "ants")
43
+ valid_ext_dependencies = ("afni", "fsl", "ants", "freesurfer")
41
44
  if name not in valid_ext_dependencies:
42
45
  raise_error(
43
46
  "Invalid value for `name`, should be one of: "
@@ -52,6 +55,9 @@ def check_ext_dependencies(
52
55
  # Check for ants
53
56
  elif name == "ants":
54
57
  found = _check_ants(**kwargs)
58
+ # Check for freesurfer
59
+ elif name == "freesurfer":
60
+ found = _check_freesurfer(**kwargs)
55
61
 
56
62
  # Check if the dependency is mandatory in case it's not found
57
63
  if not found and not optional:
@@ -245,3 +251,63 @@ def _check_ants(commands: Optional[List[str]] = None) -> bool:
245
251
  f"{commands_found_results}"
246
252
  )
247
253
  return ants_found
254
+
255
+
256
+ def _check_freesurfer(commands: Optional[List[str]] = None) -> bool:
257
+ """Check if FreeSurfer is present in the system.
258
+
259
+ Parameters
260
+ ----------
261
+ commands : list of str, optional
262
+ The commands to specifically check for from FreeSurfer. If None, only
263
+ the basic FreeSurfer help would be looked up, else, would also
264
+ check for specific commands (default None).
265
+
266
+ Returns
267
+ -------
268
+ bool
269
+ Whether FreeSurfer is found or not.
270
+
271
+ """
272
+ completed_process = subprocess.run(
273
+ "recon-all -help",
274
+ stdin=subprocess.DEVNULL,
275
+ stdout=subprocess.DEVNULL,
276
+ stderr=subprocess.STDOUT,
277
+ shell=True, # is unsafe but kept for resolution via PATH
278
+ check=False,
279
+ )
280
+ fs_found = completed_process.returncode == 0
281
+
282
+ # Check for specific commands
283
+ if fs_found and commands is not None:
284
+ if not isinstance(commands, list):
285
+ commands = [commands]
286
+ # Store command found results
287
+ commands_found_results = {}
288
+ # Set all commands found flag to True
289
+ all_commands_found = True
290
+ # Check commands' existence
291
+ for command in commands:
292
+ command_process = subprocess.run(
293
+ [command],
294
+ stdin=subprocess.DEVNULL,
295
+ stdout=subprocess.DEVNULL,
296
+ stderr=subprocess.STDOUT,
297
+ shell=True, # is unsafe but kept for resolution via PATH
298
+ check=False,
299
+ )
300
+ command_found = command_process.returncode == 0
301
+ commands_found_results[command] = (
302
+ "found" if command_found else "not found"
303
+ )
304
+ # Set flag to trigger warning
305
+ all_commands_found = all_commands_found and command_found
306
+ # One or more commands were missing
307
+ if not all_commands_found:
308
+ warn_with_log(
309
+ "FreeSurfer is installed but some of the required commands "
310
+ "were not found. These are the results: "
311
+ f"{commands_found_results}"
312
+ )
313
+ return fs_found
@@ -13,6 +13,9 @@ from ..utils import logger
13
13
  from .singleton import singleton
14
14
 
15
15
 
16
+ __all__ = ["WorkDirManager"]
17
+
18
+
16
19
  @singleton
17
20
  class WorkDirManager:
18
21
  """Class for working directory manager.
@@ -1,4 +1,4 @@
1
- """Provide imports for preprocess sub-package."""
1
+ """Preprocessors for preprocessing data before feature extraction."""
2
2
 
3
3
  # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
4
  # Leonard Sasse <l.sasse@fz-juelich.de>
@@ -7,5 +7,13 @@
7
7
 
8
8
  from .base import BasePreprocessor
9
9
  from .confounds import fMRIPrepConfoundRemover
10
- from .bold_warper import BOLDWarper
11
10
  from .warping import SpaceWarper
11
+ from .smoothing import Smoothing
12
+
13
+
14
+ __all__ = [
15
+ "BasePreprocessor",
16
+ "fMRIPrepConfoundRemover",
17
+ "SpaceWarper",
18
+ "Smoothing",
19
+ ]
@@ -11,6 +11,9 @@ from ..pipeline import PipelineStepMixin, UpdateMetaMixin
11
11
  from ..utils import logger, raise_error
12
12
 
13
13
 
14
+ __all__ = ["BasePreprocessor"]
15
+
16
+
14
17
  class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin):
15
18
  """Abstract base class for all preprocessors.
16
19
 
@@ -136,7 +139,7 @@ class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin):
136
139
  A single input from the Junifer Data object to preprocess.
137
140
  extra_input : dict, optional
138
141
  The other fields in the Junifer Data object. Useful for accessing
139
- other data kind that needs to be used in the computation. For
142
+ other data type that needs to be used in the computation. For
140
143
  example, the confound removers can make use of the
141
144
  confounds if available (default None).
142
145
 
@@ -146,8 +149,8 @@ class BasePreprocessor(ABC, PipelineStepMixin, UpdateMetaMixin):
146
149
  The computed result as dictionary.
147
150
  dict or None
148
151
  Extra "helper" data types as dictionary to add to the Junifer Data
149
- object. For example, computed BOLD mask can be passed via this.
150
- If no new "helper" data types is created, None is to be passed.
152
+ object. If no new "helper" data type(s) is(are) created, None is to
153
+ be passed.
151
154
 
152
155
  """
153
156
  raise_error(
@@ -5,3 +5,6 @@
5
5
  # License: AGPL
6
6
 
7
7
  from .fmriprep_confound_remover import fMRIPrepConfoundRemover
8
+
9
+
10
+ __all__ = ["fMRIPrepConfoundRemover"]
@@ -27,6 +27,9 @@ from ...utils import logger, raise_error
27
27
  from ..base import BasePreprocessor
28
28
 
29
29
 
30
+ __all__ = ["fMRIPrepConfoundRemover"]
31
+
32
+
30
33
  FMRIPREP_BASICS = {
31
34
  "motion": [
32
35
  "trans_x",
@@ -203,9 +206,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
203
206
  "include it in the future",
204
207
  klass=ValueError,
205
208
  )
206
- super().__init__(
207
- on="BOLD", required_data_types=["BOLD", "BOLD_confounds"]
208
- )
209
+ super().__init__(on="BOLD", required_data_types=["BOLD"])
209
210
 
210
211
  def get_valid_inputs(self) -> List[str]:
211
212
  """Get valid data types for input.
@@ -361,7 +362,7 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
361
362
  Parameters
362
363
  ----------
363
364
  input : dict
364
- Dictionary containing the ``BOLD_confounds`` value from the
365
+ Dictionary containing the ``BOLD.confounds`` value from the
365
366
  Junifer Data object.
366
367
 
367
368
  Returns
@@ -370,7 +371,6 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
370
371
  Dataframe containing the relevant confounds.
371
372
 
372
373
  """
373
-
374
374
  confounds_format = input["format"]
375
375
  if confounds_format == "adhoc":
376
376
  self._map_adhoc_to_fmriprep(input)
@@ -416,50 +416,42 @@ 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,
420
419
  ) -> None:
421
420
  """Validate input data.
422
421
 
423
422
  Parameters
424
423
  ----------
425
424
  input : dict
426
- Dictionary containing the ``BOLD`` value from the
425
+ Dictionary containing the ``BOLD`` data from the
427
426
  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.
431
427
 
432
428
  Raises
433
429
  ------
434
430
  ValueError
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
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
439
434
  if image time series and confounds have different lengths or
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.
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.
444
440
 
445
441
  """
446
442
  # BOLD must be 4D niimg
447
443
  check_niimg_4d(input["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"]
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"]
463
455
  bold_img = input["data"]
464
456
  if bold_img.get_fdata().shape[3] != len(confound_df):
465
457
  raise_error(
@@ -469,23 +461,19 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
469
461
  )
470
462
 
471
463
  # Check 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"]
464
+ t_format = input["confounds"]["format"]
475
465
  if t_format == "adhoc":
476
- if "mappings" not in extra_input["BOLD_confounds"]:
466
+ if "mappings" not in input["confounds"]:
477
467
  raise_error(
478
- "`BOLD_confounds.mappings` need to be set when "
479
- "`BOLD_confounds.format == 'adhoc'`"
468
+ "`BOLD.confounds.mappings` need to be set when "
469
+ "`BOLD.confounds.format == 'adhoc'`"
480
470
  )
481
- if "fmriprep" not in extra_input["BOLD_confounds"]["mappings"]:
471
+ if "fmriprep" not in input["confounds"]["mappings"]:
482
472
  raise_error(
483
- "`BOLD_confounds.mappings.fmriprep` need to be set when "
484
- "`BOLD_confounds.format == 'adhoc'`"
473
+ "`BOLD.confounds.mappings.fmriprep` need to be set when "
474
+ "`BOLD.confounds.format == 'adhoc'`"
485
475
  )
486
- fmriprep_mappings = extra_input["BOLD_confounds"]["mappings"][
487
- "fmriprep"
488
- ]
476
+ fmriprep_mappings = input["confounds"]["mappings"]["fmriprep"]
489
477
  wrong_names = [
490
478
  x
491
479
  for x in fmriprep_mappings.values()
@@ -525,22 +513,22 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
525
513
  input : dict
526
514
  A single input from the Junifer Data object to preprocess.
527
515
  extra_input : dict, optional
528
- The other fields in the Junifer Data object. Must include the
529
- ``BOLD_confounds`` key.
516
+ The other fields in the Junifer Data object.
530
517
 
531
518
  Returns
532
519
  -------
533
520
  dict
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.
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.
538
526
 
539
527
  """
540
528
  # Validate data
541
- self._validate_data(input, extra_input)
529
+ self._validate_data(input)
542
530
  # Pick confounds
543
- confounds_df = self._pick_confounds(extra_input["BOLD_confounds"]) # type: ignore
531
+ confounds_df = self._pick_confounds(input["confounds"]) # type: ignore
544
532
  # Get BOLD data
545
533
  bold_img = input["data"]
546
534
  # Set t_r
@@ -553,7 +541,6 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
553
541
  )
554
542
  # Set mask data
555
543
  mask_img = None
556
- bold_mask_dict = None
557
544
  if self.masks is not None:
558
545
  logger.debug(f"Masking with {self.masks}")
559
546
  mask_img = get_mask(
@@ -561,15 +548,15 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
561
548
  )
562
549
  # Return the BOLD mask and link it to the BOLD data type dict;
563
550
  # this allows to use "inherit" down the pipeline
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": {
551
+ logger.debug("Setting `BOLD.mask`")
552
+ input.update(
553
+ {
554
+ "mask": {
569
555
  "data": mask_img,
570
556
  "space": input["space"],
571
557
  }
572
558
  }
559
+ )
573
560
  # Clean image
574
561
  logger.info("Cleaning image using nilearn")
575
562
  logger.debug(f"\tdetrend: {self.detrend}")
@@ -587,4 +574,4 @@ class fMRIPrepConfoundRemover(BasePreprocessor):
587
574
  mask_img=mask_img,
588
575
  )
589
576
 
590
- return input, bold_mask_dict
577
+ return input, None