tomwer 1.4.19__py3-none-any.whl → 1.5.2rc0__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 (123) hide show
  1. orangecontrib/tomwer/tutorials/simple_volume_local_reconstruction.ows +11 -8
  2. orangecontrib/tomwer/tutorials/simple_volume_to_slurm_reconstruction.ows +12 -9
  3. orangecontrib/tomwer/widgets/control/DataDiscoveryOW.py +1 -1
  4. orangecontrib/tomwer/widgets/control/NXTomomillMixIn.py +21 -10
  5. tomwer/app/axis.py +1 -1
  6. tomwer/app/reducedarkflat.py +2 -2
  7. tomwer/core/process/control/datalistener/rpcserver.py +2 -8
  8. tomwer/core/process/drac/binning.py +2 -2
  9. tomwer/core/process/drac/output.py +1 -1
  10. tomwer/core/process/edit/imagekeyeditor.py +4 -6
  11. tomwer/core/process/edit/nxtomoeditor.py +58 -20
  12. tomwer/core/process/output.py +6 -5
  13. tomwer/core/process/reconstruction/axis/anglemode.py +2 -2
  14. tomwer/core/process/reconstruction/axis/axis.py +1 -0
  15. tomwer/core/process/reconstruction/axis/mode.py +2 -2
  16. tomwer/core/process/reconstruction/axis/params.py +4 -4
  17. tomwer/core/process/reconstruction/axis/projectiontype.py +1 -1
  18. tomwer/core/process/reconstruction/axis/side.py +1 -1
  19. tomwer/core/process/reconstruction/darkref/darkrefs.py +2 -2
  20. tomwer/core/process/reconstruction/darkref/darkrefscopy.py +1 -1
  21. tomwer/core/process/reconstruction/darkref/params.py +2 -3
  22. tomwer/core/process/reconstruction/nabu/castvolume.py +4 -1
  23. tomwer/core/process/reconstruction/nabu/helical.py +3 -1
  24. tomwer/core/process/reconstruction/nabu/nabucommon.py +2 -2
  25. tomwer/core/process/reconstruction/nabu/nabuscores.py +1 -1
  26. tomwer/core/process/reconstruction/nabu/nabuslices.py +4 -4
  27. tomwer/core/process/reconstruction/nabu/plane.py +2 -2
  28. tomwer/core/process/reconstruction/nabu/target.py +1 -1
  29. tomwer/core/process/reconstruction/nabu/test/test_castvolume.py +2 -0
  30. tomwer/core/process/reconstruction/nabu/utils.py +15 -14
  31. tomwer/core/process/reconstruction/normalization/normalization.py +1 -1
  32. tomwer/core/process/reconstruction/normalization/params.py +4 -4
  33. tomwer/core/process/reconstruction/output.py +2 -2
  34. tomwer/core/process/reconstruction/saaxis/params.py +3 -3
  35. tomwer/core/process/reconstruction/saaxis/saaxis.py +1 -1
  36. tomwer/core/process/reconstruction/scores/params.py +2 -2
  37. tomwer/core/process/reconstruction/scores/scores.py +3 -3
  38. tomwer/core/process/reconstruction/tests/test_axis.py +1 -1
  39. tomwer/core/process/stitching/metadataholder.py +5 -5
  40. tomwer/core/process/stitching/nabustitcher.py +1 -4
  41. tomwer/core/process/tests/test_normalization.py +2 -1
  42. tomwer/core/scan/edfscan.py +3 -3
  43. tomwer/core/scan/nxtomoscan.py +3 -3
  44. tomwer/core/scan/scanbase.py +3 -3
  45. tomwer/core/scan/scantype.py +1 -1
  46. tomwer/core/settings.py +1 -1
  47. tomwer/core/tomwer_object.py +1 -1
  48. tomwer/core/utils/nxtomoutils.py +2 -2
  49. tomwer/core/utils/spec.py +6 -3
  50. tomwer/gui/cluster/slurm.py +3 -3
  51. tomwer/gui/configuration/level.py +1 -1
  52. tomwer/gui/control/actions.py +1 -1
  53. tomwer/gui/control/datadiscovery.py +1 -1
  54. tomwer/gui/control/datalist.py +1 -1
  55. tomwer/gui/control/reducedarkflatselector.py +4 -4
  56. tomwer/gui/control/series/seriescreator.py +5 -5
  57. tomwer/gui/control/tomoobjdisplaymode.py +1 -1
  58. tomwer/gui/dataportal/gallery.py +6 -6
  59. tomwer/gui/edit/imagekeyeditor.py +7 -9
  60. tomwer/gui/edit/nxtomoeditor.py +420 -112
  61. tomwer/gui/edit/tests/test_nx_editor.py +155 -83
  62. tomwer/gui/reconstruction/axis/CalculationWidget.py +1 -1
  63. tomwer/gui/reconstruction/axis/EstimatedCORWidget.py +12 -8
  64. tomwer/gui/reconstruction/axis/EstimatedCorComboBox.py +2 -2
  65. tomwer/gui/reconstruction/axis/InputWidget.py +3 -3
  66. tomwer/gui/reconstruction/darkref/darkrefwidget.py +2 -2
  67. tomwer/gui/reconstruction/nabu/castvolume.py +16 -1
  68. tomwer/gui/reconstruction/nabu/nabuconfig/base.py +2 -4
  69. tomwer/gui/reconstruction/nabu/nabuconfig/ctf.py +6 -6
  70. tomwer/gui/reconstruction/nabu/nabuconfig/nabuconfig.py +1 -1
  71. tomwer/gui/reconstruction/nabu/nabuconfig/phase.py +5 -5
  72. tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +2 -4
  73. tomwer/gui/reconstruction/nabu/nabuconfig/reconstruction.py +78 -52
  74. tomwer/gui/reconstruction/nabu/nabuflow.py +3 -13
  75. tomwer/gui/reconstruction/nabu/slices.py +11 -11
  76. tomwer/gui/reconstruction/nabu/test/test_cast_volume.py +19 -0
  77. tomwer/gui/reconstruction/nabu/volume.py +1 -1
  78. tomwer/gui/reconstruction/normalization/intensity.py +8 -12
  79. tomwer/gui/reconstruction/saaxis/corrangeselector.py +2 -2
  80. tomwer/gui/reconstruction/saaxis/dimensionwidget.py +71 -67
  81. tomwer/gui/reconstruction/sacommon.py +1 -1
  82. tomwer/gui/reconstruction/scores/scoreplot.py +18 -10
  83. tomwer/gui/reconstruction/tests/test_saaxis.py +18 -16
  84. tomwer/gui/stitching/SingleAxisStitchingWidget.py +8 -8
  85. tomwer/gui/stitching/StitchingOptionsWidget.py +1 -1
  86. tomwer/gui/stitching/alignment.py +8 -8
  87. tomwer/gui/stitching/config/axisparams.py +2 -2
  88. tomwer/gui/stitching/config/output.py +1 -1
  89. tomwer/gui/stitching/config/positionoveraxis.py +1 -1
  90. tomwer/gui/stitching/config/stitchingstrategies.py +4 -6
  91. tomwer/gui/stitching/config/tomoobjdetails.py +21 -13
  92. tomwer/gui/stitching/normalization.py +6 -6
  93. tomwer/gui/stitching/tests/test_ZStitchingWindow.py +8 -1
  94. tomwer/gui/stitching/tests/test_preview.py +10 -7
  95. tomwer/gui/stitching/tests/utils.py +27 -18
  96. tomwer/gui/stitching/z_stitching/fineestimation.py +7 -9
  97. tomwer/gui/stitching/z_stitching/tests/test_raw_estimation.py +18 -7
  98. tomwer/gui/stitching/z_stitching/tests/test_stitching_window.py +7 -2
  99. tomwer/gui/utils/buttons.py +53 -35
  100. tomwer/gui/utils/flow.py +2 -2
  101. tomwer/gui/utils/loadingmode.py +1 -1
  102. tomwer/gui/utils/unitsystem.py +44 -33
  103. tomwer/gui/utils/vignettes.py +1 -1
  104. tomwer/gui/visualization/dataviewer.py +7 -7
  105. tomwer/gui/visualization/diffviewer/diffviewer.py +4 -4
  106. tomwer/gui/visualization/diffviewer/shiftwidget.py +4 -6
  107. tomwer/gui/visualization/reconstructionparameters.py +35 -23
  108. tomwer/gui/visualization/scanoverview.py +28 -11
  109. tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py +25 -13
  110. tomwer/gui/visualization/test/test_reconstruction_parameters.py +2 -2
  111. tomwer/model/dataset.py +0 -0
  112. tomwer/synctools/utils/scanstages.py +3 -3
  113. tomwer/tasks/reconstruction/cleardarkflat.py +42 -0
  114. tomwer/tests/app/test_stitching.py +1 -1
  115. tomwer/tests/orangecontrib/tomwer/widgets/edit/tests/test_nxtomo_editor.py +32 -20
  116. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_nabu_widget.py +1 -1
  117. tomwer/version.py +3 -3
  118. {tomwer-1.4.19.dist-info → tomwer-1.5.2rc0.dist-info}/METADATA +8 -8
  119. {tomwer-1.4.19.dist-info → tomwer-1.5.2rc0.dist-info}/RECORD +123 -121
  120. {tomwer-1.4.19.dist-info → tomwer-1.5.2rc0.dist-info}/WHEEL +1 -1
  121. {tomwer-1.4.19.dist-info → tomwer-1.5.2rc0.dist-info}/entry_points.txt +0 -0
  122. {tomwer-1.4.19.dist-info → tomwer-1.5.2rc0.dist-info}/licenses/LICENSE +0 -0
  123. {tomwer-1.4.19.dist-info → tomwer-1.5.2rc0.dist-info}/top_level.txt +0 -0
@@ -2,12 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  import logging
4
4
  import weakref
5
-
5
+ import pint
6
6
  import numpy
7
7
  from silx.gui import qt
8
8
 
9
9
  from nxtomo.nxobject.nxdetector import ImageKey, FOV
10
10
  from nxtomo.utils.transformation import build_matrix
11
+ from nxtomo.paths.nxtomo import LATEST_VERSION as _LATEST_NXTOMO_VERSION
11
12
 
12
13
  from tomwer.core.process.edit.nxtomoeditor import NXtomoEditorTask, NXtomoEditorKeys
13
14
  from tomwer.core.scan.nxtomoscan import NXtomoScan
@@ -18,6 +19,8 @@ from tomwer.gui.edit.nxtomowarmer import NXtomoProxyWarmer
18
19
 
19
20
  _logger = logging.getLogger(__name__)
20
21
 
22
+ _ureg = pint.get_application_registry()
23
+
21
24
 
22
25
  class NXtomoEditorDialog(qt.QDialog):
23
26
  """
@@ -119,48 +122,58 @@ class NXtomoEditor(qt.QWidget):
119
122
  self._detectorQTWI = qt.QTreeWidgetItem(self._instrumentQTWI)
120
123
  self._detectorQTWI.setText(0, "detector")
121
124
  ## pixel size
122
- self._xPixelSizeQTWI = qt.QTreeWidgetItem(self._detectorQTWI)
123
- self._xPixelSizeQTWI.setText(0, "x pixel size")
124
- self._xPixelSizeMetricEntry = MetricEntry("", parent=self)
125
- self._xPixelSizeMetricEntry.layout().setContentsMargins(2, 2, 2, 2)
126
- self._tree.setItemWidget(self._xPixelSizeQTWI, 1, self._xPixelSizeMetricEntry)
127
- self._editableWidgets.append(self._xPixelSizeMetricEntry)
128
- self._xPixelSizeLB = PadlockButton(self)
129
- self._xPixelSizeLB.setMaximumSize(30, 30)
130
- self._lockerPBs.append(self._xPixelSizeLB)
131
- self._tree.setItemWidget(self._xPixelSizeQTWI, 2, self._xPixelSizeLB)
132
-
133
- self._yPixelSizeQTWI = qt.QTreeWidgetItem(self._detectorQTWI)
134
- self._yPixelSizeQTWI.setText(0, "y pixel size")
135
- self._yPixelSizeMetricEntry = MetricEntry("", parent=self)
136
- self._yPixelSizeMetricEntry.layout().setContentsMargins(2, 2, 2, 2)
137
- self._tree.setItemWidget(self._yPixelSizeQTWI, 1, self._yPixelSizeMetricEntry)
138
- self._editableWidgets.append(self._yPixelSizeMetricEntry)
139
- self._yPixelSizeLB = PadlockButton(self)
140
- self._yPixelSizeLB.setMaximumSize(30, 30)
141
- self._lockerPBs.append(self._yPixelSizeLB)
142
- self._tree.setItemWidget(self._yPixelSizeQTWI, 2, self._yPixelSizeLB)
143
-
144
- ## distance
125
+ self._xDetectorPixelSizeQTWI = qt.QTreeWidgetItem(self._detectorQTWI)
126
+ self._xDetectorPixelSizeQTWI.setText(0, "x pixel size")
127
+ self._xDetectorPixelSizeMetricEntry = MetricEntry("", parent=self)
128
+ self._xDetectorPixelSizeMetricEntry.layout().setContentsMargins(2, 2, 2, 2)
129
+ self._tree.setItemWidget(
130
+ self._xDetectorPixelSizeQTWI, 1, self._xDetectorPixelSizeMetricEntry
131
+ )
132
+ self._editableWidgets.append(self._xDetectorPixelSizeMetricEntry)
133
+ self._xDetectorPixelSizeLB = PadlockButton(self)
134
+ self._xDetectorPixelSizeLB.setMaximumSize(30, 30)
135
+ self._lockerPBs.append(self._xDetectorPixelSizeLB)
136
+ self._tree.setItemWidget(
137
+ self._xDetectorPixelSizeQTWI, 2, self._xDetectorPixelSizeLB
138
+ )
139
+
140
+ self._yDetectorPixelSizeQTWI = qt.QTreeWidgetItem(self._detectorQTWI)
141
+ self._yDetectorPixelSizeQTWI.setText(0, "y pixel size")
142
+ self._yDetectorPixelSizeMetricEntry = MetricEntry("", parent=self)
143
+ self._yDetectorPixelSizeMetricEntry.layout().setContentsMargins(2, 2, 2, 2)
144
+ self._tree.setItemWidget(
145
+ self._yDetectorPixelSizeQTWI, 1, self._yDetectorPixelSizeMetricEntry
146
+ )
147
+ self._editableWidgets.append(self._yDetectorPixelSizeMetricEntry)
148
+ self._yDetectorPixelSizeLB = PadlockButton(self)
149
+ self._yDetectorPixelSizeLB.setMaximumSize(30, 30)
150
+ self._lockerPBs.append(self._yDetectorPixelSizeLB)
151
+ self._tree.setItemWidget(
152
+ self._yDetectorPixelSizeQTWI, 2, self._yDetectorPixelSizeLB
153
+ )
154
+
155
+ ## sample - distance
145
156
  self._sampleDetectorDistanceQTWI = qt.QTreeWidgetItem(self._detectorQTWI)
146
- self._sampleDetectorDistanceQTWI.setText(0, "distance")
147
- self._distanceMetricEntry = MetricEntry("", parent=self)
148
- self._distanceMetricEntry.layout().setContentsMargins(2, 2, 2, 2)
157
+ self._sampleDetectorDistanceQTWI.setText(0, "sample-detector distance")
158
+ self._sampleDetectorDistanceMetricEntry = MetricEntry("", parent=self)
159
+ self._sampleDetectorDistanceMetricEntry.layout().setContentsMargins(2, 2, 2, 2)
160
+ self._tree.setItemWidget(
161
+ self._sampleDetectorDistanceQTWI, 1, self._sampleDetectorDistanceMetricEntry
162
+ )
163
+ self._editableWidgets.append(self._sampleDetectorDistanceMetricEntry)
164
+ self._sampleDetectorDistanceLB = PadlockButton(self)
165
+ self._sampleDetectorDistanceLB.setMaximumSize(30, 30)
166
+ self._lockerPBs.append(self._sampleDetectorDistanceLB)
149
167
  self._tree.setItemWidget(
150
- self._sampleDetectorDistanceQTWI, 1, self._distanceMetricEntry
168
+ self._sampleDetectorDistanceQTWI, 2, self._sampleDetectorDistanceLB
151
169
  )
152
- self._editableWidgets.append(self._distanceMetricEntry)
153
- self._distanceLB = PadlockButton(self)
154
- self._distanceLB.setMaximumSize(30, 30)
155
- self._lockerPBs.append(self._distanceLB)
156
- self._tree.setItemWidget(self._sampleDetectorDistanceQTWI, 2, self._distanceLB)
157
170
 
158
171
  ## field of view
159
172
  self._fieldOfViewQTWI = qt.QTreeWidgetItem(self._detectorQTWI)
160
173
  self._fieldOfViewQTWI.setText(0, "field of view")
161
174
  self._fieldOfViewCB = qt.QComboBox(self)
162
- for value in FOV.values():
163
- self._fieldOfViewCB.addItem(value)
175
+ for FOV_item in FOV:
176
+ self._fieldOfViewCB.addItem(FOV_item.value)
164
177
  self._tree.setItemWidget(self._fieldOfViewQTWI, 1, self._fieldOfViewCB)
165
178
  self._editableWidgets.append(self._fieldOfViewCB)
166
179
  self._fieldOfViewLB = PadlockButton(self)
@@ -188,9 +201,77 @@ class NXtomoEditor(qt.QWidget):
188
201
  self._yFlippedLB.setMaximumSize(30, 30)
189
202
  self._lockerPBs.append(self._yFlippedLB)
190
203
  self._tree.setItemWidget(self._yFlippedQTWI, 2, self._yFlippedLB)
204
+ # source
205
+ self._sourceQTWI = qt.QTreeWidgetItem(self._instrumentQTWI)
206
+ self._sourceQTWI.setText(0, "source")
207
+ ## source - sample
208
+ self._sampleSourceDistanceQTWI = qt.QTreeWidgetItem(self._sourceQTWI)
209
+ self._sampleSourceDistanceQTWI.setText(0, "sample-source distance")
210
+ self._sampleSourceDistanceMetricEntry = MetricEntry("", parent=self)
211
+ self._sampleSourceDistanceMetricEntry.setToolTip(
212
+ "sample-source distance. Expected to be negative"
213
+ )
214
+ self._sampleSourceDistanceMetricEntry.layout().setContentsMargins(2, 2, 2, 2)
215
+ self._tree.setItemWidget(
216
+ self._sampleSourceDistanceQTWI, 1, self._sampleSourceDistanceMetricEntry
217
+ )
218
+ self._editableWidgets.append(self._sampleSourceDistanceMetricEntry)
219
+ self._sampleSourceDistanceLB = PadlockButton(self)
220
+ self._sampleSourceDistanceLB.setMaximumSize(30, 30)
221
+ self._lockerPBs.append(self._sampleSourceDistanceLB)
222
+ self._tree.setItemWidget(
223
+ self._sampleSourceDistanceQTWI, 2, self._sampleSourceDistanceLB
224
+ )
191
225
  # 2: sample
192
226
  self._sampleQTWI = qt.QTreeWidgetItem(self._tree)
193
227
  self._sampleQTWI.setText(0, "sample")
228
+ # ## pixel size
229
+ self._xSamplePixelSizeQTWI = qt.QTreeWidgetItem(self._sampleQTWI)
230
+ self._xSamplePixelSizeQTWI.setText(0, "x pixel size")
231
+ self._xSamplePixelSizeMetricEntry = MetricEntry("", parent=self)
232
+ self._xSamplePixelSizeMetricEntry.layout().setContentsMargins(2, 2, 2, 2)
233
+ self._tree.setItemWidget(
234
+ self._xSamplePixelSizeQTWI, 1, self._xSamplePixelSizeMetricEntry
235
+ )
236
+ self._editableWidgets.append(self._xSamplePixelSizeMetricEntry)
237
+ self._xSamplePixelSizeLB = PadlockButton(self)
238
+ self._xSamplePixelSizeLB.setMaximumSize(30, 30)
239
+ self._lockerPBs.append(self._xSamplePixelSizeLB)
240
+ self._tree.setItemWidget(
241
+ self._xSamplePixelSizeQTWI, 2, self._xSamplePixelSizeLB
242
+ )
243
+
244
+ self._ySamplePixelSizeQTWI = qt.QTreeWidgetItem(self._sampleQTWI)
245
+ self._ySamplePixelSizeQTWI.setText(0, "y pixel size")
246
+ self._ySamplePixelSizeMetricEntry = MetricEntry("", parent=self)
247
+ self._ySamplePixelSizeMetricEntry.layout().setContentsMargins(2, 2, 2, 2)
248
+ self._tree.setItemWidget(
249
+ self._ySamplePixelSizeQTWI, 1, self._ySamplePixelSizeMetricEntry
250
+ )
251
+ self._editableWidgets.append(self._ySamplePixelSizeMetricEntry)
252
+ self._ySamplePixelSizeLB = PadlockButton(self)
253
+ self._ySamplePixelSizeLB.setMaximumSize(30, 30)
254
+ self._lockerPBs.append(self._ySamplePixelSizeLB)
255
+ self._tree.setItemWidget(
256
+ self._ySamplePixelSizeQTWI, 2, self._ySamplePixelSizeLB
257
+ )
258
+
259
+ ## propagation distance
260
+ self._propagationDistanceQTWI = qt.QTreeWidgetItem(self._sampleQTWI)
261
+ self._propagationDistanceQTWI.setText(0, "propagation distance")
262
+ self._propagationDistanceMetricEntry = MetricEntry("", parent=self)
263
+ self._propagationDistanceMetricEntry.layout().setContentsMargins(2, 2, 2, 2)
264
+ self._tree.setItemWidget(
265
+ self._propagationDistanceQTWI, 1, self._propagationDistanceMetricEntry
266
+ )
267
+ self._editableWidgets.append(self._propagationDistanceMetricEntry)
268
+ self._propagationDistanceLB = PadlockButton(self)
269
+ self._propagationDistanceLB.setMaximumSize(30, 30)
270
+ self._lockerPBs.append(self._propagationDistanceLB)
271
+ self._tree.setItemWidget(
272
+ self._propagationDistanceQTWI, 2, self._propagationDistanceLB
273
+ )
274
+
194
275
  ## x translation
195
276
  self._xTranslationQTWI = qt.QTreeWidgetItem(self._sampleQTWI)
196
277
  self._xTranslationQTWI.setText(0, "x translation")
@@ -215,12 +296,26 @@ class NXtomoEditor(qt.QWidget):
215
296
  # connect signal / slot
216
297
  self._energyEntry.editingFinished.connect(self._editingFinished)
217
298
  self._energyLockerLB.toggled.connect(self._editingFinished)
218
- self._xPixelSizeMetricEntry.editingFinished.connect(self._editingFinished)
219
- self._xPixelSizeLB.toggled.connect(self._editingFinished)
220
- self._yPixelSizeMetricEntry.editingFinished.connect(self._editingFinished)
221
- self._yPixelSizeLB.toggled.connect(self._editingFinished)
222
- self._distanceMetricEntry.editingFinished.connect(self._editingFinished)
223
- self._distanceLB.toggled.connect(self._editingFinished)
299
+ self._xDetectorPixelSizeMetricEntry.editingFinished.connect(
300
+ self._editingFinished
301
+ )
302
+ self._xDetectorPixelSizeLB.toggled.connect(self._editingFinished)
303
+ self._yDetectorPixelSizeMetricEntry.editingFinished.connect(
304
+ self._editingFinished
305
+ )
306
+ self._yDetectorPixelSizeLB.toggled.connect(self._editingFinished)
307
+ self._xSamplePixelSizeMetricEntry.editingFinished.connect(self._editingFinished)
308
+ self._xSamplePixelSizeLB.toggled.connect(self._editingFinished)
309
+ self._ySamplePixelSizeMetricEntry.editingFinished.connect(self._editingFinished)
310
+ self._ySamplePixelSizeLB.toggled.connect(self._editingFinished)
311
+ self._sampleDetectorDistanceMetricEntry.editingFinished.connect(
312
+ self._editingFinished
313
+ )
314
+ self._sampleDetectorDistanceLB.toggled.connect(self._editingFinished)
315
+ self._propagationDistanceMetricEntry.editingFinished.connect(
316
+ self._editingFinished
317
+ )
318
+ self._propagationDistanceLB.toggled.connect(self._editingFinished)
224
319
  self._fieldOfViewCB.currentIndexChanged.connect(self._editingFinished)
225
320
  self._fieldOfViewLB.toggled.connect(self._editingFinished)
226
321
  self._xFlippedCB.toggled.connect(self._editingFinished)
@@ -229,37 +324,106 @@ class NXtomoEditor(qt.QWidget):
229
324
  self._yFlippedLB.toggled.connect(self._editingFinished)
230
325
 
231
326
  def update_tree(self) -> None:
232
- if self.getScan() is not None:
327
+ scan = self.getScan()
328
+ if scan is not None:
233
329
  self._updateInstrument()
234
330
  self._updateSample()
331
+ self._updateSource()
235
332
  self._tree.resizeColumnToContents(0)
236
333
 
334
+ # update items according to NXtomo nexus version
335
+ nexus_version = scan.nexus_version
336
+ if nexus_version is None:
337
+ nexus_version = _LATEST_NXTOMO_VERSION
338
+ # handle nxtomo 1.4 version
339
+ allow_source_sample_distance = bool(nexus_version >= 1.4)
340
+ self._sampleSourceDistanceMetricEntry.setEnabled(
341
+ allow_source_sample_distance
342
+ )
343
+ self._sampleSourceDistanceLB.setEnabled(allow_source_sample_distance)
344
+
345
+ # handle nxtomo 1.5 version
346
+ allow_sample_pixel_size_and_prop_distance = bool(nexus_version >= 1.5)
347
+ self._xSamplePixelSizeMetricEntry.setEnabled(
348
+ allow_sample_pixel_size_and_prop_distance
349
+ )
350
+ self._xSamplePixelSizeLB.setEnabled(
351
+ allow_sample_pixel_size_and_prop_distance
352
+ )
353
+ self._ySamplePixelSizeMetricEntry.setEnabled(
354
+ allow_sample_pixel_size_and_prop_distance
355
+ )
356
+ self._ySamplePixelSizeLB.setEnabled(
357
+ allow_sample_pixel_size_and_prop_distance
358
+ )
359
+ self._propagationDistanceMetricEntry.setEnabled(
360
+ allow_sample_pixel_size_and_prop_distance
361
+ )
362
+ self._propagationDistanceLB.setEnabled(
363
+ allow_sample_pixel_size_and_prop_distance
364
+ )
365
+
237
366
  def _updateInstrument(self) -> None:
238
367
  scan = self.getScan()
239
368
  if scan is None:
240
369
  return
241
- else:
242
- for name, fct in {
243
- "energy": self._updateEnergy,
244
- "pixel size": self._updatePixelSize,
245
- "frame flips": self._updateFlipped,
246
- "field of view": self._updateFieldOfView,
247
- "sample-detector distance": self._updateDistance,
248
- }.items():
249
- try:
250
- fct(scan=scan)
251
- except Exception as e:
252
- _logger.error(f"Failed to update {name}. Error is {e}")
370
+ update_dict = {
371
+ "energy": self._updateEnergy,
372
+ "detector pixel size": self._updateDetectorPixelSize,
373
+ "frame flips": self._updateFlipped,
374
+ "field of view": self._updateFieldOfView,
375
+ "sample-detector distance": self._updateSampleDetectorDistance,
376
+ }
377
+ if scan.nexus_version is None or scan.nexus_version >= 1.4:
378
+ update_dict.update(
379
+ {
380
+ "sample-source distance": self._updateSampleSourceDistance,
381
+ }
382
+ )
383
+ if scan.nexus_version is None or scan.nexus_version >= 1.5:
384
+ update_dict.update(
385
+ {
386
+ "propagation distance": self._updatePropagationDistance,
387
+ }
388
+ )
389
+ for name, fct in update_dict.items():
390
+ try:
391
+ fct(scan=scan)
392
+ except Exception as e:
393
+ _logger.error(f"Could not update {name}. Error is {e}")
253
394
 
254
395
  def _updateSample(self) -> None:
255
396
  scan = self.getScan()
256
397
  if scan is None:
257
398
  return
258
- else:
399
+
400
+ update_dict = {
401
+ "translations": self._updateTranslations,
402
+ }
403
+ if scan.nexus_version is None or scan.nexus_version >= 1.5:
404
+ update_dict.update(
405
+ {
406
+ "sample pixel size": self._updateSamplePixelSize,
407
+ "propagation distance": self._updatePropagationDistance,
408
+ }
409
+ )
410
+ for name, fct in update_dict.items():
411
+ try:
412
+ fct(scan=scan)
413
+ except Exception as e:
414
+ _logger.error(f"Fail to update {name}. Error is {e}")
415
+
416
+ def _updateSource(self) -> None:
417
+ scan = self.getScan()
418
+ if scan is None:
419
+ return
420
+ if scan.nexus_version is None or scan.nexus_version >= 1.4:
259
421
  try:
260
- self._updateTranslations(scan=scan)
422
+ self._updateSampleSourceDistance(scan=scan)
261
423
  except Exception as e:
262
- _logger.error(f"Fail to update translations. Error is {e}")
424
+ _logger.error(
425
+ f"Could not update sample - source distance. Error is {e}"
426
+ )
263
427
 
264
428
  def _updateTranslations(self, scan: NXtomoScan) -> None:
265
429
  assert isinstance(scan, NXtomoScan)
@@ -325,26 +489,52 @@ class NXtomoEditor(qt.QWidget):
325
489
  if (not self._yFlippedLB.isLocked()) and flip_ud is not None:
326
490
  self._yFlippedCB.setChecked(flip_ud)
327
491
 
328
- def _updateDistance(self, scan: NXtomoScan) -> None:
329
- if not self._distanceLB.isLocked():
330
- # if in ''auto mode: we want to overwrite the NXtomo existing value by the one of the GUI
331
- self._distanceMetricEntry.setValue(scan.distance)
492
+ def _updateSampleDetectorDistance(self, scan: NXtomoScan) -> None:
493
+ if not self._sampleDetectorDistanceLB.isLocked():
494
+ # if in 'auto' mode: we want to overwrite the NXtomo existing value by the one of the GUI
495
+ self._sampleDetectorDistanceMetricEntry.setValue(
496
+ scan.sample_detector_distance
497
+ )
498
+
499
+ def _updateSampleSourceDistance(self, scan: NXtomoScan) -> None:
500
+ if not self._sampleSourceDistanceLB.isLocked():
501
+ self._sampleSourceDistanceMetricEntry.setValue(scan.source_sample_distance)
502
+
503
+ def _updatePropagationDistance(self, scan: NXtomoScan) -> None:
504
+ if not self._propagationDistanceLB.isLocked():
505
+ # if in 'auto' mode: we want to overwrite the NXtomo existing value by the one of the GUI
506
+ self._propagationDistanceMetricEntry.setValue(scan.propagation_distance)
332
507
 
333
508
  def _updateEnergy(self, scan: NXtomoScan) -> None:
334
509
  assert isinstance(scan, NXtomoScan)
335
510
  if not self._energyLockerLB.isLocked():
336
- # if in ''auto mode: we want to overwrite the NXtomo existing value by the one of the GUI
337
- energy = scan.energy
338
- self._energyEntry.setValue(energy)
339
-
340
- def _updatePixelSize(self, scan: NXtomoScan) -> None:
511
+ # if in 'auto' mode: we want to overwrite the NXtomo existing value by the one of the GUI
512
+ energy_in_kev: float | None = scan.energy
513
+ if energy_in_kev is not None:
514
+ # move from float to pint.Quantity
515
+ assert not isinstance(energy_in_kev, pint.Quantity)
516
+ self._energyEntry.setValue(energy_in_kev)
517
+
518
+ def _updateDetectorPixelSize(self, scan: NXtomoScan) -> None:
341
519
  assert isinstance(scan, NXtomoScan)
342
- if not self._xPixelSizeLB.isLocked():
343
- x_pixel_size = scan.x_pixel_size
344
- self._xPixelSizeMetricEntry.setValue(x_pixel_size)
345
- if not self._yPixelSizeLB.isLocked():
346
- y_pixel_size = scan.y_pixel_size
347
- self._yPixelSizeMetricEntry.setValue(y_pixel_size)
520
+ if not self._xDetectorPixelSizeLB.isLocked():
521
+ x_pixel_size = scan.detector_x_pixel_size
522
+ self._xDetectorPixelSizeMetricEntry.setValue(x_pixel_size)
523
+ if not self._yDetectorPixelSizeLB.isLocked():
524
+ y_pixel_size = scan.detector_y_pixel_size
525
+ self._yDetectorPixelSizeMetricEntry.setValue(y_pixel_size)
526
+
527
+ def _updateSamplePixelSize(self, scan: NXtomoScan) -> None:
528
+ if not self._xSamplePixelSizeLB.isLocked():
529
+ # if in 'auto' mode: we want to overwrite the NXtomo existing value by the one of the GUI
530
+ self._xSamplePixelSizeMetricEntry.setValue(
531
+ scan.get_sample_pixel_size(which="x", fallback_to_det_pixel_size=False)
532
+ )
533
+ if not self._ySamplePixelSizeLB.isLocked():
534
+ # if in 'auto' mode: we want to overwrite the NXtomo existing value by the one of the GUI
535
+ self._ySamplePixelSizeMetricEntry.setValue(
536
+ scan.get_sample_pixel_size(which="y", fallback_to_det_pixel_size=False)
537
+ )
348
538
 
349
539
  def _editingFinished(self, *args, **kwargs):
350
540
  self.sigEditingFinished.emit()
@@ -384,24 +574,61 @@ class NXtomoEditor(qt.QWidget):
384
574
  Return a dict with field full name as key
385
575
  and a tuple as value (field_value, is_locked)
386
576
 
577
+ field_value is given in keV for energies else in 'base units'
578
+
387
579
  limitation: for now sample position are not handled because this is a 'corner case' for now
388
580
  """
581
+
582
+ def to_base_units_magnitude_if_exists(value: None | pint.Quantity):
583
+ if value is None:
584
+ return None
585
+ else:
586
+ return value.to_base_units().magnitude
587
+
588
+ energy = self._energyEntry.getValue()
589
+ if energy is not None:
590
+ assert isinstance(energy, float)
591
+
389
592
  return {
390
593
  NXtomoEditorKeys.ENERGY: (
391
- self._energyEntry.getValue(),
594
+ energy,
392
595
  self._energyLockerLB.isLocked(),
393
596
  ),
394
- NXtomoEditorKeys.X_PIXEL_SIZE: (
395
- self._xPixelSizeMetricEntry.getValue(),
396
- self._xPixelSizeLB.isLocked(),
597
+ NXtomoEditorKeys.DETECTOR_X_PIXEL_SIZE: (
598
+ to_base_units_magnitude_if_exists(
599
+ self._xDetectorPixelSizeMetricEntry.getValue()
600
+ ),
601
+ self._xDetectorPixelSizeLB.isLocked(),
397
602
  ),
398
- NXtomoEditorKeys.Y_PIXEL_SIZE: (
399
- self._yPixelSizeMetricEntry.getValue(),
400
- self._yPixelSizeLB.isLocked(),
603
+ NXtomoEditorKeys.DETECTOR_Y_PIXEL_SIZE: (
604
+ to_base_units_magnitude_if_exists(
605
+ self._yDetectorPixelSizeMetricEntry.getValue()
606
+ ),
607
+ self._yDetectorPixelSizeLB.isLocked(),
608
+ ),
609
+ NXtomoEditorKeys.SAMPLE_X_PIXEL_SIZE: (
610
+ to_base_units_magnitude_if_exists(
611
+ self._xSamplePixelSizeMetricEntry.getValue()
612
+ ),
613
+ self._xSamplePixelSizeLB.isLocked(),
614
+ ),
615
+ NXtomoEditorKeys.SAMPLE_Y_PIXEL_SIZE: (
616
+ to_base_units_magnitude_if_exists(
617
+ self._ySamplePixelSizeMetricEntry.getValue()
618
+ ),
619
+ self._ySamplePixelSizeLB.isLocked(),
401
620
  ),
402
621
  NXtomoEditorKeys.SAMPLE_DETECTOR_DISTANCE: (
403
- self._distanceMetricEntry.getValue(),
404
- self._distanceLB.isLocked(),
622
+ to_base_units_magnitude_if_exists(
623
+ self._sampleDetectorDistanceMetricEntry.getValue()
624
+ ),
625
+ self._sampleDetectorDistanceLB.isLocked(),
626
+ ),
627
+ NXtomoEditorKeys.SAMPLE_SOURCE_DISTANCE: (
628
+ to_base_units_magnitude_if_exists(
629
+ self._sampleSourceDistanceMetricEntry.getValue()
630
+ ),
631
+ self._sampleSourceDistanceLB.isLocked(),
405
632
  ),
406
633
  NXtomoEditorKeys.FIELD_OF_VIEW: (
407
634
  self._fieldOfViewCB.currentText(),
@@ -415,55 +642,124 @@ class NXtomoEditor(qt.QWidget):
415
642
  self._yFlippedCB.isChecked(),
416
643
  self._yFlippedLB.isChecked(),
417
644
  ),
418
- NXtomoEditorKeys.X_TRANSLATION: (self._xTranslationQLE.getValue(),),
419
- NXtomoEditorKeys.Z_TRANSLATION: (self._zTranslationQLE.getValue(),),
645
+ NXtomoEditorKeys.PROPAGATION_DISTANCE: (
646
+ to_base_units_magnitude_if_exists(
647
+ self._propagationDistanceMetricEntry.getValue(),
648
+ ),
649
+ self._propagationDistanceLB.isLocked(),
650
+ ),
651
+ NXtomoEditorKeys.X_TRANSLATION: (
652
+ to_base_units_magnitude_if_exists(self._xTranslationQLE.getValue()),
653
+ ),
654
+ NXtomoEditorKeys.Z_TRANSLATION: (
655
+ to_base_units_magnitude_if_exists(self._zTranslationQLE.getValue()),
656
+ ),
420
657
  }
421
658
 
422
659
  def setConfiguration(self, config: dict) -> None:
423
- energy = config.get("instrument.beam.energy", None)
660
+ """
661
+ Load given configuration. We expect all quantities to be given:
662
+ * in keV for energies
663
+ * else in base units
664
+ """
665
+ # energy
666
+ energy = config.get(NXtomoEditorKeys.ENERGY, None)
424
667
  if energy is not None:
425
668
  energy, energy_locked = energy
669
+ assert energy is None or isinstance(
670
+ energy, float
671
+ ), "energy is expected to be dimensionless (float) already in keV"
426
672
  self._energyEntry.setValue(energy)
427
673
  self._energyLockerLB.setLock(energy_locked)
428
674
 
429
- x_pixel_size = config.get("instrument.detector.x_pixel_size", None)
430
- if x_pixel_size is not None:
431
- x_pixel_size, x_pixel_size_locked = x_pixel_size
432
- self._xPixelSizeMetricEntry.setValue(x_pixel_size)
433
- self._xPixelSizeLB.setLock(x_pixel_size_locked)
434
-
435
- y_pixel_size = config.get("instrument.detector.y_pixel_size", None)
436
- if y_pixel_size is not None:
437
- y_pixel_size, y_pixel_size_locked = y_pixel_size
438
- self._yPixelSizeMetricEntry.setValue(y_pixel_size)
439
- self._yPixelSizeLB.setLock(y_pixel_size_locked)
440
-
441
- detector_sample_distance = config.get("instrument.detector.distance", None)
442
- if detector_sample_distance is not None:
443
- detector_sample_distance, distance_locked = detector_sample_distance
444
- self._distanceMetricEntry.setValue(detector_sample_distance)
445
- self._distanceLB.setLock(x_pixel_size_locked)
446
-
447
- field_of_view = config.get("instrument.detector.field_of_view", None)
675
+ # detector pixel size
676
+ detector_x_pixel_size = config.get(NXtomoEditorKeys.DETECTOR_X_PIXEL_SIZE, None)
677
+ if detector_x_pixel_size is not None:
678
+ detector_x_pixel_size, detector_x_pixel_size_locked = detector_x_pixel_size
679
+ self._xDetectorPixelSizeMetricEntry.setValue(
680
+ detector_x_pixel_size, displayed_unit=_ureg.meter
681
+ )
682
+ self._xDetectorPixelSizeLB.setLock(detector_x_pixel_size_locked)
683
+
684
+ detector_y_pixel_size = config.get(NXtomoEditorKeys.DETECTOR_Y_PIXEL_SIZE, None)
685
+ if detector_y_pixel_size is not None:
686
+ detector_y_pixel_size, detector_y_pixel_size_locked = detector_y_pixel_size
687
+ self._yDetectorPixelSizeMetricEntry.setValue(
688
+ detector_y_pixel_size, displayed_unit=_ureg.meter
689
+ )
690
+ self._yDetectorPixelSizeLB.setLock(detector_y_pixel_size_locked)
691
+
692
+ # sample detector distance
693
+ sample_detector_distance = config.get(
694
+ NXtomoEditorKeys.SAMPLE_DETECTOR_DISTANCE, None
695
+ )
696
+ if sample_detector_distance is not None:
697
+ sample_detector_distance, distance_locked = sample_detector_distance
698
+ self._sampleDetectorDistanceMetricEntry.setValue(
699
+ sample_detector_distance, displayed_unit=_ureg.meter
700
+ )
701
+ self._sampleDetectorDistanceLB.setLock(distance_locked)
702
+
703
+ # field of view
704
+ field_of_view = config.get(NXtomoEditorKeys.FIELD_OF_VIEW, None)
448
705
  if field_of_view is not None:
449
706
  field_of_view, field_of_view_locked = field_of_view
450
- self._fieldOfViewCB.setCurrentText(field_of_view)
707
+ field_of_view = FOV.from_value(field_of_view)
708
+ self._fieldOfViewCB.setCurrentText(field_of_view.value)
451
709
  self._fieldOfViewLB.setLock(field_of_view_locked)
452
710
 
453
- x_flipped = config.get("instrument.detector.x_flipped", None)
711
+ # detector flips
712
+ x_flipped = config.get(NXtomoEditorKeys.X_FLIPPED, None)
454
713
  if x_flipped is not None:
455
714
  x_flipped, x_flipped_locked = x_flipped
456
715
  x_flipped = x_flipped in (True, "True", "true")
457
716
  self._xFlippedCB.setChecked(x_flipped)
458
717
  self._xFlippedLB.setLock(x_flipped_locked)
459
718
 
460
- y_flipped = config.get("instrument.detector.y_flipped", None)
719
+ y_flipped = config.get(NXtomoEditorKeys.Y_FLIPPED, None)
461
720
  if y_flipped is not None:
462
721
  y_flipped, y_flipped_locked = y_flipped
463
722
  y_flipped = y_flipped in (True, "True", "true")
464
723
  self._yFlippedCB.setChecked(y_flipped)
465
724
  self._yFlippedLB.setLock(y_flipped_locked)
466
725
 
726
+ # sample source distance
727
+ sample_source_distance = config.get(
728
+ NXtomoEditorKeys.SAMPLE_SOURCE_DISTANCE, None
729
+ )
730
+ if sample_source_distance is not None:
731
+ sample_source_distance, sample_source_distance_locked = (
732
+ sample_source_distance
733
+ )
734
+ if sample_source_distance >= 0:
735
+ _logger.warning("the sample-source is expected to be negative")
736
+ self._sampleSourceDistanceMetricEntry.setValue(sample_source_distance)
737
+ self._sampleSourceDistanceLB.setLock(sample_source_distance_locked)
738
+
739
+ # sample pixel size
740
+ sample_x_pixel_size = config.get(NXtomoEditorKeys.SAMPLE_X_PIXEL_SIZE, None)
741
+ if sample_x_pixel_size is not None:
742
+ sample_x_pixel_size, x_sample_pixel_size_locked = sample_x_pixel_size
743
+ self._xSamplePixelSizeMetricEntry.setValue(
744
+ sample_x_pixel_size, displayed_unit=_ureg.meter
745
+ )
746
+ self._xSamplePixelSizeLB.setLock(x_sample_pixel_size_locked)
747
+
748
+ sample_y_pixel_size = config.get(NXtomoEditorKeys.SAMPLE_Y_PIXEL_SIZE, None)
749
+ if sample_y_pixel_size is not None:
750
+ sample_y_pixel_size, sample_y_pixel_size_locked = sample_y_pixel_size
751
+ self._ySamplePixelSizeMetricEntry.setValue(
752
+ sample_y_pixel_size, displayed_unit=_ureg.meter
753
+ )
754
+ self._ySamplePixelSizeLB.setLock(sample_y_pixel_size_locked)
755
+
756
+ # propagation distance
757
+ propagation_distance = config.get(NXtomoEditorKeys.PROPAGATION_DISTANCE, None)
758
+ if propagation_distance is not None:
759
+ propagation_distance, propagation_distance_locked = propagation_distance
760
+ self._propagationDistanceMetricEntry.setValue(propagation_distance)
761
+ self._propagationDistanceLB.setChecked(propagation_distance_locked)
762
+
467
763
  def getConfigurationForTask(self) -> dict:
468
764
  """
469
765
  default configuration is stored as field: (field_value, filed_is_locked) when the task expects field_key: field_value
@@ -498,7 +794,7 @@ class _TranslationMetricEntry(MetricEntry):
498
794
  else:
499
795
  return super().validate(a0, a1)
500
796
 
501
- def __init__(self, name, default_unit="m", parent=None):
797
+ def __init__(self, name, default_unit=_ureg.meter, parent=None):
502
798
  super().__init__(name, default_unit=default_unit, parent=parent)
503
799
  self._qlePixelSize.setValidator(self.TranslationValidator(self))
504
800
 
@@ -517,18 +813,30 @@ class _TranslationMetricEntry(MetricEntry):
517
813
 
518
814
 
519
815
  class EnergyEntry(qt.QLineEdit):
816
+ """
817
+ QLineEdit enabling to handle an energy in keV
818
+ """
819
+
520
820
  def __init__(self, *args, **kwargs):
521
821
  super().__init__(*args, **kwargs)
522
822
  self.setValidator(MetricEntry.DoubleValidator())
523
823
 
524
- def setValue(self, a0):
525
- if a0 is None:
526
- a0 = "unknown"
824
+ def setValue(self, value_kev: float | None):
825
+ if not isinstance(value_kev, (float, type(None))):
826
+ raise TypeError(
827
+ f"a0 is expected to be {None} or an instance of {float}. Got {type(value_kev)}"
828
+ )
829
+
830
+ if value_kev is None:
831
+ value_kev = "unknown"
527
832
  else:
528
- a0 = str(a0)
529
- super().setText(a0)
833
+ value_kev = str(value_kev)
834
+ super().setText(value_kev)
530
835
 
531
836
  def getValue(self) -> float | None:
837
+ """
838
+ Return energy in keV or None
839
+ """
532
840
  txt = self.text().replace(" ", "")
533
841
  if txt in ("unknown", ""):
534
842
  return None