tomwer 1.4.0rc0__py3-none-any.whl → 1.4.0rc1__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 (51) hide show
  1. orangecontrib/tomwer/tutorials/test_cor.ows +3 -3
  2. orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +6 -14
  3. orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +4 -2
  4. tomwer/app/axis.py +0 -3
  5. tomwer/app/multipag.py +11 -3
  6. tomwer/core/process/reconstruction/axis/axis.py +27 -736
  7. tomwer/core/process/reconstruction/axis/mode.py +86 -24
  8. tomwer/core/process/reconstruction/axis/params.py +127 -138
  9. tomwer/core/process/reconstruction/axis/side.py +8 -0
  10. tomwer/core/process/reconstruction/nabu/nabuscores.py +17 -20
  11. tomwer/core/process/reconstruction/nabu/nabuslices.py +5 -1
  12. tomwer/core/process/reconstruction/saaxis/saaxis.py +4 -4
  13. tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +4 -4
  14. tomwer/core/process/reconstruction/tests/test_axis.py +1 -1
  15. tomwer/core/process/reconstruction/tests/test_utils.py +4 -4
  16. tomwer/core/process/reconstruction/utils/cor.py +8 -4
  17. tomwer/core/process/tests/test_nabu.py +1 -3
  18. tomwer/core/scan/scanbase.py +4 -4
  19. tomwer/core/scan/tests/test_process_registration.py +0 -18
  20. tomwer/gui/fonts.py +5 -0
  21. tomwer/gui/reconstruction/axis/AxisMainWindow.py +20 -9
  22. tomwer/gui/reconstruction/axis/AxisOptionsWidget.py +239 -79
  23. tomwer/gui/reconstruction/axis/AxisSettingsWidget.py +38 -17
  24. tomwer/gui/reconstruction/axis/AxisWidget.py +16 -8
  25. tomwer/gui/reconstruction/axis/CalculationWidget.py +40 -200
  26. tomwer/gui/reconstruction/axis/ControlWidget.py +10 -2
  27. tomwer/gui/reconstruction/axis/EstimatedCORWidget.py +383 -0
  28. tomwer/gui/reconstruction/axis/EstimatedCorComboBox.py +118 -0
  29. tomwer/gui/reconstruction/axis/InputWidget.py +11 -155
  30. tomwer/gui/reconstruction/saaxis/corrangeselector.py +19 -10
  31. tomwer/gui/reconstruction/scores/scoreplot.py +5 -2
  32. tomwer/gui/reconstruction/tests/test_nabu.py +8 -0
  33. tomwer/gui/stitching/z_stitching/fineestimation.py +1 -1
  34. tomwer/gui/tests/test_axis_gui.py +31 -15
  35. tomwer/synctools/stacks/reconstruction/axis.py +5 -23
  36. tomwer/synctools/stacks/reconstruction/dkrefcopy.py +1 -1
  37. tomwer/synctools/stacks/reconstruction/nabu.py +2 -2
  38. tomwer/synctools/stacks/reconstruction/normalization.py +1 -1
  39. tomwer/synctools/stacks/reconstruction/saaxis.py +1 -1
  40. tomwer/synctools/stacks/reconstruction/sadeltabeta.py +1 -1
  41. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_axis.py +0 -16
  42. tomwer/tests/test_ewoks/test_single_node_execution.py +1 -1
  43. tomwer/tests/test_ewoks/test_workflows.py +1 -1
  44. tomwer/version.py +1 -1
  45. {tomwer-1.4.0rc0.dist-info → tomwer-1.4.0rc1.dist-info}/METADATA +2 -2
  46. {tomwer-1.4.0rc0.dist-info → tomwer-1.4.0rc1.dist-info}/RECORD +50 -47
  47. tomwer/core/process/tests/test_axis.py +0 -231
  48. {tomwer-1.4.0rc0.dist-info → tomwer-1.4.0rc1.dist-info}/LICENSE +0 -0
  49. {tomwer-1.4.0rc0.dist-info → tomwer-1.4.0rc1.dist-info}/WHEEL +0 -0
  50. {tomwer-1.4.0rc0.dist-info → tomwer-1.4.0rc1.dist-info}/entry_points.txt +0 -0
  51. {tomwer-1.4.0rc0.dist-info → tomwer-1.4.0rc1.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,16 @@
1
1
  def relative_pos_to_absolute(relative_pos: float, det_width: int):
2
2
  """
3
- convert relative center of rotation to absolute
3
+ Convert relative center of rotation to absolute.
4
4
  """
5
- return relative_pos + (det_width - 1) / 2.0
5
+ # Compute the midpoint differently for even and odd widths
6
+ midpoint = det_width / 2.0
7
+ return relative_pos + midpoint
6
8
 
7
9
 
8
10
  def absolute_pos_to_relative(absolute_pos: float, det_width: int):
9
11
  """
10
- convert absolute center of rotation to relative
12
+ Convert absolute center of rotation to relative.
11
13
  """
12
- return absolute_pos - (det_width - 1) / 2.0
14
+ # Compute the midpoint differently for even and odd widths
15
+ midpoint = det_width / 2.0
16
+ return absolute_pos - midpoint
@@ -402,9 +402,7 @@ class TestNabuAndAxis(unittest.TestCase):
402
402
  nabu_rotation_axis_value = self.get_saved_rotation_axis(nabu_conf_files[0])
403
403
  self.assertEqual(
404
404
  nabu_rotation_axis_value,
405
- str(
406
- self.scan._axis_params.relative_cor_value + (self.scan.dim_1 - 1) / 2.0
407
- ),
405
+ str(self.scan._axis_params.relative_cor_value + (self.scan.dim_1 / 2.0)),
408
406
  )
409
407
 
410
408
 
@@ -279,7 +279,6 @@ class TomwerScanBase(TomwerObject):
279
279
  def get_opposite_projections(self, mode) -> tuple:
280
280
  """
281
281
  Return the two best opposite projections for the required mode.
282
-
283
282
  """
284
283
  radios_with_angle = self.projections_with_angle()
285
284
  angles = numpy.array(
@@ -322,9 +321,10 @@ class TomwerScanBase(TomwerObject):
322
321
  nearest_c1 = find_nearest(angles=angles, angle=couples[0])
323
322
  nearest_c2 = find_nearest(angles=angles, angle=couples[1])
324
323
  if nearest_c1 is not None and nearest_c2 is not None:
325
- radio_0 = AxisResource(radios_with_angle[nearest_c1])
326
- radio_1 = AxisResource(radios_with_angle[nearest_c2])
327
- return radio_0, radio_1
324
+ return (
325
+ AxisResource(radios_with_angle[nearest_c1], angle=nearest_c1),
326
+ AxisResource(radios_with_angle[nearest_c2], angle=nearest_c2),
327
+ )
328
328
  else:
329
329
  return None, None
330
330
 
@@ -40,24 +40,6 @@ class TestProcessRegistration(unittest.TestCase):
40
40
  def tearDown(self):
41
41
  shutil.rmtree(self.tmp_dir)
42
42
 
43
- def testGetCorValues(self):
44
- from tomwer.core.process.reconstruction.axis import AxisTask
45
-
46
- for i in range(20):
47
- results = {"center_of_rotation": i}
48
- Task._register_process(
49
- self.scan.process_file,
50
- process=AxisTask,
51
- entry=self.scan.entry,
52
- configuration=None,
53
- results=results,
54
- process_index=i,
55
- )
56
- cor_value = AxisTask.get_cor_frm_process_file(
57
- self.scan.process_file, entry=self.scan.entry
58
- )
59
- self.assertEqual(cor_value, 19)
60
-
61
43
  def testGetProcessNodes(self):
62
44
  """insure it return the last dark process based on the processing index"""
63
45
 
tomwer/gui/fonts.py ADDED
@@ -0,0 +1,5 @@
1
+ from silx.gui import qt
2
+
3
+ FONT_IMPORTANT = qt.QFont("Arial", 12)
4
+ FONT_MEDIUM = qt.QFont("Arial", 10)
5
+ FONT_SMALL = qt.QFont("Arial", 8)
@@ -10,6 +10,10 @@ from silx.gui import qt
10
10
  from tomwer.gui.utils.qt_utils import block_signals
11
11
 
12
12
  from tomwer.core.process.reconstruction.axis.mode import AxisMode
13
+ from tomwer.core.process.reconstruction.utils.cor import (
14
+ absolute_pos_to_relative,
15
+ relative_pos_to_absolute,
16
+ )
13
17
  from tomwer.core.scan.scanbase import TomwerScanBase
14
18
 
15
19
  from ...utils.scandescription import ScanNameLabelAndShape
@@ -124,17 +128,15 @@ class AxisMainWindow(qt.QMainWindow):
124
128
  )
125
129
 
126
130
  def setAutoUpdateEstimatedCor(self, value):
127
- self._axisWidget.setUpdateAutomaticallyEstimatedCor(value)
128
-
129
- def getAutoUpdateEstimatedCor(self):
130
- return self._axisWidget.updateAutomaticallyEstimatedCor()
131
+ self._axisWidget._settingsWidget._mainWidget.setUpdateXRotationAxisPixelPositionOnNewScan(
132
+ value
133
+ )
131
134
 
132
135
  def manual_uses_full_image(self, value):
133
136
  self._axisWidget.manual_uses_full_image(value)
134
137
 
135
138
  def _modeChanged(self):
136
139
  self.getAxisParams().mode = self.getMode()
137
- self.getAxisParams().use_sinogram = self.useSinogram()
138
140
 
139
141
  def _CORValueLocked(self, lock):
140
142
  if lock:
@@ -152,8 +154,8 @@ class AxisMainWindow(qt.QMainWindow):
152
154
  and relative_value is not None
153
155
  ):
154
156
  try:
155
- absolute_value = (
156
- relative_value + (self._axis_params.frame_width - 1) / 2.0
157
+ absolute_value = relative_pos_to_absolute(
158
+ relative_pos=relative_value, det_width=self._axis_params.frame_width
157
159
  )
158
160
  except TypeError:
159
161
  absolute_value = None
@@ -163,8 +165,8 @@ class AxisMainWindow(qt.QMainWindow):
163
165
  and absolute_value is not None
164
166
  ):
165
167
  try:
166
- relative_value = (
167
- absolute_value - (self._axis_params.frame_width - 1) / 2.0
168
+ relative_value = absolute_pos_to_relative(
169
+ absolute_pos=absolute_value, det_width=self._axis_params.frame_width
168
170
  )
169
171
  except TypeError:
170
172
  relative_value = None
@@ -262,3 +264,12 @@ class AxisMainWindow(qt.QMainWindow):
262
264
 
263
265
  def setEstimatedCor(self, value: float):
264
266
  self._axisWidget.setEstimatedCor(value=value)
267
+
268
+ def getAutoUpdateEstimatedCor(self):
269
+ return self._axisWidget.updateXRotationAxisPixelPositionOnNewScan()
270
+
271
+ def isYAxisInverted(self) -> bool:
272
+ return self._axisWidget.isYAxisInverted()
273
+
274
+ def setYAxisInverted(self, checked: bool):
275
+ return self._axisWidget.setYAxisInverted(checked=checked)
@@ -2,9 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  from silx.gui import qt
4
4
 
5
+ from tomwer.core.process.reconstruction.axis.mode import AxisMode, AXIS_MODE_METADATAS
5
6
  from tomwer.synctools.axis import QAxisRP
6
7
  from tomwer.core.process.reconstruction.axis.params import AxisCalculationInput
7
8
  from tomwer.gui.utils.qt_utils import block_signals
9
+ from tomwer.gui.utils.scrollarea import QComboBoxIgnoreWheel, QSpinBoxIgnoreWheel
10
+ from tomwer.core.process.reconstruction.axis.params import (
11
+ DEFAULT_CMP_N_SUBSAMPLING_Y,
12
+ DEFAULT_CMP_OVERSAMPLING,
13
+ DEFAULT_CMP_TAKE_LOG,
14
+ DEFAULT_CMP_THETA,
15
+ )
8
16
 
9
17
 
10
18
  class AxisOptionsWidget(qt.QWidget):
@@ -14,12 +22,50 @@ class AxisOptionsWidget(qt.QWidget):
14
22
  Used as a tab of the AxisSettingsTabWidget
15
23
  """
16
24
 
25
+ sigChanged = qt.Signal()
26
+ """Emit when the options changed"""
27
+
17
28
  def __init__(self, parent, axis_params):
18
29
  qt.QWidget.__init__(self, parent=parent)
19
30
  assert isinstance(axis_params, QAxisRP)
20
31
  self._axis_params = axis_params
21
32
  self.setLayout(qt.QVBoxLayout())
22
33
 
34
+ # cor_options
35
+ self._corOptsWidget = qt.QWidget(self)
36
+ self._corOptsWidget.setLayout(qt.QFormLayout())
37
+ self._corOpts = qt.QLineEdit(self)
38
+ self._corOpts.setToolTip(
39
+ "Options for methods finding automatically the rotation axis position. 'side', 'near_pos' and 'near_width' are already provided by dedicated interface. The parameters are separated by commas and passed as 'name=value'. Mind the semicolon separator (;)."
40
+ )
41
+ self._corOpts.setPlaceholderText("low_pass=1; high_pass=20")
42
+ self._corOptsWidget.layout().addRow("cor advanced options", self._corOpts)
43
+ self.layout().addWidget(self._corOptsWidget)
44
+
45
+ # padding option
46
+ self._padding_widget = qt.QGroupBox("padding mode")
47
+ self._padding_widget.setCheckable(True)
48
+ self.layout().addWidget(self._padding_widget)
49
+ self._padding_widget.setLayout(qt.QHBoxLayout())
50
+
51
+ self._qbPaddingMode = QComboBoxIgnoreWheel(self._padding_widget)
52
+ for _mode in (
53
+ "constant",
54
+ "edge",
55
+ "linear_ramp",
56
+ "maximum",
57
+ "mean",
58
+ "median",
59
+ "minimum",
60
+ "reflect",
61
+ "symmetric",
62
+ "wrap",
63
+ ):
64
+ self._qbPaddingMode.addItem(_mode)
65
+ def_index = self._qbPaddingMode.findText("edge")
66
+ self._qbPaddingMode.setCurrentIndex(def_index)
67
+ self._padding_widget.layout().addWidget(self._qbPaddingMode)
68
+
23
69
  # define common options
24
70
  self._commonOpts = qt.QWidget(parent=self)
25
71
  self._commonOpts.setLayout(qt.QFormLayout())
@@ -30,39 +76,75 @@ class AxisOptionsWidget(qt.QWidget):
30
76
  self._qcbDataMode.addItem(data_mode.name(), data_mode)
31
77
  self._qcbDataMode.hide()
32
78
 
33
- # add scale option
34
- self._scaleOpt = qt.QCheckBox(parent=self)
35
- self._commonOpts.layout().addRow("scale the two images", self._scaleOpt)
36
79
  self.layout().addWidget(self._commonOpts)
37
80
 
38
- # add near options
39
- self._nearOpts = _AxisNearOptsWidget(parent=self, axis_params=self._axis_params)
40
- self.layout().addWidget(self._nearOpts)
81
+ # composite method advanced options
82
+ self._compositeOptsGroup = CompositeOptsGroup(
83
+ parent=self, axis_params=axis_params
84
+ )
85
+ self.layout().addWidget(self._compositeOptsGroup)
41
86
 
42
87
  # set up
43
88
  self.setCalculationInputType(self._axis_params.calculation_input_type)
89
+ self._compositeOptsGroup.setVisible(
90
+ self._axis_params.mode in (AxisMode.near, AxisMode.composite_coarse_to_fine)
91
+ )
44
92
 
45
93
  # connect signal / slot
46
- self._scaleOpt.toggled.connect(self._updateScaleOpt)
94
+ self._corOpts.editingFinished.connect(self._updateAdvancedCorOptions)
47
95
  self._qcbDataMode.currentIndexChanged.connect(self._updateInputType)
48
- self._axis_params.sigChanged.connect(self._updateMode)
96
+ self._axis_params.sigChanged.connect(self._axis_params_changed)
97
+ self._qbPaddingMode.currentIndexChanged.connect(self._paddingModeChanged)
98
+ self._padding_widget.toggled.connect(self._paddingModeChanged)
99
+ self._compositeOptsGroup.sigChanged.connect(self.sigChanged)
49
100
 
50
- def _updateMode(self):
101
+ def _axis_params_changed(self):
51
102
  with block_signals(self):
103
+ # update according to AxisCalculationInput
52
104
  index = self._qcbDataMode.findText(
53
105
  self._axis_params.calculation_input_type.name()
54
106
  )
55
107
  if index >= 0:
56
108
  self._qcbDataMode.setCurrentIndex(index)
57
-
58
- def _updateScaleOpt(self, *arg, **kwargs):
59
- self._axis_params.scale_img2_to_img1 = self.isImageScaled()
60
-
61
- def isImageScaled(self):
62
- return self._scaleOpt.isChecked()
109
+ # update advanced cor options visibility (not relevant if mode is manual or read)
110
+ axis_mode = self._axis_params.mode
111
+ self._corOptsWidget.setVisible(
112
+ axis_mode
113
+ not in (
114
+ AxisMode.manual,
115
+ AxisMode.read,
116
+ )
117
+ )
118
+ # update cor options value
119
+ self.setCorOptions(self._axis_params.extra_cor_options)
120
+ self.setPaddingMode(self._axis_params.padding_mode)
121
+ self._padding_widget.setVisible(
122
+ AXIS_MODE_METADATAS[axis_mode].allows_padding
123
+ )
63
124
 
64
125
  def _updateInputType(self, *arg, **kwargs):
65
126
  self._axis_params.calculation_input_type = self.getCalculationInputType()
127
+ self.sigChanged.emit()
128
+
129
+ def _paddingModeChanged(self, *args, **kwargs):
130
+ self._axis_params.padding_mode = self.getPaddingMode()
131
+ self.sigChanged.emit()
132
+
133
+ def getPaddingMode(self):
134
+ if self._padding_widget.isChecked():
135
+ return self._qbPaddingMode.currentText()
136
+ else:
137
+ return None
138
+
139
+ def setPaddingMode(self, mode):
140
+ index = self._qbPaddingMode.findText(mode)
141
+ if index >= 0:
142
+ self._qbPaddingMode.setCurrentIndex(index)
143
+ self._paddingModeChanged()
144
+
145
+ def _updateAdvancedCorOptions(self, *args, **kwargs):
146
+ self._axis_params.extra_cor_options = self.getCorOptions()
147
+ self.sigChanged.emit()
66
148
 
67
149
  def getCalculationInputType(self, *arg, **kwargs):
68
150
  return AxisCalculationInput.from_value(self._qcbDataMode.currentText())
@@ -73,81 +155,159 @@ class AxisOptionsWidget(qt.QWidget):
73
155
  self._qcbDataMode.setCurrentIndex(index_dm)
74
156
 
75
157
  def setAxisParams(self, axis):
76
- self._nearOpts.setAxisParams(axis=axis)
77
158
  self._axis_params = axis
78
159
  with block_signals(self):
79
- self._scaleOpt.setChecked(axis.scale_img2_to_img1)
80
160
  index = self._qcbDataMode.findText(axis.calculation_input_type.name())
81
161
  self._qcbDataMode.setCurrentIndex(index)
162
+ self._compositeOptsGroup.setAxisParams(axis)
82
163
 
164
+ def getCorOptions(self):
165
+ return self._corOpts.text()
83
166
 
84
- class _AxisNearOptsWidget(qt.QWidget):
85
- """GUI dedicated to the near options"""
86
-
87
- def __init__(self, parent, axis_params):
88
- qt.QWidget.__init__(self, parent=parent)
89
- assert isinstance(axis_params, QAxisRP)
90
- self._axis_params = axis_params
91
-
92
- self.setLayout(qt.QFormLayout())
93
-
94
- self._stdMaxOpt = qt.QCheckBox(parent=self)
95
- self.layout().addRow("look at max standard deviation", self._stdMaxOpt)
96
-
97
- self._nearWX = qt.QSpinBox(parent=self)
98
- self._nearWX.setMinimum(1)
99
- self._nearWX.setValue(5)
100
- self.layout().addRow("window size", self._nearWX)
101
-
102
- self._fineStepX = qt.QDoubleSpinBox(parent=self)
103
- self._fineStepX.setMinimum(0.05)
104
- self._fineStepX.setSingleStep(0.05)
105
- self._fineStepX.setMaximum(1.0)
106
- self.layout().addRow("fine step x", self._fineStepX)
107
-
108
- # connect signal / Slot
109
- self._stdMaxOpt.toggled.connect(self._lookForStxMaxChanged)
110
- self._nearWX.valueChanged.connect(self._windowSizeChanged)
111
- self._fineStepX.valueChanged.connect(self._fineStepXChanged)
112
-
113
- def _lookForStxMaxChanged(self, *args, **kwargs):
114
- self._axis_params.look_at_stdmax = self.isLookAtStdMax()
115
-
116
- def isLookAtStdMax(self) -> bool:
117
- """
167
+ def setCorOptions(self, opts: str | None):
168
+ with block_signals(self._axis_params):
169
+ self._corOpts.clear()
170
+ if opts:
171
+ self._corOpts.setText(opts)
172
+ self._updateAdvancedCorOptions()
118
173
 
119
- :return: is the option for looking at max standard deviation is
120
- activated
121
- """
122
- return self._stdMaxOpt.isChecked()
174
+ def setMode(self, mode: AxisMode):
175
+ composite_opts_visible = AxisMode.from_value(mode) in (
176
+ AxisMode.composite_coarse_to_fine,
177
+ AxisMode.near,
178
+ )
179
+ self._compositeOptsGroup.setVisible(composite_opts_visible)
123
180
 
124
- def _windowSizeChanged(self, *args, **kwargs):
125
- self._axis_params.near_wx = self.getWindowSize()
126
181
 
127
- def getWindowSize(self) -> int:
128
- """
182
+ class CompositeOptsGroup(qt.QGroupBox):
183
+ """Group box dedicated to the composite algorithms"""
129
184
 
130
- :return: window size for near search
131
- """
132
- return self._nearWX.value()
185
+ sigChanged = qt.Signal()
186
+ """Emit when the options changed"""
133
187
 
134
- def _fineStepXChanged(self, *args, **kwargs):
135
- self._axis_params.fine_step_x = self.getFineStepX()
136
-
137
- def getFineStepX(self) -> float:
138
- """
139
-
140
- :return: fine step x for near calculation
141
- """
142
- return self._fineStepX.value()
188
+ def __init__(self, title="composite options", parent=None, axis_params=None):
189
+ self._axis_params = axis_params
190
+ super().__init__(title, parent)
191
+ ## options for the composite mode
192
+ self.setLayout(qt.QFormLayout())
193
+ self.layout().setContentsMargins(0, 0, 0, 0)
194
+
195
+ self._thetaSB = QSpinBoxIgnoreWheel(self)
196
+ self._thetaSB.setRange(0, 360)
197
+ self._thetaSB.setValue(DEFAULT_CMP_THETA)
198
+ self._thetaSB.setToolTip("a radio will be picked each theta degrees")
199
+ self._thetaLabel = qt.QLabel("angle interval (in degree)", self)
200
+ self._thetaLabel.setToolTip(
201
+ "algorithm will take one projection each 'angle interval'. Also know as 'theta'"
202
+ )
203
+ self.layout().addRow(self._thetaLabel, self._thetaSB)
204
+
205
+ self._oversamplingSB = QSpinBoxIgnoreWheel(self)
206
+ self._oversamplingSB.setRange(1, 999999)
207
+ self._oversamplingSB.setValue(DEFAULT_CMP_OVERSAMPLING)
208
+ self._oversamplingSB.setToolTip("sinogram oversampling")
209
+ self.layout().addRow("oversampling", self._oversamplingSB)
210
+
211
+ self._nearWidthSB = QSpinBoxIgnoreWheel(self)
212
+ self._nearWidthSB.setRange(1, 999999)
213
+ self._nearWidthSB.setValue(0)
214
+ self._nearWidthSB.setToolTip("position to be used with near option")
215
+ self._nearWidthLabel = qt.QLabel("near width", self)
216
+ self._nearWidthLabel.setToolTip("position to be used with near option")
217
+ self.layout().addRow(self._nearWidthLabel, self._nearWidthSB)
218
+
219
+ self._subsamplingYSB = QSpinBoxIgnoreWheel(self)
220
+ self._subsamplingYSB.setRange(1, 999999)
221
+ self._subsamplingYSB.setValue(DEFAULT_CMP_N_SUBSAMPLING_Y)
222
+ self._subsamplingYSB.setToolTip("sinogram number of subsampling along y")
223
+ self.layout().addRow("n_subsampling_y", self._subsamplingYSB)
224
+
225
+ self._takeLogCB = qt.QCheckBox(self)
226
+ self._takeLogCB.setToolTip("Take logarithm")
227
+ self._takeLogCB.setChecked(DEFAULT_CMP_TAKE_LOG)
228
+ self._takeTheLogLabel = qt.QLabel("linearisation (-log(I/I0))")
229
+ self._takeTheLogLabel.setToolTip(
230
+ "take (-log(I/I0)) as input. Also know as 'take_log' option"
231
+ )
232
+ self.layout().addRow(self._takeTheLogLabel, self._takeLogCB)
143
233
 
144
- def setAxisParams(self, axis: QAxisRP):
145
- """
234
+ # connect signal / slot
235
+ self._thetaSB.valueChanged.connect(self._changed)
236
+ self._oversamplingSB.valueChanged.connect(self._changed)
237
+ self._subsamplingYSB.valueChanged.connect(self._changed)
238
+ self._nearWidthSB.valueChanged.connect(self._changed)
239
+ self._takeLogCB.toggled.connect(self._changed)
146
240
 
147
- :param axis: axis to edit
148
- """
241
+ def setAxisParams(self, axis_params):
149
242
  with block_signals(self):
150
- self._axis_params = axis
151
- self._stdMaxOpt.setChecked(axis.look_at_stdmax)
152
- self._nearWX.setValue(axis.near_wx)
153
- self._fineStepX.setValue(axis.fine_step_x)
243
+ self.setConfiguration(axis_params.composite_options)
244
+ self._axis_params = axis_params
245
+
246
+ def _changed(self):
247
+ if self._axis_params is not None:
248
+ self._axis_params.composite_options = self.getConfiguration()
249
+ self.sigChanged.emit()
250
+
251
+ def getTheta(self) -> int:
252
+ return self._thetaSB.value()
253
+
254
+ def setTheta(self, theta: int) -> None:
255
+ self._thetaSB.setValue(theta)
256
+
257
+ def getOversampling(self) -> int:
258
+ return self._oversamplingSB.value()
259
+
260
+ def setOversampling(self, oversampling: int) -> None:
261
+ self._oversamplingSB.setValue(oversampling)
262
+
263
+ def getNearWidth(self) -> int:
264
+ return self._nearWidthSB.value()
265
+
266
+ def setNearWidth(self, value) -> int:
267
+ return self._nearWidthSB.setValue(value)
268
+
269
+ def getSubSamplingY(self) -> int:
270
+ return self._subsamplingYSB.value()
271
+
272
+ def setSubSamplingY(self, subsampling: int) -> None:
273
+ self._subsamplingYSB.setValue(subsampling)
274
+
275
+ def getTakeLog(self) -> bool:
276
+ return self._takeLogCB.isChecked()
277
+
278
+ def setTakeLog(self, log: bool) -> None:
279
+ self._takeLogCB.setChecked(log)
280
+
281
+ def getConfiguration(self) -> dict:
282
+
283
+ return {
284
+ "theta": self.getTheta(),
285
+ "oversampling": self.getOversampling(),
286
+ "n_subsampling_y": self.getSubSamplingY(),
287
+ "take_log": self.getTakeLog(),
288
+ "near_width": self.getNearWidth(),
289
+ }
290
+
291
+ def setConfiguration(self, opts: dict) -> None:
292
+ if not isinstance(opts, dict):
293
+ raise TypeError("opts should be an instance of dict")
294
+ # theta
295
+ theta = opts.get("theta", None)
296
+ if theta is not None:
297
+ self.setTheta(theta=theta)
298
+ # oversampling
299
+ oversampling = opts.get("oversampling", None)
300
+ if oversampling is not None:
301
+ self.setOversampling(oversampling)
302
+ # n subsampling y
303
+ n_subsampling_y = opts.get("n_subsampling_y", None)
304
+ if n_subsampling_y is not None:
305
+ self.setSubSamplingY(n_subsampling_y)
306
+ # near_width
307
+ near_width = opts.get("near_width", None)
308
+ if near_width is not None:
309
+ self.setNearWidth(near_width)
310
+ # take log
311
+ take_log = opts.get("take_log", None)
312
+ if take_log is not None:
313
+ self.setTakeLog(take_log)
@@ -152,6 +152,7 @@ class AxisSettingsWidget(qt.QWidget):
152
152
  with block_signals(self._axisParams):
153
153
  self._axisParams.mode = mode
154
154
  self._mainWidget._calculationWidget.setMode(mode)
155
+ self._mainWidget._optionsWidget.setMode(mode)
155
156
  self._updateAxisView()
156
157
  self._axisParams.sigChanged.emit()
157
158
 
@@ -230,15 +231,15 @@ class AxisSettingsWidget(qt.QWidget):
230
231
 
231
232
  # expose API
232
233
 
233
- def updateAutomaticallyEstimatedCor(self):
234
- return self._mainWidget.updateAutomaticallyEstimatedCor()
235
-
236
- def setUpdateAutomaticallyEstimatedCor(self, value):
237
- self._mainWidget.setUpdateAutomaticallyEstimatedCor(value)
238
-
239
234
  def setEstimatedCor(self, value):
240
235
  self._mainWidget.setEstimatedCorValue(value=value)
241
236
 
237
+ def updateXRotationAxisPixelPositionOnNewScan(self) -> bool:
238
+ return self._mainWidget.updateXRotationAxisPixelPositionOnNewScan()
239
+
240
+ def setUpdateXRotationAxisPixelPositionOnNewScan(self, update: bool):
241
+ self._mainWidget.setUpdateXRotationAxisPixelPositionOnNewScan(update=update)
242
+
242
243
  def getEstimatedCor(self):
243
244
  return self._mainWidget.getEstimatedCor()
244
245
 
@@ -266,6 +267,12 @@ class AxisSettingsWidget(qt.QWidget):
266
267
  def setModeLock(self, mode):
267
268
  return self._mainWidget.setModeLock(mode=mode)
268
269
 
270
+ def isYAxisInverted(self) -> bool:
271
+ return self._mainWidget.isYAxisInverted()
272
+
273
+ def setYAxisInverted(self, checked: bool):
274
+ return self._mainWidget.setYAxisInverted(checked=checked)
275
+
269
276
 
270
277
  class ManualAxisSelectionWidget(qt.QWidget):
271
278
  sigResetZoomRequested = qt.Signal()
@@ -492,6 +499,8 @@ class AxisSettingsTabWidget(qt.QTabWidget):
492
499
  sigModeChanged = qt.Signal(str)
493
500
  """Signal emit when mode (algorithm) is changed"""
494
501
 
502
+ sigUpdateXRotAxisPixelPosOnNewScan = qt.Signal()
503
+
495
504
  def __init__(
496
505
  self,
497
506
  recons_params: QAxisRP | None,
@@ -517,12 +526,9 @@ class AxisSettingsTabWidget(qt.QTabWidget):
517
526
  spacer.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding)
518
527
  widget.layout().addWidget(spacer)
519
528
 
520
- self._optionsSA = qt.QScrollArea(parent=self)
521
- self._optionsSA.setWidget(self._optionsWidget)
522
529
  self.addTab(self._calculationWidget, "calculation")
523
- self.addTab(self._optionsSA, "options")
530
+ self.addTab(self._optionsWidget, "options")
524
531
  # simplify set up. Hide options
525
- self._optionsSA.hide()
526
532
  self.addTab(self._inputWidget, "input")
527
533
 
528
534
  # set up
@@ -532,11 +538,11 @@ class AxisSettingsTabWidget(qt.QTabWidget):
532
538
  # connect signal / slot
533
539
  self._calculationWidget.sigLockModeChanged.connect(self.sigLockModeChanged)
534
540
  self.sigModeChanged.connect(self._updatePossibleInput)
541
+ self.sigModeChanged.connect(self._updatePossibleOptions)
535
542
  self._inputWidget._sigUrlChanged.connect(self._urlChanged)
536
543
  self._calculationWidget.sigModeChanged.connect(self.sigModeChanged)
537
- # not very nice but very convenient to have the setting near at the same level
538
- self._calculationWidget._qleNearPosQLE.textChanged.connect(
539
- self._inputWidget._changed
544
+ self._calculationWidget.sigUpdateXRotAxisPixelPosOnNewScan.connect(
545
+ self.sigUpdateXRotAxisPixelPosOnNewScan
540
546
  )
541
547
 
542
548
  def _urlChanged(self):
@@ -544,6 +550,7 @@ class AxisSettingsTabWidget(qt.QTabWidget):
544
550
 
545
551
  def setScan(self, scan):
546
552
  if scan is not None:
553
+ self._calculationWidget.setScan(scan)
547
554
  self._inputWidget.setScan(scan)
548
555
 
549
556
  def setAxisParams(self, axis):
@@ -562,6 +569,10 @@ class AxisSettingsTabWidget(qt.QTabWidget):
562
569
  self._inputWidget.setEnabled(True)
563
570
  self._inputWidget.setValidInputs(valid_inputs)
564
571
 
572
+ def _updatePossibleOptions(self):
573
+ mode = self.getMode()
574
+ self._optionsWidget.setMode(mode)
575
+
565
576
  # expose API
566
577
  def isModeLock(self) -> bool:
567
578
  return self._calculationWidget.isModeLock()
@@ -575,15 +586,25 @@ class AxisSettingsTabWidget(qt.QTabWidget):
575
586
  def getEstimatedCor(self):
576
587
  return self._calculationWidget.getEstimatedCor()
577
588
 
589
+ def updateXRotationAxisPixelPositionOnNewScan(self) -> bool:
590
+ return self._calculationWidget.updateXRotationAxisPixelPositionOnNewScan()
591
+
592
+ def setUpdateXRotationAxisPixelPositionOnNewScan(self, update: bool):
593
+ self._calculationWidget.setUpdateXRotationAxisPixelPositionOnNewScan(
594
+ update=update
595
+ )
596
+
578
597
  def getMode(self):
579
598
  """Return algorithm to use for axis calculation"""
580
599
  return self._calculationWidget.getMode()
581
600
 
582
- def updateAutomaticallyEstimatedCor(self):
583
- return self._calculationWidget.updateAutomaticallyEstimatedCor()
601
+ def isYAxisInverted(self) -> bool:
602
+ return self._calculationWidget._estimatedCorWidget.isYAxisInverted()
584
603
 
585
- def setUpdateAutomaticallyEstimatedCor(self, value):
586
- self._calculationWidget.setUpdateAutomaticallyEstimatedCor(value)
604
+ def setYAxisInverted(self, checked: bool):
605
+ return self._calculationWidget._estimatedCorWidget.setYAxisInverted(
606
+ checked=checked
607
+ )
587
608
 
588
609
 
589
610
  class _ShiftControl(qt.QWidget):