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
@@ -36,7 +36,7 @@ __date__ = "26/02/2021"
36
36
 
37
37
 
38
38
  import logging
39
- from typing import Iterable, Union
39
+ from typing import Iterable, Union, Optional
40
40
 
41
41
  import numpy
42
42
  from silx.gui import qt
@@ -44,8 +44,9 @@ from silx.gui.plot import PlotWindow
44
44
  from silx.gui.plot.utils.axis import SyncAxes
45
45
  from silx.io.url import DataUrl
46
46
  from tomoscan.esrf.scan.utils import get_data
47
+ from math import floor
47
48
 
48
- from tomwer.core.process.reconstruction.scores.scores import ComputedScore
49
+ from tomwer.core.process.reconstruction.scores.scores import ComputedScore, ScoreMethod
49
50
 
50
51
  _logger = logging.getLogger(__name__)
51
52
 
@@ -55,6 +56,12 @@ class VignettesQDialog(qt.QDialog):
55
56
 
56
57
  SIZE_HINT = qt.QSize(820, 820)
57
58
 
59
+ AUTO_NB_COLUMN = "auto"
60
+
61
+ COLUMN_VALUES = (AUTO_NB_COLUMN, 1, 2, 3, 4, 5, 6, 8, 10)
62
+
63
+ RESIZE_MAX_TIME = 500
64
+
58
65
  def __init__(
59
66
  self,
60
67
  value_name,
@@ -65,10 +72,24 @@ class VignettesQDialog(qt.QDialog):
65
72
  colormap=None,
66
73
  ):
67
74
  qt.QDialog.__init__(self, parent)
75
+ self.setWindowFlags(qt.Qt.Widget)
68
76
  self._scan = None
69
77
  self._selectedValue = None
70
78
  self.setLayout(qt.QVBoxLayout())
79
+
80
+ self._nbColumnCB = qt.QComboBox(self)
81
+ for nb_column in self.COLUMN_VALUES:
82
+ self._nbColumnCB.addItem(str(nb_column))
83
+
84
+ self._columnWidget = qt.QWidget(self)
85
+ # needed intermediary widget because the 'setWidgetResizable' fails with QFormLayout on PyQt 5.14.1 and will want to keep compatiblity for now
86
+ self._columnWidget.setLayout(qt.QFormLayout())
87
+ self._columnWidget.layout().addRow("number of column", self._nbColumnCB)
88
+ self.layout().addWidget(self._columnWidget)
89
+
71
90
  self._mainWidget = qt.QScrollArea(self)
91
+ self._mainWidget.setHorizontalScrollBarPolicy(qt.Qt.ScrollBarAlwaysOff)
92
+ self._mainWidget.setVerticalScrollBarPolicy(qt.Qt.ScrollBarAsNeeded)
72
93
  self._vignettesWidget = VignettesWidget(
73
94
  self,
74
95
  with_spacer=True,
@@ -86,18 +107,30 @@ class VignettesQDialog(qt.QDialog):
86
107
  types = qt.QDialogButtonBox.Ok | qt.QDialogButtonBox.Cancel
87
108
  self._buttons = qt.QDialogButtonBox(parent=self)
88
109
  self._buttons.setStandardButtons(types)
110
+ self._buttons.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Minimum)
89
111
  self.layout().addWidget(self._buttons)
90
112
 
91
113
  # connect signal slot
92
114
  self._buttons.button(qt.QDialogButtonBox.Ok).clicked.connect(self.accept)
93
-
94
115
  self._buttons.button(qt.QDialogButtonBox.Cancel).clicked.connect(self.reject)
116
+ self._nbColumnCB.currentIndexChanged.connect(self._updateNbColumn)
117
+
118
+ self._resizeCallback = None
119
+ self._firstResizeEvent = True
95
120
 
96
121
  def sizeHint(self):
97
122
  """Return a reasonable default size for usage in :class:`PlotWindow`"""
98
123
  return self.SIZE_HINT
99
124
 
100
- def setScores(self, scores, score_method):
125
+ def setScores(self, scores: dict, score_method: ScoreMethod):
126
+ """
127
+ Expect a dictionary with possible values to select as key and
128
+ (2D numpy.array, score) as value.
129
+ Where the 2D numpy.array is the frame to display and the score if the
130
+ "indicator" score to display with the frame.
131
+ :param dict scores: with score as key and a tuple (url|numpy array, ComputedScore) as value
132
+ :param ScoreMethod score_method: score kind to be display
133
+ """
101
134
  self._vignettesWidget.setScores(scores=scores, score_method=score_method)
102
135
 
103
136
  def selectedValue(self):
@@ -116,6 +149,55 @@ class VignettesQDialog(qt.QDialog):
116
149
  self._vignettesWidget.close()
117
150
  super().reject()
118
151
 
152
+ def getNColumn(self) -> Optional[int]:
153
+ n_column = self._nbColumnCB.currentText()
154
+ if n_column == self.AUTO_NB_COLUMN:
155
+ return None
156
+ else:
157
+ return int(n_column)
158
+
159
+ def setNbColumn(self, value: Union[int, str]):
160
+ if value not in self.COLUMN_VALUES:
161
+ raise ValueError(
162
+ f"Unhandled number of column requested ({value}). Valid values are {self.COLUMN_VALUES}"
163
+ )
164
+ idx = self._nbColumnCB.findText(str(value))
165
+ self._nbColumnCB.setCurrentIndex(idx)
166
+
167
+ @staticmethod
168
+ def computeOptimalNColumn(window_width, vignette_width):
169
+ n = floor(window_width / vignette_width)
170
+ # To check with Jerome; He seems to have work on the topic.
171
+ # might have some method for a smart way to do it
172
+ return max(1, n)
173
+
174
+ def _updateNbColumn(self, force_update=False):
175
+ n_colum = self.getNColumn()
176
+ if n_colum is None:
177
+ n_colum = self.computeOptimalNColumn(
178
+ window_width=self.width(),
179
+ vignette_width=400,
180
+ )
181
+ self._vignettesWidget.setNElementsPerRow(n_colum, force_update=force_update)
182
+
183
+ def resizeEvent(self, event):
184
+ if self._firstResizeEvent:
185
+ # cheap way to filter the first resize and avoid updating it
186
+ self._firstResizeEvent = False
187
+ else:
188
+ # print("resize event received...", event)
189
+ if self._resizeCallback is not None:
190
+ self._resizeCallback.timeout.disconnect(self._updateNbColumn)
191
+ self._resizeCallback.stop()
192
+ self._resizeCallback.deleteLater()
193
+ self._resizeCallback = None
194
+ self._resizeCallback = qt.QTimer(self)
195
+ self._resizeCallback.setSingleShot(True)
196
+ self._resizeCallback.timeout.connect(self._updateNbColumn)
197
+ self._resizeCallback.start(self.RESIZE_MAX_TIME)
198
+
199
+ super().resizeEvent(event)
200
+
119
201
 
120
202
  class VignettesWidget(qt.QWidget):
121
203
  """
@@ -132,6 +214,7 @@ class VignettesWidget(qt.QWidget):
132
214
  """
133
215
 
134
216
  DEFAULT_PLOT_PER_ROW = 2
217
+ MAX_NB_COLUMN = 999999
135
218
 
136
219
  def __init__(
137
220
  self,
@@ -155,6 +238,9 @@ class VignettesWidget(qt.QWidget):
155
238
  self._scoreFormat = score_format
156
239
  self._colormap = colormap
157
240
  self._vignettes = []
241
+ self.__score_method = None
242
+
243
+ self.setLayout(qt.QGridLayout())
158
244
 
159
245
  def close(self):
160
246
  for vignette in self._vignettes:
@@ -164,24 +250,81 @@ class VignettesWidget(qt.QWidget):
164
250
  def selectedValue(self):
165
251
  sel_vignette = self._vignettesGroup.checkedButton()
166
252
  if sel_vignette is not None:
167
- return sel_vignette.getValue()
253
+ return sel_vignette.getScoreValue()
168
254
  else:
169
255
  return None
170
256
 
171
- def setNElementsPerRow(self, n: int):
172
- self._nPlotPerRow = n
257
+ def setNElementsPerRow(self, n: int, force_update):
258
+ assert isinstance(n, int), "number of column must be an int"
259
+ if self._nPlotPerRow != n:
260
+ self._nPlotPerRow = n
261
+ self.__update()
262
+ elif force_update:
263
+ self.__update()
264
+
265
+ def __update(self):
266
+ scores, score_method = self.getScores()
267
+ self.setScores(scores, score_method)
268
+ # raise NotImplementedError
269
+
270
+ def getNElementsPerRow(self):
271
+ return self._nPlotPerRow
272
+
273
+ def getScores(self) -> tuple:
274
+ """
275
+ Return currently displayed scores as (scores, scores_method)
276
+ """
277
+ scores = {}
278
+ scores_method = self.__score_method
279
+ for vignette in self._vignettes:
280
+ assert isinstance(vignette, Vignette)
281
+ if scores_method in (ScoreMethod.STD, ScoreMethod.STD_INVERSE):
282
+ compute_score_args = {
283
+ "std": vignette.getScoreValue(),
284
+ "tv": None,
285
+ }
286
+ elif scores_method in (ScoreMethod.TV, ScoreMethod.TV_INVERSE):
287
+ compute_score_args = {
288
+ "std": None,
289
+ "tv": vignette.getScoreValue(),
290
+ }
291
+ elif scores_method in (ScoreMethod.TOMO_CONSISTENCY):
292
+ compute_score_args = {
293
+ "std": None,
294
+ "tv": None,
295
+ "tomo_consitency": vignette.getScoreValue(),
296
+ }
297
+ else:
298
+ raise ValueError(f"score method unhandled: {scores_method.value}")
299
+
300
+ scores[vignette.getValue()] = (
301
+ vignette.getData(),
302
+ ComputedScore(**compute_score_args),
303
+ )
304
+ return scores, scores_method
305
+
306
+ def clearVignettes(self):
307
+ for vignette in self._vignettes:
308
+ self.layout().removeWidget(vignette)
309
+ vignette.setParent(None)
310
+ self._vignettesGroup.removeButton(vignette)
311
+ # vignette.deleteLater()
312
+ self._vignettes = []
173
313
 
174
- def setScores(self, scores: dict, score_method):
314
+ def setScores(self, scores: dict, score_method: ScoreMethod):
175
315
  """
176
316
  Expect a dictionary with possible values to select as key and
177
317
  (2D numpy.array, score) as value.
178
318
  Where the 2D numpy.array is the frame to display and the score if the
179
319
  "indicator" score to display with the frame.
180
- :param dict scores: with score as key and url or numpy array as value
320
+ :param dict scores: with score as key and a tuple (url|numpy array, ComputedScore) as value
321
+ :param ScoreMethod score_method: score kind to be display
181
322
  """
182
323
  if len(scores) < 1:
183
324
  return
184
- self.setLayout(qt.QGridLayout())
325
+
326
+ self.clearVignettes()
327
+
185
328
  i_row = 0
186
329
  scores_values = []
187
330
  for i_score, (value, (data, score_cls)) in enumerate(scores.items()):
@@ -190,6 +333,7 @@ class VignettesWidget(qt.QWidget):
190
333
  f"score is expected to be a dict with values as (v1: numpy.ndarray, v2: ComputedScore). v2 type Found: {type(score_cls)}"
191
334
  )
192
335
  scores_values.append(score_cls.get(score_method))
336
+ self.__score_method = ScoreMethod.from_value(score_method)
193
337
  highest_score_indices = numpy.nanargmax(scores_values)
194
338
  self._vignettesGroup = qt.QButtonGroup(self)
195
339
  self._vignettesGroup.setExclusive(True)
@@ -202,10 +346,9 @@ class VignettesWidget(qt.QWidget):
202
346
 
203
347
  for i_score, (value, (data, score_cls)) in enumerate(scores.items()):
204
348
  score = score_cls.get(score_method)
205
- i_column = i_score % self.DEFAULT_PLOT_PER_ROW
349
+ i_column = i_score % self.getNElementsPerRow()
206
350
  # TODO: instead of having a binary color we could use
207
- # colormap
208
- # TODO: synchronize zooms
351
+ # colormap from green (good score) to red (bad score score)
209
352
  if i_score == highest_score_indices or i_score in highest_score_indices:
210
353
  frame_color = qt.Qt.green
211
354
  else:
@@ -227,7 +370,7 @@ class VignettesWidget(qt.QWidget):
227
370
  yAxis.append(widget.getPlotWidget().getYAxis())
228
371
 
229
372
  self.layout().addWidget(widget, i_row, i_column)
230
- if i_column == self.DEFAULT_PLOT_PER_ROW - 1:
373
+ if i_column == self.getNElementsPerRow() - 1:
231
374
  i_row += 1
232
375
  if i_score == 0:
233
376
  # we cannot request all widget to keep the aspect ratio
@@ -238,7 +381,8 @@ class VignettesWidget(qt.QWidget):
238
381
  if self._withSpacer:
239
382
  spacer = qt.QWidget(self)
240
383
  spacer.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding)
241
- self.layout().addWidget(spacer, i_row + 1, self.DEFAULT_PLOT_PER_ROW - 1)
384
+ # to simplify we add a spacer at the end. We do not expect to have more than 999999 column
385
+ self.layout().addWidget(spacer, i_row + 1, self.MAX_NB_COLUMN)
242
386
 
243
387
  # constrain axis synchronization
244
388
  self.__constraintXAxis = SyncAxes(
@@ -339,6 +483,18 @@ class Vignette(qt.QToolButton):
339
483
  def getValue(self):
340
484
  return self._value
341
485
 
486
+ def getData(self) -> numpy.ndarray:
487
+ return self._plot.getImage().getData()
488
+
489
+ def getScoreValue(self):
490
+ return self._valueLabel.score
491
+
492
+ def getScoreName(self):
493
+ return self._scoreName
494
+
495
+ def getValueName(self):
496
+ return self._valueName
497
+
342
498
  def paintEvent(self, event):
343
499
  super().paintEvent(event)
344
500
  painter = qt.QPainter(self)
@@ -375,6 +531,7 @@ class ValueLabel(qt.QWidget):
375
531
  self, parent, value, score, value_name, score_name, value_format, score_format
376
532
  ):
377
533
  qt.QWidget.__init__(self, parent)
534
+ self._score = score
378
535
  self.setLayout(qt.QHBoxLayout())
379
536
  if value_format is not None:
380
537
  str_value = value_format.format(value)
@@ -390,3 +547,7 @@ class ValueLabel(qt.QWidget):
390
547
  txt = f"({score_name}: {str_score})"
391
548
  self._scoreLabel = qt.QLabel(txt, self)
392
549
  self.layout().addWidget(self._scoreLabel)
550
+
551
+ @property
552
+ def score(self):
553
+ return self._score
@@ -54,12 +54,6 @@ except ImportError: # pragma: no cover
54
54
  has_PIL = False # pragma: no cover
55
55
  else:
56
56
  has_PIL = True
57
- try:
58
- import cv2 # pragma: no cover
59
- except ImportError: # pragma: no cover
60
- has_cv2 = False # pragma: no cover
61
- else:
62
- has_cv2 = True
63
57
  import os
64
58
  import time
65
59
 
@@ -112,7 +106,7 @@ class DataViewer(qt.QMainWindow):
112
106
 
113
107
  # connect signal / slot
114
108
  self._controls.sigDisplayModeChanged.connect(self._updateDisplay)
115
- self._controls.sigDisplayModeChanged.connect(self._reportSettingsUpdate)
109
+ self._controls.sigDisplayModeChanged.connect(self.sigConfigChanged)
116
110
 
117
111
  def getPlotWidget(self):
118
112
  return self._viewer.getPlotWidget()
@@ -259,9 +253,6 @@ class DataViewer(qt.QMainWindow):
259
253
  else:
260
254
  self._viewer.reset()
261
255
 
262
- def _reportSettingsUpdate(self):
263
- self.sigConfigChanged.emit()
264
-
265
256
  def clear(self):
266
257
  self._scan = None
267
258
  self._viewer.reset()
@@ -607,14 +598,6 @@ class _TomwerUrlLoader(UrlLoader):
607
598
  def run(self):
608
599
  if self.url.file_path().endswith(".vol"):
609
600
  self.data = self._load_vol()
610
- elif self.url.scheme() == "cv2":
611
- if has_cv2:
612
- self.data = cv2.imread(self.url.file_path(), -1)
613
- else:
614
- self.data = None
615
- _logger.warning(
616
- f"need to install cv2 to read cast file {self.url.file_path()} because fabio fails to read float16"
617
- )
618
601
  elif self.url.scheme() == "tomwer":
619
602
  if has_PIL:
620
603
  self.data = numpy.array(Image.open(self.url.file_path()))
@@ -42,7 +42,7 @@ from silx.gui import qt
42
42
  from silx.io.url import DataUrl
43
43
  from silx.utils.enum import Enum as _Enum
44
44
 
45
- from tomwer.core.scan.hdf5scan import HDF5TomoScan
45
+ from tomwer.core.scan.nxtomoscan import NXtomoScan
46
46
  from tomwer.core.scan.scanbase import TomwerScanBase
47
47
  from tomwer.core.scan.scanfactory import ScanFactory
48
48
  from tomwer.core.utils.image import shift_img
@@ -158,7 +158,7 @@ class _FrameSelector(qt.QWidget):
158
158
  if self._scan.flats is not None:
159
159
  urls = self._scan.flats.values()
160
160
  elif type_selected == self.FrameType.PROJ:
161
- if isinstance(self._scan, HDF5TomoScan):
161
+ if isinstance(self._scan, NXtomoScan):
162
162
  angles_and_urls = self._scan.get_proj_angle_url(with_alignment=False)
163
163
  for angle, url in angles_and_urls.items():
164
164
  urls.append(url)
@@ -267,10 +267,10 @@ class _FramesSelector(qt.QWidget):
267
267
  self._rightSelector.layout().setContentsMargins(0, 0, 0, 0)
268
268
 
269
269
  # signal / slot connection
270
- self._leftSelector.sigCorrectionChanged.connect(self._leftFrameUpdReq)
271
- self._rightSelector.sigCorrectionChanged.connect(self._rightFrameUpdReq)
272
- self._leftSelector.sigSelectedUrlChanged.connect(self._leftFrameUpdReq)
273
- self._rightSelector.sigSelectedUrlChanged.connect(self._rightFrameUpdReq)
270
+ self._leftSelector.sigCorrectionChanged.connect(self.sigLeftFrameUpdateReq)
271
+ self._rightSelector.sigCorrectionChanged.connect(self.sigRightFrameUpdateReq)
272
+ self._leftSelector.sigSelectedUrlChanged.connect(self.sigLeftFrameUpdateReq)
273
+ self._rightSelector.sigSelectedUrlChanged.connect(self.sigRightFrameUpdateReq)
274
274
  self._leftScanCB.sigScanChanged.connect(self._leftScanChanged)
275
275
  self._rightScanCB.sigScanChanged.connect(self._rightScanChanged)
276
276
  self._leftScanCB.sigRequestScanAdd.connect(self._addScanFrmDialogLeft)
@@ -314,12 +314,6 @@ class _FramesSelector(qt.QWidget):
314
314
  def getRightUrl(self):
315
315
  return self._rightSelector.getCurrentUrl()
316
316
 
317
- def _rightFrameUpdReq(self):
318
- self.sigRightFrameUpdateReq.emit()
319
-
320
- def _leftFrameUpdReq(self):
321
- self.sigLeftFrameUpdateReq.emit()
322
-
323
317
  def _addScanFrmDialogLeft(self):
324
318
  self._addScanFrmDialog(link_to="left")
325
319
 
@@ -542,7 +536,7 @@ class _ScanComboBox(qt.QWidget):
542
536
 
543
537
  # connect signal / slot
544
538
  self._scansCB.currentIndexChanged.connect(self._scanChanged)
545
- self._addButton.pressed.connect(self._addScanCallback)
539
+ self._addButton.released.connect(self.sigRequestScanAdd)
546
540
 
547
541
  def clear(self):
548
542
  self._scans = set()
@@ -574,6 +568,3 @@ class _ScanComboBox(qt.QWidget):
574
568
  return None
575
569
  else:
576
570
  return scan
577
-
578
- def _addScanCallback(self):
579
- self.sigRequestScanAdd.emit()
@@ -91,8 +91,8 @@ class TwoFramesShiftTab(qt.QTabWidget, _FrameShiftsBase):
91
91
  self._absoluteShiftWidget = Absolute2FramesShift(self)
92
92
  self.addTab(self._absoluteShiftWidget, self.ShiftMode.ABSOLUTE.value)
93
93
  # connect signal / slot
94
- self._absoluteShiftWidget.sigShiftsChanged.connect(self._propagateSignal)
95
- self._relativeShiftWidget.sigShiftsChanged.connect(self._propagateSignal)
94
+ self._absoluteShiftWidget.sigShiftsChanged.connect(self.sigShiftsChanged)
95
+ self._relativeShiftWidget.sigShiftsChanged.connect(self.sigShiftsChanged)
96
96
  # set up
97
97
  self.setCurrentWidget(self._relativeShiftWidget)
98
98
  self._relativeShiftWidget.setFocus(qt.Qt.OtherFocusReason)
@@ -102,9 +102,6 @@ class TwoFramesShiftTab(qt.QTabWidget, _FrameShiftsBase):
102
102
  def getRelativeShiftWidget(self):
103
103
  return self._relativeShiftWidget
104
104
 
105
- def _propagateSignal(self, shift_infos: tuple):
106
- self.sigShiftsChanged.emit(shift_infos)
107
-
108
105
  def getShiftMode(self):
109
106
  if self.currentWidget() is self._relativeShiftWidget:
110
107
  return self.ShiftMode.RELATIVE
@@ -32,7 +32,7 @@ import logging
32
32
  import weakref
33
33
 
34
34
  from silx.gui import qt
35
- from tomoscan.scanbase import FOV
35
+ from nxtomo.nxobject.nxdetector import FOV
36
36
 
37
37
  from tomwer.core.scan.edfscan import EDFTomoScan
38
38
  from tomwer.core.scan.scanbase import TomwerScanBase
@@ -134,12 +134,6 @@ class SinogramViewer(qt.QMainWindow):
134
134
  self._plot.getPlotWidget().addImage(data=sinogram)
135
135
  self._plot.getPlotWidget().replot()
136
136
 
137
- def _editionLocked(self, locked):
138
- self.sigAxisEditionLocked.emit(locked)
139
-
140
- def _applyRequested(self):
141
- self.sigApply.emit()
142
-
143
137
  def _sinogram_loaded(self):
144
138
  """callback when the sinogram is loaded"""
145
139
  self._plot.setWaiting(False)
@@ -272,15 +266,12 @@ class SinogramOpts(qt.QDialog):
272
266
 
273
267
  # connect signal / slot
274
268
  self._buttons.button(qt.QDialogButtonBox.Apply).clicked.connect(
275
- self._updateSinogram
269
+ self.sigUpdateRequested
276
270
  )
277
271
 
278
272
  def setLineSelectionVisible(self, visible):
279
273
  self._lineSelWidget.setVisible(visible)
280
274
 
281
- def _updateSinogram(self):
282
- self.sigUpdateRequested.emit()
283
-
284
275
  def setScan(self, scan):
285
276
  old = self.blockSignals(True)
286
277
  # update line max and value
@@ -37,7 +37,7 @@ import unittest
37
37
  from silx.gui import qt
38
38
  from silx.gui.utils.testutils import TestCaseQt
39
39
 
40
- from tomwer.core.utils.scanutils import MockHDF5
40
+ from tomwer.core.utils.scanutils import MockNXtomo
41
41
  from tomwer.gui.visualization.diffviewer import DiffFrameViewer
42
42
 
43
43
  logging.disable(logging.INFO)
@@ -52,14 +52,14 @@ class TestDiffViewer(TestCaseQt):
52
52
  self._widget.setAttribute(qt.Qt.WA_DeleteOnClose)
53
53
 
54
54
  self.tmp_dir = tempfile.mkdtemp()
55
- self.scan1 = MockHDF5(
55
+ self.scan1 = MockNXtomo(
56
56
  scan_path=os.path.join(self.tmp_dir, "myscan"),
57
57
  n_proj=20,
58
58
  n_ini_proj=20,
59
59
  dim=10,
60
60
  ).scan
61
61
 
62
- self.scan2 = MockHDF5(
62
+ self.scan2 = MockNXtomo(
63
63
  scan_path=os.path.join(self.tmp_dir, "myscan"),
64
64
  n_proj=20,
65
65
  n_ini_proj=20,
@@ -2,11 +2,11 @@ import os
2
2
 
3
3
  import numpy
4
4
  import pytest
5
- from nxtomomill.nexus.nxtomo import NXtomo
5
+ from nxtomo.application.nxtomo import NXtomo
6
6
  from silx.gui import qt
7
- from tomoscan.esrf.scan.hdf5scan import ImageKey
7
+ from nxtomo.nxobject.nxdetector import ImageKey
8
8
 
9
- from tomwer.core.scan.hdf5scan import HDF5TomoScan
9
+ from tomwer.core.scan.nxtomoscan import NXtomoScan
10
10
  from tomwer.gui.visualization.nxtomometadata import NXtomoMetadataViewer
11
11
  from tomwer.tests.conftest import qtapp # noqa F401
12
12
 
@@ -37,7 +37,7 @@ def test_nx_editor(
37
37
  data_path=entry,
38
38
  )
39
39
 
40
- scan = HDF5TomoScan(file_path, entry)
40
+ scan = NXtomoScan(file_path, entry)
41
41
 
42
42
  # 2.0 create the widget and do the edition
43
43
  widget = NXtomoMetadataViewer()
@@ -38,7 +38,7 @@ import unittest
38
38
  from silx.gui import qt
39
39
  from silx.gui.utils.testutils import SignalListener, TestCaseQt
40
40
 
41
- from tomwer.core.utils.scanutils import MockHDF5
41
+ from tomwer.core.utils.scanutils import MockNXtomo
42
42
  from tomwer.gui.visualization import sinogramviewer
43
43
 
44
44
  logging.disable(logging.INFO)
@@ -53,7 +53,7 @@ class TestSinogramViewer(TestCaseQt):
53
53
  self._widget.setAttribute(qt.Qt.WA_DeleteOnClose)
54
54
 
55
55
  self.tmp_dir = tempfile.mkdtemp()
56
- self.scan = MockHDF5(
56
+ self.scan = MockNXtomo(
57
57
  scan_path=os.path.join(self.tmp_dir, "myscan"),
58
58
  n_proj=20,
59
59
  n_ini_proj=20,
@@ -39,7 +39,7 @@ from silx.gui import qt
39
39
  from silx.gui.utils.testutils import TestCaseQt
40
40
  from tomoscan.esrf.volume.edfvolume import EDFVolume
41
41
 
42
- from tomwer.core.utils.scanutils import MockHDF5
42
+ from tomwer.core.utils.scanutils import MockNXtomo
43
43
  from tomwer.gui.stacks import RadioStack, SliceStack
44
44
  from tomwer.tests.utils import skip_gui_test
45
45
 
@@ -67,7 +67,7 @@ class TestSliceStack(TestCaseQt):
67
67
  n_slice_per_scan = 1
68
68
  with tempfile.TemporaryDirectory() as root_dir:
69
69
  for i_scan in range(n_scan):
70
- scan = MockHDF5(
70
+ scan = MockNXtomo(
71
71
  scan_path=os.path.join(root_dir, f"scan{i_scan}"),
72
72
  n_proj=10,
73
73
  n_ini_proj=10,
@@ -103,7 +103,7 @@ class TestRadioStack(TestCaseQt):
103
103
  self.slider = weakref.ref(self._widget._viewer._qslider)
104
104
  self._tmp_dir = tempfile.mkdtemp()
105
105
  os.makedirs(self._tmp_dir, exist_ok=True)
106
- self._scan = MockHDF5(
106
+ self._scan = MockNXtomo(
107
107
  os.path.join(self._tmp_dir, "my_scan"),
108
108
  n_proj=30,
109
109
  dim=10,
@@ -39,7 +39,7 @@ from silx.gui import qt
39
39
  from silx.gui.utils.testutils import TestCaseQt
40
40
  from tomoscan.esrf.volume.hdf5volume import HDF5Volume
41
41
 
42
- from tomwer.core.utils.scanutils import MockHDF5
42
+ from tomwer.core.utils.scanutils import MockNXtomo
43
43
  from tomwer.gui.visualization.volumeviewer import VolumeViewer
44
44
 
45
45
  logging.disable(logging.INFO)
@@ -55,7 +55,7 @@ class TestDiffViewer(TestCaseQt):
55
55
 
56
56
  self.tmp_dir = tempfile.mkdtemp()
57
57
 
58
- self.scan = MockHDF5(
58
+ self.scan = MockNXtomo(
59
59
  scan_path=os.path.join(self.tmp_dir, "myscan"),
60
60
  n_proj=20,
61
61
  n_ini_proj=20,