tomwer 1.4.0__py3-none-any.whl → 1.4.0rc0__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 (53) hide show
  1. orangecontrib/tomwer/tutorials/test_cor.ows +3 -3
  2. orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +14 -6
  3. orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +2 -4
  4. tomwer/app/axis.py +3 -0
  5. tomwer/app/multipag.py +3 -11
  6. tomwer/core/process/reconstruction/axis/axis.py +736 -27
  7. tomwer/core/process/reconstruction/axis/mode.py +24 -86
  8. tomwer/core/process/reconstruction/axis/params.py +138 -127
  9. tomwer/core/process/reconstruction/nabu/nabuscores.py +22 -19
  10. tomwer/core/process/reconstruction/nabu/nabuslices.py +1 -5
  11. tomwer/core/process/reconstruction/saaxis/saaxis.py +4 -4
  12. tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +4 -4
  13. tomwer/core/process/reconstruction/tests/test_axis.py +1 -1
  14. tomwer/core/process/reconstruction/tests/test_utils.py +4 -4
  15. tomwer/core/process/reconstruction/utils/cor.py +4 -8
  16. tomwer/core/process/tests/test_axis.py +231 -0
  17. tomwer/core/process/tests/test_nabu.py +3 -1
  18. tomwer/core/scan/nxtomoscan.py +0 -2
  19. tomwer/core/scan/scanbase.py +4 -4
  20. tomwer/core/scan/tests/test_process_registration.py +18 -0
  21. tomwer/gui/reconstruction/axis/AxisMainWindow.py +9 -20
  22. tomwer/gui/reconstruction/axis/AxisOptionsWidget.py +79 -239
  23. tomwer/gui/reconstruction/axis/AxisSettingsWidget.py +17 -38
  24. tomwer/gui/reconstruction/axis/AxisWidget.py +8 -16
  25. tomwer/gui/reconstruction/axis/CalculationWidget.py +200 -44
  26. tomwer/gui/reconstruction/axis/ControlWidget.py +2 -10
  27. tomwer/gui/reconstruction/axis/InputWidget.py +155 -11
  28. tomwer/gui/reconstruction/saaxis/corrangeselector.py +10 -19
  29. tomwer/gui/reconstruction/scores/scoreplot.py +2 -5
  30. tomwer/gui/reconstruction/tests/test_nabu.py +0 -8
  31. tomwer/gui/stitching/config/axisparams.py +0 -2
  32. tomwer/gui/stitching/z_stitching/fineestimation.py +1 -1
  33. tomwer/gui/tests/test_axis_gui.py +15 -31
  34. tomwer/synctools/stacks/reconstruction/axis.py +23 -5
  35. tomwer/synctools/stacks/reconstruction/dkrefcopy.py +1 -1
  36. tomwer/synctools/stacks/reconstruction/nabu.py +2 -2
  37. tomwer/synctools/stacks/reconstruction/normalization.py +1 -1
  38. tomwer/synctools/stacks/reconstruction/saaxis.py +1 -1
  39. tomwer/synctools/stacks/reconstruction/sadeltabeta.py +1 -1
  40. tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_axis.py +16 -0
  41. tomwer/tests/test_ewoks/test_single_node_execution.py +1 -1
  42. tomwer/tests/test_ewoks/test_workflows.py +1 -1
  43. tomwer/version.py +1 -1
  44. {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/METADATA +3 -3
  45. {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/RECORD +49 -52
  46. tomwer/core/process/reconstruction/axis/side.py +0 -8
  47. tomwer/gui/fonts.py +0 -5
  48. tomwer/gui/reconstruction/axis/EstimatedCORWidget.py +0 -394
  49. tomwer/gui/reconstruction/axis/EstimatedCorComboBox.py +0 -118
  50. {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/LICENSE +0 -0
  51. {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/WHEEL +0 -0
  52. {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/entry_points.txt +0 -0
  53. {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/top_level.txt +0 -0
@@ -4,13 +4,11 @@ import logging
4
4
 
5
5
  from silx.gui import qt
6
6
 
7
- from tomwer.core.scan.scanbase import TomwerScanBase
8
7
  from tomwer.core.process.reconstruction.axis import mode as axis_mode
9
8
  from tomwer.gui.utils.buttons import PadlockButton
10
9
  from tomwer.gui.utils.qt_utils import block_signals
11
10
  from tomwer.synctools.axis import QAxisRP
12
11
  from tomwer.gui.utils.scrollarea import QComboBoxIgnoreWheel
13
- from tomwer.gui.reconstruction.axis.EstimatedCORWidget import EstimatedCORWidget
14
12
 
15
13
  _logger = logging.getLogger(__name__)
16
14
 
@@ -26,8 +24,6 @@ class CalculationWidget(qt.QWidget):
26
24
 
27
25
  sigLockModeChanged = qt.Signal(bool)
28
26
  """signal emitted when the mode has been lock or unlock"""
29
- sigUpdateXRotAxisPixelPosOnNewScan = qt.Signal()
30
- sigYAxisInvertedChanged = qt.Signal(bool)
31
27
 
32
28
  def __init__(self, parent, axis_params):
33
29
  assert isinstance(axis_params, QAxisRP)
@@ -35,7 +31,6 @@ class CalculationWidget(qt.QWidget):
35
31
  self._axis_params = None
36
32
  self.setLayout(qt.QVBoxLayout())
37
33
 
38
- # algorithm
39
34
  self._modeWidget = qt.QWidget(parent=self)
40
35
  self._modeWidget.setLayout(qt.QHBoxLayout())
41
36
  self.layout().addWidget(self._modeWidget)
@@ -58,9 +53,9 @@ class CalculationWidget(qt.QWidget):
58
53
  axis_mode.AxisMode.growing_window_sinogram,
59
54
  axis_mode.AxisMode.sino_coarse_to_fine,
60
55
  axis_mode.AxisMode.sliding_window_sinogram,
61
- axis_mode.AxisMode.fourier_angles,
56
+ axis_mode.AxisMode.sino_fourier_angles,
62
57
  ),
63
- # composite coarse to fine
58
+ # composite corase to fine
64
59
  (
65
60
  axis_mode.AxisMode.composite_coarse_to_fine,
66
61
  axis_mode.AxisMode.near,
@@ -96,52 +91,181 @@ class CalculationWidget(qt.QWidget):
96
91
  )
97
92
  self._modeWidget.layout().addWidget(self._lockMethodPB)
98
93
 
99
- # estimated cor
100
- self._estimatedCorWidget = EstimatedCORWidget(self, axis_params=axis_params)
101
- self.layout().addWidget(self._estimatedCorWidget)
94
+ self._optsWidget = qt.QWidget(self)
95
+ self._optsWidget.setLayout(qt.QVBoxLayout())
96
+ self.layout().addWidget(self._optsWidget)
97
+
98
+ # padding option
99
+ self._padding_widget = qt.QGroupBox("padding mode")
100
+ self._padding_widget.setCheckable(True)
101
+ self._optsWidget.layout().addWidget(self._padding_widget)
102
+ self._padding_widget.setLayout(qt.QHBoxLayout())
103
+
104
+ self._qbPaddingMode = QComboBoxIgnoreWheel(self._padding_widget)
105
+ for _mode in (
106
+ "constant",
107
+ "edge",
108
+ "linear_ramp",
109
+ "maximum",
110
+ "mean",
111
+ "median",
112
+ "minimum",
113
+ "reflect",
114
+ "symmetric",
115
+ "wrap",
116
+ ):
117
+ self._qbPaddingMode.addItem(_mode)
118
+ def_index = self._qbPaddingMode.findText("edge")
119
+ self._qbPaddingMode.setCurrentIndex(def_index)
120
+ self._padding_widget.layout().addWidget(self._qbPaddingMode)
121
+
122
+ # side option
123
+ self._sideWidget = qt.QWidget(self)
124
+ self._sideWidget.setLayout(qt.QHBoxLayout())
125
+ self._optsWidget.layout().addWidget(self._sideWidget)
126
+ self._sideWidget.layout().addWidget(qt.QLabel("side:", self))
127
+ self._sideCB = QComboBoxIgnoreWheel(self._optsWidget)
128
+ self._sideWidget.layout().addWidget(self._sideCB)
129
+ self._sideCB.setToolTip(
130
+ "Define a side for the sliding and growing" "window algorithms"
131
+ )
132
+
133
+ # near mode options
134
+ self._nearOptsWidget = qt.QWidget(parent=self)
135
+ self._nearOptsWidget.setLayout(qt.QVBoxLayout())
136
+ self._optsWidget.layout().addWidget(self._nearOptsWidget)
137
+
138
+ # near value lock button
139
+ self._nearValueCB = qt.QCheckBox(
140
+ "Update automatically if `x_rotation_axis_pixel_position` found"
141
+ )
142
+ self._nearValueCB.setToolTip(
143
+ "If the acquisition contains an "
144
+ "estimation of the COR value then "
145
+ "will set it automatically as estimated "
146
+ "value."
147
+ )
148
+ self._nearOptsWidget.layout().addWidget(self._nearValueCB)
149
+
150
+ # LineEdit position value
151
+ self._qleValueW = qt.QWidget(self._nearOptsWidget)
152
+ self._qleValueW.setLayout(qt.QFormLayout())
153
+ self._nearOptsWidget.layout().addWidget(self._qleValueW)
154
+
155
+ self._qleNearPosQLE = qt.QLineEdit("0", self._nearOptsWidget)
156
+ validator = qt.QDoubleValidator(self._qleNearPosQLE)
157
+ self._qleNearPosQLE.setValidator(validator)
158
+ self._qleValueW.layout().addRow(
159
+ "estimated value (in relative):", self._qleNearPosQLE
160
+ )
161
+
162
+ # cor_options
163
+ self._corOptsWidget = qt.QWidget(self)
164
+ self._corOptsWidget.setLayout(qt.QHBoxLayout())
165
+ self._corOpts = qt.QLineEdit(self)
166
+ self._corOpts.setToolTip(
167
+ "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 (;)."
168
+ )
169
+ self._corOpts.setPlaceholderText("low_pass=1; high_pass=20")
170
+ self._corOptsWidget.layout().addWidget(qt.QLabel("cor advanced options"))
171
+ self._corOptsWidget.layout().addWidget(self._corOpts)
172
+ self._optsWidget.layout().addWidget(self._corOptsWidget)
102
173
 
103
174
  # connect signal / slot
104
175
  self._qcbPosition.currentIndexChanged.connect(self._modeChanged)
176
+ self._qleNearPosQLE.editingFinished.connect(self._nearValueChanged)
177
+ self._sideCB.currentTextChanged.connect(self._sideChanged)
105
178
  self._lockMethodPB.sigLockChanged.connect(self.lockMode)
106
- self._estimatedCorWidget.sigUpdateXRotAxisPixelPosOnNewScan.connect(
107
- self.sigUpdateXRotAxisPixelPosOnNewScan
108
- )
109
- self._estimatedCorWidget.sigYAxisInvertedChanged.connect(
110
- self.sigYAxisInvertedChanged
111
- )
179
+ self._qbPaddingMode.currentIndexChanged.connect(self._paddingModeChanged)
180
+ self._padding_widget.toggled.connect(self._paddingModeChanged)
181
+ self._corOpts.editingFinished.connect(self._corOptsChanged)
112
182
 
113
183
  # set up interface
114
- self._estimatedCorWidget._updateVisibleSides(mode=self.getMode())
184
+ self._sideWidget.setVisible(False)
115
185
  self.setAxisParams(axis_params)
186
+ self._nearValueCB.setChecked(True)
187
+ self._nearOptsWidget.setHidden(True)
116
188
 
117
189
  def getMethodLockPB(self) -> qt.QPushButton:
118
190
  return self._lockMethodPB
119
191
 
120
192
  def setEstimatedCorValue(self, value):
121
- self._estimatedCorWidget.setEstimatedCor(value=value)
122
- # note: force to update the side values.
123
- self._estimatedCorWidget._updateVisibleSides(mode=self.getMode())
193
+ if value is not None:
194
+ self._qleNearPosQLE.setText(str(value))
195
+ # note: keep self._axis_params up to date.
196
+ if self._axis_params:
197
+ self._axis_params.estimated_cor = value
124
198
 
125
199
  def getEstimatedCor(self):
126
- return self._estimatedCorWidget.getEstimatedCor()
200
+ try:
201
+ return float(self._qleNearPosQLE.text())
202
+ except ValueError:
203
+ return 0
127
204
 
128
- def updateXRotationAxisPixelPositionOnNewScan(self) -> bool:
129
- return self._estimatedCorWidget.updateXRotationAxisPixelPositionOnNewScan()
205
+ def updateAutomaticallyEstimatedCor(self):
206
+ return self._nearValueCB.isChecked()
130
207
 
131
- def setUpdateXRotationAxisPixelPositionOnNewScan(self, update: bool):
132
- self._estimatedCorWidget.setUpdateXRotationAxisPixelPositionOnNewScan(
133
- update=update
134
- )
208
+ def setUpdateAutomaticallyEstimatedCor(self, checked):
209
+ self._nearValueCB.setChecked(checked)
210
+
211
+ def setSide(self, side):
212
+ if side is not None:
213
+ idx = self._sideCB.findText(side)
214
+ if idx >= 0:
215
+ self._sideCB.setCurrentIndex(idx)
216
+
217
+ def getSide(self):
218
+ return self._sideCB.currentText()
135
219
 
136
220
  def _modeChanged(self, *args, **kwargs):
137
221
  mode = self.getMode()
138
222
  with block_signals(self._qcbPosition):
139
223
  with block_signals(self._axis_params):
140
- self._estimatedCorWidget._updateVisibleSides(mode)
224
+ self._corOptsWidget.setVisible(
225
+ mode
226
+ not in (
227
+ axis_mode.AxisMode.manual,
228
+ axis_mode.AxisMode.read,
229
+ )
230
+ )
231
+
232
+ self._padding_widget.setVisible(
233
+ axis_mode.AXIS_MODE_METADATAS[mode].allows_padding
234
+ )
235
+ if axis_mode.AXIS_MODE_METADATAS[mode].is_lockable:
236
+ self._lockMethodPB.setVisible(True)
237
+ else:
238
+ self._lockMethodPB.setVisible(False)
239
+ self.lockMode(False)
240
+
241
+ sides_visible = len(axis_mode.AXIS_MODE_METADATAS[mode].valid_sides) > 0
242
+ self._sideWidget.setVisible(sides_visible)
243
+ if sides_visible is True:
244
+ self._updateSideVisible(mode)
245
+ self._nearOptsWidget.setVisible(self.getSide() == "near")
141
246
  self._axis_params.mode = mode.value
142
247
  self._axis_params.changed()
143
248
  self.sigModeChanged.emit(mode.value)
144
249
 
250
+ def _updateSideVisible(self, mode: axis_mode.AxisMode):
251
+ mode = axis_mode.AxisMode.from_value(mode)
252
+ if len(axis_mode.AXIS_MODE_METADATAS[mode].valid_sides) == 0:
253
+ return
254
+ else:
255
+ current_value = self._axis_params.side
256
+ with block_signals(self._sideCB):
257
+ self._sideCB.clear()
258
+ values = axis_mode.AXIS_MODE_METADATAS[mode].valid_sides
259
+ for value in values:
260
+ self._sideCB.addItem(value)
261
+ idx = self._sideCB.findText(current_value)
262
+ if idx == -1:
263
+ # if side doesn't exists, propose right as default when possible
264
+ idx = self._sideCB.findText("right")
265
+ if idx >= 0:
266
+ self._sideCB.setCurrentIndex(idx)
267
+ self._axis_params.side = self.getSide()
268
+
145
269
  def isModeLock(self):
146
270
  return self._lockMethodPB.isLocked()
147
271
 
@@ -179,11 +303,7 @@ class CalculationWidget(qt.QWidget):
179
303
  """Return algorithm to use for axis calculation"""
180
304
  return axis_mode.AxisMode.from_value(self._qcbPosition.currentText())
181
305
 
182
- def setMode(self, mode: axis_mode.AxisMode):
183
- if mode == self.getMode():
184
- # when set mode the estimated cor might changed.
185
- # that we want to avoid.
186
- return
306
+ def setMode(self, mode):
187
307
  with block_signals(self._qcbPosition):
188
308
  index = self._qcbPosition.findText(mode.value)
189
309
  if index >= 0:
@@ -191,13 +311,43 @@ class CalculationWidget(qt.QWidget):
191
311
  else:
192
312
  raise ValueError("Unable to find mode", mode)
193
313
  self._lockMethodPB.setVisible(mode not in (axis_mode.AxisMode.manual,))
194
- mode_metadata = axis_mode.AXIS_MODE_METADATAS[mode]
195
- estimated_cor_widget_visible = (
196
- mode_metadata.allows_estimated_cor_as_numerical_value
197
- or len(mode_metadata.valid_sides) > 0
198
- )
199
- self._estimatedCorWidget.setVisible(estimated_cor_widget_visible)
200
- self._estimatedCorWidget._updateVisibleSides(mode=mode)
314
+
315
+ def _nearValueChanged(self, *args, **kwargs):
316
+ self._axis_params.estimated_cor = self.getEstimatedCor()
317
+
318
+ def _paddingModeChanged(self, *args, **kwargs):
319
+ self._axis_params.padding_mode = self.getPaddingMode()
320
+
321
+ def _corOptsChanged(self, *args, **kwargs):
322
+ self._axis_params.extra_cor_options = self.getCorOptions()
323
+
324
+ def _sideChanged(self, *args, **kwargs):
325
+ side = self.getSide()
326
+ if side not in ("", None):
327
+ self._axis_params.side = side
328
+ self._nearOptsWidget.setVisible(side == "near")
329
+
330
+ def getCorOptions(self):
331
+ return self._corOpts.text()
332
+
333
+ def setCorOptions(self, opts: str | None):
334
+ with block_signals(self._axis_params):
335
+ self._corOpts.clear()
336
+ if opts:
337
+ self._corOpts.setText(opts)
338
+ self._corOptsChanged()
339
+
340
+ def getPaddingMode(self):
341
+ if self._padding_widget.isChecked():
342
+ return self._qbPaddingMode.currentText()
343
+ else:
344
+ return None
345
+
346
+ def setPaddingMode(self, mode):
347
+ index = self._qbPaddingMode.findText(mode)
348
+ if index >= 0:
349
+ self._qbPaddingMode.setCurrentIndex(index)
350
+ self._paddingModeChanged()
201
351
 
202
352
  def setAxisParams(self, axis):
203
353
  with block_signals(self):
@@ -209,10 +359,16 @@ class CalculationWidget(qt.QWidget):
209
359
  self._axis_params.mode = axis_mode.AxisMode.growing_window_radios
210
360
  self._axis_params.sigChanged.connect(self._axis_params_changed)
211
361
  self._axis_params_changed()
362
+ self._sideChanged()
212
363
 
213
364
  def _axis_params_changed(self, *args, **kwargs):
214
365
  self.setMode(self._axis_params.mode)
215
-
216
- def setScan(self, scan: TomwerScanBase | None):
217
- self._estimatedCorWidget.setPixelSize(pixel_size_m=scan.x_pixel_size)
218
- self._estimatedCorWidget.setImageWidth(image_width=scan.dim_1)
366
+ self.setEstimatedCorValue(self._axis_params.estimated_cor)
367
+ self.setSide(self._axis_params.side)
368
+ sides_visible = (
369
+ len(axis_mode.AXIS_MODE_METADATAS[self._axis_params.mode].valid_sides) > 0
370
+ )
371
+ self._sideWidget.setVisible(sides_visible)
372
+ self._updateSideVisible(mode=self._axis_params.mode)
373
+ self.setPaddingMode(self._axis_params.padding_mode)
374
+ self.setCorOptions(self._axis_params.extra_cor_options)
@@ -5,10 +5,6 @@ import numpy
5
5
 
6
6
  from silx.gui import qt
7
7
 
8
- from tomwer.core.process.reconstruction.utils.cor import (
9
- absolute_pos_to_relative,
10
- relative_pos_to_absolute,
11
- )
12
8
  from tomwer.gui.utils.buttons import PadlockButton
13
9
  from tomwer.gui.settings import EDITING_BACKGROUND_COLOR
14
10
  from tomwer.synctools.axis import QAxisRP
@@ -267,9 +263,7 @@ class _PositionInfoWidget(qt.QWidget):
267
263
  except Exception:
268
264
  return
269
265
  else:
270
- abs_value = relative_pos_to_absolute(
271
- relative_pos=float(rel_value), det_width=width
272
- )
266
+ abs_value = float(rel_value) + (width - 1) / 2.0
273
267
  self._absolutePositionQLE.setText(str(abs_value))
274
268
 
275
269
  def _updateRelativePosition(self, width):
@@ -279,7 +273,5 @@ class _PositionInfoWidget(qt.QWidget):
279
273
  except Exception:
280
274
  return
281
275
  else:
282
- rel_value = absolute_pos_to_relative(
283
- absolute_pos=float(abs_value), det_width=width
284
- )
276
+ rel_value = float(abs_value) - (width - 1) / 2.0
285
277
  self._relativePositionQLE.setText(str(rel_value))
@@ -6,10 +6,17 @@ from silx.gui import qt
6
6
 
7
7
  from tomwer.core.process.reconstruction.axis import mode as axis_mode
8
8
  from tomwer.core.process.reconstruction.axis.anglemode import CorAngleMode
9
+ from tomwer.core.process.reconstruction.axis.params import (
10
+ DEFAULT_CMP_N_SUBSAMPLING_Y,
11
+ DEFAULT_CMP_OVERSAMPLING,
12
+ DEFAULT_CMP_TAKE_LOG,
13
+ DEFAULT_CMP_THETA,
14
+ )
9
15
  from tomwer.gui.utils.scrollarea import QComboBoxIgnoreWheel
10
16
  from tomwer.core.scan.scanbase import TomwerScanBase
11
17
  from tomwer.gui.utils.qt_utils import block_signals
12
18
  from tomwer.synctools.axis import QAxisRP
19
+ from .CalculationWidget import CalculationWidget
13
20
  from .ManualFramesSelection import ManualFramesSelection
14
21
 
15
22
  _logger = logging.getLogger(__name__)
@@ -48,24 +55,74 @@ class InputWidget(qt.QWidget):
48
55
 
49
56
  # sinogram input
50
57
  self._sinogramGB = qt.QGroupBox(self)
51
- self._sinogramGB.setLayout(qt.QFormLayout())
58
+ self._sinogramGB.setLayout(qt.QVBoxLayout())
59
+ self._standardSinogramOpts = qt.QGroupBox(self)
60
+ self._sinogramGB.layout().addWidget(self._standardSinogramOpts)
61
+ self._standardSinogramOpts.setLayout(qt.QFormLayout())
62
+ self._standardSinogramOpts.layout().setContentsMargins(0, 0, 0, 0)
63
+ self._standardSinogramOpts.setTitle("standard options")
52
64
 
53
65
  self._sinogramGB.setTitle("sinogram")
54
66
  self._sinogramGB.setCheckable(True)
55
67
  self.layout().addWidget(self._sinogramGB)
56
68
  ## sinogram line
57
69
  self._sinogramLineSB = _SliceSelector(self)
58
- self._sinogramGB.layout().addRow("line", self._sinogramLineSB)
70
+ self._standardSinogramOpts.layout().addRow("line", self._sinogramLineSB)
59
71
  ## sinogram subsampling
60
72
  self._sinogramSubsampling = qt.QSpinBox(self)
61
73
  self._sinogramSubsampling.setRange(1, 1000)
62
74
  self._sinogramSubsampling.setValue(10)
63
- self._sinogramGB.layout().addRow("subsampling", self._sinogramSubsampling)
75
+ self._standardSinogramOpts.layout().addRow(
76
+ "subsampling", self._sinogramSubsampling
77
+ )
64
78
 
65
79
  self._spacer = qt.QWidget(self)
66
80
  self._spacer.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding)
67
81
  self.layout().addWidget(self._spacer)
68
82
 
83
+ ## options for the composite mode
84
+ self._compositeOpts = qt.QGroupBox(self)
85
+ self._compositeOpts.setTitle("composite options")
86
+ self._sinogramGB.layout().addWidget(self._compositeOpts)
87
+ self._compositeOpts.setLayout(qt.QFormLayout())
88
+ self._compositeOpts.layout().setContentsMargins(0, 0, 0, 0)
89
+ self._thetaSB = qt.QSpinBox(self)
90
+ self._thetaSB.setRange(0, 360)
91
+ self._thetaSB.setValue(DEFAULT_CMP_THETA)
92
+ self._thetaSB.setToolTip("a radio will be picked each theta degres")
93
+ self._thetaLabel = qt.QLabel("angle interval (in degree)", self)
94
+ self._thetaLabel.setToolTip(
95
+ "algorithm will take one projection each 'angle interval'. Also know as 'theta'"
96
+ )
97
+ self._compositeOpts.layout().addRow(self._thetaLabel, self._thetaSB)
98
+
99
+ self._oversamplingSB = qt.QSpinBox(self)
100
+ self._oversamplingSB.setValue(DEFAULT_CMP_OVERSAMPLING)
101
+ self._oversamplingSB.setToolTip("sinogram oversampling")
102
+ self._compositeOpts.layout().addRow("oversampling", self._oversamplingSB)
103
+
104
+ self._nearwidthSB = qt.QSpinBox(self)
105
+ self._nearwidthSB.setRange(-40000, 40000)
106
+ self._nearwidthSB.setValue(0)
107
+ self._nearwidthSB.setToolTip("position to be used with near option")
108
+ self._nearwidthLabel = qt.QLabel("near width", self)
109
+ self._nearwidthLabel.setToolTip("position to be used with near option")
110
+ self._compositeOpts.layout().addRow(self._nearwidthLabel, self._nearwidthSB)
111
+
112
+ self._subsamplingYSB = qt.QSpinBox(self)
113
+ self._subsamplingYSB.setValue(DEFAULT_CMP_N_SUBSAMPLING_Y)
114
+ self._subsamplingYSB.setToolTip("sinogram number of subsampling along y")
115
+ self._compositeOpts.layout().addRow("n_subsampling_y", self._subsamplingYSB)
116
+
117
+ self._takeLogCB = qt.QCheckBox(self)
118
+ self._takeLogCB.setToolTip("Take logarithm")
119
+ self._takeLogCB.setChecked(DEFAULT_CMP_TAKE_LOG)
120
+ self._takeTheLogLabel = qt.QLabel("linearisation (-log(I/I0))")
121
+ self._takeTheLogLabel.setToolTip(
122
+ "take (-log(I/I0)) as input. Also know as 'take_log' option"
123
+ )
124
+ self._compositeOpts.layout().addRow(self._takeTheLogLabel, self._takeLogCB)
125
+
69
126
  # set up
70
127
  self._sinogramGB.setChecked(False)
71
128
 
@@ -74,6 +131,11 @@ class InputWidget(qt.QWidget):
74
131
  self._radioGB.toggled.connect(self._radiosChecked)
75
132
  self._sinogramSubsampling.valueChanged.connect(self._changed)
76
133
  self._sinogramLineSB.sigChanged.connect(self._changed)
134
+ self._thetaSB.valueChanged.connect(self._changed)
135
+ self._oversamplingSB.valueChanged.connect(self._changed)
136
+ self._subsamplingYSB.valueChanged.connect(self._changed)
137
+ self._nearwidthSB.valueChanged.connect(self._changed)
138
+ self._takeLogCB.toggled.connect(self._changed)
77
139
  self._angleModeWidget.sigChanged.connect(self._sigUrlChanged)
78
140
 
79
141
  def setScan(self, scan: TomwerScanBase):
@@ -89,11 +151,10 @@ class InputWidget(qt.QWidget):
89
151
  if axis_params is not None:
90
152
  assert isinstance(axis_params, QAxisRP)
91
153
  with block_signals(self._sinogramGB):
92
- self._sinogramChecked(
93
- axis_params.mode.requires_sinogram_index(), on_load=True
94
- )
154
+ self._sinogramChecked(axis_params.use_sinogram, on_load=True)
95
155
  self._sinogramLineSB.setSlice(axis_params.sinogram_line)
96
156
  self._sinogramSubsampling.setValue(axis_params.sinogram_subsampling)
157
+ self.setCompositeOptions(axis_params.composite_options)
97
158
  self._angleModeWidget.setAxisParams(axis_params)
98
159
  self._axis_params = axis_params
99
160
 
@@ -141,8 +202,10 @@ class InputWidget(qt.QWidget):
141
202
 
142
203
  def _updateAxisParams(self):
143
204
  if not self._blockUpdateAxisParams:
205
+ self._axis_params.use_sinogram = self._sinogramGB.isChecked()
144
206
  self._axis_params.sinogram_line = self.getSinogramLine()
145
207
  self._axis_params.sinogram_subsampling = self.getSinogramSubsampling()
208
+ self._axis_params.composite_options = self.getCompositeOptions()
146
209
 
147
210
  def setValidInputs(self, modes: list | tuple):
148
211
  """
@@ -167,21 +230,102 @@ class InputWidget(qt.QWidget):
167
230
  raise ValueError("modes is empty")
168
231
  else:
169
232
  mode = axis_mode._InputType.from_value(modes.pop())
170
- if mode is axis_mode._InputType.SINOGRAM:
233
+ if mode in (axis_mode._InputType.SINOGRAM, axis_mode._InputType.COMPOSITE):
171
234
  self._sinogramGB.setEnabled(True)
172
235
  self._radioGB.setEnabled(False)
173
236
  self._sinogramGB.setChecked(True)
237
+ self._compositeOpts.setEnabled(mode is axis_mode._InputType.COMPOSITE)
238
+ self._standardSinogramOpts.setEnabled(
239
+ mode is not axis_mode._InputType.COMPOSITE
240
+ )
174
241
  elif mode is axis_mode._InputType.RADIOS_X2:
175
242
  self._radioGB.setEnabled(True)
176
243
  self._sinogramGB.setEnabled(False)
177
244
  self._radioGB.setChecked(True)
178
- elif mode is axis_mode._InputType.COMPOSITE:
179
- # those mode are neither sinogram neither radio. Now one of the two will be checked but without any much meaning
180
- self._radioGB.setEnabled(False)
181
- self._sinogramGB.setEnabled(False)
182
245
  else:
183
246
  raise ValueError(f"Nothing implemented for {mode.value}")
184
247
 
248
+ def getCompositeOptions(self) -> dict:
249
+ return {
250
+ "theta": self.getTheta(),
251
+ "oversampling": self.getOversampling(),
252
+ "n_subsampling_y": self.getSubsamplingY(),
253
+ "take_log": self.getTakeLog(),
254
+ "near_pos": self.getNearpos(),
255
+ "near_width": self.getNearwidth(),
256
+ }
257
+
258
+ def setCompositeOptions(self, opts: dict) -> None:
259
+ if not isinstance(opts, dict):
260
+ raise TypeError("opts should be an instance of dict")
261
+ for key in opts.keys():
262
+ if key not in (
263
+ "theta",
264
+ "oversampling",
265
+ "n_subsampling_y",
266
+ "take_log",
267
+ "near_pos",
268
+ "near_width",
269
+ ):
270
+ raise KeyError(f"{key} is not recogized")
271
+ theta = opts.get("theta", None)
272
+ if theta is not None:
273
+ self.setTheta(theta=theta)
274
+ oversampling = opts.get("oversampling", None)
275
+ if oversampling is not None:
276
+ self.setOversampling(oversampling)
277
+ n_subsampling_y = opts.get("n_subsampling_y", None)
278
+ if n_subsampling_y is not None:
279
+ self.setSubsamplingY(n_subsampling_y)
280
+
281
+ near_width = opts.get("near_width", None)
282
+ if near_width is not None:
283
+ self.setNearwidth(near_width)
284
+
285
+ take_log = opts.get("take_log", None)
286
+ if take_log is not None:
287
+ self.setTakeLog(take_log)
288
+
289
+ def getTheta(self) -> int:
290
+ return self._thetaSB.value()
291
+
292
+ def setTheta(self, theta: int) -> None:
293
+ self._thetaSB.setValue(theta)
294
+
295
+ def getOversampling(self) -> int:
296
+ return self._oversamplingSB.value()
297
+
298
+ def setOversampling(self, oversampling: int) -> None:
299
+ self._oversamplingSB.setValue(oversampling)
300
+
301
+ def getNearpos(self) -> int:
302
+ cal_widget = self.parentWidget().widget(0)
303
+ assert isinstance(cal_widget, CalculationWidget)
304
+ return cal_widget.getEstimatedCor()
305
+
306
+ def setNearpos(self, value) -> int:
307
+ cal_widget = self.parentWidget().widget(0)
308
+ assert isinstance(cal_widget, CalculationWidget)
309
+ cal_widget.setNearPosition(value)
310
+
311
+ def getNearwidth(self) -> int:
312
+ return self._nearwidthSB.value()
313
+
314
+ def setNearwidth(self, value) -> int:
315
+ return self._nearwidthSB.setValue(value)
316
+
317
+ def getSubsamplingY(self) -> int:
318
+ return self._subsamplingYSB.value()
319
+
320
+ def setSubsamplingY(self, subsampling: int) -> None:
321
+ self._subsamplingYSB.setValue(subsampling)
322
+
323
+ def getTakeLog(self) -> bool:
324
+ return self._takeLogCB.isChecked()
325
+
326
+ def setTakeLog(self, log: bool) -> None:
327
+ self._takeLogCB.setChecked(log)
328
+
185
329
 
186
330
  class _AngleSelectionWidget(qt.QWidget):
187
331
  """Group box to select the angle to used for cor calculation