tomwer 1.2.0a1__py3-none-any.whl → 1.2.0a3__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 (219) hide show
  1. orangecontrib/tomwer/tutorials/append_raw_darks_and_flats_frames_to_NXtomos.ows +44 -0
  2. orangecontrib/tomwer/tutorials/copy_reduced_darks_and_flats_meth1.ows +55 -0
  3. orangecontrib/tomwer/tutorials/copy_reduced_darks_and_flats_meth2.ows +48 -0
  4. orangecontrib/tomwer/tutorials/default_cor_search.ows +40 -0
  5. orangecontrib/tomwer/tutorials/hello_world_python_script.ows +50 -0
  6. orangecontrib/tomwer/tutorials/simple_slice_reconstruction_on_slurm.ows +50 -0
  7. orangecontrib/tomwer/tutorials/simple_volume_to_slurm_reconstruction.ows +8 -8
  8. orangecontrib/tomwer/widgets/__init__.py +1 -1
  9. orangecontrib/tomwer/widgets/cluster/FutureSupervisorOW.py +0 -1
  10. orangecontrib/tomwer/widgets/cluster/SlurmClusterOW.py +8 -6
  11. orangecontrib/tomwer/widgets/control/AdvancementOW.py +0 -1
  12. orangecontrib/tomwer/widgets/control/DataDiscoveryOW.py +1 -6
  13. orangecontrib/tomwer/widgets/control/DataListOW.py +0 -1
  14. orangecontrib/tomwer/widgets/control/DataListenerOW.py +4 -4
  15. orangecontrib/tomwer/widgets/control/DataSelectorOW.py +0 -1
  16. orangecontrib/tomwer/widgets/control/DataTransfertOW.py +7 -7
  17. orangecontrib/tomwer/widgets/control/DataValidatorOW.py +0 -1
  18. orangecontrib/tomwer/widgets/control/DataWatcherOW.py +0 -3
  19. orangecontrib/tomwer/widgets/control/EDF2NXTomomillOW.py +3 -2
  20. orangecontrib/tomwer/widgets/control/EmailOW.py +82 -0
  21. orangecontrib/tomwer/widgets/control/FilterOW.py +3 -3
  22. orangecontrib/tomwer/widgets/control/NXTomomillOW.py +1 -1
  23. orangecontrib/tomwer/widgets/control/NotifierOW.py +0 -1
  24. orangecontrib/tomwer/widgets/control/ReduceDarkFlatSelectorOW.py +93 -0
  25. orangecontrib/tomwer/widgets/control/SingleTomoObjOW.py +29 -5
  26. orangecontrib/tomwer/widgets/control/TimerOW.py +1 -2
  27. orangecontrib/tomwer/widgets/control/TomoObjSerieOW.py +0 -1
  28. orangecontrib/tomwer/widgets/control/VolumeSelector.py +0 -1
  29. orangecontrib/tomwer/widgets/control/VolumeSymLinkOW.py +4 -10
  30. orangecontrib/tomwer/widgets/control/icons/email.png +0 -0
  31. orangecontrib/tomwer/widgets/control/icons/email.svg +58 -0
  32. orangecontrib/tomwer/widgets/control/icons/reduced_darkflat_selector.png +0 -0
  33. orangecontrib/tomwer/widgets/control/icons/reduced_darkflat_selector.svg +199 -0
  34. orangecontrib/tomwer/widgets/debugtools/DatasetGeneratorOW.py +0 -1
  35. orangecontrib/tomwer/widgets/debugtools/ObjectInspectorOW.py +0 -1
  36. orangecontrib/tomwer/widgets/edit/DarkFlatPatchOW.py +1 -2
  37. orangecontrib/tomwer/widgets/edit/ImageKeyEditorOW.py +1 -2
  38. orangecontrib/tomwer/widgets/edit/ImageKeyUpgraderOW.py +0 -1
  39. orangecontrib/tomwer/widgets/edit/NXtomoEditorOW.py +0 -1
  40. orangecontrib/tomwer/widgets/other/PythonScriptOW.py +29 -1
  41. orangecontrib/tomwer/widgets/other/TomoObjsHub.py +28 -0
  42. orangecontrib/tomwer/widgets/other/icons/hub.png +0 -0
  43. orangecontrib/tomwer/widgets/other/icons/hub.svg +113 -0
  44. orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +18 -12
  45. orangecontrib/tomwer/widgets/reconstruction/CastNabuVolumeOW.py +0 -2
  46. orangecontrib/tomwer/widgets/reconstruction/DarkRefAndCopyOW.py +21 -6
  47. orangecontrib/tomwer/widgets/reconstruction/NabuOW.py +29 -7
  48. orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +18 -5
  49. orangecontrib/tomwer/widgets/reconstruction/SAAxisOW.py +40 -13
  50. orangecontrib/tomwer/widgets/reconstruction/SADeltaBetaOW.py +37 -10
  51. orangecontrib/tomwer/widgets/reconstruction/SinoNormOW.py +2 -3
  52. orangecontrib/tomwer/widgets/reconstruction/TofuOW.py +5 -4
  53. orangecontrib/tomwer/widgets/stitching/StitcherOW.py +0 -1
  54. orangecontrib/tomwer/widgets/stitching/ZStitchingConfigOW.py +0 -1
  55. orangecontrib/tomwer/widgets/visualization/DataViewerOW.py +10 -4
  56. orangecontrib/tomwer/widgets/visualization/DiffViewerOW.py +1 -1
  57. orangecontrib/tomwer/widgets/visualization/LivesliceOW.py +0 -1
  58. orangecontrib/tomwer/widgets/visualization/NXtomoMetadataViewerOW.py +0 -1
  59. orangecontrib/tomwer/widgets/visualization/RadioStackOW.py +7 -5
  60. orangecontrib/tomwer/widgets/visualization/SampleMovedOW.py +1 -1
  61. orangecontrib/tomwer/widgets/visualization/SinogramViewerOW.py +0 -3
  62. orangecontrib/tomwer/widgets/visualization/SliceStackOW.py +7 -5
  63. orangecontrib/tomwer/widgets/visualization/VolumeViewerOW.py +4 -4
  64. tomwer/__main__.py +139 -5
  65. tomwer/app/axis.py +16 -5
  66. tomwer/app/canvas_launcher/config.py +1 -1
  67. tomwer/app/canvas_launcher/mainwindow.py +164 -6
  68. tomwer/app/darkref.py +10 -181
  69. tomwer/app/darkrefpatch.py +10 -131
  70. tomwer/app/diffframe.py +11 -0
  71. tomwer/app/imagekeyeditor.py +12 -19
  72. tomwer/app/intensitynormalization.py +1 -0
  73. tomwer/app/lamino.py +7 -2
  74. tomwer/app/patchrawdarkflat.py +131 -0
  75. tomwer/app/radiostack.py +10 -0
  76. tomwer/app/reducedarkflat.py +205 -0
  77. tomwer/app/saaxis.py +27 -8
  78. tomwer/app/sadeltabeta.py +29 -8
  79. tomwer/app/samplemoved.py +11 -0
  80. tomwer/app/scanviewer.py +12 -0
  81. tomwer/app/sinogramviewer.py +11 -0
  82. tomwer/app/slicestack.py +11 -0
  83. tomwer/app/zstitching.py +12 -0
  84. tomwer/core/futureobject.py +4 -2
  85. tomwer/core/process/conditions/filters.py +26 -4
  86. tomwer/core/process/control/datadiscovery.py +4 -0
  87. tomwer/core/process/control/datawatcher/datawatcher.py +5 -1
  88. tomwer/core/process/control/email.py +148 -0
  89. tomwer/core/process/control/nxtomoconcatenate.py +9 -2
  90. tomwer/core/process/control/nxtomomill.py +58 -16
  91. tomwer/core/process/control/scanselector.py +4 -0
  92. tomwer/core/process/control/scantransfer.py +52 -23
  93. tomwer/core/process/control/test/test_concatenate_nxtomos.py +1 -0
  94. tomwer/core/process/control/test/test_email.py +52 -0
  95. tomwer/core/process/control/test/test_h52nx_process.py +106 -0
  96. tomwer/core/process/control/test/test_volume_link.py +5 -4
  97. tomwer/core/process/control/timer.py +27 -6
  98. tomwer/core/process/control/tomoobjserie.py +4 -0
  99. tomwer/core/process/control/volumeselector.py +4 -0
  100. tomwer/core/process/control/volumesymlink.py +47 -8
  101. tomwer/core/process/edit/darkflatpatch.py +49 -8
  102. tomwer/core/process/edit/imagekeyeditor.py +63 -13
  103. tomwer/core/process/reconstruction/axis/__init__.py +1 -1
  104. tomwer/core/process/reconstruction/axis/axis.py +61 -41
  105. tomwer/core/process/reconstruction/axis/params.py +4 -6
  106. tomwer/core/process/reconstruction/darkref/darkrefs.py +53 -16
  107. tomwer/core/process/reconstruction/darkref/darkrefscopy.py +12 -2
  108. tomwer/core/process/reconstruction/lamino/__init__.py +1 -1
  109. tomwer/core/process/reconstruction/lamino/tofu.py +22 -2
  110. tomwer/core/process/reconstruction/nabu/nabucommon.py +93 -14
  111. tomwer/core/process/reconstruction/nabu/nabuscores.py +70 -33
  112. tomwer/core/process/reconstruction/nabu/nabuslices.py +219 -41
  113. tomwer/core/process/reconstruction/nabu/nabuvolume.py +240 -108
  114. tomwer/core/process/reconstruction/nabu/utils.py +10 -36
  115. tomwer/core/process/reconstruction/normalization/normalization.py +10 -3
  116. tomwer/core/process/reconstruction/saaxis/__init__.py +1 -0
  117. tomwer/core/process/reconstruction/saaxis/saaxis.py +564 -376
  118. tomwer/core/process/reconstruction/sadeltabeta/__init__.py +1 -0
  119. tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +481 -268
  120. tomwer/core/process/reconstruction/scores/params.py +21 -8
  121. tomwer/core/process/reconstruction/test/test_darkref_copy.py +2 -0
  122. tomwer/core/process/reconstruction/test/test_saaxis.py +21 -8
  123. tomwer/core/process/reconstruction/test/test_sadeltabeta.py +8 -5
  124. tomwer/core/process/script/python.py +7 -2
  125. tomwer/core/process/stitching/nabustitcher.py +10 -3
  126. tomwer/core/process/task.py +2 -9
  127. tomwer/core/process/test/test_axis.py +25 -15
  128. tomwer/core/process/test/test_conditions.py +6 -6
  129. tomwer/core/process/test/test_dark_and_flat.py +20 -15
  130. tomwer/core/process/test/test_data_transfer.py +8 -8
  131. tomwer/core/process/test/test_data_watcher.py +1 -1
  132. tomwer/core/process/test/test_lamino.py +6 -6
  133. tomwer/core/process/test/test_nabu.py +13 -8
  134. tomwer/core/process/test/test_normalization.py +1 -0
  135. tomwer/core/process/test/test_timer.py +6 -6
  136. tomwer/core/process/visualization/dataviewer.py +4 -0
  137. tomwer/core/process/visualization/diffviewer.py +4 -0
  138. tomwer/core/process/visualization/imagestackviewer.py +4 -0
  139. tomwer/core/process/visualization/radiostack.py +4 -0
  140. tomwer/core/process/visualization/samplemoved.py +4 -0
  141. tomwer/core/process/visualization/sinogramviewer.py +4 -0
  142. tomwer/core/process/visualization/slicestack.py +4 -0
  143. tomwer/core/process/visualization/volumeviewer.py +4 -0
  144. tomwer/core/scan/hdf5scan.py +4 -4
  145. tomwer/core/scan/scanbase.py +5 -1
  146. tomwer/core/scan/test/test_process_registration.py +9 -9
  147. tomwer/core/settings.py +59 -1
  148. tomwer/core/test/test_lamino.py +2 -1
  149. tomwer/core/utils/__init__.py +16 -0
  150. tomwer/core/utils/locker.py +0 -1
  151. tomwer/core/utils/resource.py +6 -11
  152. tomwer/core/utils/scanutils.py +2 -0
  153. tomwer/gui/cluster/slurm.py +91 -7
  154. tomwer/gui/cluster/supervisor.py +16 -11
  155. tomwer/gui/cluster/test/test_cluster.py +16 -1
  156. tomwer/gui/conditions/filter.py +3 -3
  157. tomwer/gui/control/datalist.py +24 -11
  158. tomwer/gui/control/email.py +183 -0
  159. tomwer/gui/control/reducedarkflatselector.py +545 -0
  160. tomwer/gui/control/singletomoobj.py +23 -1
  161. tomwer/gui/control/test/test_email.py +35 -0
  162. tomwer/gui/control/test/test_reducedarkflat_selector.py +280 -0
  163. tomwer/gui/reconstruction/axis/CompareImages.py +1 -1
  164. tomwer/gui/reconstruction/axis/axis.py +10 -6
  165. tomwer/gui/reconstruction/axis/radioaxis.py +14 -6
  166. tomwer/gui/reconstruction/darkref/darkrefcopywidget.py +2 -0
  167. tomwer/gui/reconstruction/darkref/darkrefwidget.py +4 -4
  168. tomwer/gui/reconstruction/nabu/nabuconfig/phase.py +3 -1
  169. tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +34 -33
  170. tomwer/gui/reconstruction/nabu/nabuconfig/reconstruction.py +1 -1
  171. tomwer/gui/reconstruction/normalization/intensity.py +5 -5
  172. tomwer/gui/reconstruction/saaxis/corrangeselector.py +1 -0
  173. tomwer/gui/reconstruction/saaxis/saaxis.py +6 -6
  174. tomwer/gui/reconstruction/sadeltabeta/saadeltabeta.py +6 -6
  175. tomwer/gui/reconstruction/scores/scoreplot.py +6 -4
  176. tomwer/gui/samplemoved/__init__.py +2 -2
  177. tomwer/gui/stackplot.py +18 -7
  178. tomwer/gui/stacks.py +2 -2
  179. tomwer/gui/stitching/stitchandbackground.py +2 -2
  180. tomwer/gui/stitching/stitching.py +1 -1
  181. tomwer/gui/stitching/stitching_raw.py +1 -1
  182. tomwer/gui/utils/__init__.py +1 -85
  183. tomwer/gui/utils/illustrations.py +1 -1
  184. tomwer/gui/utils/inputwidget.py +41 -36
  185. tomwer/gui/utils/slider.py +2 -2
  186. tomwer/gui/utils/utils.py +93 -0
  187. tomwer/gui/visualization/dataviewer.py +8 -5
  188. tomwer/gui/visualization/diffviewer/diffviewer.py +4 -4
  189. tomwer/gui/visualization/reconstructionparameters.py +26 -6
  190. tomwer/gui/visualization/sinogramviewer.py +7 -1
  191. tomwer/gui/visualization/test/test_reconstruction_parameters.py +2 -4
  192. tomwer/gui/visualization/volumeviewer.py +2 -0
  193. tomwer/resources/__init__.py +55 -43
  194. tomwer/resources/gui/icons/compose.png +0 -0
  195. tomwer/resources/gui/icons/compose.svg +75 -0
  196. tomwer/synctools/datatransfert.py +3 -1
  197. tomwer/synctools/stacks/edit/darkflatpatch.py +39 -34
  198. tomwer/synctools/stacks/edit/imagekeyeditor.py +8 -27
  199. tomwer/synctools/stacks/processingstack.py +45 -9
  200. tomwer/synctools/stacks/reconstruction/axis.py +6 -5
  201. tomwer/synctools/stacks/reconstruction/dkrefcopy.py +1 -0
  202. tomwer/synctools/stacks/reconstruction/lamino.py +3 -3
  203. tomwer/synctools/stacks/reconstruction/nabu.py +49 -140
  204. tomwer/synctools/stacks/reconstruction/normalization.py +1 -0
  205. tomwer/synctools/stacks/reconstruction/saaxis.py +19 -33
  206. tomwer/synctools/stacks/reconstruction/sadeltabeta.py +16 -32
  207. tomwer/synctools/test/test_darkRefs.py +19 -10
  208. tomwer/synctools/test/test_foldertransfer.py +7 -7
  209. tomwer/third_party/nabu/preproc/phase.py +6 -8
  210. tomwer/third_party/nabu/utils.py +2 -3
  211. tomwer/version.py +1 -1
  212. {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/METADATA +15 -54
  213. {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/RECORD +219 -192
  214. {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/WHEEL +1 -1
  215. {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/entry_points.txt +3 -3
  216. /tomwer-1.2.0a1-py3.9-nspkg.pth → /tomwer-1.2.0a3-py3.11-nspkg.pth +0 -0
  217. {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/LICENSE +0 -0
  218. {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/namespace_packages.txt +0 -0
  219. {tomwer-1.2.0a1.dist-info → tomwer-1.2.0a3.dist-info}/top_level.txt +0 -0
@@ -48,10 +48,8 @@ from .anglemode import CorAngleMode
48
48
  from .mode import AxisMode
49
49
  from .projectiontype import ProjectionType
50
50
 
51
- try:
52
- from nabu.preproc.phase import PaganinPhaseRetrieval
53
- except ImportError:
54
- from tomwer.third_party.nabu.preproc.phase import PaganinPhaseRetrieval
51
+ from nabu.preproc.phase import PaganinPhaseRetrieval
52
+
55
53
 
56
54
  _logger = logging.getLogger(__name__)
57
55
 
@@ -734,10 +732,10 @@ class AxisRP:
734
732
  :return: center of rotation or none
735
733
  :rtype: Union[None, float]
736
734
  """
737
- from .axis import AxisProcess # avoid cyclic import
735
+ from .axis import AxisTask # avoid cyclic import
738
736
 
739
737
  processes = Task.get_processes_frm_type(
740
- process_file=file_path, process_type=AxisProcess, entry=entry
738
+ process_file=file_path, process_type=AxisTask, entry=entry
741
739
  )
742
740
  if len(processes) == 0:
743
741
  _logger.warning("failed to find any information on center " "of rotation")
@@ -39,6 +39,8 @@ from processview.core.manager import DatasetState, ProcessManager
39
39
  from processview.core.superviseprocess import SuperviseProcess
40
40
  from silx.io.url import DataUrl
41
41
  from silx.io.utils import h5py_read_dataset
42
+ from silx.utils.deprecation import deprecated_warning
43
+
42
44
  from tomoscan.framereducerbase import REDUCER_TARGET, ReduceMethod
43
45
  from tomoscan.io import HDF5File
44
46
 
@@ -56,8 +58,13 @@ from . import params as dkrf_reconsparams
56
58
  logger = logging.getLogger(__name__)
57
59
 
58
60
 
59
- class DarkRefs(
60
- Task, SuperviseProcess, Queue, input_names=("data",), output_names=("data",)
61
+ class DarkRefsTask(
62
+ Task,
63
+ SuperviseProcess,
64
+ Queue, # TODO: fixme: this does not make much sense for a task
65
+ input_names=("data",),
66
+ output_names=("data",),
67
+ optional_input_names=("serialize_output_data",),
61
68
  ):
62
69
  """Compute median/mean dark and ref from originals (dark and ref files)"""
63
70
 
@@ -236,7 +243,9 @@ class DarkRefs(
236
243
  """
237
244
  if entry is None:
238
245
  with HDF5File(process_file, "r", swmr=True) as h5f:
239
- entries = DarkRefs._get_process_nodes(root_node=h5f, process=DarkRefs)
246
+ entries = DarkRefsTask._get_process_nodes(
247
+ root_node=h5f, process=DarkRefsTask
248
+ )
240
249
  if len(entries) == 0:
241
250
  logger.info("unable to find a DarkRef process in %s" % process_file)
242
251
  return None
@@ -250,8 +259,8 @@ class DarkRefs(
250
259
  if entry not in h5f.keys():
251
260
  logger.info(f"no dark found for {entry}")
252
261
  return {}
253
- dark_nodes = DarkRefs._get_process_nodes(
254
- root_node=h5f[entry], process=DarkRefs
262
+ dark_nodes = DarkRefsTask._get_process_nodes(
263
+ root_node=h5f[entry], process=DarkRefsTask
255
264
  )
256
265
  index_to_path = {}
257
266
  for key, index in dark_nodes.items():
@@ -298,7 +307,9 @@ class DarkRefs(
298
307
  """
299
308
  if entry is None:
300
309
  with HDF5File(process_file, "r", swmr=True) as h5f:
301
- entries = DarkRefs._get_process_nodes(root_node=h5f, process=DarkRefs)
310
+ entries = DarkRefsTask._get_process_nodes(
311
+ root_node=h5f, process=DarkRefsTask
312
+ )
302
313
  if len(entries) == 0:
303
314
  logger.info("unable to find a DarkRef process in %s" % process_file)
304
315
  return None
@@ -312,8 +323,8 @@ class DarkRefs(
312
323
  if entry not in h5f.keys():
313
324
  logger.info(f"no flats found for {entry}")
314
325
  return {}
315
- dkref_nodes = DarkRefs._get_process_nodes(
316
- root_node=h5f[entry], process=DarkRefs
326
+ dkref_nodes = DarkRefsTask._get_process_nodes(
327
+ root_node=h5f[entry], process=DarkRefsTask
317
328
  )
318
329
  index_to_path = {}
319
330
  for key, index in dkref_nodes.items():
@@ -407,7 +418,7 @@ class DarkRefs(
407
418
 
408
419
  self.outputs.data = None
409
420
  return
410
- whats = (DarkRefs.WHAT_REF, DarkRefs.WHAT_DARK)
421
+ whats = (DarkRefsTask.WHAT_REF, DarkRefsTask.WHAT_DARK)
411
422
  overwrites = (
412
423
  self.recons_params.overwrite_flat,
413
424
  self.recons_params.overwrite_dark,
@@ -451,7 +462,7 @@ class DarkRefs(
451
462
  interpretations["/".join(("darks", str(index)))] = "image"
452
463
  results["darks"] = f_darks
453
464
 
454
- scan.save_reduced_darks(f_darks)
465
+ scan.save_reduced_darks(f_darks, overwrite=True)
455
466
  if (
456
467
  self.recons_params.flat_calc_method
457
468
  is not dkrf_reconsparams.ReduceMethod.NONE
@@ -465,7 +476,7 @@ class DarkRefs(
465
476
  interpretations["/".join(("flats", str(index)))] = "image"
466
477
  results["flats"] = f_flats
467
478
 
468
- scan.save_reduced_flats(f_flats)
479
+ scan.save_reduced_flats(f_flats, overwrite=True)
469
480
 
470
481
  if len(results) > 0:
471
482
  # if some processing to be registered
@@ -490,7 +501,7 @@ class DarkRefs(
490
501
  dataset=scan, state=DatasetState.SUCCEED, details=None
491
502
  )
492
503
 
493
- if self._return_dict:
504
+ if self.get_input_value("serialize_output_data", True):
494
505
  self.outputs.data = scan.to_dict()
495
506
  else:
496
507
  self.outputs.data = scan
@@ -519,7 +530,9 @@ class DarkRefs(
519
530
  )
520
531
  scan.set_reduced_darks(darks=reduced_darks, darks_infos=metadata)
521
532
  try:
522
- scan.save_reduced_darks(darks=reduced_darks, darks_infos=metadata)
533
+ scan.save_reduced_darks(
534
+ darks=reduced_darks, darks_infos=metadata, overwrite=True
535
+ )
523
536
  except Exception as e:
524
537
  logger.error(f"Fail to save reduced darks. Error is {e}")
525
538
  elif target is REDUCER_TARGET.FLATS:
@@ -530,7 +543,9 @@ class DarkRefs(
530
543
  )
531
544
  scan.set_reduced_flats(flats=reduced_flats, flats_infos=metadata)
532
545
  try:
533
- scan.save_reduced_flats(flats=reduced_flats, flats_infos=metadata)
546
+ scan.save_reduced_flats(
547
+ flats=reduced_flats, flats_infos=metadata, overwrite=True
548
+ )
534
549
  except Exception as e:
535
550
  logger.error(f"Fail to save reduced flats. Error is {e}")
536
551
  else:
@@ -572,10 +587,11 @@ def requires_reduced_dark_and_flat(scan: TomwerScanBase, logger_=None) -> tuple:
572
587
  recons_params.dark_calc_method = dkrf_reconsparams.ReduceMethod.NONE
573
588
  recons_params.flat_calc_method = dkrf_reconsparams.ReduceMethod.FIRST
574
589
 
575
- drp = DarkRefs(
590
+ drp = DarkRefsTask(
576
591
  inputs={
577
592
  "data": scan,
578
593
  "dark_ref_params": recons_params,
594
+ "serialize_output_data": False,
579
595
  }
580
596
  )
581
597
  if logger_ is not None:
@@ -593,10 +609,11 @@ def requires_reduced_dark_and_flat(scan: TomwerScanBase, logger_=None) -> tuple:
593
609
  recons_params.dark_calc_method = dkrf_reconsparams.ReduceMethod.FIRST
594
610
  recons_params.flat_calc_method = dkrf_reconsparams.ReduceMethod.NONE
595
611
 
596
- drp = DarkRefs(
612
+ drp = DarkRefsTask(
597
613
  inputs={
598
614
  "data": scan,
599
615
  "dark_ref_params": recons_params,
616
+ "serialize_output_data": False,
600
617
  }
601
618
  )
602
619
  if logger_ is not None:
@@ -607,3 +624,23 @@ def requires_reduced_dark_and_flat(scan: TomwerScanBase, logger_=None) -> tuple:
607
624
  computed.append("dark")
608
625
 
609
626
  return tuple(computed)
627
+
628
+
629
+ class DarkRefs(DarkRefsTask):
630
+ def __init__(
631
+ self,
632
+ process_id=None,
633
+ varinfo=None,
634
+ inputs=None,
635
+ node_id=None,
636
+ node_attrs=None,
637
+ execinfo=None,
638
+ ):
639
+ deprecated_warning(
640
+ name="tomwer.core.process.reconstruction.darkref.darkref.DarkRefs",
641
+ type_="class",
642
+ reason="improve readibility",
643
+ since_version="1.2",
644
+ replacement="DarkRefsTask",
645
+ )
646
+ super().__init__(process_id, varinfo, inputs, node_id, node_attrs, execinfo)
@@ -47,14 +47,14 @@ from tomoscan.esrf.scan.utils import (
47
47
  from tomoscan.framereducerbase import REDUCER_TARGET
48
48
  from tomoscan.io import HDF5File
49
49
 
50
- from tomwer.core.process.reconstruction.darkref.darkrefs import DarkRefs
50
+ from tomwer.core.process.reconstruction.darkref.darkrefs import DarkRefsTask
51
51
  from tomwer.core.scan.scanbase import TomwerScanBase
52
52
  from tomwer.core.utils.scanutils import data_identifier_to_scan
53
53
 
54
54
  logger = logging.getLogger(__name__)
55
55
 
56
56
 
57
- class DarkRefsCopy(DarkRefs):
57
+ class DarkRefsCopy(DarkRefsTask):
58
58
  """
59
59
  Reimplement Dark ref to deal with copy when there is no median/mean files
60
60
  """
@@ -166,6 +166,10 @@ class DarkRefsCopy(DarkRefs):
166
166
  if data is None:
167
167
  return
168
168
  flat_url = DarkRefsCopy.get_flats_url(save_dir)
169
+ # remove group if already exists: else can end up by copying several dark / flat from different datasets
170
+ with HDF5File(flat_url.file_path(), mode="a") as h5f:
171
+ if flat_url.data_path() in h5f:
172
+ del h5f[flat_url.data_path()]
169
173
  dicttoh5(
170
174
  data,
171
175
  h5file=flat_url.file_path(),
@@ -181,6 +185,10 @@ class DarkRefsCopy(DarkRefs):
181
185
  if data is None:
182
186
  return
183
187
  dark_url = DarkRefsCopy.get_darks_url(save_dir)
188
+ # remove group if already exists: else can end up by copying several dark / flat from different datasets
189
+ with HDF5File(dark_url.file_path(), mode="a") as h5f:
190
+ if dark_url.data_path() in h5f:
191
+ del h5f[dark_url.data_path()]
184
192
  dicttoh5(
185
193
  data,
186
194
  h5file=dark_url.file_path(),
@@ -227,6 +235,7 @@ class DarkRefsCopy(DarkRefs):
227
235
  darks_url=self._darks_url,
228
236
  save=True,
229
237
  raise_error_if_url_empty=True,
238
+ overwrite=True,
230
239
  )
231
240
  if self.has_flat_stored():
232
241
  copy_h5_dict_flats_to(
@@ -234,6 +243,7 @@ class DarkRefsCopy(DarkRefs):
234
243
  flats_url=self._flats_url,
235
244
  save=True,
236
245
  raise_error_if_url_empty=True,
246
+ overwrite=True,
237
247
  )
238
248
 
239
249
  def clean_save_files(self):
@@ -1 +1 @@
1
- from .tofu import LaminoReconstruction # noqa F401
1
+ from .tofu import LaminoReconstructionTask # noqa F401
@@ -49,6 +49,7 @@ from tomwer.core.scan.scanbase import TomwerScanBase
49
49
  from tomwer.core.scan.scanfactory import ScanFactory
50
50
  from tomwer.core.utils import getDim1Dim2
51
51
  from tomwer.core.utils.char import PSI_CHAR
52
+ from silx.utils.deprecation import deprecated_warning
52
53
 
53
54
  logger = logging.getLogger(__name__)
54
55
 
@@ -462,7 +463,12 @@ def has_tofu():
462
463
  return False
463
464
 
464
465
 
465
- class LaminoReconstruction(Task, input_names=("data",), output_names=("data",)):
466
+ class LaminoReconstructionTask(
467
+ Task,
468
+ input_names=("data",),
469
+ optional_input_names=("serialize_output_data",),
470
+ output_names=("data",),
471
+ ):
466
472
  """
467
473
  Process to launch the lamino reconstruction
468
474
 
@@ -879,7 +885,7 @@ class LaminoReconstruction(Task, input_names=("data",), output_names=("data",)):
879
885
 
880
886
  if res is False:
881
887
  logger.error(f"Reconstruction of {_scan.path} failed")
882
- if self._return_dict:
888
+ if self.get_input_value("serialize_output_data", True):
883
889
  self.outputs.data = _scan.to_dict()
884
890
  else:
885
891
  self.outputs.data = _scan
@@ -978,3 +984,17 @@ def getFlats(scan):
978
984
  else:
979
985
  logger.warning("Found more than two refHST files")
980
986
  return (os.path.join(scan, refHSTFiles[0]), os.path.join(scan, refHSTFiles[-1]))
987
+
988
+
989
+ class LaminoReconstruction(LaminoReconstructionTask):
990
+ def __init__(
991
+ self, varinfo=None, inputs=None, node_id=None, node_attrs=None, execinfo=None
992
+ ):
993
+ deprecated_warning(
994
+ name="tomwer.core.process.reconstruction.lamino.tofu.LaminoReconstruction",
995
+ type_="class",
996
+ reason="improve readibility",
997
+ since_version="1.2",
998
+ replacement="LaminoReconstructionTask",
999
+ )
1000
+ super().__init__(varinfo, inputs, node_id, node_attrs, execinfo)
@@ -37,6 +37,8 @@ import os
37
37
  import subprocess
38
38
  import uuid
39
39
  from typing import Iterable, Optional, Union
40
+ import signal
41
+ import psutil
40
42
 
41
43
  import numpy
42
44
  from silx.io.url import DataUrl
@@ -234,6 +236,9 @@ class _NabuBaseReconstructor:
234
236
  self._target = Target.from_value(target)
235
237
  self._dry_run = dry_run
236
238
  self._process_name = process_name
239
+ self._process = None
240
+ self._cancelled = False
241
+ # nabu subprocess if run locally
237
242
  if isinstance(cluster_config, SlurmClusterConfiguration):
238
243
  self._cluster_config = cluster_config
239
244
  elif isinstance(cluster_config, dict):
@@ -322,6 +327,18 @@ class _NabuBaseReconstructor:
322
327
  else:
323
328
  raise ValueError(f"{self.target} is not recognized as a valid target")
324
329
 
330
+ @staticmethod
331
+ def _get_gpu_and_cpu_mem_fraction(config_to_dump: dict):
332
+ gpu_mem_fraction = config_to_dump.get("resources", {}).get(
333
+ "gpu_mem_fraction", config_to_dump.get("gpu_mem_fraction", 0.9)
334
+ )
335
+ assert gpu_mem_fraction <= 1
336
+ cpu_mem_fraction = config_to_dump.get("resources", {}).get(
337
+ "cpu_mem_fraction", config_to_dump.get("cpu_mem_fraction", 0.9)
338
+ )
339
+ assert cpu_mem_fraction <= 1
340
+ return gpu_mem_fraction, cpu_mem_fraction
341
+
325
342
  def _run_nabu_locally(
326
343
  self,
327
344
  conf_file: str,
@@ -341,18 +358,37 @@ class _NabuBaseReconstructor:
341
358
  """
342
359
  if not has_nabu:
343
360
  raise ImportError("Fail to import nabu")
361
+ assert isinstance(config_to_dump, dict)
362
+ gpu_mem_fraction, cpu_mem_fraction = self._get_gpu_and_cpu_mem_fraction(
363
+ config_to_dump
364
+ )
365
+
344
366
  command = " ".join(
345
- ("python3", "-m", settings.NABU_FULL_FIELD_APP_PATH, conf_file)
367
+ (
368
+ "python3",
369
+ "-m",
370
+ settings.NABU_FULL_FIELD_APP_PATH,
371
+ conf_file,
372
+ "--gpu_mem_fraction",
373
+ str(gpu_mem_fraction),
374
+ "--cpu_mem_fraction",
375
+ str(cpu_mem_fraction),
376
+ )
346
377
  )
347
378
  _logger.info(f'call nabu from "{command}"')
348
- process = subprocess.Popen(
379
+
380
+ self._process = subprocess.Popen(
349
381
  command,
350
382
  shell=True,
351
383
  cwd=self.scan.path,
352
384
  stdout=subprocess.PIPE,
353
385
  stderr=subprocess.PIPE,
354
386
  )
355
- outs, errs = process.communicate()
387
+ try:
388
+ outs, errs = self._process.communicate()
389
+ except (TimeoutError, KeyboardInterrupt):
390
+ self._process.kill()
391
+ outs, errs = self._process.communicate()
356
392
 
357
393
  recons_urls = utils.get_recons_volume_identifier(
358
394
  file_prefix=config_to_dump["output"]["file_prefix"],
@@ -412,13 +448,21 @@ class _NabuBaseReconstructor:
412
448
  project_name = project_name.replace(" ", "_")
413
449
  cluster_config["job_name"] = project_name
414
450
 
451
+ # extract gpu_mem_fraction and cpu_mem_fraction
452
+ assert isinstance(config_to_dump, dict)
453
+ gpu_mem_fraction, cpu_mem_fraction = self._get_gpu_and_cpu_mem_fraction(
454
+ config_to_dump
455
+ )
456
+
415
457
  # submit job
416
458
  script_name = get_slurm_script_name(prefix="nabu")
417
459
  # for now force job name
418
460
  cluster_config["job_name"] = f"tomwer-nabu {conf_file}"
419
461
  job = SBatchScriptJob(
420
462
  slurm_config=cluster_config,
421
- script=(f"python3 -m {settings.NABU_FULL_FIELD_APP_PATH} {conf_file}",),
463
+ script=(
464
+ f"python3 -m {settings.NABU_FULL_FIELD_APP_PATH} {conf_file} --gpu_mem_fraction {gpu_mem_fraction} --cpu_mem_fraction {cpu_mem_fraction}",
465
+ ),
422
466
  script_path=os.path.join(self.scan.path, "slurm_scripts", script_name),
423
467
  clean_script=False,
424
468
  working_directory=self.scan.working_directory,
@@ -445,10 +489,12 @@ class _NabuBaseReconstructor:
445
489
  """Return a tuple a potential callback to be launch once the future is done"""
446
490
  return tuple()
447
491
 
448
- def _treateOutputSliceConfig(self, config):
492
+ def _treateOutputSliceConfig(self, config) -> tuple:
449
493
  """
450
494
  - add or overwrite some parameters of the dictionary
451
495
  - create the output directory if does not exist
496
+
497
+ :return: (config: dict, nabu_cfg_folder: str)
452
498
  """
453
499
  # handle phase
454
500
  pag = False
@@ -482,16 +528,26 @@ class _NabuBaseReconstructor:
482
528
  location = config["output"].get("location", None)
483
529
  if location not in ("", None):
484
530
  location = format_output_location(location, scan=self.scan)
485
- # if user specify the location
486
- if not os.path.isdir(config["output"]["location"]):
487
- os.makedirs(location)
531
+ location_cfg_files = location
488
532
  else:
489
533
  # otherwise default location will be the data root level
490
534
  location = self.scan.path
535
+ location_cfg_files = location
536
+ # TODO: if is a single file - append prefix
537
+ if config["output"].get("file_format") in (
538
+ NabuOutputFileFormat.EDF.value,
539
+ NabuOutputFileFormat.TIFF.value,
540
+ NabuOutputFileFormat.JP2K.value,
541
+ ): # if user specify the location
542
+ location = "/".join([location, _file_name])
543
+
491
544
  # add reconstruction path to the list. scan `reconstruction_paths` register all the existing path where
492
545
  # reconstruction are saved in order to be able to browse them all
493
546
  self.scan.add_reconstruction_path(location)
494
547
  config["output"]["location"] = location
548
+ else:
549
+ # don't think this could ever happen
550
+ location_cfg_files = self.scan.path
495
551
  # handle preproc
496
552
  if "preproc" not in config:
497
553
  config["preproc"] = {}
@@ -504,14 +560,14 @@ class _NabuBaseReconstructor:
504
560
 
505
561
  extra_infos = self.scan.intensity_normalization.get_extra_infos()
506
562
 
507
- nabu_cfg_folders = os.path.join(
508
- config["output"]["location"], settings.NABU_CFG_FILE_FOLDER
563
+ nabu_cfg_folder = os.path.join(
564
+ location_cfg_files, settings.NABU_CFG_FILE_FOLDER
509
565
  )
510
- os.makedirs(nabu_cfg_folders, exist_ok=True)
566
+ os.makedirs(nabu_cfg_folder, exist_ok=True)
511
567
 
512
568
  # configuration file and nabu_tomwer_serving_hatch must be in the same folder
513
569
  serving_hatch_file = os.path.join(
514
- nabu_cfg_folders, settings.NABU_TOMWER_SERVING_HATCH
570
+ nabu_cfg_folder, settings.NABU_TOMWER_SERVING_HATCH
515
571
  )
516
572
 
517
573
  source = extra_infos.get("source", INormSource.NONE)
@@ -566,13 +622,36 @@ class _NabuBaseReconstructor:
566
622
  )
567
623
  else:
568
624
  raise NotImplementedError(f"source type {source.value} is not handled")
569
-
570
- return config
625
+ return config, nabu_cfg_folder
571
626
 
572
627
  def _get_file_basename_reconstruction(self, pag, db, ctf):
573
628
  """return created file base name"""
574
629
  raise NotImplementedError("Base class")
575
630
 
631
+ def cancel(self):
632
+ self._cancelled = True
633
+ if self._process:
634
+ # kill childs processes
635
+ try:
636
+ parent = psutil.Process(self._process.pid)
637
+ except psutil.NoSuchProcess:
638
+ pass
639
+ else:
640
+ # TODO: see with Pierre. But from my point of view this
641
+ # might be handled by nabu it self....
642
+ childrens = parent.children(recursive=True)
643
+ for child in childrens:
644
+ try:
645
+ child.send_signal(signal.SIGKILL)
646
+ except psutil.NoSuchProcess:
647
+ pass
648
+ else:
649
+ child.wait()
650
+ self._process.kill()
651
+ self._process.wait()
652
+ finally:
653
+ self._process = None
654
+
576
655
 
577
656
  def dump_normalization_array_for_nabu(
578
657
  scan: TomwerScanBase, output_file: str, array: Union[numpy.ndarray, float, int]