junifer 0.0.3.dev188__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.dev188.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.dev188.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.dev188.dist-info/RECORD +0 -199
  175. {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/AUTHORS.rst +0 -0
  176. {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/LICENSE.md +0 -0
  177. {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/entry_points.txt +0 -0
  178. {junifer-0.0.3.dev188.dist-info → junifer-0.0.4.dist-info}/top_level.txt +0 -0
@@ -44,7 +44,7 @@ class PipelineStepMixin:
44
44
  raise_error(
45
45
  msg="Concrete classes need to implement validate_input().",
46
46
  klass=NotImplementedError,
47
- )
47
+ ) # pragma: no cover
48
48
 
49
49
  def get_output_type(self, input_type: str) -> str:
50
50
  """Get output type.
@@ -63,10 +63,11 @@ class PipelineStepMixin:
63
63
  raise_error(
64
64
  msg="Concrete classes need to implement get_output_type().",
65
65
  klass=NotImplementedError,
66
- )
66
+ ) # pragma: no cover
67
67
 
68
68
  def _fit_transform(
69
- self, input: Dict[str, Dict], **kwargs: Any
69
+ self,
70
+ input: Dict[str, Dict],
70
71
  ) -> Dict[str, Dict]:
71
72
  """Fit and transform.
72
73
 
@@ -74,8 +75,6 @@ class PipelineStepMixin:
74
75
  ----------
75
76
  input : dict
76
77
  The Junifer Data object.
77
- **kwargs : dict
78
- Extra keyword arguments.
79
78
 
80
79
  Returns
81
80
  -------
@@ -86,7 +85,7 @@ class PipelineStepMixin:
86
85
  raise_error(
87
86
  msg="Concrete classes need to implement _fit_transform().",
88
87
  klass=NotImplementedError,
89
- )
88
+ ) # pragma: no cover
90
89
 
91
90
  def validate(self, input: List[str]) -> List[str]:
92
91
  """Validate the the pipeline step.
@@ -101,44 +100,109 @@ class PipelineStepMixin:
101
100
  list of str
102
101
  The output of the pipeline step.
103
102
 
104
- Raises
105
- ------
106
- ValueError
107
- If the pipeline step object is missing dependencies required for
108
- its working or if the input does not have the required data.
109
-
110
103
  """
111
- # Check if _DEPENDENCIES attribute is found;
112
- # (markers and preprocessors will have them but not datareaders
113
- # as of now)
114
- dependencies_not_found = []
115
- if hasattr(self, "_DEPENDENCIES"):
116
- # Check if dependencies are importable
117
- for dependency in self._DEPENDENCIES: # type: ignore
118
- # First perform an easy check
119
- if find_spec(dependency) is None:
120
- # Then check mapped names
121
- if dependency not in list(
122
- chain.from_iterable(packages_distributions().values())
123
- ):
124
- dependencies_not_found.append(dependency)
125
- # Raise error if any dependency is not found
126
- if dependencies_not_found:
127
- raise_error(
128
- msg=f"{dependencies_not_found} are not installed but are "
129
- "required for using {self.name}.",
130
- klass=ImportError,
131
- )
132
- # Check if _EXT_DEPENDENCIES attribute is found;
133
- # (some markers might have them like ReHo-family)
134
- if hasattr(self, "_EXT_DEPENDENCIES"):
135
- for dependency in self._EXT_DEPENDENCIES: # type: ignore
136
- out = check_ext_dependencies(**dependency)
137
- if getattr(self, f"use_{dependency['name']}", None) is None:
138
- # Set attribute for using external tools
139
- setattr(self, f"use_{dependency['name']}", out)
140
104
 
105
+ def _check_dependencies(obj) -> None:
106
+ """Check obj._DEPENDENCIES.
107
+
108
+ Parameters
109
+ ----------
110
+ obj : object
111
+ Object to check _DEPENDENCIES of.
112
+
113
+ Raises
114
+ ------
115
+ ImportError
116
+ If the pipeline step object is missing dependencies required
117
+ for its working.
118
+
119
+ """
120
+ # Check if _DEPENDENCIES attribute is found;
121
+ # (markers and preprocessors will have them but not datareaders
122
+ # as of now)
123
+ dependencies_not_found = []
124
+ if hasattr(obj, "_DEPENDENCIES"):
125
+ # Check if dependencies are importable
126
+ for dependency in obj._DEPENDENCIES:
127
+ # First perform an easy check
128
+ if find_spec(dependency) is None:
129
+ # Then check mapped names
130
+ if dependency not in list(
131
+ chain.from_iterable(
132
+ packages_distributions().values()
133
+ )
134
+ ):
135
+ dependencies_not_found.append(dependency)
136
+ # Raise error if any dependency is not found
137
+ if dependencies_not_found:
138
+ raise_error(
139
+ msg=(
140
+ f"{dependencies_not_found} are not installed but are "
141
+ f"required for using {obj.__class__.__name__}."
142
+ ),
143
+ klass=ImportError,
144
+ )
145
+
146
+ def _check_ext_dependencies(obj) -> None:
147
+ """Check obj._EXT_DEPENDENCIES.
148
+
149
+ Parameters
150
+ ----------
151
+ obj : object
152
+ Object to check _EXT_DEPENDENCIES of.
153
+
154
+ """
155
+ # Check if _EXT_DEPENDENCIES attribute is found;
156
+ # (some markers and preprocessors might have them)
157
+ if hasattr(obj, "_EXT_DEPENDENCIES"):
158
+ for dependency in obj._EXT_DEPENDENCIES:
159
+ check_ext_dependencies(**dependency)
160
+
161
+ def _check_conditional_dependencies(obj) -> None:
162
+ """Check obj._CONDITIONAL_DEPENDENCIES.
163
+
164
+ Parameters
165
+ ----------
166
+ obj : object
167
+ Object to check _CONDITIONAL_DEPENDENCIES of.
168
+
169
+ Raises
170
+ ------
171
+ AttributeError
172
+ If the pipeline step object does not have `using` as a
173
+ constructor parameter.
174
+
175
+ """
176
+ # Check if _CONDITIONAL_DEPENDENCIES attribute is found;
177
+ # (some markers and preprocessors might have them)
178
+ if hasattr(obj, "_CONDITIONAL_DEPENDENCIES"):
179
+ if not hasattr(obj, "using"):
180
+ raise_error(
181
+ msg=(
182
+ f"The pipeline step: {obj.__class__.__name__} has "
183
+ "`_CONDITIONAL_DEPENDENCIES` but does not have "
184
+ "`using` as a constructor parameter"
185
+ ),
186
+ klass=AttributeError,
187
+ )
188
+ else:
189
+ for dependency in obj._CONDITIONAL_DEPENDENCIES:
190
+ if dependency["using"] == obj.using:
191
+ depends_on = dependency["depends_on"]
192
+ # Check dependencies
193
+ _check_dependencies(depends_on)
194
+ # Check external dependencies
195
+ _check_ext_dependencies(depends_on)
196
+
197
+ # Check dependencies
198
+ _check_dependencies(self)
199
+ # Check external dependencies
200
+ _check_ext_dependencies(self)
201
+ # Check conditional dependencies
202
+ _check_conditional_dependencies(self)
203
+ # Validate input
141
204
  fit_input = self.validate_input(input=input)
205
+ # Validate output type
142
206
  outputs = [self.get_output_type(t_input) for t_input in fit_input]
143
207
  return outputs
144
208
 
@@ -41,6 +41,11 @@ def register(step: str, name: str, klass: type) -> None:
41
41
  klass : class
42
42
  Class to be registered.
43
43
 
44
+ Raises
45
+ ------
46
+ ValueError
47
+ If the ``step`` is invalid.
48
+
44
49
  """
45
50
  # Verify step
46
51
  if step not in _VALID_STEPS:
@@ -63,6 +68,11 @@ def get_step_names(step: str) -> List[str]:
63
68
  list
64
69
  List of registered function names.
65
70
 
71
+ Raises
72
+ ------
73
+ ValueError
74
+ If the ``step`` is invalid.
75
+
66
76
  """
67
77
  # Verify step
68
78
  if step not in _VALID_STEPS:
@@ -86,6 +96,11 @@ def get_class(step: str, name: str) -> type:
86
96
  class
87
97
  Registered function class.
88
98
 
99
+ Raises
100
+ ------
101
+ ValueError
102
+ If the ``step`` or ``name`` is invalid.
103
+
89
104
  """
90
105
  # Verify step
91
106
  if step not in _VALID_STEPS:
@@ -123,6 +138,8 @@ def build(
123
138
 
124
139
  Raises
125
140
  ------
141
+ RuntimeError
142
+ If there is a problem creating the instance.
126
143
  ValueError
127
144
  If the created object with the given name is not an instance of the
128
145
  base class.
@@ -0,0 +1,45 @@
1
+ """Provide a singleton class to be used by pipeline components."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from typing import Any, Dict, Type
7
+
8
+
9
+ def singleton(cls: Type) -> Type:
10
+ """Make a class singleton.
11
+
12
+ Parameters
13
+ ----------
14
+ cls : class
15
+ The class to designate as singleton.
16
+
17
+ Returns
18
+ -------
19
+ class
20
+ The only instance of the class.
21
+
22
+ """
23
+ instances: Dict = {}
24
+
25
+ def get_instance(*args: Any, **kwargs: Any) -> Type:
26
+ """Get the only instance for a class.
27
+
28
+ Parameters
29
+ ----------
30
+ *args : tuple
31
+ The positional arguments to pass to the class.
32
+ **kwargs : dict
33
+ The keyword arguments to pass to the class.
34
+
35
+ Returns
36
+ -------
37
+ class
38
+ The only instance of the class.
39
+
40
+ """
41
+ if cls not in instances:
42
+ instances[cls] = cls(*args, **kwargs)
43
+ return instances[cls]
44
+
45
+ return get_instance
@@ -1,11 +1,11 @@
1
- """Provide tests for pipeline mixin."""
1
+ """Provide tests for PipelineStepMixin."""
2
2
 
3
3
  # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
7
  import warnings
8
- from typing import ClassVar, Dict, List, Set, Union
8
+ from typing import ClassVar, Dict, List, Set, Type, Union
9
9
 
10
10
  import pytest
11
11
 
@@ -13,24 +13,13 @@ from junifer.pipeline.pipeline_step_mixin import PipelineStepMixin
13
13
  from junifer.pipeline.utils import _check_afni
14
14
 
15
15
 
16
- def test_PipelineStepMixin() -> None:
17
- """Test PipelineStepMixin."""
18
- mixin = PipelineStepMixin()
19
- with pytest.raises(NotImplementedError):
20
- mixin.validate_input([])
21
- with pytest.raises(NotImplementedError):
22
- mixin.get_output_type("")
23
- with pytest.raises(NotImplementedError):
24
- mixin._fit_transform({})
25
-
26
-
27
- def test_pipeline_step_mixin_validate_correct_dependencies() -> None:
28
- """Test validate with correct dependencies."""
16
+ def test_PipelineStepMixin_correct_dependencies() -> None:
17
+ """Test fit-transform with correct dependencies."""
29
18
 
30
19
  class CorrectMixer(PipelineStepMixin):
31
20
  """Test class for validation."""
32
21
 
33
- _DEPENDENCIES: ClassVar[Set[str]] = {"setuptools"}
22
+ _DEPENDENCIES: ClassVar[Set[str]] = {"math"}
34
23
 
35
24
  def validate_input(self, input: List[str]) -> List[str]:
36
25
  return input
@@ -38,15 +27,15 @@ def test_pipeline_step_mixin_validate_correct_dependencies() -> None:
38
27
  def get_output_type(self, input_type: str) -> str:
39
28
  return input_type
40
29
 
41
- def fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
30
+ def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
42
31
  return {"input": input}
43
32
 
44
33
  mixer = CorrectMixer()
45
- mixer.validate([])
34
+ mixer.fit_transform({})
46
35
 
47
36
 
48
- def test_pipeline_step_mixin_validate_incorrect_dependencies() -> None:
49
- """Test validate with incorrect dependencies."""
37
+ def test_PipelineStepMixin_incorrect_dependencies() -> None:
38
+ """Test fit-transform with incorrect dependencies."""
50
39
 
51
40
  class IncorrectMixer(PipelineStepMixin):
52
41
  """Test class for validation."""
@@ -59,26 +48,24 @@ def test_pipeline_step_mixin_validate_incorrect_dependencies() -> None:
59
48
  def get_output_type(self, input_type: str) -> str:
60
49
  return input_type
61
50
 
62
- def fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
51
+ def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
63
52
  return {"input": input}
64
53
 
65
54
  mixer = IncorrectMixer()
66
55
  with pytest.raises(ImportError, match="not installed"):
67
- mixer.validate([])
56
+ mixer.fit_transform({})
68
57
 
69
58
 
70
59
  @pytest.mark.skipif(
71
- _check_afni() is False, reason="requires afni to be in PATH"
60
+ _check_afni() is False, reason="requires AFNI to be in PATH"
72
61
  )
73
- def test_pipeline_step_mixin_validate_correct_ext_dependencies() -> None:
74
- """Test validate with correct external dependencies."""
62
+ def test_PipelineStepMixin_correct_ext_dependencies() -> None:
63
+ """Test fit-transform with correct external dependencies."""
75
64
 
76
65
  class CorrectMixer(PipelineStepMixin):
77
66
  """Test class for validation."""
78
67
 
79
- _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, bool]]]] = [
80
- {"name": "afni", "optional": False}
81
- ]
68
+ _EXT_DEPENDENCIES: ClassVar[List[Dict[str, str]]] = [{"name": "afni"}]
82
69
 
83
70
  def validate_input(self, input: List[str]) -> List[str]:
84
71
  return input
@@ -86,25 +73,25 @@ def test_pipeline_step_mixin_validate_correct_ext_dependencies() -> None:
86
73
  def get_output_type(self, input_type: str) -> str:
87
74
  return input_type
88
75
 
89
- def fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
76
+ def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
90
77
  return {"input": input}
91
78
 
92
79
  mixer = CorrectMixer()
93
- mixer.validate([])
80
+ mixer.fit_transform({})
94
81
 
95
82
 
96
83
  @pytest.mark.skipif(
97
- _check_afni() is False, reason="requires afni to be in PATH"
84
+ _check_afni() is False, reason="requires AFNI to be in PATH"
98
85
  )
99
- def test_pipeline_step_mixin_validate_ext_deps_correct_commands() -> None:
100
- """Test validate with correct external dependencies' correct commands."""
86
+ def test_PipelineStepMixin_ext_deps_correct_commands() -> None:
87
+ """Test fit-transform with correct external dependency commands."""
101
88
 
102
89
  class CorrectMixer(PipelineStepMixin):
103
90
  """Test class for validation."""
104
91
 
105
- _EXT_DEPENDENCIES: ClassVar[
106
- List[Dict[str, Union[str, bool, List[str]]]]
107
- ] = [{"name": "afni", "optional": False, "commands": ["3dReHo"]}]
92
+ _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
93
+ {"name": "afni", "commands": ["3dReHo"]}
94
+ ]
108
95
 
109
96
  def validate_input(self, input: List[str]) -> List[str]:
110
97
  return input
@@ -112,27 +99,27 @@ def test_pipeline_step_mixin_validate_ext_deps_correct_commands() -> None:
112
99
  def get_output_type(self, input_type: str) -> str:
113
100
  return input_type
114
101
 
115
- def fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
102
+ def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
116
103
  return {"input": input}
117
104
 
118
105
  mixer = CorrectMixer()
119
106
  with warnings.catch_warnings():
120
107
  warnings.simplefilter("error")
121
- mixer.validate([])
108
+ mixer.fit_transform({})
122
109
 
123
110
 
124
111
  @pytest.mark.skipif(
125
- _check_afni() is False, reason="requires afni to be in PATH"
112
+ _check_afni() is False, reason="requires AFNI to be in PATH"
126
113
  )
127
- def test_pipeline_step_mixin_validate_ext_deps_incorrect_commands() -> None:
128
- """Test validate with correct external dependencies' incorrect commands."""
114
+ def test_PipelineStepMixin_ext_deps_incorrect_commands() -> None:
115
+ """Test fit-transform with inccorrect external dependency commands."""
129
116
 
130
117
  class CorrectMixer(PipelineStepMixin):
131
118
  """Test class for validation."""
132
119
 
133
- _EXT_DEPENDENCIES: ClassVar[
134
- List[Dict[str, Union[str, bool, List[str]]]]
135
- ] = [{"name": "afni", "optional": False, "commands": ["3d"]}]
120
+ _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
121
+ {"name": "afni", "commands": ["3d"]}
122
+ ]
136
123
 
137
124
  def validate_input(self, input: List[str]) -> List[str]:
138
125
  return input
@@ -140,16 +127,16 @@ def test_pipeline_step_mixin_validate_ext_deps_incorrect_commands() -> None:
140
127
  def get_output_type(self, input_type: str) -> str:
141
128
  return input_type
142
129
 
143
- def fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
130
+ def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
144
131
  return {"input": input}
145
132
 
146
133
  mixer = CorrectMixer()
147
134
  with pytest.warns(RuntimeWarning, match="AFNI is installed"):
148
- mixer.validate([])
135
+ mixer.fit_transform({})
149
136
 
150
137
 
151
- def test_pipeline_step_mixin_validate_incorrect_ext_dependencies() -> None:
152
- """Test validate with incorrect external dependencies."""
138
+ def test_PipelineStepMixin_incorrect_ext_dependencies() -> None:
139
+ """Test fit-transform with incorrect external dependencies."""
153
140
 
154
141
  class IncorrectMixer(PipelineStepMixin):
155
142
  """Test class for validation."""
@@ -164,9 +151,110 @@ def test_pipeline_step_mixin_validate_incorrect_ext_dependencies() -> None:
164
151
  def get_output_type(self, input_type: str) -> str:
165
152
  return input_type
166
153
 
167
- def fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
154
+ def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
155
+ return {"input": input}
156
+
157
+ mixer = IncorrectMixer()
158
+ with pytest.raises(ValueError, match="Invalid value"):
159
+ mixer.fit_transform({})
160
+
161
+
162
+ def test_PipelineStepMixin_correct_conditional_dependencies() -> None:
163
+ """Test fit-transform with correct conditional dependencies."""
164
+
165
+ class Dependency:
166
+ _DEPENDENCIES: ClassVar[Set[str]] = {"math"}
167
+
168
+ class CorrectMixer(PipelineStepMixin):
169
+ """Test class for validation."""
170
+
171
+ _CONDITIONAL_DEPENDENCIES: ClassVar[
172
+ List[Dict[str, Union[str, Type]]]
173
+ ] = [
174
+ {
175
+ "using": "math",
176
+ "depends_on": Dependency,
177
+ },
178
+ ]
179
+
180
+ using = "math"
181
+
182
+ def validate_input(self, input: List[str]) -> List[str]:
183
+ return input
184
+
185
+ def get_output_type(self, input_type: str) -> str:
186
+ return input_type
187
+
188
+ def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
189
+ return {"input": input}
190
+
191
+ mixer = CorrectMixer()
192
+ mixer.fit_transform({})
193
+
194
+
195
+ def test_PipelineStepMixin_incorrect_conditional_dependencies() -> None:
196
+ """Test fit-transform with incorrect conditional dependencies."""
197
+
198
+ class Dependency:
199
+ _DEPENDENCIES: ClassVar[Set[str]] = {"math"}
200
+
201
+ class IncorrectMixer(PipelineStepMixin):
202
+ """Test class for validation."""
203
+
204
+ _CONDITIONAL_DEPENDENCIES: ClassVar[
205
+ List[Dict[str, Union[str, Type]]]
206
+ ] = [
207
+ {
208
+ "using": "math",
209
+ "depends_on": Dependency,
210
+ },
211
+ ]
212
+
213
+ def validate_input(self, input: List[str]) -> List[str]:
214
+ return input
215
+
216
+ def get_output_type(self, input_type: str) -> str:
217
+ return input_type
218
+
219
+ def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
168
220
  return {"input": input}
169
221
 
170
222
  mixer = IncorrectMixer()
171
- with pytest.raises(ValueError, match="too adventurous"):
172
- mixer.validate([])
223
+ with pytest.raises(AttributeError, match="`using`"):
224
+ mixer.fit_transform({})
225
+
226
+
227
+ @pytest.mark.skipif(
228
+ _check_afni() is False, reason="requires AFNI to be in PATH"
229
+ )
230
+ def test_PipelineStepMixin_correct_conditional_ext_dependencies() -> None:
231
+ """Test fit-transform with correct conditional external dependencies."""
232
+
233
+ class ExternalDependency:
234
+ _EXT_DEPENDENCIES: ClassVar[List[Dict[str, str]]] = [{"name": "afni"}]
235
+
236
+ class CorrectMixer(PipelineStepMixin):
237
+ """Test class for validation."""
238
+
239
+ _CONDITIONAL_DEPENDENCIES: ClassVar[
240
+ List[Dict[str, Union[str, Type]]]
241
+ ] = [
242
+ {
243
+ "using": "afni",
244
+ "depends_on": ExternalDependency,
245
+ },
246
+ ]
247
+
248
+ using = "afni"
249
+
250
+ def validate_input(self, input: List[str]) -> List[str]:
251
+ return input
252
+
253
+ def get_output_type(self, input_type: str) -> str:
254
+ return input_type
255
+
256
+ def _fit_transform(self, input: Dict[str, Dict]) -> Dict[str, Dict]:
257
+ return {"input": input}
258
+
259
+ mixer = CorrectMixer()
260
+ mixer.fit_transform({})
@@ -38,6 +38,7 @@ def test_UpdateMetaMixin(
38
38
  The dependencies of the pipeline step.
39
39
  expected : set
40
40
  The expected dependencies.
41
+
41
42
  """
42
43
 
43
44
  class TestUpdateMetaMixin(UpdateMetaMixin):
@@ -0,0 +1,104 @@
1
+ """Provide tests for WorkDirManager."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from pathlib import Path
7
+
8
+ from junifer.pipeline import WorkDirManager
9
+
10
+
11
+ def test_workdir_manager_singleton() -> None:
12
+ """Test that WorkDirManager is a singleton."""
13
+ workdir_mgr_1 = WorkDirManager()
14
+ workdir_mgr_2 = WorkDirManager()
15
+ assert id(workdir_mgr_1) == id(workdir_mgr_2)
16
+
17
+
18
+ def test_workdir_manager_workdir(tmp_path: Path) -> None:
19
+ """Test WorkDirManager correctly sets workdir.
20
+
21
+ Parameters
22
+ ----------
23
+ tmp_path : pathlib.Path
24
+ The path to the test directory.
25
+
26
+ """
27
+ workdir_mgr = WorkDirManager()
28
+ workdir_mgr.workdir = tmp_path
29
+ assert workdir_mgr.workdir == tmp_path
30
+
31
+
32
+ def test_workdir_manager_get_and_delete_element_tempdir(
33
+ tmp_path: Path,
34
+ ) -> None:
35
+ """Test WorkDirManager gets and deletes element tempdirs correctly.
36
+
37
+ Parameters
38
+ ----------
39
+ tmp_path : pathlib.Path
40
+ The path to the test directory.
41
+
42
+ """
43
+ workdir_mgr = WorkDirManager()
44
+ workdir_mgr.workdir = tmp_path
45
+ # Check no element directory
46
+ assert workdir_mgr.elementdir is None
47
+
48
+ element_tempdir = workdir_mgr.get_element_tempdir()
49
+ # Should create a temporary directory
50
+ assert workdir_mgr.elementdir is not None
51
+
52
+ workdir_mgr.delete_element_tempdir(element_tempdir)
53
+ workdir_mgr._cleanup()
54
+ # Should remove temporary directory
55
+ assert workdir_mgr.elementdir is None
56
+
57
+
58
+ def test_workdir_manager_cleanup_elementdir(
59
+ tmp_path: Path,
60
+ ) -> None:
61
+ """Test WorkDirManager cleans up element directory correctly.
62
+
63
+ Parameters
64
+ ----------
65
+ tmp_path : pathlib.Path
66
+ The path to the test directory.
67
+
68
+ """
69
+ workdir_mgr = WorkDirManager()
70
+ workdir_mgr.workdir = tmp_path
71
+ # Check no element directory
72
+ assert workdir_mgr.elementdir is None
73
+
74
+ workdir_mgr.get_element_tempdir()
75
+ # Should create a temporary directory
76
+ assert workdir_mgr.elementdir is not None
77
+
78
+ workdir_mgr.cleanup_elementdir()
79
+ # Should remove temporary directory
80
+ assert workdir_mgr.elementdir is None
81
+
82
+
83
+ def test_workdir_manager_get_and_delete_tempdir(tmp_path: Path) -> None:
84
+ """Test WorkDirManager gets and deletes temporary directories correctly.
85
+
86
+ Parameters
87
+ ----------
88
+ tmp_path : pathlib.Path
89
+ The path to the test directory.
90
+
91
+ """
92
+ workdir_mgr = WorkDirManager()
93
+ workdir_mgr.workdir = tmp_path
94
+ # Check no root temporary directory
95
+ assert workdir_mgr.root_tempdir is None
96
+
97
+ tempdir = workdir_mgr.get_tempdir()
98
+ # Should create a temporary directory
99
+ assert workdir_mgr.root_tempdir is not None
100
+
101
+ workdir_mgr.delete_tempdir(tempdir)
102
+ workdir_mgr._cleanup()
103
+ # Should remove temporary directory
104
+ assert workdir_mgr.root_tempdir is None