tomwer 1.4.19__py3-none-any.whl → 1.5.0__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 (126) 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/test/test_nabu_utils.py +9 -0
  31. tomwer/core/process/reconstruction/nabu/utils.py +18 -14
  32. tomwer/core/process/reconstruction/normalization/normalization.py +1 -1
  33. tomwer/core/process/reconstruction/normalization/params.py +4 -4
  34. tomwer/core/process/reconstruction/output.py +2 -2
  35. tomwer/core/process/reconstruction/saaxis/params.py +3 -3
  36. tomwer/core/process/reconstruction/saaxis/saaxis.py +5 -1
  37. tomwer/core/process/reconstruction/scores/params.py +2 -2
  38. tomwer/core/process/reconstruction/scores/scores.py +3 -3
  39. tomwer/core/process/reconstruction/tests/test_axis.py +1 -1
  40. tomwer/core/process/reconstruction/tests/test_saaxis.py +56 -66
  41. tomwer/core/process/stitching/metadataholder.py +5 -5
  42. tomwer/core/process/stitching/nabustitcher.py +1 -4
  43. tomwer/core/process/tests/test_normalization.py +2 -1
  44. tomwer/core/scan/edfscan.py +3 -3
  45. tomwer/core/scan/nxtomoscan.py +3 -3
  46. tomwer/core/scan/scanbase.py +3 -3
  47. tomwer/core/scan/scantype.py +1 -1
  48. tomwer/core/settings.py +1 -1
  49. tomwer/core/tomwer_object.py +1 -1
  50. tomwer/core/utils/nxtomoutils.py +2 -2
  51. tomwer/core/utils/spec.py +6 -3
  52. tomwer/gui/cluster/slurm.py +3 -3
  53. tomwer/gui/configuration/level.py +1 -1
  54. tomwer/gui/control/actions.py +1 -1
  55. tomwer/gui/control/datadiscovery.py +1 -1
  56. tomwer/gui/control/datalist.py +1 -1
  57. tomwer/gui/control/reducedarkflatselector.py +4 -4
  58. tomwer/gui/control/series/seriescreator.py +5 -5
  59. tomwer/gui/control/tomoobjdisplaymode.py +1 -1
  60. tomwer/gui/dataportal/gallery.py +6 -6
  61. tomwer/gui/edit/imagekeyeditor.py +7 -9
  62. tomwer/gui/edit/nxtomoeditor.py +420 -112
  63. tomwer/gui/edit/tests/test_nx_editor.py +155 -83
  64. tomwer/gui/reconstruction/axis/CalculationWidget.py +1 -1
  65. tomwer/gui/reconstruction/axis/EstimatedCORWidget.py +12 -8
  66. tomwer/gui/reconstruction/axis/EstimatedCorComboBox.py +2 -2
  67. tomwer/gui/reconstruction/axis/InputWidget.py +3 -3
  68. tomwer/gui/reconstruction/darkref/darkrefwidget.py +2 -2
  69. tomwer/gui/reconstruction/nabu/castvolume.py +16 -1
  70. tomwer/gui/reconstruction/nabu/nabuconfig/base.py +2 -4
  71. tomwer/gui/reconstruction/nabu/nabuconfig/ctf.py +6 -6
  72. tomwer/gui/reconstruction/nabu/nabuconfig/nabuconfig.py +1 -1
  73. tomwer/gui/reconstruction/nabu/nabuconfig/phase.py +5 -5
  74. tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +2 -4
  75. tomwer/gui/reconstruction/nabu/nabuconfig/reconstruction.py +78 -52
  76. tomwer/gui/reconstruction/nabu/nabuflow.py +3 -13
  77. tomwer/gui/reconstruction/nabu/slices.py +11 -11
  78. tomwer/gui/reconstruction/nabu/test/test_cast_volume.py +19 -0
  79. tomwer/gui/reconstruction/nabu/volume.py +1 -1
  80. tomwer/gui/reconstruction/normalization/intensity.py +8 -12
  81. tomwer/gui/reconstruction/saaxis/corrangeselector.py +2 -2
  82. tomwer/gui/reconstruction/saaxis/dimensionwidget.py +71 -67
  83. tomwer/gui/reconstruction/sacommon.py +1 -1
  84. tomwer/gui/reconstruction/scores/scoreplot.py +18 -10
  85. tomwer/gui/reconstruction/tests/test_saaxis.py +18 -16
  86. tomwer/gui/stitching/SingleAxisStitchingWidget.py +8 -8
  87. tomwer/gui/stitching/StitchingOptionsWidget.py +1 -1
  88. tomwer/gui/stitching/alignment.py +8 -8
  89. tomwer/gui/stitching/config/axisparams.py +2 -2
  90. tomwer/gui/stitching/config/output.py +1 -1
  91. tomwer/gui/stitching/config/positionoveraxis.py +1 -1
  92. tomwer/gui/stitching/config/stitchingstrategies.py +4 -6
  93. tomwer/gui/stitching/config/tomoobjdetails.py +21 -13
  94. tomwer/gui/stitching/normalization.py +6 -6
  95. tomwer/gui/stitching/tests/test_ZStitchingWindow.py +8 -1
  96. tomwer/gui/stitching/tests/test_preview.py +10 -7
  97. tomwer/gui/stitching/tests/utils.py +27 -18
  98. tomwer/gui/stitching/z_stitching/fineestimation.py +7 -9
  99. tomwer/gui/stitching/z_stitching/tests/test_raw_estimation.py +18 -7
  100. tomwer/gui/stitching/z_stitching/tests/test_stitching_window.py +7 -2
  101. tomwer/gui/utils/buttons.py +54 -36
  102. tomwer/gui/utils/flow.py +2 -2
  103. tomwer/gui/utils/loadingmode.py +1 -1
  104. tomwer/gui/utils/unitsystem.py +44 -33
  105. tomwer/gui/utils/vignettes.py +1 -1
  106. tomwer/gui/visualization/dataviewer.py +7 -7
  107. tomwer/gui/visualization/diffviewer/diffviewer.py +4 -4
  108. tomwer/gui/visualization/diffviewer/shiftwidget.py +4 -6
  109. tomwer/gui/visualization/reconstructionparameters.py +35 -23
  110. tomwer/gui/visualization/scanoverview.py +28 -11
  111. tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py +25 -13
  112. tomwer/gui/visualization/test/test_reconstruction_parameters.py +2 -2
  113. tomwer/model/dataset.py +0 -0
  114. tomwer/resources/gui/icons/borders.png +0 -0
  115. tomwer/synctools/utils/scanstages.py +3 -3
  116. tomwer/tasks/reconstruction/cleardarkflat.py +42 -0
  117. tomwer/tests/app/test_stitching.py +1 -1
  118. tomwer/tests/orangecontrib/tomwer/widgets/edit/tests/test_nxtomo_editor.py +32 -20
  119. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_nabu_widget.py +1 -1
  120. tomwer/version.py +2 -2
  121. {tomwer-1.4.19.dist-info → tomwer-1.5.0.dist-info}/METADATA +8 -8
  122. {tomwer-1.4.19.dist-info → tomwer-1.5.0.dist-info}/RECORD +126 -123
  123. {tomwer-1.4.19.dist-info → tomwer-1.5.0.dist-info}/WHEEL +1 -1
  124. {tomwer-1.4.19.dist-info → tomwer-1.5.0.dist-info}/entry_points.txt +0 -0
  125. {tomwer-1.4.19.dist-info → tomwer-1.5.0.dist-info}/licenses/LICENSE +0 -0
  126. {tomwer-1.4.19.dist-info → tomwer-1.5.0.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,13 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import h5py
3
5
 
4
6
  import numpy
5
7
  import pytest
8
+ import pint
6
9
  from silx.gui import qt
7
10
 
8
- from pyunitsystem.energysystem import EnergySI
9
- from pyunitsystem.metricsystem import MetricSystem
10
-
11
11
  from nxtomo.application.nxtomo import NXtomo
12
12
  from nxtomo.nxobject.nxdetector import ImageKey, FOV
13
13
  from nxtomo.utils.transformation import (
@@ -22,41 +22,63 @@ from tomwer.core.scan.nxtomoscan import NXtomoScan
22
22
  from tomwer.gui.edit.nxtomoeditor import NXtomoEditor, _TranslationMetricEntry
23
23
  from tomwer.tests.conftest import qtapp # noqa F401
24
24
 
25
+ _ureg = pint.get_application_registry()
25
26
 
26
- @pytest.mark.parametrize("x_pixel_size", (None, 0.12))
27
- @pytest.mark.parametrize("y_pixel_size", (None, 0.0065))
28
- @pytest.mark.parametrize("field_of_view", FOV.values())
29
- @pytest.mark.parametrize("distance", (None, 1.2))
30
- @pytest.mark.parametrize("energy", (None, 23.5))
31
- @pytest.mark.parametrize("x_flipped", (True, False))
27
+
28
+ @pytest.mark.parametrize("detector_x_pixel_size", (0.12 * _ureg.millimeter,))
29
+ @pytest.mark.parametrize("detector_y_pixel_size", (None,))
30
+ @pytest.mark.parametrize("sample_x_pixel_size", (None,))
31
+ @pytest.mark.parametrize("sample_y_pixel_size", (None, 0.0066 * _ureg.meter))
32
+ @pytest.mark.parametrize("field_of_view", [item.value for item in FOV])
33
+ @pytest.mark.parametrize("sample_detector_distance", (1.2 * _ureg.meter,))
34
+ @pytest.mark.parametrize("sample_source_distance", (-10.2 * _ureg.meter,))
35
+ @pytest.mark.parametrize("propagation_distance", (1.01 * _ureg.meter,))
36
+ @pytest.mark.parametrize("energy", (None, 23.5 * _ureg.keV))
37
+ @pytest.mark.parametrize("x_flipped", (False,))
32
38
  @pytest.mark.parametrize("y_flipped", (True, False))
33
- @pytest.mark.parametrize("x_translation", (None, numpy.ones(12), numpy.arange(12)))
34
- @pytest.mark.parametrize("z_translation", (None, numpy.zeros(12), numpy.arange(12, 24)))
39
+ @pytest.mark.parametrize(
40
+ "x_translation",
41
+ (None, numpy.ones(12) * _ureg.meter, numpy.arange(12) * _ureg.meter),
42
+ )
43
+ @pytest.mark.parametrize(
44
+ "z_translation",
45
+ (None, numpy.zeros(12) * _ureg.meter, numpy.arange(12, 24) * _ureg.meter),
46
+ )
35
47
  def test_nx_editor(
36
48
  tmp_path,
37
49
  qtapp, # noqa F811
38
- x_pixel_size,
39
- y_pixel_size,
50
+ detector_x_pixel_size: pint.Quantity | None,
51
+ detector_y_pixel_size: pint.Quantity | None,
52
+ sample_x_pixel_size: pint.Quantity | None,
53
+ sample_y_pixel_size: pint.Quantity | None,
54
+ propagation_distance: pint.Quantity | None,
55
+ sample_source_distance: pint.Quantity | None,
40
56
  field_of_view,
41
- distance,
42
- energy,
57
+ sample_detector_distance: pint.Quantity,
58
+ energy: pint.Quantity,
43
59
  x_flipped,
44
60
  y_flipped,
45
- x_translation,
46
- z_translation,
61
+ x_translation: pint.Quantity,
62
+ z_translation: pint.Quantity,
47
63
  ):
48
64
  # 1.0 create nx tomo with raw data
49
65
  nx_tomo = NXtomo()
50
- nx_tomo.instrument.detector.x_pixel_size = x_pixel_size
51
- nx_tomo.instrument.detector.y_pixel_size = y_pixel_size
52
- nx_tomo.instrument.detector.field_of_view = field_of_view
53
- nx_tomo.instrument.detector.distance = distance
54
66
  nx_tomo.energy = energy
55
- nx_tomo.sample.x_translation = x_translation
56
- nx_tomo.sample.z_translation = z_translation
67
+ nx_tomo.instrument.detector.x_pixel_size = detector_x_pixel_size
68
+ nx_tomo.instrument.detector.y_pixel_size = detector_y_pixel_size
69
+ nx_tomo.instrument.detector.field_of_view = field_of_view
70
+ nx_tomo.instrument.detector.distance = sample_detector_distance
57
71
  nx_tomo.instrument.detector.image_key_control = [ImageKey.PROJECTION.value] * 12
58
72
  nx_tomo.instrument.detector.data = numpy.empty(shape=(12, 10, 10))
59
- nx_tomo.sample.rotation_angle = numpy.linspace(0, 20, num=12)
73
+
74
+ nx_tomo.sample.rotation_angle = numpy.linspace(0, 20, num=12) * _ureg.degree
75
+ nx_tomo.sample.x_translation = x_translation
76
+ nx_tomo.sample.z_translation = z_translation
77
+ nx_tomo.sample.x_pixel_size = sample_x_pixel_size
78
+ nx_tomo.sample.y_pixel_size = sample_y_pixel_size
79
+ nx_tomo.sample.propagation_distance = propagation_distance
80
+
81
+ nx_tomo.instrument.source.distance = sample_source_distance
60
82
 
61
83
  nx_tomo.instrument.detector.transformations.add_transformation(
62
84
  DetZFlipTransformation(flip=x_flipped)
@@ -82,16 +104,32 @@ def test_nx_editor(
82
104
  def check_metric(expected_value, current_value):
83
105
  if expected_value is None:
84
106
  return current_value is None
85
- return numpy.isclose(expected_value, float(current_value))
86
-
87
- assert check_metric(x_pixel_size, widget._xPixelSizeMetricEntry.getValue())
88
- assert widget._xPixelSizeMetricEntry._qcbUnit.currentText() == "m"
89
- assert check_metric(y_pixel_size, widget._yPixelSizeMetricEntry.getValue())
90
- assert widget._yPixelSizeMetricEntry._qcbUnit.currentText() == "m"
91
-
92
- assert check_metric(distance, widget._distanceMetricEntry.getValue())
93
- assert widget._distanceMetricEntry._qcbUnit.currentText() == "m"
107
+ return numpy.isclose(expected_value, current_value)
94
108
 
109
+ assert check_metric(
110
+ detector_x_pixel_size, widget._xDetectorPixelSizeMetricEntry.getValue()
111
+ )
112
+ assert widget._xDetectorPixelSizeMetricEntry._qcbUnit.currentText() == "m"
113
+ assert check_metric(
114
+ detector_y_pixel_size, widget._yDetectorPixelSizeMetricEntry.getValue()
115
+ )
116
+ assert widget._yDetectorPixelSizeMetricEntry._qcbUnit.currentText() == "m"
117
+ assert check_metric(
118
+ sample_detector_distance, widget._sampleDetectorDistanceMetricEntry.getValue()
119
+ )
120
+ assert widget._sampleDetectorDistanceMetricEntry._qcbUnit.currentText() == "m"
121
+ assert check_metric(
122
+ sample_x_pixel_size, widget._xSamplePixelSizeMetricEntry.getValue()
123
+ )
124
+ assert check_metric(
125
+ sample_y_pixel_size, widget._ySamplePixelSizeMetricEntry.getValue()
126
+ )
127
+ assert check_metric(
128
+ sample_source_distance, widget._sampleSourceDistanceMetricEntry.getValue()
129
+ )
130
+ assert check_metric(
131
+ propagation_distance, widget._propagationDistanceMetricEntry.getValue()
132
+ )
95
133
  assert field_of_view == widget._fieldOfViewCB.currentText()
96
134
  assert x_flipped == widget._xFlippedCB.isChecked()
97
135
  assert y_flipped == widget._yFlippedCB.isChecked()
@@ -99,12 +137,18 @@ def test_nx_editor(
99
137
  if energy is None:
100
138
  assert widget._energyEntry.getValue() is None
101
139
  else:
102
- assert numpy.isclose(energy, widget._energyEntry.getValue())
140
+ assert numpy.isclose(
141
+ energy.to(_ureg.keV).magnitude, widget._energyEntry.getValue()
142
+ )
103
143
 
104
144
  def check_translation(expected_value, current_value):
105
145
  if expected_value is None:
106
146
  return current_value is None
107
147
  else:
148
+ expected_value = expected_value.to_base_units().magnitude
149
+ assert current_value is not None
150
+ if isinstance(current_value, pint.Quantity):
151
+ current_value = current_value.to_base_units().magnitude
108
152
  u_values = numpy.unique(expected_value)
109
153
  if u_values.size == 1:
110
154
  return float(current_value) == u_values[0]
@@ -118,15 +162,18 @@ def test_nx_editor(
118
162
 
119
163
  # 4.0 edit some parameters
120
164
  widget._energyEntry.setText("23.789")
121
- widget._xPixelSizeMetricEntry.setUnit("nm")
122
- widget._yPixelSizeMetricEntry.setValue(2.1e-7)
123
- widget._distanceMetricEntry.setValue("unknown")
165
+ widget._xDetectorPixelSizeMetricEntry.setUnit(_ureg.nanometer)
166
+ widget._yDetectorPixelSizeMetricEntry.setValue(2.1e-7)
167
+ widget._xSamplePixelSizeMetricEntry.setValue(value_m=5.6e-6)
168
+ widget._sampleDetectorDistanceMetricEntry.setValue("unknown")
169
+ widget._sampleSourceDistanceMetricEntry.setValue(-99)
170
+ widget._propagationDistanceMetricEntry.setValue(88)
124
171
  widget._fieldOfViewCB.setCurrentText(FOV.HALF.value)
125
172
  widget._xFlippedCB.setChecked(not x_flipped)
126
173
  widget._xTranslationQLE.setValue(1.8)
127
- widget._xTranslationQLE.setUnit("mm")
174
+ widget._xTranslationQLE.setUnit(_ureg.millimeter)
128
175
  widget._zTranslationQLE.setValue(2.8)
129
- widget._zTranslationQLE.setUnit("m")
176
+ widget._zTranslationQLE.setUnit(_ureg.meter)
130
177
 
131
178
  # 5.0
132
179
  task = NXtomoEditorTask(
@@ -143,19 +190,18 @@ def test_nx_editor(
143
190
  data_path=entry,
144
191
  )
145
192
 
146
- assert overwrite_nx_tomo.energy.value == 23.789
147
- assert overwrite_nx_tomo.energy.unit == EnergySI.KILOELECTRONVOLT
193
+ assert overwrite_nx_tomo.energy == 23.789 * _ureg.keV
148
194
 
149
- if x_pixel_size is None:
150
- assert overwrite_nx_tomo.instrument.detector.x_pixel_size.value is None
195
+ if detector_x_pixel_size is None:
196
+ assert overwrite_nx_tomo.instrument.detector.x_pixel_size is None
151
197
  else:
152
198
  assert numpy.isclose(
153
- overwrite_nx_tomo.instrument.detector.x_pixel_size.si_value,
154
- x_pixel_size * MetricSystem.NANOMETER.value,
199
+ overwrite_nx_tomo.instrument.detector.x_pixel_size,
200
+ 0.12 * _ureg.nanometer,
155
201
  )
156
- assert overwrite_nx_tomo.instrument.detector.y_pixel_size.si_value == 2.1e-7
202
+ assert overwrite_nx_tomo.instrument.detector.y_pixel_size == 2.1e-7 * _ureg.meter
157
203
 
158
- assert overwrite_nx_tomo.instrument.detector.distance.value is None
204
+ assert overwrite_nx_tomo.instrument.detector.distance is None
159
205
  assert overwrite_nx_tomo.instrument.detector.field_of_view is FOV.HALF
160
206
 
161
207
  final_transformation = NXtransformations()
@@ -172,15 +218,13 @@ def test_nx_editor(
172
218
  )
173
219
 
174
220
  numpy.testing.assert_array_almost_equal(
175
- overwrite_nx_tomo.sample.x_translation.si_value,
176
- numpy.array([1.8 * MetricSystem.MILLIMETER.value] * 12),
221
+ overwrite_nx_tomo.sample.x_translation,
222
+ (numpy.array([1.8] * 12) * _ureg.millimeter).to_base_units(),
177
223
  )
178
- assert overwrite_nx_tomo.sample.x_translation.unit is MetricSystem.METER
179
224
  numpy.testing.assert_array_almost_equal(
180
- overwrite_nx_tomo.sample.z_translation.si_value,
181
- numpy.array([2.8 * MetricSystem.METER.value] * 12),
225
+ overwrite_nx_tomo.sample.z_translation,
226
+ numpy.array([2.8] * 12) * _ureg.meter,
182
227
  )
183
- assert overwrite_nx_tomo.sample.z_translation.unit is MetricSystem.METER
184
228
  # end
185
229
  widget.setAttribute(qt.Qt.WA_DeleteOnClose)
186
230
  widget.close()
@@ -194,16 +238,20 @@ def test_nx_editor_lock(
194
238
  """test the pad lock buttons of the NXtomo editor"""
195
239
  # 1.0 create nx tomos with raw data
196
240
  nx_tomo_1 = NXtomo()
197
- nx_tomo_1.instrument.detector.x_pixel_size = 0.023
198
- nx_tomo_1.instrument.detector.y_pixel_size = 0.025
241
+ nx_tomo_1.instrument.source.distance = 54.8 * _ureg.meter
242
+ nx_tomo_1.instrument.detector.x_pixel_size = 0.023 * _ureg.meter
243
+ nx_tomo_1.instrument.detector.y_pixel_size = 0.025 * _ureg.meter
199
244
  nx_tomo_1.instrument.detector.field_of_view = "full"
200
- nx_tomo_1.instrument.detector.distance = 2.4
245
+ nx_tomo_1.instrument.detector.distance = 2.4 * _ureg.meter
201
246
  nx_tomo_1.instrument.detector.x_flipped = False
202
247
  nx_tomo_1.instrument.detector.y_flipped = True
203
- nx_tomo_1.energy = 5.9
248
+ nx_tomo_1.energy = 5.9 * _ureg.keV
204
249
  nx_tomo_1.instrument.detector.image_key_control = [ImageKey.PROJECTION.value] * 12
205
250
  nx_tomo_1.instrument.detector.data = numpy.empty(shape=(12, 10, 10))
206
- nx_tomo_1.sample.rotation_angle = numpy.linspace(0, 20, num=12)
251
+ nx_tomo_1.sample.rotation_angle = numpy.linspace(0, 20, num=12) * _ureg.degree
252
+ nx_tomo_1.sample.propagation_distance = 22.0 * _ureg.meter
253
+ nx_tomo_1.sample.x_pixel_size = 0.023 * _ureg.millimeter
254
+ nx_tomo_1.sample.y_pixel_size = 0.025 * _ureg.millimeter
207
255
 
208
256
  file_path = os.path.join(tmp_path, "nxtomo.nx")
209
257
  entry = "entry0000"
@@ -215,20 +263,21 @@ def test_nx_editor_lock(
215
263
  scan_1 = NXtomoScan(file_path, entry)
216
264
 
217
265
  nx_tomo_2 = NXtomo()
218
- nx_tomo_2.instrument.detector.x_pixel_size = 4.023
219
- nx_tomo_2.instrument.detector.y_pixel_size = 6.025
266
+ nx_tomo_2.instrument.source.distance = 8 * _ureg.meter
267
+ nx_tomo_2.instrument.detector.x_pixel_size = 4.023 * _ureg.meter
268
+ nx_tomo_2.instrument.detector.y_pixel_size = 6.025 * _ureg.meter
220
269
  nx_tomo_2.instrument.detector.field_of_view = "full"
221
- nx_tomo_2.instrument.detector.distance = 2.89
270
+ nx_tomo_2.instrument.detector.distance = 2.89 * _ureg.meter
222
271
  nx_tomo_2.instrument.detector.x_flipped = (
223
272
  not nx_tomo_1.instrument.detector.x_flipped
224
273
  )
225
274
  nx_tomo_2.instrument.detector.y_flipped = (
226
275
  not nx_tomo_1.instrument.detector.y_flipped
227
276
  )
228
- nx_tomo_2.energy = 5.754
277
+ nx_tomo_2.energy = 5.754 * _ureg.keV
229
278
  nx_tomo_2.instrument.detector.image_key_control = [ImageKey.PROJECTION.value] * 12
230
279
  nx_tomo_2.instrument.detector.data = numpy.empty(shape=(12, 10, 10))
231
- nx_tomo_2.sample.rotation_angle = numpy.linspace(0, 20, num=12)
280
+ nx_tomo_2.sample.rotation_angle = numpy.linspace(0, 20, num=12) * _ureg.degree
232
281
 
233
282
  file_path = os.path.join(tmp_path, "nxtomo.nx")
234
283
  entry = "entry0001"
@@ -249,12 +298,16 @@ def test_nx_editor_lock(
249
298
  widget.setScan(scan=scan_2)
250
299
  # widget values must be the same (NXtomo field value not loaded if the lockers are active)
251
300
  assert widget._energyEntry.getValue() == 5.9
252
- assert widget._xPixelSizeMetricEntry.getValue() == 0.023
253
- assert widget._yPixelSizeMetricEntry.getValue() == 0.025
254
- assert widget._distanceMetricEntry.getValue() == 2.4
301
+ assert widget._xDetectorPixelSizeMetricEntry.getValue() == 0.023 * _ureg.meter
302
+ assert widget._yDetectorPixelSizeMetricEntry.getValue() == 0.025 * _ureg.meter
303
+ assert widget._sampleDetectorDistanceMetricEntry.getValue() == 2.4 * _ureg.meter
304
+ assert widget._propagationDistanceMetricEntry.getValue() == 22.0 * _ureg.meter
255
305
  assert widget._fieldOfViewCB.currentText() == "Full"
256
306
  assert not widget._xFlippedCB.isChecked()
257
307
  assert widget._yFlippedCB.isChecked()
308
+ assert widget._xSamplePixelSizeMetricEntry.getValue() == 0.023 * _ureg.millimeter
309
+ assert widget._ySamplePixelSizeMetricEntry.getValue() == 0.025 * _ureg.millimeter
310
+ assert widget._sampleSourceDistanceMetricEntry.getValue() == 54.8 * _ureg.meter
258
311
 
259
312
  # 3.0 save the nxtomo
260
313
  task = NXtomoEditorTask(
@@ -271,20 +324,20 @@ def test_nx_editor_lock(
271
324
  data_path=entry,
272
325
  )
273
326
  assert (
274
- overwrite_nx_tomo.instrument.detector.x_pixel_size.value
275
- == nx_tomo_1.instrument.detector.x_pixel_size.value
327
+ overwrite_nx_tomo.instrument.detector.x_pixel_size
328
+ == nx_tomo_1.instrument.detector.x_pixel_size
276
329
  )
277
330
  assert (
278
- overwrite_nx_tomo.instrument.detector.y_pixel_size.value
279
- == nx_tomo_1.instrument.detector.y_pixel_size.value
331
+ overwrite_nx_tomo.instrument.detector.y_pixel_size
332
+ == nx_tomo_1.instrument.detector.y_pixel_size
280
333
  )
281
334
  assert (
282
335
  overwrite_nx_tomo.instrument.detector.field_of_view
283
336
  == nx_tomo_1.instrument.detector.field_of_view
284
337
  )
285
338
  assert (
286
- overwrite_nx_tomo.instrument.detector.distance.value
287
- == nx_tomo_1.instrument.detector.distance.value
339
+ overwrite_nx_tomo.instrument.detector.distance
340
+ == nx_tomo_1.instrument.detector.distance
288
341
  )
289
342
  assert (
290
343
  overwrite_nx_tomo.instrument.detector.x_flipped
@@ -294,8 +347,17 @@ def test_nx_editor_lock(
294
347
  overwrite_nx_tomo.instrument.detector.y_flipped
295
348
  == nx_tomo_1.instrument.detector.y_flipped
296
349
  )
297
- assert overwrite_nx_tomo.energy.value == nx_tomo_1.energy.value
298
-
350
+ assert overwrite_nx_tomo.energy == nx_tomo_1.energy
351
+ assert (
352
+ overwrite_nx_tomo.sample.propagation_distance
353
+ == nx_tomo_1.sample.propagation_distance
354
+ )
355
+ assert overwrite_nx_tomo.sample.x_pixel_size == nx_tomo_1.sample.x_pixel_size
356
+ assert overwrite_nx_tomo.sample.y_pixel_size == nx_tomo_1.sample.y_pixel_size
357
+ assert (
358
+ overwrite_nx_tomo.instrument.source.distance
359
+ == nx_tomo_1.instrument.source.distance
360
+ )
299
361
  assert widget.getConfiguration() == {
300
362
  "instrument.beam.energy": (5.9, True),
301
363
  "instrument.detector.distance": (2.4, True),
@@ -304,6 +366,10 @@ def test_nx_editor_lock(
304
366
  "instrument.detector.y_pixel_size": (0.025, True),
305
367
  "instrument.detector.x_flipped": (False, True),
306
368
  "instrument.detector.y_flipped": (True, True),
369
+ "instrument.source.distance": (54.8, True),
370
+ "sample.propagation_distance": (22.0, True),
371
+ "sample.x_pixel_size": (2.3e-5, True),
372
+ "sample.y_pixel_size": (2.5e-5, True),
307
373
  "sample.x_translation": (None,),
308
374
  "sample.z_translation": (None,),
309
375
  }
@@ -319,6 +385,12 @@ def test_nx_editor_lock(
319
385
  "instrument.detector.y_pixel_size": (0.025, False),
320
386
  "instrument.detector.x_flipped": (False, False),
321
387
  "instrument.detector.y_flipped": (True, False),
388
+ "instrument.source.distance": (54.8, False),
389
+ "sample.x_translation": (None,),
390
+ "sample.z_translation": (None,),
391
+ "sample.propagation_distance": (22.0, False),
392
+ "sample.x_pixel_size": (2.3e-5, False),
393
+ "sample.y_pixel_size": (2.5e-5, False),
322
394
  "sample.x_translation": (None,),
323
395
  "sample.z_translation": (None,),
324
396
  }
@@ -336,7 +408,7 @@ def test_nxtomo_editor_with_missing_paths(
336
408
  nx_tomo = NXtomo()
337
409
  nx_tomo.instrument.detector.image_key_control = [ImageKey.PROJECTION.value] * 12
338
410
  nx_tomo.instrument.detector.data = numpy.empty(shape=(12, 10, 10))
339
- nx_tomo.sample.rotation_angle = numpy.linspace(0, 20, num=12)
411
+ nx_tomo.sample.rotation_angle = numpy.linspace(0, 20, num=12) * _ureg.degree
340
412
 
341
413
  file_path = os.path.join(tmp_path, "nxtomo.nx")
342
414
  entry = "entry0000"
@@ -361,10 +433,10 @@ def test_nxtomo_editor_with_missing_paths(
361
433
 
362
434
  widget.setScan(scan=scan)
363
435
 
364
- widget._distanceMetricEntry.setValue(0.05)
365
- widget._energyEntry.setValue(50)
366
- widget._xPixelSizeMetricEntry.setValue(0.02)
367
- widget._yPixelSizeMetricEntry.setValue(0.03)
436
+ widget._sampleDetectorDistanceMetricEntry.setValue(0.05)
437
+ widget._energyEntry.setValue(50.0)
438
+ widget._xDetectorPixelSizeMetricEntry.setValue(0.02)
439
+ widget._yDetectorPixelSizeMetricEntry.setValue(0.03)
368
440
 
369
441
  # overwrite the NXtomo
370
442
  task = NXtomoEditorTask(
@@ -380,7 +452,7 @@ def test_nxtomo_editor_with_missing_paths(
380
452
  file_path=file_path,
381
453
  data_path=entry,
382
454
  )
383
- assert overwrite_nx_tomo.instrument.detector.x_pixel_size.value == 0.02
384
- assert overwrite_nx_tomo.instrument.detector.y_pixel_size.value == 0.03
385
- assert overwrite_nx_tomo.energy.value == 50
386
- assert overwrite_nx_tomo.instrument.detector.distance.value == 0.05
455
+ assert overwrite_nx_tomo.instrument.detector.x_pixel_size == 0.02 * _ureg.meter
456
+ assert overwrite_nx_tomo.instrument.detector.y_pixel_size == 0.03 * _ureg.meter
457
+ assert overwrite_nx_tomo.energy == 50 * _ureg.keV
458
+ assert overwrite_nx_tomo.instrument.detector.distance == 0.05 * _ureg.meter
@@ -212,5 +212,5 @@ class CalculationWidget(qt.QWidget):
212
212
  self.setMode(self._axis_params.mode)
213
213
 
214
214
  def setScan(self, scan: TomwerScanBase | None):
215
- self._estimatedCorWidget.setPixelSize(pixel_size_m=scan.x_pixel_size)
215
+ self._estimatedCorWidget.setPixelSize(pixel_size_m=scan.sample_x_pixel_size)
216
216
  self._estimatedCorWidget.setImageWidth(image_width=scan.dim_1)
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import pint
3
4
  from silx.gui import qt
4
5
  from processview.gui.DropDownWidget import DropDownWidget
5
6
  from tomwer.synctools.axis import QAxisRP
@@ -10,7 +11,8 @@ from tomwer.core.process.reconstruction.axis.side import Side
10
11
  from tomwer.core.process.reconstruction.axis import mode as axis_mode
11
12
  from tomwer.gui.fonts import FONT_SMALL
12
13
  from tomwer.gui.utils.scrollarea import QDoubleSpinBoxIgnoreWheel
13
- from pyunitsystem.metricsystem import MetricSystem
14
+
15
+ _ureg = pint.get_application_registry()
14
16
 
15
17
 
16
18
  class EstimatedCORWidget(qt.QGroupBox):
@@ -345,19 +347,19 @@ class _OffsetCalibration(qt.QGroupBox):
345
347
  def _motorOffsetChanged(self):
346
348
  """Recalculate the offset when the motor offset changes."""
347
349
  if self._pixelSizeSB.value() and self._motorOffsetSB.value():
348
- pixel_size_m = self._pixelSizeSB.value() * MetricSystem.MICROMETER.value
349
- motor_offset_m = self._motorOffsetSB.value() * MetricSystem.MILLIMETER.value
350
+ pixel_size = self._pixelSizeSB.value() * _ureg.micrometer
351
+ motor_offset = self._motorOffsetSB.value() * _ureg.millimeter
350
352
  with block_signals(self):
351
- self.setOffset(motor_offset_m / pixel_size_m)
353
+ self.setOffset((motor_offset / pixel_size).to_base_units().magnitude)
352
354
  self.sigOffsetChanged.emit()
353
355
 
354
356
  def _pixelSizeChanged(self):
355
357
  """Recalculate the offset when the pixel size changes."""
356
358
  if self._pixelSizeSB.value() and self._motorOffsetSB.value():
357
- pixel_size_m = self._pixelSizeSB.value() * MetricSystem.MICROMETER.value
358
- motor_offset_m = self._motorOffsetSB.value() * MetricSystem.MILLIMETER.value
359
+ pixel_size = self._pixelSizeSB.value() * _ureg.micrometer
360
+ motor_offset = self._motorOffsetSB.value() * _ureg.millimeter
359
361
  with block_signals(self):
360
- self.setOffset(motor_offset_m / pixel_size_m)
362
+ self.setOffset((motor_offset / pixel_size).to_base_units().magnitude)
361
363
  self.sigOffsetChanged.emit()
362
364
 
363
365
  def _xRotationAxisPixelPositionEdited(self):
@@ -387,7 +389,9 @@ class _OffsetCalibration(qt.QGroupBox):
387
389
  if pixel_size_m is None:
388
390
  self._pixelSizeSB.clear()
389
391
  else:
390
- self._pixelSizeSB.setValue(pixel_size_m / MetricSystem.MICROMETER.value)
392
+ self._pixelSizeSB.setValue(
393
+ (pixel_size_m * _ureg.meter).to(_ureg.micrometer).magnitude
394
+ )
391
395
 
392
396
  def _togglePixelSizeLock(self, locked: bool):
393
397
  """Lock or unlock the pixel size spin box."""
@@ -13,8 +13,8 @@ class _EstimatedCorValidator(qt.QDoubleValidator):
13
13
 
14
14
  def validate(self, a0: str, a1: int):
15
15
  """validate float or string that could be part of the side values..."""
16
- for value in Side.values():
17
- if value.startswith(a0):
16
+ for side in Side:
17
+ if side.value.startswith(a0):
18
18
  return (qt.QDoubleValidator.Acceptable, a0, a1)
19
19
  return super().validate(a0, a1)
20
20
 
@@ -153,8 +153,8 @@ class InputWidget(qt.QWidget):
153
153
  modes = set(modes)
154
154
  for mode in modes:
155
155
  try:
156
- axis_mode._InputType.from_value(mode)
157
- except Exception:
156
+ axis_mode._InputType(mode)
157
+ except ValueError:
158
158
  raise ValueError(
159
159
  f"mode {mode} should be an instance of {axis_mode._InputType}"
160
160
  )
@@ -166,7 +166,7 @@ class InputWidget(qt.QWidget):
166
166
  elif len(modes) < 0:
167
167
  raise ValueError("modes is empty")
168
168
  else:
169
- mode = axis_mode._InputType.from_value(modes.pop())
169
+ mode = axis_mode._InputType(modes.pop())
170
170
  if mode is axis_mode._InputType.SINOGRAM:
171
171
  self._sinogramGB.setEnabled(True)
172
172
  self._radioGB.setEnabled(False)
@@ -125,7 +125,7 @@ class _WhatCheckBox(qt.QWidget):
125
125
 
126
126
  def getMode(self) -> dkrf.ReduceMethod:
127
127
  if self._checkbox.isChecked():
128
- return dkrf.ReduceMethod.from_value(self._modeCB.currentText())
128
+ return dkrf.ReduceMethod(self._modeCB.currentText())
129
129
  else:
130
130
  return dkrf.ReduceMethod.NONE
131
131
 
@@ -136,7 +136,7 @@ class _WhatCheckBox(qt.QWidget):
136
136
  self.sigChanged.emit(self.getMode())
137
137
 
138
138
  def setMode(self, mode):
139
- mode = dkrf.ReduceMethod.from_value(mode)
139
+ mode = dkrf.ReduceMethod(mode)
140
140
  assert mode in dkrf.ReduceMethod
141
141
  self._checkbox.toggled.disconnect(self._modeChange)
142
142
  self._modeCB.currentIndexChanged.disconnect(self._modeChange)
@@ -130,7 +130,11 @@ class CastVolumeWidget(qt.QWidget):
130
130
  # overwrite
131
131
  self._overwriteCB = qt.QCheckBox("overwrite", self)
132
132
  self._overwriteCB.setChecked(True)
133
- self.layout().addWidget(self._overwriteCB, 16, 0, 1, 1)
133
+ self.layout().addWidget(self._overwriteCB, 16, 0, 1, 2)
134
+ # remove input volume
135
+ self._removeInputvolumeCB = qt.QCheckBox("remove input volume", self)
136
+ self._removeInputvolumeCB.setChecked(False)
137
+ self.layout().addWidget(self._removeInputvolumeCB, 17, 0, 1, 2)
134
138
  # spacer
135
139
  self._spacer = qt.QWidget(self)
136
140
  self._spacer.setSizePolicy(
@@ -156,6 +160,7 @@ class CastVolumeWidget(qt.QWidget):
156
160
  self._maxPixValue.textChanged.connect(self._configChanged)
157
161
  self._lowPercentileQSB.valueChanged.connect(self._configChanged)
158
162
  self._highPercentileQSB.valueChanged.connect(self._configChanged)
163
+ self._removeInputvolumeCB.toggled.connect(self._configChanged)
159
164
 
160
165
  def _updateCRatiosVis(self, *args, **kwargs):
161
166
  self._cRatiosLabel.setVisible(
@@ -200,6 +205,12 @@ class CastVolumeWidget(qt.QWidget):
200
205
  def setOverwrite(self, remove) -> None:
201
206
  self._overwriteCB.setChecked(remove)
202
207
 
208
+ def isRemoveInputVolume(self) -> bool:
209
+ return self._removeInputvolumeCB.isChecked()
210
+
211
+ def setRemoveInputVolume(self, remove: bool) -> None:
212
+ self._removeInputvolumeCB.setChecked(remove)
213
+
203
214
  def getDataMin(self) -> float | None:
204
215
  if (
205
216
  self._minMaxAuto.isChecked()
@@ -281,6 +292,7 @@ class CastVolumeWidget(qt.QWidget):
281
292
  "data_min": data_min,
282
293
  "data_max": data_max,
283
294
  "compression_ratios": self.getCompressionRatios(),
295
+ "remove_input_volume": self.isRemoveInputVolume(),
284
296
  }
285
297
 
286
298
  def setConfiguration(self, config: dict) -> None:
@@ -306,6 +318,9 @@ class CastVolumeWidget(qt.QWidget):
306
318
  self.setDataMax(config["data_max"])
307
319
  if "compression_ratios" in config:
308
320
  self.setCompressionRatios(config["compression_ratios"])
321
+ remove_input_volume = config.get("remove_input_volume", None)
322
+ if remove_input_volume is not None:
323
+ self.setRemoveInputVolume(remove=remove_input_volume)
309
324
 
310
325
  def _configChanged(self, *args, **kwargs):
311
326
  self.sigConfigChanged.emit()
@@ -38,7 +38,7 @@ class _NabuStageConfigBase:
38
38
  if stage is None:
39
39
  self.__stage = None
40
40
  else:
41
- self.__stage = _NabuStages.from_value(stage)
41
+ self.__stage = _NabuStages(stage)
42
42
  self._registeredWidgets = {}
43
43
  # list required widgets. Key is widget, value is the configuration
44
44
  # level
@@ -49,9 +49,7 @@ class _NabuStageConfigBase:
49
49
  :returns: _FilteringObject to use to define widget visibility
50
50
  """
51
51
  filteringObj = _FilteringObject(widget=widget)
52
- self._registeredWidgets[filteringObj] = ConfigurationLevel.from_value(
53
- config_level
54
- )
52
+ self._registeredWidgets[filteringObj] = ConfigurationLevel(config_level)
55
53
  return filteringObj
56
54
 
57
55
  def getConfiguration(self) -> dict:
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from silx.gui import qt
4
4
  from silx.gui.utils import blockSignals
5
- from silx.utils.enum import Enum as _Enum
5
+ from enum import Enum as _Enum
6
6
 
7
7
  from tomwer.core.process.reconstruction.nabu.utils import _NabuStages
8
8
  from tomwer.gui.reconstruction.nabu.nabuconfig import base
@@ -159,8 +159,8 @@ class CTFGeometry(qt.QGroupBox):
159
159
  super().__init__(parent, title=title)
160
160
  self.setLayout(qt.QFormLayout())
161
161
  self._beamShapeCB = QComboBoxIgnoreWheel(self)
162
- for shape in _BeamShape.values():
163
- self._beamShapeCB.addItem(shape)
162
+ for shape in _BeamShape:
163
+ self._beamShapeCB.addItem(shape.value)
164
164
  self.layout().addRow("shape", self._beamShapeCB)
165
165
 
166
166
  # cone beam settings
@@ -188,10 +188,10 @@ class CTFGeometry(qt.QGroupBox):
188
188
  self._beamShapeCB.currentIndexChanged.connect(self._confChanged)
189
189
 
190
190
  def getBeamShape(self):
191
- return _BeamShape.from_value(self._beamShapeCB.currentText())
191
+ return _BeamShape(self._beamShapeCB.currentText())
192
192
 
193
193
  def setBeamShape(self, shape: str | _BeamShape):
194
- shape = _BeamShape.from_value(shape)
194
+ shape = _BeamShape(shape)
195
195
  self._beamShapeCB.setCurrentText(shape.value)
196
196
 
197
197
  def _updateView(self, *args, **kwargs):
@@ -228,7 +228,7 @@ class CTFGeometry(qt.QGroupBox):
228
228
 
229
229
  beam_shape = ddict.get("beam_shape", None)
230
230
  if beam_shape is not None:
231
- if _BeamShape.from_value(beam_shape) is _BeamShape.CONE:
231
+ if _BeamShape(beam_shape) is _BeamShape.CONE:
232
232
  self._coneBeamSettings.setGeometry(params)
233
233
  self.setBeamShape(beam_shape)
234
234
 
@@ -135,7 +135,7 @@ class NabuConfiguration(qt.QWidget):
135
135
  ):
136
136
  widget.setVisible(True)
137
137
  else:
138
- stage = _NabuStages.from_value(stage)
138
+ stage = _NabuStages(stage)
139
139
  self._preProcessingGB.setVisible(stage is _NabuStages.PRE)
140
140
  self._reconstructionGB.setVisible(stage is _NabuStages.PROC)
141
141
  self._outputGB.setVisible(stage is _NabuStages.POST)