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
junifer/api/functions.py CHANGED
@@ -6,8 +6,6 @@
6
6
  # License: AGPL
7
7
 
8
8
  import shutil
9
- import subprocess
10
- import textwrap
11
9
  import typing
12
10
  from pathlib import Path
13
11
  from typing import Dict, List, Optional, Tuple, Union
@@ -15,11 +13,12 @@ from typing import Dict, List, Optional, Tuple, Union
15
13
  from ..datagrabber.base import BaseDataGrabber
16
14
  from ..markers.base import BaseMarker
17
15
  from ..markers.collection import MarkerCollection
16
+ from ..pipeline import WorkDirManager
18
17
  from ..pipeline.registry import build
19
18
  from ..preprocess.base import BasePreprocessor
20
19
  from ..storage.base import BaseFeatureStorage
21
20
  from ..utils import logger, raise_error
22
- from ..utils.fs import make_executable
21
+ from .queue_context import GnuParallelLocalAdapter, HTCondorAdapter
23
22
  from .utils import yaml
24
23
 
25
24
 
@@ -80,7 +79,7 @@ def run(
80
79
  datagrabber: Dict,
81
80
  markers: List[Dict],
82
81
  storage: Dict,
83
- preprocessor: Optional[Dict] = None,
82
+ preprocessors: Optional[List[Dict]] = None,
84
83
  elements: Union[str, List[Union[str, Tuple]], Tuple, None] = None,
85
84
  ) -> None:
86
85
  """Run the pipeline on the selected element.
@@ -103,10 +102,10 @@ def run(
103
102
  Storage to use. Must have a key ``kind`` with the kind of
104
103
  storage to use. All other keys are passed to the storage
105
104
  init function.
106
- preprocessor : dict, optional
107
- Preprocessor to use. Must have a key ``kind`` with the kind of
108
- preprocessor to use. All other keys are passed to the preprocessor
109
- init function (default None).
105
+ preprocessors : list of dict, optional
106
+ List of preprocessors to use. Each preprocessor is a dict with at
107
+ least a key ``kind`` specifying the preprocessor to use. All other keys
108
+ are passed to the preprocessor init function (default None).
110
109
  elements : str or tuple or list of str or tuple, optional
111
110
  Element(s) to process. Will be used to index the DataGrabber
112
111
  (default None).
@@ -115,6 +114,8 @@ def run(
115
114
  # Convert str to Path
116
115
  if isinstance(workdir, str):
117
116
  workdir = Path(workdir)
117
+ # Initiate working directory manager
118
+ WorkDirManager(workdir)
118
119
 
119
120
  if not isinstance(elements, list) and elements is not None:
120
121
  elements = [elements]
@@ -149,21 +150,29 @@ def run(
149
150
  storage_object = typing.cast(BaseFeatureStorage, storage_object)
150
151
 
151
152
  # Get preprocessor to use (if provided)
152
- if preprocessor is not None:
153
- preprocessor_object = _get_preprocessor(preprocessor)
153
+ if preprocessors is not None:
154
+ _preprocessors = [x.copy() for x in preprocessors]
155
+ built_preprocessors = []
156
+ for preprocessor in _preprocessors:
157
+ preprocessor_object = _get_preprocessor(preprocessor)
158
+ built_preprocessors.append(preprocessor_object)
154
159
  else:
155
- preprocessor_object = None
160
+ built_preprocessors = None
156
161
 
157
162
  # Create new marker collection
158
163
  mc = MarkerCollection(
159
164
  markers=built_markers,
160
- preprocessing=preprocessor_object,
165
+ preprocessors=built_preprocessors,
161
166
  storage=storage_object,
162
167
  )
168
+ mc.validate(datagrabber_object)
169
+
163
170
  # Fit elements
164
171
  with datagrabber_object:
165
172
  if elements is not None:
166
- for t_element in elements:
173
+ for t_element in datagrabber_object.filter(
174
+ elements # type: ignore
175
+ ):
167
176
  mc.fit(datagrabber_object[t_element])
168
177
  else:
169
178
  for t_element in datagrabber_object:
@@ -213,7 +222,7 @@ def queue(
213
222
  ----------
214
223
  config : dict
215
224
  The configuration to be used for queueing the job.
216
- kind : {"HTCondor", "SLURM"}
225
+ kind : {"HTCondor", "GNUParallelLocal"}
217
226
  The kind of job queue system to use.
218
227
  jobname : str, optional
219
228
  The name of the job (default "junifer_job").
@@ -228,13 +237,20 @@ def queue(
228
237
  Raises
229
238
  ------
230
239
  ValueError
231
- If the value of ``kind`` is invalid.
240
+ If ``kind`` is invalid or
241
+ if the ``jobdir`` exists and ``overwrite = False``.
232
242
 
233
243
  """
244
+ valid_kind = ["HTCondor", "GNUParallelLocal"]
245
+ if kind not in valid_kind:
246
+ raise_error(
247
+ f"Invalid value for `kind`: {kind}, "
248
+ f"must be one of {valid_kind}"
249
+ )
250
+
234
251
  # Create a folder within the CWD to store the job files / config
235
- cwd = Path.cwd()
236
- jobdir = cwd / "junifer_jobs" / jobname
237
- logger.info(f"Creating job in {jobdir.absolute()!s}")
252
+ jobdir = Path.cwd() / "junifer_jobs" / jobname
253
+ logger.info(f"Creating job directory at {jobdir.resolve()!s}")
238
254
  if jobdir.exists():
239
255
  if not overwrite:
240
256
  raise_error(
@@ -242,15 +258,16 @@ def queue(
242
258
  "This error is raised to prevent overwriting job files "
243
259
  "that might be scheduled but not yet executed. "
244
260
  f"Either delete the directory {jobdir.absolute()!s} "
245
- "or set overwrite=True."
261
+ "or set `overwrite=True.`"
246
262
  )
247
263
  else:
248
264
  logger.info(
249
- f"Deleting existing job directory at {jobdir.absolute()!s}"
265
+ f"Deleting existing job directory at {jobdir.resolve()!s}"
250
266
  )
251
267
  shutil.rmtree(jobdir)
252
268
  jobdir.mkdir(exist_ok=True, parents=True)
253
269
 
270
+ # Load modules
254
271
  if "with" in config:
255
272
  to_load = config["with"]
256
273
  # If there is a list of files to load, copy and remove the path
@@ -260,15 +277,16 @@ def queue(
260
277
  to_load = [to_load]
261
278
  for item in to_load:
262
279
  if item.endswith(".py"):
263
- logger.debug(f"Copying {item} to jobdir ({jobdir.absolute()})")
264
- shutil.copy(item, jobdir)
280
+ logger.debug(f"Copying {item} to ({jobdir.resolve()!s})")
281
+ shutil.copy(src=item, dst=jobdir)
265
282
  fixed_load.append(Path(item).name)
266
283
  else:
267
284
  fixed_load.append(item)
268
285
  config["with"] = fixed_load
269
286
 
287
+ # Save YAML
270
288
  yaml_config = jobdir / "config.yaml"
271
- logger.info(f"Writing YAML config to {yaml_config.absolute()!s}")
289
+ logger.info(f"Writing YAML config to {yaml_config.resolve()!s}")
272
290
  yaml.dump(config, stream=yaml_config)
273
291
 
274
292
  # Get list of elements
@@ -281,415 +299,65 @@ def queue(
281
299
  datagrabber = _get_datagrabber(config["datagrabber"])
282
300
  with datagrabber as dg:
283
301
  elements = dg.get_elements()
284
-
285
- # TODO: Fix typing of elements
302
+ # Listify elements
286
303
  if not isinstance(elements, list):
287
- elements = [elements] # type: ignore
288
-
289
- typing.cast(List[Union[str, Tuple]], elements)
304
+ elements: List[Union[str, Tuple]] = [elements]
290
305
 
306
+ # Check job queueing system
307
+ adapter = None
291
308
  if kind == "HTCondor":
292
- _queue_condor(
293
- jobname=jobname,
294
- jobdir=jobdir,
295
- yaml_config=yaml_config,
296
- elements=elements, # type: ignore
297
- config=config,
298
- **kwargs,
309
+ adapter = HTCondorAdapter(
310
+ job_name=jobname,
311
+ job_dir=jobdir,
312
+ yaml_config_path=yaml_config,
313
+ elements=elements,
314
+ **kwargs, # type: ignore
299
315
  )
300
- elif kind == "SLURM":
301
- _queue_slurm(
302
- jobname=jobname,
303
- jobdir=jobdir,
304
- yaml_config=yaml_config,
305
- elements=elements, # type: ignore
306
- config=config,
307
- **kwargs,
316
+ elif kind == "GNUParallelLocal":
317
+ adapter = GnuParallelLocalAdapter(
318
+ job_name=jobname,
319
+ job_dir=jobdir,
320
+ yaml_config_path=yaml_config,
321
+ elements=elements,
322
+ **kwargs, # type: ignore
308
323
  )
309
- else:
310
- raise_error(f"Unknown queue kind: {kind}")
311
324
 
325
+ adapter.prepare() # type: ignore
312
326
  logger.info("Queue done")
313
327
 
314
328
 
315
- def _queue_condor(
316
- jobname: str,
317
- jobdir: Path,
318
- yaml_config: Path,
319
- elements: List[Union[str, Tuple]],
320
- config: Dict,
321
- env: Optional[Dict[str, str]] = None,
322
- mem: str = "8G",
323
- cpus: int = 1,
324
- disk: str = "1G",
325
- extra_preamble: str = "",
326
- pre_run: Optional[str] = None,
327
- verbose: str = "info",
328
- collect: str = "yes",
329
- submit: bool = False,
330
- ) -> None:
331
- """Submit job to HTCondor.
329
+ def reset(config: Dict) -> None:
330
+ """Reset the storage and jobs directory.
332
331
 
333
332
  Parameters
334
333
  ----------
335
- jobname : str
336
- The name of the job.
337
- jobdir : pathlib.Path
338
- The path to the job directory.
339
- yaml_config : pathlib.Path
340
- The path to the YAML config file.
341
- elements : list of str or tuple
342
- Element(s) to process. Will be used to index the DataGrabber.
343
334
  config : dict
344
- The configuration to be used for queueing the job.
345
- env : dict, optional
346
- The environment variables passed as dictionary (default None).
347
- mem : str, optional
348
- The size of memory (RAM) to use (default "8G").
349
- cpus : int, optional
350
- The number of CPU cores to use (default 1).
351
- disk : str, optional
352
- The size of disk (HDD or SSD) to use (default "1G").
353
- extra_preamble : str, optional
354
- Extra commands to pass to HTCondor (default "").
355
- pre_run : str, optional
356
- Extra bash commands to source before the job (default None).
357
- verbose : str, optional
358
- The level of verbosity (default "info").
359
- collect : str, optional
360
- Whether to submit "collect" task for junifer (default "yes").
361
- Valid options are:
362
-
363
- * "yes": Submit "collect" task and run even if some of the jobs
364
- fail.
365
- * "on_success_only": Submit "collect" task and run only if all jobs
366
- succeed.
367
- * "no": Do not submit "collect" task.
368
-
369
- submit : bool, optional
370
- Whether to submit the jobs. In any case, .dag files will be created
371
- for submission (default False).
372
-
373
- Raises
374
- ------
375
- ValueError
376
- If the value of `env` is invalid.
335
+ The configuration to be used for resetting.
377
336
 
378
337
  """
379
- logger.debug("Creating HTCondor job")
380
- run_junifer_args = (
381
- f"run {yaml_config.absolute()!s} "
382
- f"--verbose {verbose} --element $(element)"
383
- )
384
- collect_junifer_args = (
385
- f"collect {yaml_config.absolute()!s} --verbose {verbose} "
386
- )
387
-
388
- if not isinstance(collect, str):
389
- raise_error("collect must be a string")
390
-
391
- collect = collect.lower()
392
- if collect not in ["yes", "no", "on_success_only"]:
393
- raise_error(f"Invalid value for collect: {collect}")
394
-
395
- # Set up the env_name, executable and arguments according to the
396
- # environment type
397
- if env is None:
398
- env = {"kind": "local"}
399
- if env["kind"] == "conda":
400
- env_name = env["name"]
401
- executable = "run_conda.sh"
402
- arguments = f"{env_name} junifer"
403
- exec_path = jobdir / executable
404
- logger.info(f"Copying {executable} to {exec_path.absolute()!s}")
405
- shutil.copy(Path(__file__).parent / "res" / executable, exec_path)
406
- make_executable(exec_path)
407
- elif env["kind"] == "venv":
408
- env_name = env["name"]
409
- executable = "run_venv.sh"
410
- arguments = f"{env_name} junifer"
411
- # TODO: Copy run_venv.sh to jobdir
412
- elif env["kind"] == "local":
413
- executable = "junifer"
414
- arguments = ""
415
- else:
416
- raise ValueError(f'Unknown env kind: {env["kind"]}')
417
-
418
- logger.info("Writing pre_run.sh to jobdir")
419
- pre_run_fname = jobdir / "pre_run.sh"
420
- with open(pre_run_fname, "w") as f:
421
- f.write("#!/bin/bash\n\n")
422
- f.write("# Force datalad to run in non-interactive mode\n")
423
- f.write("DATALAD_UI_INTERACTIVE=false\n\n")
424
- if pre_run is not None:
425
- f.write(pre_run)
426
- make_executable(pre_run_fname)
427
-
428
- # Create log directory
429
- log_dir = jobdir / "logs"
430
- log_dir.mkdir(exist_ok=True, parents=True)
431
-
432
- # Add preamble data
433
- run_preamble = f"""
434
- # The environment
435
- universe = vanilla
436
- getenv = True
437
-
438
- # Resources
439
- request_cpus = {cpus}
440
- request_memory = {mem}
441
- request_disk = {disk}
442
-
443
- # Executable
444
- initial_dir = {jobdir.absolute()!s}
445
- executable = $(initial_dir)/{executable}
446
- transfer_executable = False
447
-
448
- arguments = {arguments} {run_junifer_args}
449
-
450
- {extra_preamble}
451
-
452
- # Logs
453
- log = {log_dir.absolute()!s}/junifer_run_$(log_element).log
454
- output = {log_dir.absolute()!s}/junifer_run_$(log_element).out
455
- error = {log_dir.absolute()!s}/junifer_run_$(log_element).err
456
- """
457
-
458
- submit_run_fname = jobdir / f"run_{jobname}.submit"
459
- submit_collect_fname = jobdir / f"collect_{jobname}.submit"
460
- dag_fname = jobdir / f"{jobname}.dag"
461
-
462
- # Write to run submit files
463
- with open(submit_run_fname, "w") as submit_file:
464
- submit_file.write(textwrap.dedent(run_preamble))
465
- submit_file.write("queue\n")
466
-
467
- collect_preamble = f"""
468
- # The environment
469
- universe = vanilla
470
- getenv = True
471
-
472
- # Resources
473
- request_cpus = {cpus}
474
- request_memory = {mem}
475
- request_disk = {disk}
476
-
477
- # Executable
478
- initial_dir = {jobdir.absolute()!s}
479
- executable = $(initial_dir)/{executable}
480
- transfer_executable = False
481
-
482
- arguments = {arguments} {collect_junifer_args}
483
-
484
- {extra_preamble}
485
-
486
- # Logs
487
- log = {log_dir.absolute()!s}/junifer_collect.log
488
- output = {log_dir.absolute()!s}/junifer_collect.out
489
- error = {log_dir.absolute()!s}/junifer_collect.err
490
- """
491
-
492
- # Now create the collect submit file
493
- with open(submit_collect_fname, "w") as submit_file:
494
- submit_file.write(textwrap.dedent(collect_preamble))
495
- submit_file.write("queue\n")
496
-
497
- with open(dag_fname, "w") as dag_file:
498
- # Get all subject and session names from file list
499
- for i_job, t_elem in enumerate(elements):
500
- str_elem = (
501
- ",".join(t_elem) if isinstance(t_elem, tuple) else t_elem
502
- )
503
- log_elem = (
504
- "_".join(t_elem) if isinstance(t_elem, tuple) else t_elem
505
- )
506
- dag_file.write(f"JOB run{i_job} {submit_run_fname}\n")
507
- dag_file.write(
508
- f'VARS run{i_job} element="{str_elem}" '
509
- f'log_element="{log_elem}"\n\n'
510
- )
511
- if collect == "yes":
512
- dag_file.write(f"FINAL collect {submit_collect_fname}\n")
513
- collect_pre_fname = jobdir / "collect_pre.sh"
514
- dag_file.write(
515
- f"SCRIPT PRE collect {collect_pre_fname.as_posix()} "
516
- "$DAG_STATUS\n"
517
- )
518
- with open(collect_pre_fname, "w") as pre_file:
519
- pre_file.write("#!/bin/bash\n\n")
520
- pre_file.write('if [ "${1}" == "4" ]; then\n')
521
- pre_file.write(" exit 1\n")
522
- pre_file.write("fi\n")
523
-
524
- make_executable(collect_pre_fname)
525
- elif collect == "on_success_only":
526
- dag_file.write(f"JOB collect {submit_collect_fname}\n")
527
- dag_file.write("PARENT ")
528
- for i_job, _ in enumerate(elements):
529
- dag_file.write(f"run{i_job} ")
530
- dag_file.write("CHILD collect\n\n")
531
-
532
- # Submit job(s)
533
- if submit is True:
534
- logger.info("Submitting HTCondor job")
535
- subprocess.run(["condor_submit_dag", dag_fname])
536
- logger.info("HTCondor job submitted")
537
- else:
538
- cmd = f"condor_submit_dag {dag_fname.absolute()!s}"
539
- logger.info(
540
- f"HTCondor job files created, to submit the job, run `{cmd}`"
338
+ # Fetch storage
339
+ storage = config["storage"]
340
+ storage_uri = Path(storage["uri"])
341
+ logger.info(f"Deleting {storage_uri.resolve()!s}")
342
+ # Delete storage; will be str
343
+ if storage_uri.exists():
344
+ # Delete files in the directory
345
+ for file in storage_uri.iterdir():
346
+ file.unlink(missing_ok=True)
347
+ # Remove directory
348
+ storage_uri.parent.rmdir()
349
+
350
+ # Fetch job name (if present)
351
+ if config.get("queue") is not None:
352
+ queue = config["queue"]
353
+ job_dir = (
354
+ Path.cwd()
355
+ / "junifer_jobs"
356
+ / (queue.get("jobname") or "junifer_job")
541
357
  )
542
-
543
-
544
- def _queue_slurm(
545
- jobname: str,
546
- jobdir: Path,
547
- yaml_config: Path,
548
- elements: List[Union[str, Tuple]],
549
- config: Dict,
550
- ) -> None:
551
- """Submit job to SLURM.
552
-
553
- Parameters
554
- ----------
555
- jobname : str
556
- The name of the job.
557
- jobdir : pathlib.Path
558
- The path to the job directory.
559
- yaml_config : pathlib.Path
560
- The path to the YAML config file.
561
- elements : str or tuple or list[str or tuple], optional
562
- Element(s) to process. Will be used to index the DataGrabber
563
- (default None).
564
- config : dict
565
- The configuration to be used for queueing the job.
566
- """
567
- pass
568
- # logger.debug("Creating SLURM job")
569
- # run_junifer_args = (
570
- # f"run {str(yaml_config.absolute())} "
571
- # f"--verbose {verbose} --element $(element)"
572
- # )
573
- # collect_junifer_args = \
574
- # f"collect {str(yaml_config.absolute())} --verbose {verbose} "
575
-
576
- # # Set up the env_name, executable and arguments according to the
577
- # # environment type
578
- # if env is None:
579
- # env = {
580
- # "kind": "local",
581
- # }
582
- # if env["kind"] == "conda":
583
- # env_name = env["name"]
584
- # executable = "run_conda.sh"
585
- # arguments = f"{env_name} junifer"
586
- # # TODO: Copy run_conda.sh to jobdir
587
- # exec_path = jobdir / executable
588
- # shutil.copy(Path(__file__).parent / "res" / executable, exec_path)
589
- # make_executable(exec_path)
590
- # elif env["kind"] == "venv":
591
- # env_name = env["name"]
592
- # executable = "run_venv.sh"
593
- # arguments = f"{env_name} junifer"
594
- # # TODO: Copy run_venv.sh to jobdir
595
- # elif env["kind"] == "local":
596
- # executable = "junifer"
597
- # arguments = ""
598
- # else:
599
- # raise ValueError(f"Unknown env kind: {env['kind']}")
600
-
601
- # # Create log directory
602
- # log_dir = jobdir / 'logs'
603
- # log_dir.mkdir(exist_ok=True, parents=True)
604
-
605
- # # Add preamble data
606
- # run_preamble = f"""
607
- # #!/bin/bash
608
-
609
- # #SBATCH --job-name={}
610
- # #SBATCH --account={}
611
- # #SBATCH --partition={}
612
- # #SBATCH --time={}
613
- # #SBATCH --ntasks={}
614
- # #SBATCH --cpus-per-task={cpus}
615
- # #SBATCH --mem-per-cpu={mem}
616
- # #SBATCH --mail-type={}
617
- # #SBATCH --mail-user={}
618
- # #SBATCH --output={}
619
- # #SBATCH --error={}
620
-
621
- # # Executable
622
- # initial_dir = {str(jobdir.absolute())}
623
- # executable = $(initial_dir)/{executable}
624
- # transfer_executable = False
625
-
626
- # arguments = {arguments} {run_junifer_args}
627
-
628
- # {extra_preamble}
629
-
630
- # # Logs
631
- # log = {str(log_dir.absolute())}/junifer_run_$(element).log
632
- # output = {str(log_dir.absolute())}/junifer_run_$(element).out
633
- # error = {str(log_dir.absolute())}/junifer_run_$(element).err
634
- # """
635
-
636
- # submit_run_fname = jobdir / f'run_{jobname}.sh'
637
- # submit_collect_fname = jobdir / f'collect_{jobname}.sh'
638
-
639
- # # Write to run submit files
640
- # with open(submit_run_fname, 'w') as submit_file:
641
- # submit_file.write(run_preamble)
642
- # submit_file.write('queue\n')
643
-
644
- # collect_preamble = f"""
645
- # # The environment
646
- # universe = vanilla
647
- # getenv = True
648
-
649
- # # Resources
650
- # request_cpus = {cpus}
651
- # request_memory = {mem}
652
- # request_disk = {disk}
653
-
654
- # # Executable
655
- # initial_dir = {str(jobdir.absolute())}
656
- # executable = $(initial_dir)/{executable}
657
- # transfer_executable = False
658
-
659
- # arguments = {arguments} {collect_junifer_args}
660
-
661
- # {extra_preamble}
662
-
663
- # # Logs
664
- # log = {str(log_dir.absolute())}/junifer_collect.log
665
- # output = {str(log_dir.absolute())}/junifer_collect.out
666
- # error = {str(log_dir.absolute())}/junifer_collect.err
667
- # """
668
-
669
- # # Now create the collect submit file
670
- # with open(submit_collect_fname, 'w') as submit_file:
671
- # submit_file.write(collect_preamble) # Eval preamble here
672
- # submit_file.write('queue\n')
673
-
674
- # with open(dag_fname, 'w') as dag_file:
675
- # # Get all subject and session names from file list
676
- # for i_job, t_elem in enumerate(elements):
677
- # dag_file.write(f'JOB run{i_job} {submit_run_fname}\n')
678
- # dag_file.write(f'VARS run{i_job} element="{t_elem}"\n\n')
679
- # if collect is True:
680
- # dag_file.write(f'JOB collect {submit_collect_fname}\n')
681
- # dag_file.write('PARENT ')
682
- # for i_job, _t_elem in enumerate(elements):
683
- # dag_file.write(f'run{i_job} ')
684
- # dag_file.write('CHILD collect\n\n')
685
-
686
- # # Submit job(s)
687
- # if submit is True:
688
- # logger.info('Submitting SLURM job')
689
- # subprocess.run(['condor_submit_dag', dag_fname])
690
- # logger.info('HTCondor SLURM submitted')
691
- # else:
692
- # cmd = f"condor_submit_dag {str(dag_fname.absolute())}"
693
- # logger.info(
694
- # f"SLURM job files created, to submit the job, run `{cmd}`"
695
- # )
358
+ logger.info(f"Deleting job directory at {job_dir.resolve()!s}")
359
+ if job_dir.exists():
360
+ # Remove files and directories
361
+ shutil.rmtree(job_dir)
362
+ # Remove directory
363
+ job_dir.parent.rmdir()
junifer/api/parser.py CHANGED
@@ -50,6 +50,8 @@ def parse_yaml(filepath: Union[str, Path]) -> Dict:
50
50
  # Convert load modules to list
51
51
  if not isinstance(to_load, list):
52
52
  to_load = [to_load]
53
+ # Initialize list to have absolute paths for custom modules
54
+ final_to_load = []
53
55
  for t_module in to_load:
54
56
  if t_module.endswith(".py"):
55
57
  logger.debug(f"Importing file: {t_module}")
@@ -65,9 +67,18 @@ def parse_yaml(filepath: Union[str, Path]) -> Dict:
65
67
  module = importlib.util.module_from_spec(spec) # type: ignore
66
68
  sys.modules[t_module] = module
67
69
  spec.loader.exec_module(module) # type: ignore
70
+ # Add absolute path to final list
71
+ final_to_load.append(str(file_path.resolve()))
68
72
  else:
69
73
  logger.info(f"Importing module: {t_module}")
70
74
  importlib.import_module(t_module)
75
+ # Add module to final list
76
+ final_to_load.append(t_module)
77
+
78
+ # Replace modules to be loaded so that custom modules will take the
79
+ # absolute path. This was not the case as found in #224. Similar thing
80
+ # is done with the storage URI below.
81
+ contents["with"] = final_to_load
71
82
 
72
83
  # Compute path for the URI parameter in storage files that are relative
73
84
  # This is a tricky thing that appeared in #127. The problem is that
@@ -88,4 +99,17 @@ def parse_yaml(filepath: Union[str, Path]) -> Dict:
88
99
  contents["storage"]["uri"] = str(
89
100
  (filepath.parent / uri_path).resolve()
90
101
  )
102
+
103
+ # Allow relative path if queue env kind is venv; same motivation as above
104
+ if "queue" in contents:
105
+ if "env" in contents["queue"]:
106
+ if "venv" == contents["queue"]["env"]["kind"]:
107
+ # Check if the env name is relative
108
+ venv_path = Path(contents["queue"]["env"]["name"])
109
+ if not venv_path.is_absolute():
110
+ # Compute the absolute path
111
+ contents["queue"]["env"]["name"] = str(
112
+ (filepath.parent / venv_path).resolve()
113
+ )
114
+
91
115
  return contents
@@ -0,0 +1,8 @@
1
+ """Provide imports for queue context sub-package."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from .queue_context_adapter import QueueContextAdapter
7
+ from .htcondor_adapter import HTCondorAdapter
8
+ from .gnu_parallel_local_adapter import GnuParallelLocalAdapter