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
@@ -9,14 +9,14 @@ from __future__ import annotations
9
9
 
10
10
 
11
11
  import logging
12
+ import pint
12
13
 
13
14
  from silx.gui import qt
14
- from pyunitsystem.metricsystem import MetricSystem
15
-
16
- from tomwer.core.utils.char import MU_CHAR
17
15
 
18
16
  _logger = logging.getLogger(__name__)
19
17
 
18
+ _ureg = pint.get_application_registry()
19
+
20
20
 
21
21
  class PixelEntry(qt.QWidget):
22
22
  valueChanged = qt.Signal()
@@ -69,6 +69,17 @@ class MetricEntry(qt.QWidget):
69
69
  editingFinished = qt.Signal()
70
70
  """emit when editing is finished"""
71
71
 
72
+ valueChanged = qt.Signal()
73
+ """emit when the metric value change"""
74
+
75
+ _ExposedUnits: set[_ureg.Unit] = {
76
+ _ureg.nanometer,
77
+ _ureg.micrometer,
78
+ _ureg.mm,
79
+ _ureg.cm,
80
+ _ureg.m,
81
+ }
82
+
72
83
  class DoubleValidator(qt.QDoubleValidator):
73
84
  def __init__(self, *args, **kwargs):
74
85
  super().__init__(*args, **kwargs)
@@ -80,22 +91,15 @@ class MetricEntry(qt.QWidget):
80
91
  else:
81
92
  return super().validate(a0, a1)
82
93
 
83
- _CONVERSION = {
84
- "nm": MetricSystem.NANOMETER.value,
85
- f"{MU_CHAR}m": MetricSystem.MICROMETER.value,
86
- "mm": MetricSystem.MILLIMETER.value,
87
- "cm": MetricSystem.CENTIMETER.value,
88
- "m": MetricSystem.METER.value,
89
- }
90
-
91
- valueChanged = qt.Signal()
92
- """emit when the metric value change"""
93
-
94
- def __init__(self, name, value=0.0, default_unit="m", parent=None):
94
+ def __init__(
95
+ self, name, value=0.0, default_unit: pint.Unit = _ureg.meter, parent=None
96
+ ):
95
97
  qt.QWidget.__init__(self, parent)
96
- assert type(default_unit) is str
97
- assert default_unit in ("nm", "mm", "cm", "m", f"{MU_CHAR}m")
98
- self._base_unit = default_unit
98
+ if default_unit not in self._ExposedUnits:
99
+ raise ValueError(
100
+ f"Unable to use {default_unit} as default unit. Must be in {self._ExposedUnits}"
101
+ )
102
+ self._base_unit: pint.Unit = default_unit
99
103
 
100
104
  self.setLayout(qt.QHBoxLayout())
101
105
  self._label = qt.QLabel(name, parent=self)
@@ -105,11 +109,9 @@ class MetricEntry(qt.QWidget):
105
109
  self.layout().addWidget(self._qlePixelSize)
106
110
 
107
111
  self._qcbUnit = qt.QComboBox(parent=self)
108
- self._qcbUnit.addItem("nm")
109
- self._qcbUnit.addItem(f"{MU_CHAR}m")
110
- self._qcbUnit.addItem("mm")
111
- self._qcbUnit.addItem("cm")
112
- self._qcbUnit.addItem("m")
112
+ for unit in self._ExposedUnits:
113
+ self._qcbUnit.addItem(f"{unit:~}")
114
+
113
115
  self.layout().addWidget(self._qcbUnit)
114
116
  self._resetBaseUnit()
115
117
 
@@ -128,10 +130,9 @@ class MetricEntry(qt.QWidget):
128
130
  self._label.setText(text)
129
131
 
130
132
  def getCurrentUnit(self):
131
- assert self._qcbUnit.currentText() in self._CONVERSION
132
- return self._CONVERSION[self._qcbUnit.currentText()]
133
+ return _ureg.Unit(self._qcbUnit.currentText())
133
134
 
134
- def setValue(self, value_m, displayed_unit: str = "m"):
135
+ def setValue(self, value_m, displayed_unit: pint.Unit = _ureg.meter):
135
136
  """
136
137
 
137
138
  :param value: pixel size in international metric system (meter)
@@ -155,16 +156,20 @@ class MetricEntry(qt.QWidget):
155
156
  self._qlePixelSize.setText(txt)
156
157
  self._resetBaseUnit(displayed_unit=displayed_unit)
157
158
 
158
- def _resetBaseUnit(self, displayed_unit=None):
159
+ def _resetBaseUnit(self, displayed_unit: pint.Unit | None = None):
159
160
  """Simple reset of the combobox according to the base_unit"""
161
+ if displayed_unit is not None and not isinstance(displayed_unit, pint.Unit):
162
+ raise TypeError(
163
+ f"'displayed_unit' should be a {pint.Unit}. Got {type(displayed_unit)}"
164
+ )
160
165
  displayed_unit = displayed_unit or self._base_unit
161
- index = self._qcbUnit.findText(displayed_unit)
162
- if index is None:
166
+ index = self._qcbUnit.findText(f"{displayed_unit:~}")
167
+ if index < 0:
163
168
  raise ValueError("unrecognized base unit")
164
169
  else:
165
170
  self._qcbUnit.setCurrentIndex(index)
166
171
 
167
- def getValue(self) -> float:
172
+ def getValue(self) -> pint.Quantity | None:
168
173
  """
169
174
 
170
175
  :return: the value in meter
@@ -177,7 +182,13 @@ class MetricEntry(qt.QWidget):
177
182
  def setValidator(self, validator):
178
183
  self._qlePixelSize.setValidator(validator)
179
184
 
180
- def setUnit(self, unit):
181
- unit = str(MetricSystem.from_value(unit))
185
+ def setUnit(self, unit: pint.Unit):
186
+ assert isinstance(
187
+ unit, pint.Unit
188
+ ), f"unit is expected to be a pint.Unit. Got {type(unit)}"
189
+ unit = f"{unit:~}"
182
190
  idx = self._qcbUnit.findText(unit)
183
- self._qcbUnit.setCurrentIndex(idx)
191
+ if idx >= 0:
192
+ self._qcbUnit.setCurrentIndex(idx)
193
+ else:
194
+ _logger.error(f"Unhandled unit ({unit})")
@@ -302,7 +302,7 @@ class VignettesWidget(qt.QWidget):
302
302
  f"score is expected to be a dict with values as (v1: numpy.ndarray, v2: ComputedScore). v2 type Found: {type(score_cls)}"
303
303
  )
304
304
  scores_values.append(score_cls.get(score_method))
305
- self.__score_method = ScoreMethod.from_value(score_method)
305
+ self.__score_method = ScoreMethod(score_method)
306
306
  highest_score_indices = numpy.nanargmax(scores_values)
307
307
  self._vignettesGroup = qt.QButtonGroup(self)
308
308
  self._vignettesGroup.setExclusive(True)
@@ -1,7 +1,7 @@
1
1
  import weakref
2
2
 
3
3
  from silx.gui import qt
4
- from silx.utils.enum import Enum as _Enum
4
+ from enum import Enum as _Enum
5
5
 
6
6
  from tomwer.core.scan.scanbase import TomwerScanBase
7
7
  from tomwer.core.volume.volumebase import TomwerVolumeBase
@@ -318,26 +318,26 @@ class DisplayControl(qt.QWidget):
318
318
 
319
319
  :return: selected mode: display slices or radios
320
320
  """
321
- return _DisplayMode.from_value(self._displayMode.currentText())
321
+ return _DisplayMode(self._displayMode.currentText())
322
322
 
323
323
  def setDisplayMode(self, mode):
324
- mode = _DisplayMode.from_value(mode)
324
+ mode = _DisplayMode(mode)
325
325
  idx = self._displayMode.findText(mode.value)
326
326
  self._displayMode.setCurrentIndex(idx)
327
327
 
328
328
  def getRadioOption(self) -> _RadioMode:
329
- return _RadioMode.from_value(self._radioMode.currentText())
329
+ return _RadioMode(self._radioMode.currentText())
330
330
 
331
331
  def setRadioOption(self, opt):
332
- opt = _RadioMode.from_value(opt)
332
+ opt = _RadioMode(opt)
333
333
  idx = self._radioMode.findText(opt.value)
334
334
  self._radioMode.setCurrentIndex(idx)
335
335
 
336
336
  def getSliceOption(self) -> _SliceMode:
337
- return _SliceMode.from_value(self._sliceMode.currentText())
337
+ return _SliceMode(self._sliceMode.currentText())
338
338
 
339
339
  def setSliceOption(self, opt):
340
- opt = _SliceMode.from_value(opt)
340
+ opt = _SliceMode(opt)
341
341
  idx = self._sliceMode.findText(opt.value)
342
342
  self._sliceMode.setCurrentIndex(idx)
343
343
 
@@ -7,13 +7,13 @@ from __future__ import annotations
7
7
  import functools
8
8
  import logging
9
9
  import os
10
+ from enum import Enum
10
11
 
11
12
  import numpy
12
13
  from processview.core.dataset import DatasetIdentifier
13
14
  from silx.gui import icons as silx_icons
14
15
  from silx.gui import qt
15
16
  from silx.io.url import DataUrl
16
- from silx.utils.enum import Enum as _Enum
17
17
 
18
18
  from tomwer.core.scan.nxtomoscan import NXtomoScan
19
19
  from tomwer.core.scan.scanbase import TomwerScanBase
@@ -55,7 +55,7 @@ class _FrameSelector(qt.QWidget):
55
55
  sigSelectedUrlChanged = qt.Signal()
56
56
  """signal emitted when the selected url changed"""
57
57
 
58
- class FrameType(_Enum):
58
+ class FrameType(Enum):
59
59
  DARKS = "darks"
60
60
  FLATS = "flats"
61
61
  PROJ = "projections"
@@ -110,10 +110,10 @@ class _FrameSelector(qt.QWidget):
110
110
  return None
111
111
 
112
112
  def getTypeSelected(self):
113
- return self.FrameType.from_value(self._frameTypeCB.currentText())
113
+ return self.FrameType(self._frameTypeCB.currentText())
114
114
 
115
115
  def _typeChanged(self, *args, **kwargs):
116
- type_selected = self.FrameType.from_value(self.getTypeSelected())
116
+ type_selected = self.FrameType(self.getTypeSelected())
117
117
  self._proj_normalized.setVisible(
118
118
  type_selected in (self.FrameType.PROJ, self.FrameType.ALIGN_PROJ)
119
119
  )
@@ -5,11 +5,11 @@ contains gui for diffviewer shift
5
5
  """
6
6
  from __future__ import annotations
7
7
 
8
+ from enum import Enum
8
9
 
9
10
  import sys
10
11
 
11
12
  from silx.gui import qt
12
- from silx.utils.enum import Enum as _Enum
13
13
 
14
14
  from tomwer.utils import docstring
15
15
 
@@ -54,7 +54,7 @@ class TwoFramesShiftTab(qt.QTabWidget, _FrameShiftsBase):
54
54
  lrflip is a boolean notifying if we should flip image or not before applying the shift
55
55
  """
56
56
 
57
- class ShiftMode(_Enum):
57
+ class ShiftMode(Enum):
58
58
  RELATIVE = "relative shift"
59
59
  ABSOLUTE = "absolute shift"
60
60
 
@@ -206,7 +206,7 @@ class Relative2FramesShift(qt.QWidget, _FrameShiftsBase):
206
206
  self._controlWidget.setFocus(qt.Qt.OtherFocusReason)
207
207
 
208
208
  def move(self, direction: str):
209
- direction = _ControlArrowWidget.Direction.from_value(direction)
209
+ direction = _ControlArrowWidget.Direction(direction)
210
210
  shift = self._shiftStepSize.value()
211
211
  if direction is _ControlArrowWidget.Direction.RIGHT:
212
212
  self._xShiftQLE.setValue(self._xShiftQLE.value() + shift)
@@ -290,14 +290,12 @@ class _ControlArrowWidget(qt.QWidget):
290
290
  sigMoved = qt.Signal(str)
291
291
  """signal emit when one direction is activated. Will contain the direction in which we want to move"""
292
292
 
293
- class Direction(_Enum):
293
+ class Direction(Enum):
294
294
  LEFT = "left"
295
295
  RIGHT = "right"
296
296
  UP = "up"
297
297
  DOWN = "down"
298
298
 
299
- VALID_DIRECTIONS = Direction.members()
300
-
301
299
  ARROW_BUTTON_SIZE = 30
302
300
 
303
301
  QKEY_TO_DIR = {
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import numpy
3
4
  import logging
4
5
  from silx.gui import qt
5
6
  from tomwer.core.utils.char import BETA_CHAR, DELTA_CHAR
@@ -28,14 +29,16 @@ class ReconstructionParameters(qt.QWidget):
28
29
  self._deltaBetaQLE = qt.QLineEdit("", self)
29
30
  self._deltaBetaQLE.setReadOnly(True)
30
31
  self.layout().addRow(self._deltaBetaLabel, self._deltaBetaQLE)
31
- # distance
32
- self._distanceQLE = qt.QLineEdit("", self)
33
- self._distanceQLE.setReadOnly(True)
34
- self.layout().addRow("distance (cm)", self._distanceQLE)
32
+ # sample_detector distance
33
+ self._sampleDetectorDistanceQLE = qt.QLineEdit("", self)
34
+ self._sampleDetectorDistanceQLE.setReadOnly(True)
35
+ self.layout().addRow(
36
+ "sample-detector distance (cm)", self._sampleDetectorDistanceQLE
37
+ )
35
38
  # pixel size
36
- self._pixelSizeQLE = qt.QLineEdit("", self)
37
- self._pixelSizeQLE.setReadOnly(True)
38
- self.layout().addRow("pixel size (cm)", self._pixelSizeQLE)
39
+ self._voxelSizeQLE = qt.QLineEdit("", self)
40
+ self._voxelSizeQLE.setReadOnly(True)
41
+ self.layout().addRow("voxel size (cm)", self._voxelSizeQLE)
39
42
  # cor
40
43
  self._corQLE = qt.QLineEdit("", self)
41
44
  self._corQLE.setReadOnly(True)
@@ -89,7 +92,7 @@ class ReconstructionParameters(qt.QWidget):
89
92
  self._setPhaseMethod,
90
93
  self._setDeltaBeta,
91
94
  self._setDistance,
92
- self._setPixelSize,
95
+ self._setVoxelSize,
93
96
  self._setCor,
94
97
  self._setPaddingType,
95
98
  self._setHalfTomo,
@@ -136,29 +139,38 @@ class ReconstructionParameters(qt.QWidget):
136
139
  distance_cm = f"{distance_cm:.2}"
137
140
  else:
138
141
  distance_cm = ""
139
- self._distanceQLE.setText(distance_cm)
142
+ self._sampleDetectorDistanceQLE.setText(distance_cm)
140
143
 
141
- def _setPixelSize(self, metadata: dict):
144
+ def _setVoxelSize(self, metadata: dict):
142
145
  # voxel size can be stored as pixel size (old version) or voxel size (new version)
143
146
  recons_params = metadata.get("processing_options", {}).get("reconstruction", {})
144
- voxel_size_cm = recons_params.get("voxel_size_cm", None)
147
+ voxel_size_cm = recons_params.get("voxel_size_cm", [None] * 3)
148
+ # back compatibility when voxel was a scalar ( ~ nabu 2023 ?)
149
+ if numpy.isscalar(voxel_size_cm):
150
+ voxel_size_cm = [voxel_size_cm] * 3
145
151
 
146
152
  # now voxel size is expected to be a tuple of three elements
147
153
  if voxel_size_cm is not None:
148
- voxel_size_cm = voxel_size_cm[0]
149
- # FIXME: load_ini seems to fail to remove some char like '(' or ')'... to be fixed or investigate
150
- # simplest might be to filter those when dumping it to text file... ??? or to handle those at silx level
151
- if isinstance(voxel_size_cm, str):
152
- for char_to_ignore in (" ", "(", ")", "[", "]"):
153
- voxel_size_cm = voxel_size_cm.replace(char_to_ignore, "")
154
+
155
+ def clean_voxel_value(value):
156
+ if isinstance(value, str):
157
+ for char_to_ignore in (" ", "(", ")", "[", "]"):
158
+ value = value.replace(char_to_ignore, "")
159
+ return value
160
+
161
+ voxel_size_cm = [clean_voxel_value(value) for value in voxel_size_cm]
162
+
154
163
  else:
155
164
  # backward compatibility with old volume
156
- voxel_size_cm = recons_params.get("pixel_size_cm", None)
157
- if voxel_size_cm is not None:
158
- voxel_size_cm = f"{float(voxel_size_cm):.8}"
159
- self._pixelSizeQLE.setText(
160
- str(voxel_size_cm) if voxel_size_cm is not None else ""
161
- )
165
+ voxel_size_cm = recons_params.get("pixel_size_cm", [None] * 3)
166
+
167
+ voxel_size_cm = filter(None, voxel_size_cm)
168
+
169
+ def cast_voxel_value(value: float | None):
170
+ return f"{float(value):.8}"
171
+
172
+ voxel_size_cm = [cast_voxel_value(value) for value in voxel_size_cm]
173
+ self._voxelSizeQLE.setText("x".join(voxel_size_cm))
162
174
 
163
175
  def _setCor(self, metadata: dict):
164
176
  cor = (
@@ -47,10 +47,10 @@ class ScanOverviewWidget(qt.QWidget):
47
47
  self._estimatedCOR = qt.QTreeWidgetItem(self._frames)
48
48
  self._estimatedCOR.setText(0, "estimated cor")
49
49
 
50
- self._x_pixel_size = qt.QTreeWidgetItem(self._instrument)
51
- self._x_pixel_size.setText(0, "x pixel size")
52
- self._y_pixel_size = qt.QTreeWidgetItem(self._instrument)
53
- self._y_pixel_size.setText(0, "y pixel size")
50
+ self._detector_x_pixel_size = qt.QTreeWidgetItem(self._instrument)
51
+ self._detector_x_pixel_size.setText(0, "x pixel size")
52
+ self._detector_y_pixel_size = qt.QTreeWidgetItem(self._instrument)
53
+ self._detector_y_pixel_size.setText(0, "y pixel size")
54
54
 
55
55
  # 2: define sample
56
56
  self._sample = qt.QTreeWidgetItem(self._tree)
@@ -58,6 +58,11 @@ class ScanOverviewWidget(qt.QWidget):
58
58
  self._sample_name = qt.QTreeWidgetItem(self._sample)
59
59
  self._sample_name.setText(0, "name")
60
60
 
61
+ self._sample_x_pixel_size = qt.QTreeWidgetItem(self._sample)
62
+ self._sample_x_pixel_size.setText(0, "x pixel size")
63
+ self._sample_y_pixel_size = qt.QTreeWidgetItem(self._sample)
64
+ self._sample_y_pixel_size.setText(0, "y pixel size")
65
+
61
66
  # 3: other hight level items
62
67
  self._startTime = qt.QTreeWidgetItem(self._tree)
63
68
  self._startTime.setText(0, "start_time")
@@ -96,6 +101,7 @@ class ScanOverviewWidget(qt.QWidget):
96
101
  "times": self._updateTimes,
97
102
  "names": self._updateNames,
98
103
  "scan-range": self._updateScanRange,
104
+ "sample": self._updateSample,
99
105
  }
100
106
  for part_name, fct in parts.items():
101
107
  try:
@@ -107,7 +113,7 @@ class ScanOverviewWidget(qt.QWidget):
107
113
  def _updateInstrument(self, scan: TomwerScanBase):
108
114
  self._updateFrames(scan=scan)
109
115
  self._updateEnergy(scan=scan)
110
- self._updatePixelSize(scan=scan)
116
+ self._updateDetectorPixelSize(scan=scan)
111
117
 
112
118
  def _setColoredTxt(
113
119
  self, item, text, column=1, hightlight_red=False, hightlight_orange=False
@@ -125,7 +131,18 @@ class ScanOverviewWidget(qt.QWidget):
125
131
  item.setBackground(0, qt.QBrush(bkg_color))
126
132
 
127
133
  def _updateSample(self, scan: TomwerScanBase):
128
- pass
134
+ x_pixel_size = scan.sample_x_pixel_size
135
+ y_pixel_size = scan.sample_y_pixel_size
136
+ self._setColoredTxt(
137
+ item=self._sample_x_pixel_size,
138
+ text=f"{x_pixel_size} (m)",
139
+ hightlight_red=x_pixel_size in (None, 0.0, 1.0),
140
+ )
141
+ self._setColoredTxt(
142
+ item=self._sample_y_pixel_size,
143
+ text=f"{y_pixel_size} (m)",
144
+ hightlight_red=y_pixel_size in (None, 0.0, 1.0),
145
+ )
129
146
 
130
147
  def _updateTimes(self, scan: TomwerScanBase):
131
148
  self._startTime.setText(1, str(scan.start_time))
@@ -213,20 +230,20 @@ class ScanOverviewWidget(qt.QWidget):
213
230
  text=str(scan_range),
214
231
  )
215
232
 
216
- def _updatePixelSize(self, scan: TomwerScanBase):
233
+ def _updateDetectorPixelSize(self, scan: TomwerScanBase):
217
234
  assert isinstance(scan, TomwerScanBase)
218
235
  if isinstance(scan, EDFTomoScan):
219
236
  x_pixel_size = y_pixel_size = scan.pixel_size
220
237
  else:
221
- x_pixel_size = scan.x_pixel_size
222
- y_pixel_size = scan.y_pixel_size
238
+ x_pixel_size = scan.detector_x_pixel_size
239
+ y_pixel_size = scan.detector_y_pixel_size
223
240
  self._setColoredTxt(
224
- item=self._x_pixel_size,
241
+ item=self._detector_x_pixel_size,
225
242
  text=f"{x_pixel_size} (m)",
226
243
  hightlight_red=x_pixel_size in (None, 0.0, 1.0),
227
244
  )
228
245
  self._setColoredTxt(
229
- item=self._y_pixel_size,
246
+ item=self._detector_y_pixel_size,
230
247
  text=f"{y_pixel_size} (m)",
231
248
  hightlight_red=y_pixel_size in (None, 0.0, 1.0),
232
249
  )
@@ -1,6 +1,7 @@
1
1
  import os
2
2
 
3
3
  import numpy
4
+ import pint
4
5
  from nxtomo.application.nxtomo import NXtomo
5
6
  from silx.gui import qt
6
7
  from nxtomo.nxobject.nxdetector import ImageKey
@@ -9,6 +10,8 @@ from tomwer.core.scan.nxtomoscan import NXtomoScan
9
10
  from tomwer.gui.visualization.nxtomometadata import NXtomoMetadataViewer
10
11
  from tomwer.tests.conftest import qtapp # noqa F401
11
12
 
13
+ _ureg = pint.get_application_registry()
14
+
12
15
 
13
16
  def test_nx_editor(
14
17
  tmp_path,
@@ -16,18 +19,18 @@ def test_nx_editor(
16
19
  ):
17
20
  # 1.0 create nx tomo with raw data
18
21
  nx_tomo = NXtomo()
19
- nx_tomo.instrument.detector.x_pixel_size = 2.6e-6
20
- nx_tomo.instrument.detector.y_pixel_size = 2.5e-6
22
+ nx_tomo.instrument.detector.x_pixel_size = 2.6e-6 * _ureg.meter
23
+ nx_tomo.instrument.detector.y_pixel_size = 2.5e-6 * _ureg.meter
21
24
  nx_tomo.instrument.detector.field_of_view = "Half"
22
- nx_tomo.instrument.detector.distance = 59.0
25
+ nx_tomo.instrument.detector.distance = 59.0 * _ureg.meter
23
26
  nx_tomo.instrument.detector.x_flipped = True
24
27
  nx_tomo.instrument.detector.y_flipped = False
25
- nx_tomo.energy = 12.8
26
- nx_tomo.sample.x_translation = numpy.arange(12)
27
- nx_tomo.sample.z_translation = numpy.arange(2, 14)
28
+ nx_tomo.energy = 12.8 * _ureg.keV
29
+ nx_tomo.sample.x_translation = numpy.arange(12) * _ureg.meter
30
+ nx_tomo.sample.z_translation = numpy.arange(2, 14) * _ureg.meter
28
31
  nx_tomo.instrument.detector.image_key_control = [ImageKey.PROJECTION.value] * 12
29
32
  nx_tomo.instrument.detector.data = numpy.empty(shape=(12, 10, 10))
30
- nx_tomo.sample.rotation_angle = numpy.linspace(0, 180, num=12)
33
+ nx_tomo.sample.rotation_angle = numpy.linspace(0, 180, num=12) * _ureg.degree
31
34
 
32
35
  file_path = os.path.join(tmp_path, "nxtomo.nx")
33
36
  entry = "entry0000"
@@ -49,13 +52,22 @@ def test_nx_editor(
49
52
  return current_value is None
50
53
  return expected_value == current_value
51
54
 
52
- assert check_metric(2.6e-6, widget._xPixelSizeMetricEntry.getValue())
53
- assert widget._xPixelSizeMetricEntry._qcbUnit.currentText() == "m"
54
- assert check_metric(2.5e-6, widget._yPixelSizeMetricEntry.getValue())
55
- assert widget._yPixelSizeMetricEntry._qcbUnit.currentText() == "m"
55
+ assert check_metric(
56
+ 2.6e-6,
57
+ widget._xDetectorPixelSizeMetricEntry.getValue().to(_ureg.meter).magnitude,
58
+ )
59
+ assert widget._xDetectorPixelSizeMetricEntry._qcbUnit.currentText() == "m"
60
+ assert check_metric(
61
+ 2.5e-6,
62
+ widget._yDetectorPixelSizeMetricEntry.getValue().to(_ureg.meter).magnitude,
63
+ )
64
+ assert widget._yDetectorPixelSizeMetricEntry._qcbUnit.currentText() == "m"
56
65
 
57
- assert check_metric(59, widget._distanceMetricEntry.getValue())
58
- assert widget._distanceMetricEntry._qcbUnit.currentText() == "m"
66
+ assert check_metric(
67
+ 59,
68
+ widget._sampleDetectorDistanceMetricEntry.getValue().to(_ureg.meter).magnitude,
69
+ )
70
+ assert widget._sampleDetectorDistanceMetricEntry._qcbUnit.currentText() == "m"
59
71
 
60
72
  assert "Half" == widget._fieldOfViewCB.currentText()
61
73
  assert widget._xFlippedCB.isChecked()
@@ -46,8 +46,8 @@ def test_ReconstructionParameters(qtapp, phase_method): # noqa F401
46
46
  assert window._methodQLE.text() == "FBP"
47
47
  assert window._paganinQLE.text() == phase_method
48
48
  assert window._deltaBetaQLE.text() == "110.0"
49
- assert window._distanceQLE.text() == "0.4"
50
- assert window._pixelSizeQLE.text() == "0.2"
49
+ assert window._sampleDetectorDistanceQLE.text() == "0.4"
50
+ assert window._voxelSizeQLE.text() == "0.2x0.2x0.2"
51
51
  assert window._corQLE.text() == "104.00"
52
52
  assert window._halfTomoCB.isChecked()
53
53
  assert window._fbpFilterQLE.text() == "Hilbert"
File without changes
@@ -10,7 +10,7 @@ import os
10
10
  import shutil
11
11
 
12
12
  from silx.io.url import DataUrl
13
- from silx.utils.enum import Enum as _Enum
13
+ from enum import Enum as _Enum
14
14
 
15
15
  from tomwer.core.scan.edfscan import EDFTomoScan
16
16
  from tomwer.core.scan.nxtomoscan import NXtomoScan
@@ -59,7 +59,7 @@ class ScanStages:
59
59
  :param stage:
60
60
  :param dest_dir:
61
61
  """
62
- stage = ScanStages.AcquisitionStage.from_value(stage)
62
+ stage = ScanStages.AcquisitionStage(stage)
63
63
  if not dest_dir.endswith(os.path.basename(self.scan.path)):
64
64
  dest_dir = os.path.join(dest_dir, os.path.basename(self.scan.path))
65
65
  for t_stage in ScanStages.AcquisitionStage:
@@ -75,7 +75,7 @@ class ScanStages:
75
75
  """
76
76
  if not dest_dir.endswith(os.path.basename(self.scan.path)):
77
77
  dest_dir = os.path.join(dest_dir, os.path.basename(self.scan.path))
78
- stage = ScanStages.AcquisitionStage.from_value(stage)
78
+ stage = ScanStages.AcquisitionStage(stage)
79
79
  if stage is ScanStages.AcquisitionStage.ACQUI_NOT_STARTED:
80
80
  return
81
81
  elif stage is ScanStages.AcquisitionStage.ACQUI_STARTED:
@@ -0,0 +1,42 @@
1
+ """
2
+ Contains task to clear reduced dark and flat frames
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from tomoscan.scanbase import TomoScanBase as TomoscanScanBase
8
+
9
+ from processview.core.manager import DatasetState, ProcessManager
10
+ from processview.core.superviseprocess import SuperviseProcess
11
+
12
+ from tomwer.tasks.task import Task
13
+ from tomwer.core.scan.scanbase import TomwerScanBase
14
+ from tomwer.core.scan.scanfactory import ScanFactory
15
+ from tomwer.core.utils.scanutils import data_identifier_to_scan
16
+ from tomwer.core.reconstruction.darkflat import params as dkrf_reconsparams
17
+ from tomwer.utils import docstring
18
+
19
+
20
+ class ClearReducedDarkAndFlat(
21
+ Task,
22
+ SuperviseProcess,
23
+ input_names=("data",),
24
+ output_names=("data",),
25
+ ):
26
+ """
27
+ Task to clear reduced darks and flats. Both on disk and on the object cache.
28
+ th goal of this task is to make sure the scan is cleared of any reduced frames to reprocess it later.
29
+ """
30
+
31
+ def run(self):
32
+ scan = self.inputs.data
33
+ if not isinstance(scan, TomoscanScanBase):
34
+ raise TypeError(
35
+ f"scan should be an instance of {TomoscanScanBase}. Got {type(scan)}"
36
+ )
37
+ scan.set_reduced_flats(None)
38
+ scan.reduced_flats_infos = None
39
+ scan.set_reduced_darks(None)
40
+ scan.reduced_darks_infos = None
41
+
42
+ self.outputs.data = scan
@@ -32,7 +32,7 @@ def _create_objects_for_stitching(stitching_type: StitchingType, output_dir) ->
32
32
  ]
33
33
  )
34
34
 
35
- stitching_type = StitchingType.from_value(stitching_type)
35
+ stitching_type = StitchingType(stitching_type)
36
36
  if stitching_type is StitchingType.Y_PREPROC:
37
37
  nxtomos, positions, _ = test_y_preprocessing_stitching.build_nxtomos(
38
38
  output_dir=output_dir, flip_lr=False, flip_ud=False