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.
- orangecontrib/tomwer/tutorials/test_cor.ows +3 -3
- orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +14 -6
- orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +2 -4
- tomwer/app/axis.py +3 -0
- tomwer/app/multipag.py +3 -11
- tomwer/core/process/reconstruction/axis/axis.py +736 -27
- tomwer/core/process/reconstruction/axis/mode.py +24 -86
- tomwer/core/process/reconstruction/axis/params.py +138 -127
- tomwer/core/process/reconstruction/nabu/nabuscores.py +22 -19
- tomwer/core/process/reconstruction/nabu/nabuslices.py +1 -5
- tomwer/core/process/reconstruction/saaxis/saaxis.py +4 -4
- tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +4 -4
- tomwer/core/process/reconstruction/tests/test_axis.py +1 -1
- tomwer/core/process/reconstruction/tests/test_utils.py +4 -4
- tomwer/core/process/reconstruction/utils/cor.py +4 -8
- tomwer/core/process/tests/test_axis.py +231 -0
- tomwer/core/process/tests/test_nabu.py +3 -1
- tomwer/core/scan/nxtomoscan.py +0 -2
- tomwer/core/scan/scanbase.py +4 -4
- tomwer/core/scan/tests/test_process_registration.py +18 -0
- tomwer/gui/reconstruction/axis/AxisMainWindow.py +9 -20
- tomwer/gui/reconstruction/axis/AxisOptionsWidget.py +79 -239
- tomwer/gui/reconstruction/axis/AxisSettingsWidget.py +17 -38
- tomwer/gui/reconstruction/axis/AxisWidget.py +8 -16
- tomwer/gui/reconstruction/axis/CalculationWidget.py +200 -44
- tomwer/gui/reconstruction/axis/ControlWidget.py +2 -10
- tomwer/gui/reconstruction/axis/InputWidget.py +155 -11
- tomwer/gui/reconstruction/saaxis/corrangeselector.py +10 -19
- tomwer/gui/reconstruction/scores/scoreplot.py +2 -5
- tomwer/gui/reconstruction/tests/test_nabu.py +0 -8
- tomwer/gui/stitching/config/axisparams.py +0 -2
- tomwer/gui/stitching/z_stitching/fineestimation.py +1 -1
- tomwer/gui/tests/test_axis_gui.py +15 -31
- tomwer/synctools/stacks/reconstruction/axis.py +23 -5
- tomwer/synctools/stacks/reconstruction/dkrefcopy.py +1 -1
- tomwer/synctools/stacks/reconstruction/nabu.py +2 -2
- tomwer/synctools/stacks/reconstruction/normalization.py +1 -1
- tomwer/synctools/stacks/reconstruction/saaxis.py +1 -1
- tomwer/synctools/stacks/reconstruction/sadeltabeta.py +1 -1
- tomwer/tests/orangecontrib/tomwer/widgets/reconstruction/tests/test_axis.py +16 -0
- tomwer/tests/test_ewoks/test_single_node_execution.py +1 -1
- tomwer/tests/test_ewoks/test_workflows.py +1 -1
- tomwer/version.py +1 -1
- {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/METADATA +3 -3
- {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/RECORD +49 -52
- tomwer/core/process/reconstruction/axis/side.py +0 -8
- tomwer/gui/fonts.py +0 -5
- tomwer/gui/reconstruction/axis/EstimatedCORWidget.py +0 -394
- tomwer/gui/reconstruction/axis/EstimatedCorComboBox.py +0 -118
- {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/LICENSE +0 -0
- {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/WHEEL +0 -0
- {tomwer-1.4.0.dist-info → tomwer-1.4.0rc0.dist-info}/entry_points.txt +0 -0
- {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.
|
56
|
+
axis_mode.AxisMode.sino_fourier_angles,
|
62
57
|
),
|
63
|
-
# composite
|
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
|
-
|
100
|
-
self.
|
101
|
-
self.layout().addWidget(self.
|
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.
|
107
|
-
|
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.
|
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
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
200
|
+
try:
|
201
|
+
return float(self._qleNearPosQLE.text())
|
202
|
+
except ValueError:
|
203
|
+
return 0
|
127
204
|
|
128
|
-
def
|
129
|
-
return self.
|
205
|
+
def updateAutomaticallyEstimatedCor(self):
|
206
|
+
return self._nearValueCB.isChecked()
|
130
207
|
|
131
|
-
def
|
132
|
-
self.
|
133
|
-
|
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.
|
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
|
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
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
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 =
|
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 =
|
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.
|
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.
|
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.
|
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
|
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
|