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
@@ -1,334 +0,0 @@
1
- """Provide estimator class for (f)ALFF."""
2
-
3
- # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
- # Federico Raimondo <f.raimondo@fz-juelich.de>
5
- # License: AGPL
6
-
7
- import shutil
8
- import subprocess
9
- import tempfile
10
- import typing
11
- from functools import lru_cache
12
- from pathlib import Path
13
- from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union
14
-
15
- import nibabel as nib
16
- import numpy as np
17
- from nilearn import image as nimg
18
- from scipy.fft import fft, fftfreq
19
-
20
- from ...utils import logger, raise_error
21
- from ..utils import singleton
22
-
23
-
24
- if TYPE_CHECKING:
25
- from nibabel import Nifti1Image, Nifti2Image
26
-
27
-
28
- @singleton
29
- class ALFFEstimator:
30
- """Estimator class for (fractional) Amplitude Low Frequency Fluctuation.
31
-
32
- This class is a singleton and is used for efficient computation of fALFF,
33
- by caching the voxel-wise ALFF map for a given set of file path and
34
- computation parameters.
35
-
36
- .. warning:: This class can only be used via :class:`.ALFFBase` as it
37
- serves a specific purpose.
38
-
39
- Parameters
40
- ----------
41
- use_afni : bool
42
- Whether to use afni for computation. If False, will use python.
43
-
44
- """
45
-
46
- def __init__(self) -> None:
47
- self._file_path = None
48
- # Create temporary directory for intermittent storage of assets during
49
- # computation via afni's 3dReHo
50
- self.temp_dir_path = Path(tempfile.mkdtemp())
51
-
52
- def __del__(self) -> None:
53
- """Cleanup."""
54
- print("Cleaning up temporary directory...")
55
- # Delete temporary directory and ignore errors for read-only files
56
- shutil.rmtree(self.temp_dir_path, ignore_errors=True)
57
-
58
- @staticmethod
59
- def _run_afni_cmd(cmd: str) -> None:
60
- """Run AFNI command.
61
-
62
- Parameters
63
- ----------
64
- cmd : str
65
- AFNI command to be executed.
66
-
67
- Raises
68
- ------
69
- RuntimeError
70
- If AFNI command fails.
71
- """
72
- logger.info(f"AFNI command to be executed: {cmd}")
73
- process = subprocess.run(
74
- cmd,
75
- stdin=subprocess.DEVNULL,
76
- stdout=subprocess.PIPE,
77
- stderr=subprocess.STDOUT,
78
- shell=True,
79
- check=False,
80
- )
81
- if process.returncode == 0:
82
- logger.info(
83
- "AFNI command succeeded with the following output: "
84
- f"{process.stdout}"
85
- )
86
- else:
87
- raise_error(
88
- msg="AFNI command failed with the following error: "
89
- f"{process.stdout}",
90
- klass=RuntimeError,
91
- )
92
-
93
- def _compute_alff_afni(
94
- self,
95
- data: Union["Nifti1Image", "Nifti2Image"],
96
- highpass: float,
97
- lowpass: float,
98
- tr: Optional[float],
99
- ) -> Tuple["Nifti1Image", "Nifti1Image"]:
100
- """Compute ALFF map via afni's commands.
101
-
102
- Parameters
103
- ----------
104
- data : 4D Niimg-like object
105
- Images to process.
106
- highpass : positive float
107
- Highpass cutoff frequency.
108
- lowpass : positive float
109
- Lowpass cutoff frequency.
110
- tr : positive float, optional
111
- The Repetition Time of the BOLD data.
112
-
113
- Returns
114
- -------
115
- alff: Niimg-like object
116
- ALFF map.
117
- falff: Niimg-like object
118
- fALFF map.
119
-
120
- Raises
121
- ------
122
- RuntimeError
123
- If the AFNI commands fails due to some issues
124
-
125
- """
126
-
127
- # Save niimg to nii.gz
128
- nifti_in_file_path = self.temp_dir_path / "input.nii"
129
- nib.save(data, nifti_in_file_path)
130
-
131
- params_suffix = f"_{highpass}_{lowpass}_{tr}"
132
- alff_fname = self.temp_dir_path / f"alff{params_suffix}.nii"
133
- falff_fname = self.temp_dir_path / f"falff{params_suffix}.nii"
134
-
135
- # Use afni's 3dRSFC to compute ALFF and fALFF
136
- falff_afni_out_path_prefix = self.temp_dir_path / "temp_falff"
137
-
138
- bp_cmd = (
139
- "3dRSFC "
140
- f"-prefix {falff_afni_out_path_prefix.resolve()} "
141
- f"-input {nifti_in_file_path.resolve()} "
142
- f"-band {highpass} {lowpass} "
143
- "-no_rsfa -nosat -nodetrend "
144
- )
145
- if tr is not None:
146
- bp_cmd += f"-dt {tr} "
147
- self._run_afni_cmd(bp_cmd)
148
-
149
- # Convert afni's output to nifti
150
- convert_cmd = (
151
- "3dAFNItoNIFTI "
152
- f"-prefix {alff_fname.resolve()} "
153
- f"{falff_afni_out_path_prefix}_ALFF+tlrc.BRIK "
154
- )
155
- self._run_afni_cmd(convert_cmd)
156
-
157
- convert_cmd = (
158
- "3dAFNItoNIFTI "
159
- f"-prefix {falff_fname.resolve()} "
160
- f"{falff_afni_out_path_prefix}_fALFF+tlrc.BRIK "
161
- )
162
- self._run_afni_cmd(convert_cmd)
163
-
164
- # Cleanup intermediate files
165
- for fname in self.temp_dir_path.glob("temp_*"):
166
- fname.unlink()
167
-
168
- # Load niftis
169
- alff_img = nib.load(alff_fname)
170
- falff_img = nib.load(falff_fname)
171
-
172
- return alff_img, falff_img
173
-
174
- def _compute_alff_python(
175
- self,
176
- data: Union["Nifti1Image", "Nifti2Image"],
177
- highpass: float,
178
- lowpass: float,
179
- tr: Optional[float],
180
- ) -> Tuple["Nifti1Image", "Nifti1Image"]:
181
- """Compute (f)ALFF map.
182
-
183
- Parameters
184
- ----------
185
- data : 4D Niimg-like object
186
- Images to process.
187
- highpass : positive float
188
- Highpass cutoff frequency.
189
- lowpass : positive float
190
- Lowpass cutoff frequency.
191
- tr : positive float, optional
192
- The Repetition Time of the BOLD data.
193
-
194
- Returns
195
- -------
196
- alff: Niimg-like object
197
- ALFF map.
198
- falff: Niimg-like object
199
- fALFF map.
200
- """
201
- timeseries = data.get_fdata().copy()
202
- if tr is None:
203
- tr = float(data.header["pixdim"][4]) # type: ignore
204
- logger.info(f"TR Not provided, using TR from header = {tr}")
205
- # bandpass the data within the lowpass and highpass cutoff freqs
206
-
207
- ts_fft = fft(timeseries, axis=-1)
208
- ts_fft = typing.cast(np.ndarray, ts_fft)
209
- fft_freqs = np.abs(fftfreq(timeseries.shape[-1], tr))
210
-
211
- dFreq = fft_freqs[1] - fft_freqs[0]
212
- nyquist = np.max(fft_freqs)
213
- nfft = len(fft_freqs)
214
- logger.info(
215
- f"FFT: nfft = {nfft}, dFreq = {dFreq}, nyquist = {nyquist}"
216
- )
217
-
218
- # First compute the denominator on the broadband signal
219
- all_freq_mask = fft_freqs > 0
220
- denominator = np.sum(np.abs(ts_fft[..., all_freq_mask]), axis=-1)
221
-
222
- # Compute the numerator on the bandpassed signal
223
- freq_mask = np.logical_and(fft_freqs > highpass, fft_freqs < lowpass)
224
- # Compute ALFF
225
- numerator = np.sum(np.abs(ts_fft[..., freq_mask]), axis=-1)
226
-
227
- # Compute fALFF, but avoid division by zero
228
- denom_mask = denominator <= 0.000001
229
- denominator[denom_mask] = 1 # set to 1 to avoid division by zero
230
- python_falff = np.divide(numerator, denominator)
231
- # Set the values where denominator is zero to zero
232
- python_falff[denom_mask] = 0
233
-
234
- python_alff = numerator / np.sqrt(timeseries.shape[-1])
235
- alff_img = nimg.new_img_like(data, python_alff)
236
- falff_img = nimg.new_img_like(data, python_falff)
237
- return alff_img, falff_img
238
-
239
- @lru_cache(maxsize=None, typed=True)
240
- def _compute(
241
- self,
242
- use_afni: bool,
243
- data: Union["Nifti1Image", "Nifti2Image"],
244
- highpass: float,
245
- lowpass: float,
246
- tr: Optional[float],
247
- ) -> Tuple["Nifti1Image", "Nifti1Image"]:
248
- """Compute the ALFF map with memorization.
249
-
250
- Parameters
251
- ----------
252
- use_afni : bool
253
- Whether to use AFNI for computing.
254
- data : 4D Niimg-like object
255
- Images to process.
256
- highpass : positive float
257
- Highpass cutoff frequency.
258
- lowpass : positive float
259
- Lowpass cutoff frequency.
260
- tr : positive float, optional
261
- The Repetition Time of the BOLD data.
262
-
263
- Returns
264
- -------
265
- alff: Niimg-like object
266
- ALFF map.
267
- falff: Niimg-like object
268
- fALFF map.
269
- """
270
- if use_afni:
271
- output = self._compute_alff_afni(
272
- data=data,
273
- highpass=highpass,
274
- lowpass=lowpass,
275
- tr=tr,
276
- )
277
- else:
278
- output = self._compute_alff_python(
279
- data, highpass=highpass, lowpass=lowpass, tr=tr
280
- )
281
- return output
282
-
283
- def fit_transform(
284
- self,
285
- use_afni: bool,
286
- input_data: Dict[str, Any],
287
- highpass: float,
288
- lowpass: float,
289
- tr: Optional[float],
290
- ) -> Tuple["Nifti1Image", "Nifti1Image"]:
291
- """Fit and transform for the estimator.
292
-
293
- Parameters
294
- ----------
295
- use_afni : bool
296
- Whether to use AFNI for computing.
297
- input_data : dict
298
- The BOLD data as dictionary.
299
- highpass : positive float
300
- Highpass cutoff frequency.
301
- lowpass : positive float
302
- Lowpass cutoff frequency.
303
- tr : positive float, optional
304
- The Repetition Time of the BOLD data.
305
-
306
- Returns
307
- -------
308
- alff: Niimg-like object
309
- ALFF map.
310
- falff: Niimg-like object
311
- fALFF map.
312
- """
313
- bold_path = input_data["path"]
314
- bold_data = input_data["data"]
315
- # Clear cache if file path is different from when caching was done
316
- if self._file_path != bold_path:
317
- logger.info(f"Removing fALFF map cache at {self._file_path}.")
318
- # Clear the cache
319
- self._compute.cache_clear()
320
- # Clear temporary directory files
321
- for file_ in self.temp_dir_path.iterdir():
322
- file_.unlink(missing_ok=True)
323
- # Set the new file path
324
- self._file_path = bold_path
325
- else:
326
- logger.info(f"Using fALFF map cache at {self._file_path}.")
327
- # Compute
328
- return self._compute(
329
- use_afni=use_afni,
330
- data=bold_data,
331
- highpass=highpass,
332
- lowpass=lowpass,
333
- tr=tr,
334
- )
@@ -1,238 +0,0 @@
1
- """Provide test for (f)ALFF estimator."""
2
-
3
- # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
- # License: AGPL
5
-
6
- import time
7
-
8
- import pytest
9
- from nibabel import Nifti1Image
10
- from scipy.stats import pearsonr
11
-
12
- from junifer.datareader import DefaultDataReader
13
- from junifer.markers.falff.falff_estimator import ALFFEstimator
14
- from junifer.pipeline.utils import _check_afni
15
- from junifer.testing.datagrabbers import PartlyCloudyTestingDataGrabber
16
- from junifer.utils import logger
17
-
18
-
19
- def test_ALFFEstimator_cache_python() -> None:
20
- """Test that the cache works properly when using python."""
21
- with PartlyCloudyTestingDataGrabber() as dg:
22
- input = dg["sub-01"]
23
-
24
- input = DefaultDataReader().fit_transform(input)
25
-
26
- estimator = ALFFEstimator()
27
- start_time = time.time()
28
- alff, falff = estimator.fit_transform(
29
- use_afni=False,
30
- input_data=input["BOLD"],
31
- highpass=0.01,
32
- lowpass=0.1,
33
- tr=None,
34
- )
35
- first_time = time.time() - start_time
36
- logger.info(f"ALFF Estimator First time: {first_time}")
37
- assert isinstance(alff, Nifti1Image)
38
- assert isinstance(falff, Nifti1Image)
39
- n_files = len(list(estimator.temp_dir_path.glob("*")))
40
- assert n_files == 0 # no files in python
41
-
42
- # Now fit again, should be faster
43
- start_time = time.time()
44
- alff, falff = estimator.fit_transform(
45
- use_afni=False,
46
- input_data=input["BOLD"],
47
- highpass=0.01,
48
- lowpass=0.1,
49
- tr=None,
50
- )
51
- second_time = time.time() - start_time
52
- logger.info(f"ALFF Estimator Second time: {second_time}")
53
- assert second_time < (first_time / 1000)
54
- n_files = len(list(estimator.temp_dir_path.glob("*")))
55
- assert n_files == 0 # no files in python
56
-
57
- # Now change a parameter, should compute again, without clearing the
58
- # cache
59
- start_time = time.time()
60
- alff, falff = estimator.fit_transform(
61
- use_afni=False,
62
- input_data=input["BOLD"],
63
- highpass=0.01,
64
- lowpass=0.11,
65
- tr=None,
66
- )
67
- third_time = time.time() - start_time
68
- logger.info(f"ALFF Estimator Third time: {third_time}")
69
- assert third_time > (first_time / 10)
70
- n_files = len(list(estimator.temp_dir_path.glob("*")))
71
- assert n_files == 0 # no files in python
72
-
73
- # Now fit again with the previous params, should be fast
74
- start_time = time.time()
75
- alff, falff = estimator.fit_transform(
76
- use_afni=False,
77
- input_data=input["BOLD"],
78
- highpass=0.01,
79
- lowpass=0.1,
80
- tr=None,
81
- )
82
- fourth = time.time() - start_time
83
- logger.info(f"ALFF Estimator Fourth time: {fourth}")
84
- assert fourth < (first_time / 1000)
85
- n_files = len(list(estimator.temp_dir_path.glob("*")))
86
- assert n_files == 0 # no files in python
87
-
88
- # Now change the data, it should clear the cache
89
- with PartlyCloudyTestingDataGrabber() as dg:
90
- input = dg["sub-02"]
91
-
92
- input = DefaultDataReader().fit_transform(input)
93
-
94
- start_time = time.time()
95
- alff, falff = estimator.fit_transform(
96
- use_afni=False,
97
- input_data=input["BOLD"],
98
- highpass=0.01,
99
- lowpass=0.1,
100
- tr=None,
101
- )
102
- fifth = time.time() - start_time
103
- logger.info(f"ALFF Estimator Fifth time: {fifth}")
104
- assert fifth > (first_time / 10)
105
- n_files = len(list(estimator.temp_dir_path.glob("*")))
106
- assert n_files == 0 # no files in python
107
-
108
-
109
- @pytest.mark.skipif(
110
- _check_afni() is False, reason="requires afni to be in PATH"
111
- )
112
- def test_ALFFEstimator_cache_afni() -> None:
113
- """Test that the cache works properly when using afni."""
114
- with PartlyCloudyTestingDataGrabber() as dg:
115
- input = dg["sub-01"]
116
-
117
- input = DefaultDataReader().fit_transform(input)
118
-
119
- estimator = ALFFEstimator()
120
- start_time = time.time()
121
- alff, falff = estimator.fit_transform(
122
- use_afni=True,
123
- input_data=input["BOLD"],
124
- highpass=0.01,
125
- lowpass=0.1,
126
- tr=None,
127
- )
128
- first_time = time.time() - start_time
129
- logger.info(f"ALFF Estimator First time: {first_time}")
130
- assert isinstance(alff, Nifti1Image)
131
- assert isinstance(falff, Nifti1Image)
132
- n_files = len(list(estimator.temp_dir_path.glob("*")))
133
- assert n_files == 3 # input + alff + falff
134
-
135
- # Now fit again, should be faster
136
- start_time = time.time()
137
- alff, falff = estimator.fit_transform(
138
- use_afni=True,
139
- input_data=input["BOLD"],
140
- highpass=0.01,
141
- lowpass=0.1,
142
- tr=None,
143
- )
144
- second_time = time.time() - start_time
145
- logger.info(f"ALFF Estimator Second time: {second_time}")
146
- assert second_time < (first_time / 1000)
147
- n_files = len(list(estimator.temp_dir_path.glob("*")))
148
- assert n_files == 3 # input + alff + falff
149
-
150
- # Now change a parameter, should compute again, without clearing the
151
- # cache
152
- start_time = time.time()
153
- alff, falff = estimator.fit_transform(
154
- use_afni=True,
155
- input_data=input["BOLD"],
156
- highpass=0.01,
157
- lowpass=0.11,
158
- tr=None,
159
- )
160
- third_time = time.time() - start_time
161
- logger.info(f"ALFF Estimator Third time: {third_time}")
162
- assert third_time > (first_time / 10)
163
- n_files = len(list(estimator.temp_dir_path.glob("*")))
164
- assert n_files == 5 # input + 2 * alff + 2 * falff
165
-
166
- # Now fit again with the previous params, should be fast
167
- start_time = time.time()
168
- alff, falff = estimator.fit_transform(
169
- use_afni=True,
170
- input_data=input["BOLD"],
171
- highpass=0.01,
172
- lowpass=0.1,
173
- tr=None,
174
- )
175
- fourth = time.time() - start_time
176
- logger.info(f"ALFF Estimator Fourth time: {fourth}")
177
- assert fourth < (first_time / 1000)
178
- n_files = len(list(estimator.temp_dir_path.glob("*")))
179
- assert n_files == 5 # input + 2 * alff + 2 * falff
180
-
181
- # Now change the data, it should clear the cache
182
- with PartlyCloudyTestingDataGrabber() as dg:
183
- input = dg["sub-02"]
184
-
185
- input = DefaultDataReader().fit_transform(input)
186
-
187
- start_time = time.time()
188
- alff, falff = estimator.fit_transform(
189
- use_afni=True,
190
- input_data=input["BOLD"],
191
- highpass=0.01,
192
- lowpass=0.1,
193
- tr=None,
194
- )
195
- fifth = time.time() - start_time
196
- logger.info(f"ALFF Estimator Fifth time: {fifth}")
197
- assert fifth > (first_time / 10)
198
- n_files = len(list(estimator.temp_dir_path.glob("*")))
199
- assert n_files == 3 # input + alff + falff
200
-
201
-
202
- @pytest.mark.skipif(
203
- _check_afni() is False, reason="requires afni to be in PATH"
204
- )
205
- def test_ALFFEstimator_afni_vs_python() -> None:
206
- """Test that the cache works properly when using afni."""
207
- with PartlyCloudyTestingDataGrabber() as dg:
208
- input = dg["sub-01"]
209
-
210
- input = DefaultDataReader().fit_transform(input)
211
- estimator = ALFFEstimator()
212
-
213
- # Use an arbitrary TR to test the AFNI vs Python implementation
214
- afni_alff, afni_falff = estimator.fit_transform(
215
- use_afni=True,
216
- input_data=input["BOLD"],
217
- highpass=0.01,
218
- lowpass=0.1,
219
- tr=2.5,
220
- )
221
-
222
- python_alff, python_falff = estimator.fit_transform(
223
- use_afni=False,
224
- input_data=input["BOLD"],
225
- highpass=0.01,
226
- lowpass=0.1,
227
- tr=2.5,
228
- )
229
-
230
- r, _ = pearsonr(
231
- afni_alff.get_fdata().flatten(), python_alff.get_fdata().flatten()
232
- )
233
- assert r > 0.99
234
-
235
- r, _ = pearsonr(
236
- afni_falff.get_fdata().flatten(), python_falff.get_fdata().flatten()
237
- )
238
- assert r > 0.99