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
@@ -1,394 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from silx.gui import qt
4
- from processview.gui.DropDownWidget import DropDownWidget
5
- from tomwer.synctools.axis import QAxisRP
6
- from tomwer.gui.utils.qt_utils import block_signals
7
- from tomwer.gui.utils.buttons import PadlockButton
8
- from tomwer.gui.reconstruction.axis.EstimatedCorComboBox import EstimatedCorComboBox
9
- from tomwer.core.process.reconstruction.axis.side import Side
10
- from tomwer.core.process.reconstruction.axis import mode as axis_mode
11
- from tomwer.gui.fonts import FONT_SMALL
12
- from tomwer.gui.utils.scrollarea import QDoubleSpinBoxIgnoreWheel
13
- from pyunitsystem.metricsystem import MetricSystem
14
-
15
-
16
- class EstimatedCORWidget(qt.QGroupBox):
17
- """
18
- Widget to define the estimated center of rotation.
19
- (based on the motor offset and the 'x_rotation_axis_pixel_position')
20
- """
21
-
22
- sigValueChanged = qt.Signal()
23
- """Emit when one of the value changed"""
24
- sigUpdateXRotAxisPixelPosOnNewScan = qt.Signal()
25
- """Emit when user want to stop / activate x rotation axis pixel position when a new scan arrives"""
26
- sigYAxisInvertedChanged = qt.Signal(bool)
27
-
28
- def __init__(self, parent, axis_params: QAxisRP):
29
- self._axis_params = axis_params
30
- self._imageWidth = None
31
-
32
- super().__init__(parent)
33
- self.setLayout(qt.QGridLayout())
34
- # estimated cor
35
- self._estimatedCORLabel = qt.QLabel("Estimated CoR (relative)", self)
36
- self.layout().addWidget(self._estimatedCORLabel, 0, 0, 1, 1)
37
-
38
- self._estimatedCORQCB = EstimatedCorComboBox(self)
39
- self.layout().addWidget(self._estimatedCORQCB, 0, 1, 1, 1)
40
-
41
- # offset calibration
42
- self._offsetWidgetDropdown = DropDownWidget(
43
- parent=self, direction=qt.Qt.LayoutDirection.RightToLeft
44
- )
45
- self._offsetWidget = _OffsetCalibration(parent=self, axis_params=axis_params)
46
- self.layout().addWidget(self._offsetWidgetDropdown, 1, 0, 2, 2)
47
- self._offsetWidgetDropdown.setWidget(self._offsetWidget)
48
-
49
- # set up
50
- self._offsetWidgetDropdown.setChecked(False)
51
-
52
- # connect signal / slot
53
- self._estimatedCORQCB.sigEstimatedCorChanged.connect(self._corChanged)
54
- self._offsetWidget.sigOffsetChanged.connect(
55
- self._updateEstimatedCorFromMotorOffsetWidget
56
- )
57
- self._offsetWidget.sigXRotationAxisPixelPositionChanged.connect(
58
- self._updateEstimatedCorFromMotorOffsetWidget
59
- )
60
- self._offsetWidget.sigUpdateXRotAxisPixelPosOnNewScan.connect(
61
- self.sigUpdateXRotAxisPixelPosOnNewScan
62
- )
63
- self._offsetWidget.sigYAxisInvertedChanged.connect(self.sigYAxisInvertedChanged)
64
-
65
- def getEstimatedCor(self):
66
- return self._estimatedCORQCB.getCurrentCorValue()
67
-
68
- def setEstimatedCor(
69
- self, value: float | Side | str, provided_with_offset: bool = False
70
- ):
71
-
72
- if isinstance(value, float):
73
- if self._offsetWidget.isYAxisInverted():
74
- value = -1.0 * value
75
-
76
- if provided_with_offset:
77
- value_with_offset = value
78
- value_without_offset = value - self._offsetWidget.getOffset()
79
- else:
80
- value_with_offset = value + self._offsetWidget.getOffset()
81
- value_without_offset = value
82
- with block_signals(self._offsetWidget):
83
- self._offsetWidget.setXRotationAxisPixelPosition(
84
- value_without_offset, apply_flip=False
85
- )
86
- else:
87
- # case this is a side
88
- value_with_offset = value
89
-
90
- self._estimatedCORQCB.setCurrentCorValue(value_with_offset)
91
-
92
- with block_signals(self._axis_params):
93
- self._axis_params.estimated_cor = value_with_offset
94
-
95
- def _corChanged(self, value):
96
- assert value is None or isinstance(value, (float, Side, None))
97
- with block_signals(self._axis_params):
98
- self._axis_params.estimated_cor = value
99
- self.sigValueChanged.emit()
100
-
101
- def _updateVisibleSides(self, mode: axis_mode.AxisMode):
102
- """
103
- Update the visibility and selection of sides (Left, Center, Right)
104
- for the Estimated Center of Rotation (CoR) ComboBox based on the
105
- provided axis mode and a calculated CoR guess.
106
-
107
- This method adjusts the available sides and determines the new
108
- side (Left, Center, or Right) based on the position of a CoR guess
109
- relative to the width of the image. The image is divided into thirds:
110
- - Left: From -infinity to the first third.
111
- - Center: From the first third to the second third.
112
- - Right: From the second third to infinity.
113
-
114
- If the CoR guess is not valid or falls outside the calculated bounds,
115
- a default valid side is selected.
116
-
117
- Behavior:
118
- ---------
119
- - By default if the method allows it, the side will be the estimated CoR.
120
- - If the method does not allow it, it will update the visible sides in
121
- the Estimated CoR ComboBox based on the valid sides defined in the axis
122
- mode metadata and dynamically determines the new side (Left, Center, Right)
123
- based on the CoR guess position within the image width.
124
-
125
- """
126
- mode = axis_mode.AxisMode.from_value(mode)
127
- valid_sides = axis_mode.AXIS_MODE_METADATAS[mode].valid_sides
128
- self._estimatedCORQCB.setSidesVisible(valid_sides)
129
- first_guess_available = axis_mode.AXIS_MODE_METADATAS[
130
- mode
131
- ].allows_estimated_cor_as_numerical_value
132
- self._estimatedCORQCB.setFirstGuessAvailable(first_guess_available)
133
-
134
- if first_guess_available:
135
- # if the first guess is valid, when the sides visibility is modify we want to activate it.
136
- self._estimatedCORQCB.selectFirstGuess()
137
- elif valid_sides:
138
- # Proceed only if there are valid sides
139
- current_side = self._estimatedCORQCB.getCurrentCorValue()
140
- if not isinstance(current_side, Side) or current_side not in valid_sides:
141
- with block_signals(self._estimatedCORQCB):
142
- cor_guess = self._estimatedCORQCB.getCurrentCorValue()
143
- if cor_guess is not None and self._imageWidth is not None:
144
- left_boundary = -float("inf")
145
- right_boundary = float("inf")
146
- middle_left_boundary = (
147
- self._imageWidth / 3 - self._imageWidth / 2
148
- )
149
- middle_right_boundary = (
150
- 2 * self._imageWidth / 3 - self._imageWidth / 2
151
- )
152
-
153
- if (
154
- Side.LEFT in valid_sides
155
- and left_boundary <= cor_guess < middle_left_boundary
156
- ):
157
- new_side = Side.LEFT
158
- elif (
159
- Side.CENTER in valid_sides
160
- and middle_left_boundary
161
- <= cor_guess
162
- < middle_right_boundary
163
- ):
164
- new_side = Side.CENTER
165
- elif (
166
- Side.RIGHT in valid_sides
167
- and middle_right_boundary <= cor_guess <= right_boundary
168
- ):
169
- new_side = Side.RIGHT
170
- else:
171
- # Fallback to the first available valid side
172
- new_side = valid_sides[0]
173
- else:
174
- # If no guess or boundaries are available, fallback to the first valid side
175
- new_side = valid_sides[0]
176
- self._estimatedCORQCB.setCurrentCorValue(new_side)
177
- self._axis_params.estimated_cor = new_side
178
-
179
- def _updateEstimatedCorFromMotorOffsetWidget(self):
180
- self._estimatedCORQCB.setCurrentCorValue(self._offsetWidget.getEstimatedCor())
181
- self.sigValueChanged.emit()
182
-
183
- def setImageWidth(self, image_width: float | None):
184
- self._imageWidth = image_width
185
-
186
- # expose API
187
- def updateXRotationAxisPixelPositionOnNewScan(self) -> bool:
188
- return self._offsetWidget.updateXRotationAxisPixelPositionOnNewScan()
189
-
190
- def setUpdateXRotationAxisPixelPositionOnNewScan(self, update: bool):
191
- self._offsetWidget.setUpdateXRotationAxisPixelPositionOnNewScan(update=update)
192
-
193
- def setPixelSize(self, pixel_size_m: float | None) -> None:
194
- self._offsetWidget.setPixelSize(pixel_size_m=pixel_size_m)
195
-
196
- def isYAxisInverted(self) -> bool:
197
- return self._offsetWidget.isYAxisInverted()
198
-
199
- def setYAxisInverted(self, checked: bool):
200
- return self._offsetWidget.setYAxisInverted(checked=checked)
201
-
202
-
203
- class _OffsetCalibration(qt.QGroupBox):
204
-
205
- sigOffsetChanged = qt.Signal()
206
- sigXRotationAxisPixelPositionChanged = qt.Signal()
207
- sigUpdateXRotAxisPixelPosOnNewScan = qt.Signal()
208
- sigYAxisInvertedChanged = qt.Signal(bool)
209
-
210
- def __init__(self, parent, axis_params: QAxisRP):
211
- super().__init__(parent)
212
- self._axis_params = axis_params
213
-
214
- self.setLayout(qt.QVBoxLayout())
215
-
216
- # x_rotation_axis_pixel_position
217
- self._xRotationAxisPixelPositionGroup = qt.QGroupBox("NXtomo metadata ")
218
- self._xRotationAxisPixelPositionGroup.setLayout(qt.QGridLayout())
219
- self.layout().addWidget(self._xRotationAxisPixelPositionGroup)
220
- self._xRotationAxisPixelPositionLabel = qt.QLabel(
221
- "x_rotation_axis_pixel_position"
222
- )
223
- self._xRotationAxisPixelPositionGroup.layout().addWidget(
224
- self._xRotationAxisPixelPositionLabel, 0, 0, 2, 1
225
- )
226
- self._xRotationAxisPixelPositionDSB = QDoubleSpinBoxIgnoreWheel(self)
227
- self._xRotationAxisPixelPositionDSB.setDecimals(2)
228
- self._xRotationAxisPixelPositionDSB.setRange(-float("inf"), float("inf"))
229
- self._xRotationAxisPixelPositionDSB.setSuffix(" px")
230
- self._xRotationAxisPixelPositionDSB.setEnabled(False)
231
- self._xRotationAxisPixelPositionGroup.layout().addWidget(
232
- self._xRotationAxisPixelPositionDSB, 0, 1, 2, 1
233
- )
234
- self._xRotationAxisPixelPositionKeepUpdatedCB = qt.QCheckBox(
235
- "Update with\n new scan"
236
- )
237
- self._xRotationAxisPixelPositionKeepUpdatedCB.setFont(FONT_SMALL)
238
- self._xRotationAxisPixelPositionKeepUpdatedCB.setToolTip(
239
- "Updates the value when a new scan arrives.\n"
240
- "Once updated this will change the numerical value of the estimated cor (from estimated relative cor)"
241
- )
242
- self._xRotationAxisPixelPositionGroup.layout().addWidget(
243
- self._xRotationAxisPixelPositionKeepUpdatedCB, 0, 2, 1, 1
244
- )
245
- self._yAxisInvertedCB = qt.QCheckBox("Y axis inverted")
246
- self._yAxisInvertedCB.setFont(FONT_SMALL)
247
- self._yAxisInvertedCB.setToolTip(
248
- "Sometime the y axis can be inverted (like on ID11).\nIn this case the estimation of the CoR has a flip that must be handled downstream"
249
- )
250
- self._xRotationAxisPixelPositionGroup.layout().addWidget(
251
- self._yAxisInvertedCB, 1, 2, 1, 1
252
- )
253
-
254
- # offset group
255
- self._offsetGroup = qt.QGroupBox("Custom Offset")
256
- self._offsetGroup.setLayout(qt.QFormLayout())
257
- self.layout().addWidget(self._offsetGroup)
258
-
259
- # offset
260
- self._offsetSB = QDoubleSpinBoxIgnoreWheel(self)
261
- self._offsetSB.setSuffix(" px")
262
- self._offsetSB.setDecimals(2)
263
- self._offsetSB.setRange(-float("inf"), float("inf"))
264
- self._offsetSB.setEnabled(False)
265
- self._offsetGroup.layout().addRow("Offset", self._offsetSB)
266
-
267
- # motor offset
268
- self._motorOffsetSB = QDoubleSpinBoxIgnoreWheel(self)
269
- self._motorOffsetSB.setSuffix(" mm")
270
- self._motorOffsetSB.setDecimals(4)
271
- self._motorOffsetSB.setRange(-float("inf"), float("inf"))
272
- self._offsetGroup.layout().addRow("Y Motor Offset", self._motorOffsetSB)
273
-
274
- # pixel size
275
- self._pixelSizeSB = QDoubleSpinBoxIgnoreWheel(self)
276
- self._pixelSizeSB.setSuffix(" µm")
277
- self._pixelSizeSB.setDecimals(3)
278
- self._pixelSizeSB.setRange(0, float("inf"))
279
-
280
- # Add a horizontal layout for the pixel size and the padlock
281
- pixelSizeLayout = qt.QHBoxLayout()
282
-
283
- # Add the spin box to the layout
284
- pixelSizeLayout.addWidget(self._pixelSizeSB)
285
-
286
- # Add a padlock button
287
- self._pixelSizePadlock = PadlockButton(self)
288
- self._pixelSizePadlock.setChecked(True) # Default to locked
289
- self._pixelSizePadlock.setFixedSize(24, 24)
290
- self._pixelSizePadlock.setToolTip("Lock/Unlock pixel size")
291
- pixelSizeLayout.addWidget(self._pixelSizePadlock)
292
-
293
- # Add the layout to the offset group
294
- self._offsetGroup.layout().addRow("Pixel Size", pixelSizeLayout)
295
-
296
- # Initialize the spin box as disabled (locked by default)
297
- self._pixelSizeSB.setEnabled(False)
298
-
299
- # Connect signal to handle locking behavior
300
- self._pixelSizePadlock.toggled.connect(self._togglePixelSizeLock)
301
-
302
- # set up
303
- self._xRotationAxisPixelPositionKeepUpdatedCB.setChecked(True)
304
- self._yAxisInvertedCB.setChecked(False)
305
-
306
- # connect signal / slot
307
- self._offsetSB.editingFinished.connect(self._offsetEdited)
308
- self._xRotationAxisPixelPositionKeepUpdatedCB.toggled.connect(
309
- self.sigUpdateXRotAxisPixelPosOnNewScan
310
- )
311
- self._motorOffsetSB.valueChanged.connect(self._motorOffsetChanged)
312
- self._pixelSizeSB.valueChanged.connect(self._pixelSizeChanged)
313
- self._yAxisInvertedCB.toggled.connect(self._yAxisInverted)
314
-
315
- def getXRotationAxisPixelPosition(self) -> float:
316
- return self._xRotationAxisPixelPositionDSB.value()
317
-
318
- def updateXRotationAxisPixelPositionOnNewScan(self) -> bool:
319
- return self._xRotationAxisPixelPositionKeepUpdatedCB.isChecked()
320
-
321
- def setUpdateXRotationAxisPixelPositionOnNewScan(self, update):
322
- self._xRotationAxisPixelPositionKeepUpdatedCB.setChecked(update)
323
-
324
- def setXRotationAxisPixelPosition(
325
- self, value: float, apply_flip: bool = True
326
- ) -> float:
327
- """Set the 'x_rotation_axis_pixel_position' and flip it if necessary"""
328
- if apply_flip and self.isYAxisInverted():
329
- value = -1.0 * value
330
- self._xRotationAxisPixelPositionDSB.setValue(value)
331
- return value
332
-
333
- def getOffset(self) -> float:
334
- return self._offsetSB.value()
335
-
336
- def setOffset(self, value: float) -> None:
337
- self._offsetSB.setValue(value)
338
- self._offsetEdited()
339
-
340
- def _offsetEdited(self):
341
- with block_signals(self):
342
- self._axis_params.x_rotation_axis_pos_px_offset = self.getOffset()
343
- self.sigOffsetChanged.emit()
344
-
345
- def _motorOffsetChanged(self):
346
- """Recalculate the offset when the motor offset changes."""
347
- if self._pixelSizeSB.value() and self._motorOffsetSB.value():
348
- pixel_size_m = self._pixelSizeSB.value() * MetricSystem.MICROMETER.value
349
- motor_offset_m = self._motorOffsetSB.value() * MetricSystem.MILLIMETER.value
350
- with block_signals(self):
351
- self.setOffset(motor_offset_m / pixel_size_m)
352
- self.sigOffsetChanged.emit()
353
-
354
- def _pixelSizeChanged(self):
355
- """Recalculate the offset when the pixel size changes."""
356
- if self._pixelSizeSB.value() and self._motorOffsetSB.value():
357
- pixel_size_m = self._pixelSizeSB.value() * MetricSystem.MICROMETER.value
358
- motor_offset_m = self._motorOffsetSB.value() * MetricSystem.MILLIMETER.value
359
- with block_signals(self):
360
- self.setOffset(motor_offset_m / pixel_size_m)
361
- self.sigOffsetChanged.emit()
362
-
363
- def _xRotationAxisPixelPositionEdited(self):
364
- with block_signals(self._axis_params):
365
- self._axis_params.x_rotation_axis_pixel_position = (
366
- self.getXRotationAxisPixelPosition()
367
- )
368
- self.sigXRotationAxisPixelPositionChanged.emit()
369
-
370
- def _yAxisInverted(self):
371
- self.setXRotationAxisPixelPosition(
372
- value=-1.0 * self.getXRotationAxisPixelPosition(),
373
- apply_flip=False,
374
- )
375
- self._xRotationAxisPixelPositionEdited()
376
-
377
- def isYAxisInverted(self) -> bool:
378
- return self._yAxisInvertedCB.isChecked()
379
-
380
- def setYAxisInverted(self, checked: bool):
381
- self._yAxisInvertedCB.setChecked(checked)
382
-
383
- def getEstimatedCor(self) -> float:
384
- return self.getXRotationAxisPixelPosition() + self.getOffset()
385
-
386
- def setPixelSize(self, pixel_size_m: float | None):
387
- if pixel_size_m is None:
388
- self._pixelSizeSB.clear()
389
- else:
390
- self._pixelSizeSB.setValue(pixel_size_m / MetricSystem.MICROMETER.value)
391
-
392
- def _togglePixelSizeLock(self, locked: bool):
393
- """Lock or unlock the pixel size spin box."""
394
- self._pixelSizeSB.setEnabled(not locked)
@@ -1,118 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from silx.gui import qt
4
- from tomwer.core.process.reconstruction.axis.side import Side
5
- from tomwer.gui.utils.scrollarea import QComboBoxIgnoreWheel
6
- from tomwer.gui.utils.qt_utils import block_signals
7
-
8
-
9
- class _EstimatedCorValidator(qt.QDoubleValidator):
10
- def __init__(self, parent=None):
11
- super().__init__(parent)
12
- self.setDecimals(3)
13
-
14
- def validate(self, a0: str, a1: int):
15
- """validate float or string that could be part of the side values..."""
16
- for value in Side.values():
17
- if value.startswith(a0):
18
- return (qt.QDoubleValidator.Acceptable, a0, a1)
19
- return super().validate(a0, a1)
20
-
21
-
22
- class EstimatedCorComboBox(QComboBoxIgnoreWheel):
23
- """
24
- Combobox that display the sides available according to the cor algorithm (left, right, center, all)
25
- and a dedicated item for cor given manually.
26
-
27
- This combobox is also editable and and make sure the 'estimated cor' item is up to date according to the QCombobox current value
28
- """
29
-
30
- ESTIMATED_COR_ITEM_DATA = "estimated_cor"
31
-
32
- sigEstimatedCorChanged = qt.Signal(object)
33
- """Emit when the estimated cor changed. Value can be a float (cor value) or a Side"""
34
-
35
- def __init__(self, parent=None):
36
- super().__init__(parent)
37
- self.addItem("0.0", self.ESTIMATED_COR_ITEM_DATA)
38
- for side in Side:
39
- self.addItem(side.value, side)
40
-
41
- self.setValidator(_EstimatedCorValidator())
42
- self.setEditable(True)
43
-
44
- self.setToolTip(
45
- """Estimated position of the center of rotation (COR) to be given to the cor algorithms. \n
46
- If you don't have a fair estimate you can provide only a side
47
- """
48
- )
49
- # connect signal / slot
50
- self.lineEdit().editingFinished.connect(self._corHasBeenEdited)
51
- self.currentIndexChanged.connect(self._corChanged)
52
-
53
- def _corHasBeenEdited(self):
54
- current_cor = self.getCurrentCorValue()
55
- if isinstance(current_cor, float):
56
- # keep the item up to date
57
- self._setCorItemValue(current_cor)
58
- self.sigEstimatedCorChanged.emit(current_cor)
59
-
60
- def _corChanged(self):
61
- self.sigEstimatedCorChanged.emit(self.getCurrentCorValue())
62
-
63
- def getCurrentCorValue(self) -> Side | float:
64
- try:
65
- return float(self.currentText())
66
- except ValueError:
67
- return Side(self.currentText())
68
-
69
- def setCurrentCorValue(self, value: float | Side):
70
- if isinstance(value, float):
71
- self._setCorItemValue(value)
72
- else:
73
- side = Side(value)
74
- item = self.findData(side)
75
- self.setCurrentIndex(item)
76
-
77
- def _setCorItemValue(self, value: float):
78
- item_index = self.findData(self.ESTIMATED_COR_ITEM_DATA)
79
- view = self.view()
80
- hidden = view.isRowHidden(item_index)
81
- with block_signals(self):
82
- self.setItemText(item_index, f"{value:.2f}")
83
- view.setRowHidden(item_index, hidden)
84
-
85
- def get_hidden_sides(self) -> tuple[Side]:
86
- """Return all sides currently hidden"""
87
- view = self.view()
88
- return tuple(
89
- filter(
90
- lambda side: view.isRowHidden(self.findData(side)),
91
- [Side(side) for side in Side],
92
- )
93
- )
94
-
95
- def setSidesVisible(self, sides: tuple[Side]):
96
- """Set side to be visible to the user"""
97
- sides_visibles = tuple([Side(side) for side in sides])
98
- view = self.view()
99
- for side in Side:
100
- item_idx = self.findData(side)
101
- view.setRowHidden(item_idx, side not in sides_visibles)
102
-
103
- need_new_current_value = self.getCurrentCorValue() in self.get_hidden_sides()
104
- if need_new_current_value:
105
- # if the current side used cannot be used fall back to the estimated cor from motor
106
- self.setCurrentIndex(self.findData(self.ESTIMATED_COR_ITEM_DATA))
107
-
108
- def setFirstGuessAvailable(self, available: bool):
109
- """
110
- For some method (only growing window at the moment) a first guess (estimated cor as a float) cannot be given
111
- """
112
- view = self.view()
113
- item_index = self.findData(self.ESTIMATED_COR_ITEM_DATA)
114
- view.setRowHidden(item_index, not available)
115
-
116
- def selectFirstGuess(self):
117
- item_index = self.findData(self.ESTIMATED_COR_ITEM_DATA)
118
- self.setCurrentIndex(item_index)