tomwer 1.2.8__py3-none-any.whl → 1.3.0a0__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 (253) hide show
  1. orangecontrib/tomwer/tutorials/icat_publication.ows +58 -0
  2. orangecontrib/tomwer/widgets/__init__.py +1 -0
  3. orangecontrib/tomwer/widgets/control/DataDiscoveryOW.py +2 -2
  4. orangecontrib/tomwer/widgets/control/DataListOW.py +9 -7
  5. orangecontrib/tomwer/widgets/control/DataSelectorOW.py +21 -10
  6. orangecontrib/tomwer/widgets/control/EDF2NXTomomillOW.py +11 -5
  7. orangecontrib/tomwer/widgets/control/EmailOW.py +4 -4
  8. orangecontrib/tomwer/widgets/control/NXTomomillOW.py +31 -18
  9. orangecontrib/tomwer/widgets/control/NXtomoConcatenate.py +14 -7
  10. orangecontrib/tomwer/widgets/control/NotifierOW.py +1 -0
  11. orangecontrib/tomwer/widgets/control/VolumeSelector.py +7 -4
  12. orangecontrib/tomwer/widgets/control/VolumeSymLinkOW.py +182 -182
  13. orangecontrib/tomwer/widgets/debugtools/DatasetGeneratorOW.py +4 -4
  14. orangecontrib/tomwer/widgets/edit/DarkFlatPatchOW.py +4 -4
  15. orangecontrib/tomwer/widgets/edit/ImageKeyEditorOW.py +3 -3
  16. orangecontrib/tomwer/widgets/edit/ImageKeyUpgraderOW.py +2 -0
  17. orangecontrib/tomwer/widgets/edit/NXtomoEditorOW.py +3 -3
  18. orangecontrib/tomwer/widgets/edit/test/test_nxtomo_editor.py +3 -3
  19. orangecontrib/tomwer/widgets/icat/PublishProcessedDataOW.py +115 -0
  20. orangecontrib/tomwer/widgets/icat/RawDataScreenshotCreatorOW.py +98 -0
  21. orangecontrib/tomwer/widgets/icat/SaveToGalleryAndPublishOW.py +129 -0
  22. orangecontrib/tomwer/widgets/icat/__init__.py +13 -0
  23. orangecontrib/tomwer/widgets/icat/icons/add_gallery.png +0 -0
  24. orangecontrib/tomwer/widgets/icat/icons/add_gallery.svg +82 -0
  25. orangecontrib/tomwer/widgets/icat/icons/publish_processed_data.png +0 -0
  26. orangecontrib/tomwer/widgets/icat/icons/publish_processed_data.svg +95 -0
  27. orangecontrib/tomwer/widgets/icat/icons/raw_screenshots.png +0 -0
  28. orangecontrib/tomwer/widgets/icat/icons/raw_screenshots.svg +143 -0
  29. orangecontrib/tomwer/widgets/icons/tomwer_data_portal.png +0 -0
  30. orangecontrib/tomwer/widgets/icons/tomwer_data_portal.svg +76 -0
  31. orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +9 -8
  32. orangecontrib/tomwer/widgets/reconstruction/CastNabuVolumeOW.py +3 -3
  33. orangecontrib/tomwer/widgets/reconstruction/NabuHelicalPrepareWeightsDoubleOW.py +179 -169
  34. orangecontrib/tomwer/widgets/reconstruction/NabuOW.py +23 -0
  35. orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +39 -5
  36. orangecontrib/tomwer/widgets/reconstruction/SAAxisOW.py +7 -13
  37. orangecontrib/tomwer/widgets/reconstruction/SADeltaBetaOW.py +7 -17
  38. orangecontrib/tomwer/widgets/reconstruction/SinoNormOW.py +3 -4
  39. orangecontrib/tomwer/widgets/visualization/LivesliceOW.py +1 -1
  40. orangecontrib/tomwer/widgets/visualization/NXtomoMetadataViewerOW.py +3 -3
  41. orangecontrib/tomwer/widgets/visualization/VolumeViewerOW.py +3 -29
  42. tomwer/__main__.py +11 -58
  43. tomwer/app/canvas.py +8 -0
  44. tomwer/app/canvas_launcher/config.py +13 -11
  45. tomwer/app/darkref.py +1 -1
  46. tomwer/app/darkrefpatch.py +1 -1
  47. tomwer/app/imagekeyeditor.py +5 -5
  48. tomwer/app/imagekeyupgrader.py +5 -5
  49. tomwer/app/intensitynormalization.py +2 -2
  50. tomwer/app/radiostack.py +2 -2
  51. tomwer/app/zstitching.py +74 -3
  52. tomwer/core/cluster/cluster.py +26 -0
  53. tomwer/core/log/logger.py +7 -5
  54. tomwer/core/process/conditions/filters.py +1 -1
  55. tomwer/core/process/control/datalistener/datalistener.py +3 -3
  56. tomwer/core/process/control/nxtomoconcatenate.py +13 -13
  57. tomwer/core/process/control/nxtomomill.py +83 -25
  58. tomwer/core/process/control/scantransfer.py +11 -10
  59. tomwer/core/process/control/scanvalidator.py +3 -2
  60. tomwer/core/process/control/test/test_concatenate_nxtomos.py +9 -9
  61. tomwer/core/process/control/test/test_email.py +4 -4
  62. tomwer/core/process/control/test/test_h52nx_process.py +59 -7
  63. tomwer/core/process/control/test/test_volume_link.py +64 -64
  64. tomwer/core/process/control/timer.py +1 -1
  65. tomwer/core/process/control/volumesymlink.py +200 -200
  66. tomwer/core/process/edit/darkflatpatch.py +6 -6
  67. tomwer/core/process/edit/imagekeyeditor.py +17 -18
  68. tomwer/core/process/icat/__init__.py +0 -0
  69. tomwer/core/process/icat/createscreenshots.py +100 -0
  70. tomwer/core/process/icat/gallery.py +377 -0
  71. tomwer/core/process/icat/icatbase.py +36 -0
  72. tomwer/core/process/icat/publish.py +228 -0
  73. tomwer/core/process/icat/screenshots.py +26 -0
  74. tomwer/core/process/output.py +52 -0
  75. tomwer/core/process/reconstruction/axis/axis.py +17 -10
  76. tomwer/core/process/reconstruction/axis/mode.py +4 -0
  77. tomwer/core/process/reconstruction/axis/params.py +9 -4
  78. tomwer/core/process/reconstruction/darkref/darkrefs.py +8 -6
  79. tomwer/core/process/reconstruction/darkref/darkrefscopy.py +1 -1
  80. tomwer/core/process/reconstruction/darkref/params.py +1 -1
  81. tomwer/core/process/reconstruction/lamino/tofu.py +4 -4
  82. tomwer/core/process/reconstruction/nabu/castvolume.py +1 -1
  83. tomwer/core/process/reconstruction/nabu/helical.py +9 -5
  84. tomwer/core/process/reconstruction/nabu/nabucommon.py +32 -62
  85. tomwer/core/process/reconstruction/nabu/nabuscores.py +387 -61
  86. tomwer/core/process/reconstruction/nabu/nabuslices.py +33 -21
  87. tomwer/core/process/reconstruction/nabu/nabuvolume.py +37 -14
  88. tomwer/core/process/reconstruction/nabu/settings.py +2 -2
  89. tomwer/core/process/reconstruction/nabu/utils.py +129 -24
  90. tomwer/core/process/reconstruction/output.py +108 -0
  91. tomwer/core/process/reconstruction/saaxis/saaxis.py +233 -263
  92. tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +140 -86
  93. tomwer/core/process/reconstruction/scores/params.py +4 -1
  94. tomwer/core/process/reconstruction/scores/scores.py +13 -0
  95. tomwer/core/process/reconstruction/test/test_axis_params.py +2 -2
  96. tomwer/core/process/reconstruction/test/test_darkref.py +3 -3
  97. tomwer/core/process/reconstruction/test/test_darkref_copy.py +3 -3
  98. tomwer/core/process/reconstruction/test/test_saaxis.py +3 -4
  99. tomwer/core/process/reconstruction/test/test_sadeltabeta.py +2 -2
  100. tomwer/core/process/stitching/nabustitcher.py +2 -2
  101. tomwer/core/process/test/test_axis.py +6 -6
  102. tomwer/core/process/test/test_dark_and_flat.py +10 -7
  103. tomwer/core/process/test/test_data_transfer.py +7 -6
  104. tomwer/core/process/test/test_nabu.py +4 -4
  105. tomwer/core/process/test/test_normalization.py +2 -2
  106. tomwer/core/scan/edfscan.py +4 -1
  107. tomwer/core/scan/hdf5scan.py +19 -500
  108. tomwer/core/scan/nxtomoscan.py +532 -0
  109. tomwer/core/scan/scanbase.py +42 -20
  110. tomwer/core/scan/scanfactory.py +13 -13
  111. tomwer/core/scan/test/test_future_scan.py +2 -2
  112. tomwer/core/scan/test/test_h5.py +12 -10
  113. tomwer/core/scan/test/test_process_registration.py +2 -2
  114. tomwer/core/scan/test/test_scan.py +4 -3
  115. tomwer/core/settings.py +20 -0
  116. tomwer/core/test/test_scanutils.py +8 -7
  117. tomwer/core/test/test_utils.py +33 -26
  118. tomwer/core/utils/__init__.py +0 -466
  119. tomwer/core/utils/deprecation.py +1 -1
  120. tomwer/core/utils/dictutils.py +14 -0
  121. tomwer/core/utils/lbsram.py +35 -0
  122. tomwer/core/utils/nxtomoutils.py +1 -1
  123. tomwer/core/utils/scanutils.py +6 -6
  124. tomwer/core/utils/spec.py +263 -0
  125. tomwer/core/volume/volumefactory.py +2 -2
  126. tomwer/gui/cluster/slurm.py +260 -60
  127. tomwer/gui/cluster/test/test_cluster.py +13 -0
  128. tomwer/gui/cluster/test/test_supervisor.py +2 -2
  129. tomwer/gui/configuration/__init__.py +0 -0
  130. tomwer/gui/{reconstruction/nabu → configuration}/action.py +1 -32
  131. tomwer/gui/configuration/level.py +22 -0
  132. tomwer/gui/control/actions.py +54 -0
  133. tomwer/gui/control/datalist.py +78 -16
  134. tomwer/gui/control/datalistener.py +4 -16
  135. tomwer/gui/control/{email.py → emailnotifier.py} +9 -18
  136. tomwer/gui/control/history.py +2 -2
  137. tomwer/gui/control/observations.py +2 -2
  138. tomwer/gui/control/reducedarkflatselector.py +1 -1
  139. tomwer/gui/control/selectorwidgetbase.py +36 -9
  140. tomwer/gui/control/serie/seriecreator.py +5 -22
  141. tomwer/gui/control/test/test_email.py +1 -1
  142. tomwer/gui/control/test/test_scanvalidator.py +6 -5
  143. tomwer/gui/control/test/test_single_tomo_obj.py +2 -2
  144. tomwer/gui/control/tomoobjdisplaymode.py +8 -0
  145. tomwer/gui/debugtools/datasetgenerator.py +3 -3
  146. tomwer/gui/edit/dkrfpatch.py +16 -22
  147. tomwer/gui/edit/imagekeyeditor.py +8 -11
  148. tomwer/gui/edit/nxtomoeditor.py +111 -44
  149. tomwer/gui/edit/nxtomowarmer.py +4 -4
  150. tomwer/gui/edit/test/test_dkrf_patch.py +7 -7
  151. tomwer/gui/edit/test/test_image_key_editor.py +3 -3
  152. tomwer/gui/edit/test/test_nx_editor.py +40 -16
  153. tomwer/gui/icat/__init__.py +0 -0
  154. tomwer/gui/icat/createscreenshots.py +80 -0
  155. tomwer/gui/icat/gallery.py +214 -0
  156. tomwer/gui/icat/publish.py +187 -0
  157. tomwer/gui/reconstruction/axis/axis.py +171 -57
  158. tomwer/gui/reconstruction/axis/radioaxis.py +80 -95
  159. tomwer/gui/reconstruction/darkref/darkrefcopywidget.py +3 -2
  160. tomwer/gui/reconstruction/lamino/tofu/projections.py +1 -1
  161. tomwer/gui/reconstruction/lamino/tofu/tofuoutput.py +3 -6
  162. tomwer/gui/reconstruction/nabu/castvolume.py +1 -1
  163. tomwer/gui/reconstruction/nabu/check.py +9 -9
  164. tomwer/gui/reconstruction/nabu/helical.py +29 -12
  165. tomwer/gui/reconstruction/nabu/nabuconfig/base.py +2 -4
  166. tomwer/gui/reconstruction/nabu/nabuconfig/output.py +110 -33
  167. tomwer/gui/reconstruction/nabu/nabuconfig/phase.py +9 -12
  168. tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +219 -29
  169. tomwer/gui/reconstruction/nabu/nabuconfig/reconstruction.py +3 -6
  170. tomwer/gui/reconstruction/nabu/nabuflow.py +12 -20
  171. tomwer/gui/reconstruction/nabu/slices.py +6 -7
  172. tomwer/gui/reconstruction/nabu/volume.py +22 -10
  173. tomwer/gui/reconstruction/normalization/intensity.py +15 -23
  174. tomwer/gui/reconstruction/saaxis/corrangeselector.py +7 -23
  175. tomwer/gui/reconstruction/saaxis/dimensionwidget.py +1 -1
  176. tomwer/gui/reconstruction/saaxis/saaxis.py +7 -9
  177. tomwer/gui/reconstruction/sadeltabeta/saadeltabeta.py +2 -1
  178. tomwer/gui/reconstruction/scores/control.py +2 -9
  179. tomwer/gui/reconstruction/scores/scoreplot.py +11 -5
  180. tomwer/gui/reconstruction/test/test_axis.py +23 -12
  181. tomwer/gui/reconstruction/test/test_lamino.py +8 -3
  182. tomwer/gui/reconstruction/test/test_nabu.py +28 -9
  183. tomwer/gui/reconstruction/test/test_saaxis.py +3 -3
  184. tomwer/gui/reconstruction/test/test_sadeltabeta.py +2 -2
  185. tomwer/gui/settings.py +5 -28
  186. tomwer/gui/stackplot.py +2 -5
  187. tomwer/gui/stitching/action.py +49 -0
  188. tomwer/gui/stitching/config/axisparams.py +7 -24
  189. tomwer/gui/stitching/config/output.py +10 -8
  190. tomwer/gui/stitching/config/positionoveraxis.py +22 -23
  191. tomwer/gui/stitching/normalization.py +117 -0
  192. tomwer/gui/stitching/stitchandbackground.py +4 -6
  193. tomwer/gui/stitching/stitching.py +265 -43
  194. tomwer/gui/stitching/stitching_preview.py +62 -5
  195. tomwer/gui/stitching/stitching_raw.py +2 -5
  196. tomwer/gui/stitching/z_stitching/fineestimation.py +0 -60
  197. tomwer/gui/utils/buttons.py +112 -29
  198. tomwer/gui/utils/inputwidget.py +33 -25
  199. tomwer/gui/utils/scandescription.py +4 -0
  200. tomwer/gui/utils/step.py +144 -0
  201. tomwer/gui/utils/unitsystem.py +2 -5
  202. tomwer/gui/utils/vignettes.py +176 -15
  203. tomwer/gui/visualization/dataviewer.py +1 -18
  204. tomwer/gui/visualization/diffviewer/diffviewer.py +7 -16
  205. tomwer/gui/visualization/diffviewer/shiftwidget.py +2 -5
  206. tomwer/gui/visualization/scanoverview.py +1 -1
  207. tomwer/gui/visualization/sinogramviewer.py +1 -10
  208. tomwer/gui/visualization/test/test_diffviewer.py +3 -3
  209. tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py +4 -4
  210. tomwer/gui/visualization/test/test_sinogramviewer.py +2 -2
  211. tomwer/gui/visualization/test/test_stacks.py +3 -3
  212. tomwer/gui/visualization/test/test_volumeviewer.py +2 -2
  213. tomwer/io/utils/raw_and_processed_data.py +84 -0
  214. tomwer/io/utils/tomoobj.py +4 -6
  215. tomwer/resources/gui/icons/ruler.png +0 -0
  216. tomwer/resources/gui/icons/ruler.svg +273 -0
  217. tomwer/resources/gui/icons/short_description.png +0 -0
  218. tomwer/resources/gui/icons/short_description.svg +58 -0
  219. tomwer/resources/gui/icons/url.png +0 -0
  220. tomwer/resources/gui/icons/url.svg +58 -0
  221. tomwer/synctools/stacks/edit/darkflatpatch.py +2 -2
  222. tomwer/synctools/stacks/edit/imagekeyeditor.py +2 -2
  223. tomwer/synctools/stacks/reconstruction/axis.py +4 -4
  224. tomwer/synctools/stacks/reconstruction/castvolume.py +2 -2
  225. tomwer/synctools/stacks/reconstruction/dkrefcopy.py +4 -10
  226. tomwer/synctools/stacks/reconstruction/nabu.py +2 -2
  227. tomwer/synctools/stacks/reconstruction/normalization.py +2 -2
  228. tomwer/synctools/stacks/reconstruction/saaxis.py +2 -2
  229. tomwer/synctools/stacks/reconstruction/sadeltabeta.py +2 -2
  230. tomwer/synctools/test/test_darkRefs.py +7 -58
  231. tomwer/synctools/test/test_foldertransfer.py +6 -6
  232. tomwer/synctools/utils/scanstages.py +6 -6
  233. tomwer/tests/conftest.py +34 -0
  234. tomwer/tests/datasets.py +13 -0
  235. tomwer/tests/test_scripts.py +92 -39
  236. tomwer/tests/utils.py +5 -0
  237. tomwer/version.py +3 -3
  238. {tomwer-1.2.8.dist-info → tomwer-1.3.0a0.dist-info}/METADATA +39 -44
  239. {tomwer-1.2.8.dist-info → tomwer-1.3.0a0.dist-info}/RECORD +248 -209
  240. tomwer/resources/gui/icons/esrf_1.svg +0 -307
  241. tomwer/resources/gui/icons/triangle.svg +0 -80
  242. tomwer/synctools/test/test_scanstages.py +0 -162
  243. tomwer/tests/utils/__init__.py +0 -247
  244. tomwer/tests/utils/utilstest.py +0 -220
  245. /tomwer/app/{saaxis.py → multicor.py} +0 -0
  246. /tomwer/app/{sadeltabeta.py → multipag.py} +0 -0
  247. /tomwer/core/process/control/{email.py → emailnotifier.py} +0 -0
  248. /tomwer-1.2.8-py3.11-nspkg.pth → /tomwer-1.3.0a0-py3.11-nspkg.pth +0 -0
  249. {tomwer-1.2.8.dist-info → tomwer-1.3.0a0.dist-info}/LICENSE +0 -0
  250. {tomwer-1.2.8.dist-info → tomwer-1.3.0a0.dist-info}/WHEEL +0 -0
  251. {tomwer-1.2.8.dist-info → tomwer-1.3.0a0.dist-info}/entry_points.txt +0 -0
  252. {tomwer-1.2.8.dist-info → tomwer-1.3.0a0.dist-info}/namespace_packages.txt +0 -0
  253. {tomwer-1.2.8.dist-info → tomwer-1.3.0a0.dist-info}/top_level.txt +0 -0
@@ -32,27 +32,26 @@ __license__ = "MIT"
32
32
  __date__ = "10/02/2021"
33
33
 
34
34
 
35
- import copy
36
35
  import logging
37
36
  import os
38
37
  from typing import Optional
39
38
 
40
39
  import h5py
41
40
  import numpy
41
+ from multiprocessing import Pool
42
42
  from processview.core.manager import DatasetState, ProcessManager
43
43
  from processview.core.superviseprocess import SuperviseProcess
44
44
  from silx.io.url import DataUrl
45
- from silx.utils.deprecation import deprecated_warning
45
+ from tomwer.core.utils.deprecation import deprecated_warning
46
46
 
47
47
  from tomoscan.io import HDF5File
48
48
 
49
49
  import tomwer.version
50
50
  from tomwer.core.process.reconstruction.axis import AxisRP
51
51
  from tomwer.core.process.reconstruction.nabu.nabuscores import (
52
- run_nabu_one_slice_several_config,
52
+ run_nabu_multicor,
53
53
  )
54
54
  from tomwer.core.process.reconstruction.nabu.nabuslices import (
55
- SingleSliceRunner,
56
55
  interpret_tomwer_configuration,
57
56
  )
58
57
  from tomwer.core.process.reconstruction.scores import (
@@ -63,9 +62,7 @@ from tomwer.core.process.reconstruction.scores import (
63
62
  )
64
63
  from tomwer.core.process.reconstruction.scores.params import ScoreMethod
65
64
  from tomwer.core.process.task import Task
66
- from tomwer.core.progress import Progress
67
- from tomwer.core.scan.edfscan import EDFTomoScan
68
- from tomwer.core.scan.hdf5scan import HDF5TomoScan
65
+ from tomwer.core.scan.nxtomoscan import NXtomoScan
69
66
  from tomwer.core.scan.scanbase import TomwerScanBase
70
67
  from tomwer.core.scan.scanfactory import ScanFactory
71
68
  from tomwer.core.utils import logconfig
@@ -82,6 +79,11 @@ from tomwer.core.process.reconstruction.nabu.nabucommon import (
82
79
  )
83
80
  from tomwer.core.futureobject import FutureTomwerObject
84
81
  from tomwer.core.process.reconstruction.saaxis.params import ReconstructionMode
82
+ from tomwer.core.process.reconstruction.nabu.utils import (
83
+ get_multi_cor_recons_volume_identifiers,
84
+ get_nabu_multicor_file_prefix,
85
+ )
86
+ from tomwer.core.process.reconstruction.nabu.nabuscores import _ReconstructorMultiCor
85
87
 
86
88
  from ..nabu import utils
87
89
  from .params import SAAxisParams
@@ -139,6 +141,8 @@ class SAAxisTask(
139
141
  "dump_roi",
140
142
  "dump_process",
141
143
  "serialize_output_data",
144
+ "compute_scores", # for GUI we want to post pone the score calculation
145
+ "pool_size",
142
146
  ),
143
147
  ):
144
148
  """
@@ -148,6 +152,8 @@ class SAAxisTask(
148
152
  As the saaxis is integrating the score calculation we will never get a future_tomo_scan as output
149
153
  """
150
154
 
155
+ DEFAULT_POOL_SIZE = 10
156
+
151
157
  def __init__(
152
158
  self, process_id=None, inputs=None, varinfo=None, node_attrs=None, execinfo=None
153
159
  ):
@@ -214,7 +220,7 @@ class SAAxisTask(
214
220
  return best_cor
215
221
 
216
222
  def _config_preprocessing(
217
- self, scan, config, cor_positions, file_format, output_dir, cluster_config
223
+ self, scan, config, file_format, output_dir, cluster_config
218
224
  ):
219
225
  """convert general configuration to nabu - single reconstruction - configuration"""
220
226
  nabu_configurations = interpret_tomwer_configuration(config, scan=None)
@@ -232,17 +238,8 @@ class SAAxisTask(
232
238
  # work on file name...
233
239
  if output_dir is None:
234
240
  output_dir = os.path.join(scan.path, DEFAULT_RECONS_FOLDER)
235
- if scan.process_file is not None:
236
- steps_file_basename, _ = os.path.splitext(scan.process_file)
237
- steps_file_basename = "_".join(
238
- ("steps_file_basename", "nabu", "sinogram", "save", "step")
239
- )
240
- steps_file_basename = steps_file_basename + ".hdf5"
241
- steps_file = os.path.join(output_dir, steps_file_basename)
242
- else:
243
- steps_file = ""
244
241
 
245
- base_config = nabu_configurations[0][0]
242
+ nabu_configuration = nabu_configurations[0][0]
246
243
  if cluster_config == {}:
247
244
  cluster_config = None
248
245
  is_cluster_job = cluster_config is not None
@@ -250,98 +247,73 @@ class SAAxisTask(
250
247
  raise ValueError(
251
248
  "job on cluster requested but no access to slurm cluster found"
252
249
  )
253
- configs = {}
254
-
255
- for i_cor, cor in enumerate(cor_positions):
256
- nabu_configuration = copy.deepcopy(base_config)
257
- nabu_configuration["pipeline"] = {
258
- "save_steps": "sinogram" if i_cor == 0 else "",
259
- "resume_from_step": "sinogram",
260
- "steps_file": steps_file,
261
- }
262
- # convert cor from tomwer ref to nabu ref
263
- if scan.dim_1 is not None:
264
- cor_nabu_ref = cor + scan.dim_1 / 2.0
265
- else:
266
- _logger.warning("enable to get image half width. Set it to 1024")
267
- cor_nabu_ref = cor + 1024
268
- # handle reconstruction section
269
- if "reconstruction" not in nabu_configuration:
270
- nabu_configuration["reconstruction"] = {}
271
- nabu_configuration["reconstruction"]["rotation_axis_position"] = str(
272
- cor_nabu_ref
273
- )
274
- # handle output section
275
- if "output" not in nabu_configuration:
276
- nabu_configuration["output"] = {}
277
- nabu_configuration["output"]["location"] = output_dir
278
- nabu_configuration["output"]["file_format"] = file_format
279
- # handle resources section
280
- nabu_configuration["resources"] = utils.get_nabu_resources_desc(
281
- scan=scan, workers=1, method="local"
282
- )
283
- configs[cor] = nabu_configuration
284
- return configs
285
250
 
286
- def _run_slice_recons_per_cor(
251
+ # handle reconstruction section
252
+ if "reconstruction" not in nabu_configuration:
253
+ nabu_configuration["reconstruction"] = {}
254
+ nabu_configuration["reconstruction"]["rotation_axis_position"] = ""
255
+ # handle output section
256
+ if "output" not in nabu_configuration:
257
+ nabu_configuration["output"] = {}
258
+ nabu_configuration["output"]["location"] = output_dir
259
+ nabu_configuration["output"]["file_format"] = file_format
260
+ # handle resources section
261
+ nabu_configuration["resources"] = utils.get_nabu_resources_desc(
262
+ scan=scan, workers=1, method="local"
263
+ )
264
+ return nabu_configuration
265
+
266
+ def _run_nabu_multicor(
287
267
  self,
288
268
  scan,
289
- configs,
269
+ nabu_config,
270
+ cors,
290
271
  slice_index,
291
272
  file_format,
292
- advancement,
293
- cluster_config,
273
+ cluster_config: Optional[dict],
294
274
  dry_run=False,
295
275
  ):
296
- runners = run_nabu_one_slice_several_config(
297
- nabu_configs=configs,
276
+ if not (cluster_config is None or isinstance(cluster_config, dict)):
277
+ raise TypeError(
278
+ f"cluster_config is expected to be a dict. Get {type(cluster_config)} instead."
279
+ )
280
+ runner = run_nabu_multicor(
281
+ nabu_config=nabu_config,
298
282
  scan=scan,
283
+ cors=cors,
299
284
  slice_index=slice_index,
300
285
  dry_run=dry_run,
301
286
  file_format=file_format,
302
- advancement=advancement,
303
- cluster_config=cluster_config.to_dict()
304
- if cluster_config is not None
305
- else None,
287
+ cluster_config=cluster_config if cluster_config is not None else None,
306
288
  process_id=self.process_id,
307
289
  instanciate_classes_only=True,
308
290
  output_file_prefix_pattern="cor_{file_name}_{value}", # as the cor is evolving, create different files to make sure the name will be unique
309
291
  )
310
292
 
311
- future_tomo_objs = {}
312
- success = True
313
- recons_urls = {}
293
+ future_tomo_obj = None
294
+ recons_urls = dict()
314
295
  std_outs = []
315
296
  std_errs = []
316
297
 
317
- for runner in runners:
318
- if self._cancelled:
319
- break
320
- self._current_processing = runner
321
- try:
322
- results = runner.run()
323
- except TimeoutError as e:
324
- _logger.error(e)
325
- else:
326
- assert isinstance(
327
- results, dict
328
- ), "results should be a dictionary with cor as key and urls as value"
329
-
330
- for cor, res in results.items():
331
- success = success and res.success
332
- if isinstance(res, ResultsWithStd):
333
- std_outs.append(res.std_out)
334
- std_errs.append(res.std_err)
335
- if isinstance(res, ResultsLocalRun):
336
- recons_urls[cor] = res.results_urls
337
- if isinstance(res, ResultSlurmRun):
338
- future_tomo_obj = FutureTomwerObject(
339
- tomo_obj=scan,
340
- process_requester_id=self.process_id,
341
- futures=res.future_slurm_jobs,
342
- )
343
- future_tomo_objs[cor] = future_tomo_obj
344
- return success, recons_urls, future_tomo_objs, std_outs, std_errs
298
+ self._current_processing = runner
299
+ try:
300
+ result = runner.run()
301
+ except TimeoutError as e:
302
+ _logger.error(e)
303
+ else:
304
+ success = result.success
305
+ if isinstance(result, ResultsWithStd):
306
+ std_outs.append(result.std_out)
307
+ std_errs.append(result.std_err)
308
+ if isinstance(result, ResultsLocalRun):
309
+ recons_urls = result.results_identifiers
310
+ if isinstance(result, ResultSlurmRun):
311
+ future_tomo_obj = FutureTomwerObject(
312
+ tomo_obj=scan,
313
+ process_requester_id=self.process_id,
314
+ futures=result.future_slurm_jobs,
315
+ )
316
+ return success, recons_urls, (future_tomo_obj,), std_outs, std_errs
345
317
 
346
318
  def _resolve_futures(
347
319
  self,
@@ -349,6 +321,7 @@ class SAAxisTask(
349
321
  nabu_config,
350
322
  slice_index,
351
323
  file_format,
324
+ cors,
352
325
  cor_reconstructions,
353
326
  future_tomo_objs: dict,
354
327
  output_dir,
@@ -359,54 +332,50 @@ class SAAxisTask(
359
332
  if output_dir is None:
360
333
  output_dir = os.path.join(scan.path, DEFAULT_RECONS_FOLDER)
361
334
 
362
- db = None
363
- pag = False
364
- ctf = False
365
- if "phase" in nabu_config:
366
- phase_method = nabu_config["phase"].get("method", "").lower()
367
- if phase_method in ("pag", "paganin"):
368
- pag = True
369
- elif phase_method in ("ctf",):
370
- ctf = True
371
- if "delta_beta" in nabu_config["phase"]:
372
- db = round(float(nabu_config["phase"]["delta_beta"]))
373
-
374
- for cor, future_tomo_obj in future_tomo_objs.items():
335
+ file_prefix = get_nabu_multicor_file_prefix(scan)
336
+
337
+ for future_tomo_obj in future_tomo_objs:
375
338
  if self._cancelled:
376
339
  break
340
+
341
+ if future_tomo_obj is None:
342
+ continue
343
+
377
344
  future_tomo_obj.results()
378
- # for saaxis we need to retrieve reconstruction url
379
345
  if future_tomo_obj.cancelled() or future_tomo_obj.exceptions():
380
346
  continue
381
- else:
382
- _file_name = SingleSliceRunner.get_file_basename_reconstruction(
347
+
348
+ for cor in cors:
349
+ cor_nabu_ref = _ReconstructorMultiCor.convert_cor_from_rel_to_abs(
383
350
  scan=scan,
384
- slice_index=slice_index,
385
- pag=pag,
386
- db=db,
387
- ctf=ctf,
351
+ cor=cor,
388
352
  )
389
- file_prefix = f"cor_{_file_name}_{cor}"
390
-
391
- recons_vol_id = utils.get_recons_volume_identifier(
353
+ volume_identifiers = get_multi_cor_recons_volume_identifiers(
392
354
  scan=scan,
393
- file_format=file_format,
355
+ slice_index=slice_index,
356
+ location=nabu_config["output"]["location"],
394
357
  file_prefix=file_prefix,
395
- location=output_dir,
396
- slice_index=None,
397
- start_z=None,
398
- end_z=None,
399
- expects_single_slice=True,
358
+ file_format=file_format,
359
+ cors=(cor_nabu_ref,),
400
360
  )
401
- assert len(recons_vol_id) == 1, "only one volume reconstructed expected"
402
- cor_reconstructions[cor] = recons_vol_id
361
+ volume_identifier = volume_identifiers.get(cor_nabu_ref, None)
362
+ if volume_identifier is None:
363
+ _logger.warning(
364
+ f"failed to load volume for {cor}. Something went wrong on slurm submission job"
365
+ )
366
+ cor_reconstructions[cor] = volume_identifier
403
367
 
404
- def _post_processing(self, scan, slice_index, cor_reconstructions):
368
+ def _post_processing(self, scan, slice_index, cor_reconstructions: dict):
405
369
  """
406
370
  compute score along the different slices
371
+
372
+ :param dict cor_reconstructions: key is expected to be a float with the cor value and the value is expected to be a volume identifier (volume with a single frame)
407
373
  """
408
374
  post_processing = _PostProcessing(
409
- slice_index=slice_index, scan=scan, cor_reconstructions=cor_reconstructions
375
+ slice_index=slice_index,
376
+ scan=scan,
377
+ cor_reconstructions=cor_reconstructions,
378
+ pool_size=self.get_input_value("pool_size", self.DEFAULT_POOL_SIZE),
410
379
  )
411
380
  post_processing._cancelled = self._cancelled
412
381
  self._current_processing = post_processing
@@ -435,6 +404,16 @@ class SAAxisTask(
435
404
  else:
436
405
  return list(slice_index.values())[0]
437
406
 
407
+ def get_output_dir(self, params: SAAxisParams, scan: TomwerScanBase):
408
+ output_dir = params.output_dir or None
409
+ if output_dir is None:
410
+ output_dir = (
411
+ params.nabu_params.get("output", {}).get("location", None) or None
412
+ )
413
+ if output_dir is None:
414
+ output_dir = os.path.join(scan.path, DEFAULT_RECONS_FOLDER)
415
+ return output_dir
416
+
438
417
  def run(self):
439
418
  scan = data_identifier_to_scan(self.inputs.data)
440
419
  if scan is None:
@@ -451,10 +430,10 @@ class SAAxisTask(
451
430
  configuration = self.inputs.sa_axis_params
452
431
  params = SAAxisParams.from_dict(configuration)
453
432
  # insure output dir is created
454
- if params.output_dir in (None, ""):
455
- params.output_dir = os.path.join(scan.path, "saaxis_results")
456
- if not os.path.exists(params.output_dir):
457
- os.makedirs(params.output_dir)
433
+ params.output_dir = self.get_output_dir(params=params, scan=scan)
434
+ if not os.path.exists(params.output_dir):
435
+ os.makedirs(params.output_dir)
436
+
458
437
  # try to find an estimated cor
459
438
  # from a previously computed cor
460
439
  if params.estimated_cor is None and scan.axis_params is not None:
@@ -480,9 +459,7 @@ class SAAxisTask(
480
459
  if mode is not ReconstructionMode.VERTICAL:
481
460
  raise ValueError(f"{mode} is not handled for now")
482
461
 
483
- output_dir = params.output_dir
484
- if output_dir is None:
485
- output_dir = os.path.join(scan.path, DEFAULT_RECONS_FOLDER)
462
+ nabu_config = configuration.get("nabu_params", {})
486
463
  nabu_output_config = configuration.get("output", {})
487
464
  file_format = nabu_output_config.get("file_format", "hdf5")
488
465
  slice_index = self._preprocess_slice_index(
@@ -493,18 +470,14 @@ class SAAxisTask(
493
470
  dry_run = self._dry_run
494
471
 
495
472
  # step one: complete nabu configuration(s)
496
- configs = self._config_preprocessing(
473
+ nabu_config = self._config_preprocessing(
497
474
  scan=scan,
498
- config=configuration,
499
- cor_positions=params.cors,
475
+ config=nabu_config,
500
476
  file_format=file_format,
501
- output_dir=output_dir,
477
+ output_dir=params.output_dir,
502
478
  cluster_config=cluster_config,
503
479
  )
504
480
  # step 2: run reconstructions
505
- advancement = Progress(
506
- f"sa-axis - slice {slice_index} of {scan.get_identifier().short_description()}"
507
- )
508
481
  cors_res = {}
509
482
  rois = {}
510
483
 
@@ -515,12 +488,12 @@ class SAAxisTask(
515
488
  future_tomo_objs,
516
489
  self._std_outs,
517
490
  self._std_errs,
518
- ) = self._run_slice_recons_per_cor(
491
+ ) = self._run_nabu_multicor(
519
492
  scan=scan,
520
- configs=configs,
493
+ nabu_config=nabu_config,
494
+ cors=tuple(params.cors),
521
495
  slice_index=slice_index,
522
496
  file_format=file_format,
523
- advancement=advancement,
524
497
  cluster_config=cluster_config,
525
498
  dry_run=dry_run,
526
499
  )
@@ -532,29 +505,35 @@ class SAAxisTask(
532
505
  # step 3: wait for future if any
533
506
  self._resolve_futures(
534
507
  scan=scan,
535
- nabu_config=configuration,
508
+ nabu_config=nabu_config,
536
509
  slice_index=slice_index,
537
510
  file_format=file_format,
538
511
  cor_reconstructions=cor_reconstructions,
512
+ cors=tuple(params.cors),
539
513
  future_tomo_objs=future_tomo_objs,
540
- output_dir=output_dir,
514
+ output_dir=params.output_dir,
541
515
  )
542
516
 
543
517
  # step 4: run post processing (compute score for each slice)
544
- try:
545
- cors_res, rois = self._post_processing(
546
- scan=scan,
547
- slice_index=slice_index,
548
- cor_reconstructions=cor_reconstructions,
549
- )
550
- except Exception as e:
551
- _logger.error(e)
552
- mess = f"sa-axis -post-processing- computation for {str(scan)} failed."
553
- state = DatasetState.FAILED
554
- cors_res = {}
518
+ if self.get_input_value("compute_scores", True):
519
+ try:
520
+ cors_res, rois = self._post_processing(
521
+ scan=scan,
522
+ slice_index=slice_index,
523
+ cor_reconstructions=cor_reconstructions,
524
+ )
525
+ except Exception as e:
526
+ _logger.error(e)
527
+ mess = (
528
+ f"sa-axis -post-processing- computation for {str(scan)} failed."
529
+ )
530
+ state = DatasetState.FAILED
531
+ cors_res = {}
532
+ else:
533
+ state = DatasetState.WAIT_USER_VALIDATION
534
+ mess = "sa-axis computation succeeded"
555
535
  else:
556
- state = DatasetState.WAIT_USER_VALIDATION
557
- mess = "sa-axis computation succeeded"
536
+ cors_res = {}
558
537
 
559
538
  if self._cancelled:
560
539
  state = DatasetState.CANCELLED
@@ -671,7 +650,7 @@ class SAAxisTask(
671
650
  def process_to_tomwer_processes(scan):
672
651
  if scan.process_file is not None:
673
652
  entry = "entry"
674
- if isinstance(scan, HDF5TomoScan):
653
+ if isinstance(scan, NXtomoScan):
675
654
  entry = scan.entry
676
655
 
677
656
  cor = None
@@ -749,128 +728,119 @@ class SAAxisTask(
749
728
  class _PostProcessing:
750
729
  """class used to run SA-axis post-processing on reconstructed slices"""
751
730
 
752
- def __init__(self, cor_reconstructions, slice_index, scan) -> None:
731
+ def __init__(self, cor_reconstructions, slice_index, scan, pool_size) -> None:
753
732
  self._cor_reconstructions = cor_reconstructions
754
733
  self._slice_index = slice_index
755
734
  self._scan = scan
756
735
  self._cancelled = False
736
+ self.pool_size = pool_size
737
+
738
+ @staticmethod
739
+ def compute_score(item: tuple):
740
+ cor, (url, data), mask_disk_radius, cancelled = item
741
+ if cancelled:
742
+ return (None, None), None
743
+
744
+ if data is None:
745
+ score = None
746
+ data_roi = None
747
+ else:
748
+ if not isinstance(data, numpy.ndarray):
749
+ raise TypeError(
750
+ f"data should be a numpy array. Get {type(data)} instead"
751
+ )
752
+ assert data.ndim == 2, f"data should be 2D. Get {data.ndim} instead"
753
+ data_roi = apply_roi(data=data, radius=mask_disk_radius, url=url)
754
+
755
+ # move data_roi to [0-1] range
756
+ # preprocessing: get percentile 0 and 99 from image and
757
+ # "clean" highest and lowest pixels from it
758
+ min_p, max_p = numpy.percentile(data_roi, (1, 99))
759
+ data_roi_int = data_roi[...]
760
+ data_roi_int[data_roi_int < min_p] = min_p
761
+ data_roi_int[data_roi_int > max_p] = max_p
762
+ data_roi_int = (data_roi_int - min_p) / (max_p - min_p)
763
+
764
+ score = ComputedScore(
765
+ tv=compute_score(data=data_roi_int, method=ScoreMethod.TV),
766
+ std=compute_score(data=data_roi_int, method=ScoreMethod.STD),
767
+ tomo_consistency=None,
768
+ )
769
+ return {cor: (url, score)}, {cor: data_roi}
757
770
 
758
771
  def run(self):
759
772
  datasets = self.load_datasets()
760
-
773
+ assert isinstance(datasets, dict)
761
774
  mask_disk_radius = get_disk_mask_radius(datasets)
775
+ with Pool(self.pool_size) as pool:
776
+ res = pool.map(
777
+ self.compute_score,
778
+ [
779
+ (
780
+ *item,
781
+ mask_disk_radius,
782
+ self._cancelled,
783
+ )
784
+ for item in datasets.items()
785
+ ],
786
+ )
762
787
  scores = {}
763
788
  rois = {}
764
- for cor, (url, data) in datasets.items():
765
- if self._cancelled:
766
- break
767
-
768
- if data is None:
769
- score = None
770
- else:
771
- assert data.ndim == 2
772
- data_roi = apply_roi(data=data, radius=mask_disk_radius, url=url)
773
- rois[cor] = data_roi
774
-
775
- # move data_roi to [0-1] range
776
- # preprocessing: get percentile 0 and 99 from image and
777
- # "clean" highest and lowest pixels from it
778
- min_p, max_p = numpy.percentile(data_roi, (1, 99))
779
- data_roi_int = data_roi[...]
780
- data_roi_int[data_roi_int < min_p] = min_p
781
- data_roi_int[data_roi_int > max_p] = max_p
782
- data_roi_int = (data_roi_int - min_p) / (max_p - min_p)
783
-
784
- if isinstance(self._scan, EDFTomoScan):
785
- _logger.info("tomo consistency is not handled for EDF scan")
786
- tomo_consistency_score = None
787
- else:
788
- try:
789
- projections_with_angle = self._scan.projections_with_angle()
790
- angles_ = [
791
- frame_angle
792
- for frame_angle, frame in projections_with_angle.items()
793
- ]
794
- angles = []
795
- for angle in angles_:
796
- if not isinstance(angle, str):
797
- angles.append(angle)
798
- if self._slice_index == "middle":
799
- if self._scan.dim_2 is not None:
800
- self._slice_index = self._scan.dim_2 // 2
801
- else:
802
- _logger.warning(
803
- "scan.dim_2 returns None, unable to deduce middle "
804
- "pick 1024"
805
- )
806
- self._slice_index = 1024
807
- tomo_consistency_score = compute_score(
808
- data=data,
809
- method=ScoreMethod.TOMO_CONSISTENCY,
810
- angles=angles,
811
- original_sinogram=self._scan.get_sinogram(
812
- self._slice_index
813
- ),
814
- detector_width=self._scan.dim_1,
815
- original_axis_position=cor + self._scan.dim_1 / 2.0,
816
- )
817
- except Exception as e:
818
- _logger.error(e)
819
- tomo_consistency_score = None
820
- score = ComputedScore(
821
- tv=compute_score(data=data_roi_int, method=ScoreMethod.TV),
822
- std=compute_score(data=data_roi_int, method=ScoreMethod.STD),
823
- tomo_consistency=tomo_consistency_score,
824
- )
825
- scores[cor] = (url, score)
789
+ for mydict in res:
790
+ myscores, myrois = mydict
791
+ scores.update(myscores)
792
+ rois.update(myrois)
826
793
  return scores, rois
827
794
 
828
- def load_datasets(self):
829
- datasets_ = {}
830
- for cor, volume_identifiers in self._cor_reconstructions.items():
831
- if self._cancelled:
832
- break
833
-
834
- if len(volume_identifiers) == 0:
835
- # in the case failed to load the url
836
- continue
837
- elif len(volume_identifiers) > 1:
838
- raise ValueError("only one slice reconstructed expected per cor")
839
- volume = VolumeFactory.create_tomo_object_from_identifier(
840
- volume_identifiers[0]
795
+ @staticmethod
796
+ def _load_dataset(item: tuple):
797
+ cor, volume_identifier = item
798
+ if volume_identifier is None:
799
+ return {cor: (None, None)}
800
+
801
+ volume = VolumeFactory.create_tomo_object_from_identifier(volume_identifier)
802
+ urls = tuple(volume.browse_data_urls())
803
+ if len(urls) == 0:
804
+ _logger.error(
805
+ f"volume {volume.get_identifier().to_str()} has no url / slices. Unable to load any data."
841
806
  )
842
- urls = tuple(volume.browse_data_urls())
843
- if len(urls) != 1:
844
- raise ValueError(
845
- f"volume is expected to have at most one url (single slice volume). get {len(urls)} - most likely nabu reconstruction failed. Do you have GPU ? Are the requested COR values valid ? - Especially for Half-acquisition"
846
- )
847
- url = urls[0]
848
- if not isinstance(url, (DataUrl, str)):
849
- raise TypeError(
850
- f"url is expected to be a str or DataUrl not {type(url)}"
851
- )
807
+ return {cor: (None, None)}
808
+ if len(urls) != 1:
809
+ _logger.error(
810
+ f"volume is expected to have at most one url (single slice volume). get {len(urls)} - most likely nabu reconstruction failed. Do you have GPU ? Are the requested COR values valid ? - Especially for Half-acquisition"
811
+ )
812
+ url = urls[0]
813
+ if not isinstance(url, (DataUrl, str)):
814
+ raise TypeError(f"url is expected to be a str or DataUrl not {type(url)}")
852
815
 
853
- try:
854
- data = get_slice_data(url=url)
855
- except Exception as e:
856
- _logger.error(
857
- f"Fail to compute a score for {url.path()}. Reason is {e}"
858
- )
859
- datasets_[cor] = (url, None)
860
- else:
861
- if data.ndim == 3:
862
- if data.shape[0] == 1:
863
- data = data.reshape(data.shape[1], data.shape[2])
864
- elif data.shape[2] == 1:
865
- data = data.reshape(data.shape[0], data.shape[1])
866
- else:
867
- raise ValueError(f"Data is expected to be 2D. Not {data.ndim}D")
868
- elif data.ndim == 2:
869
- pass
816
+ try:
817
+ data = get_slice_data(url=url)
818
+ except Exception as e:
819
+ _logger.error(f"Fail to compute a score for {url.path()}. Reason is {e}")
820
+ return {cor: (url, None)}
821
+ else:
822
+ if data.ndim == 3:
823
+ if data.shape[0] == 1:
824
+ data = data.reshape(data.shape[1], data.shape[2])
825
+ elif data.shape[2] == 1:
826
+ data = data.reshape(data.shape[0], data.shape[1])
870
827
  else:
871
- raise ValueError("Data is expected to be 2D. Not {data.ndim}D")
828
+ raise ValueError(f"Data is expected to be 2D. Not {data.ndim}D")
829
+ elif data.ndim == 2:
830
+ pass
831
+ else:
832
+ raise ValueError("Data is expected to be 2D. Not {data.ndim}D")
833
+ return {cor: (url, data)}
872
834
 
873
- datasets_[cor] = (url, data)
835
+ def load_datasets(self):
836
+ with Pool(self.pool_size) as pool:
837
+ res = pool.map(
838
+ self._load_dataset,
839
+ self._cor_reconstructions.items(),
840
+ )
841
+ datasets_ = {}
842
+ for mydict in res:
843
+ datasets_.update(mydict)
874
844
  return datasets_
875
845
 
876
846
  def cancel(self):