feat(xrayeye): reorganize control panel; exposure time as slider, not spinbox
Moves the auto-exposure/auto-gain toggles out of the shutter/camera switch grid into their own section, bracketed by horizontal separators: switches -> line -> exposure/gain section -> line -> alignment values (2D positioner + zoom). Also replaces the exposure-time QDoubleSpinBox with a QSlider bounded to the device's real exposure_time_min/max (from IDSCamera, seeded from hardware) instead of a hardcoded 0.01-1000ms placeholder -- QSlider is int-only, so the widget tracks tenths of a ms internally and shows one decimal on a companion label. Submission still fires once on release, not per tick.
This commit is contained in:
1 parent
8c1c322393
commit
557ca42a0c
1 file changed
+77
-27
@@ -13,7 +13,6 @@ from bec_widgets.widgets.plots.roi.image_roi import BaseROI, CircularROI, Rectan
|
||||
from bec_widgets.widgets.utility.toggle.toggle import ToggleSwitch
|
||||
from qtpy.QtCore import Qt, QTimer
|
||||
from qtpy.QtWidgets import (
|
||||
QDoubleSpinBox,
|
||||
QFrame,
|
||||
QGridLayout,
|
||||
QHBoxLayout,
|
||||
@@ -21,6 +20,7 @@ from qtpy.QtWidgets import (
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QSlider,
|
||||
QSpinBox,
|
||||
QToolButton,
|
||||
QVBoxLayout,
|
||||
@@ -270,6 +270,10 @@ class OMNY_XRayEye(BECWidget, QWidget):
|
||||
PIXEL_CALIBRATION_USER_PARAM = "pixel_calibration"
|
||||
PIXEL_CALIBRATION_DEFAULT = 1.0
|
||||
|
||||
# exposure_time_slider works in tenths of a ms internally (QSlider is
|
||||
# int-only); divide by this to get ms.
|
||||
_EXPOSURE_SLIDER_SCALE = 10
|
||||
|
||||
def __init__(self, parent=None, **kwargs):
|
||||
super().__init__(parent=parent, **kwargs)
|
||||
self._live_view_signal = CAMERA[1]
|
||||
@@ -403,8 +407,25 @@ class OMNY_XRayEye(BECWidget, QWidget):
|
||||
switch_grid.addWidget(self.smear_preview_label, 1, 3, _right_vcenter)
|
||||
switch_grid.addWidget(self.smear_preview_toggle, 1, 4, Qt.AlignmentFlag.AlignVCenter)
|
||||
|
||||
# Exposure/gain control knobs (row 2): auto-exposure and auto-gain
|
||||
# enable toggles, mirroring the shutter/camera-running row above.
|
||||
self.control_panel_layout.addWidget(self.switch_grid_widget)
|
||||
|
||||
# separator: below shutter/camera-running/smear switches, above the
|
||||
# exposure/gain section
|
||||
self.control_panel_layout.addWidget(self._create_separator())
|
||||
|
||||
# Exposure/gain section: auto-exposure and auto-gain enable toggles
|
||||
# (row 0, same layout idiom as switch_grid above), plus a manual
|
||||
# exposure-time slider (row 1) that's only enabled while
|
||||
# auto-exposure is off. Its range is a placeholder until seeded from
|
||||
# the hardware-reported (min, max) at connect time -- see
|
||||
# getting_camera_status().
|
||||
self.exposure_grid_widget = QWidget(parent=self)
|
||||
exposure_grid = QGridLayout(self.exposure_grid_widget)
|
||||
exposure_grid.setContentsMargins(0, 0, 0, 0)
|
||||
exposure_grid.setHorizontalSpacing(8)
|
||||
exposure_grid.setVerticalSpacing(4)
|
||||
exposure_grid.setColumnStretch(0, 1)
|
||||
|
||||
self.auto_exposure_label = QLabel("Auto exposure", parent=self)
|
||||
self.auto_exposure_toggle = ToggleSwitch(parent=self)
|
||||
self.auto_exposure_toggle.checked = True
|
||||
@@ -415,14 +436,35 @@ class OMNY_XRayEye(BECWidget, QWidget):
|
||||
self.auto_gain_toggle.checked = True
|
||||
self.auto_gain_toggle.enabled.connect(self.auto_gain_enabled_changed)
|
||||
|
||||
switch_grid.addWidget(self.auto_exposure_label, 2, 1, _right_vcenter)
|
||||
switch_grid.addWidget(self.auto_exposure_toggle, 2, 2, Qt.AlignmentFlag.AlignVCenter)
|
||||
switch_grid.addWidget(self.auto_gain_label, 2, 3, _right_vcenter)
|
||||
switch_grid.addWidget(self.auto_gain_toggle, 2, 4, Qt.AlignmentFlag.AlignVCenter)
|
||||
exposure_grid.addWidget(self.auto_exposure_label, 0, 1, _right_vcenter)
|
||||
exposure_grid.addWidget(self.auto_exposure_toggle, 0, 2, Qt.AlignmentFlag.AlignVCenter)
|
||||
exposure_grid.addWidget(self.auto_gain_label, 0, 3, _right_vcenter)
|
||||
exposure_grid.addWidget(self.auto_gain_toggle, 0, 4, Qt.AlignmentFlag.AlignVCenter)
|
||||
|
||||
self.control_panel_layout.addWidget(self.switch_grid_widget)
|
||||
# QSlider only takes ints, so exposure time is tracked in tenths of a
|
||||
# ms internally (_EXPOSURE_SLIDER_SCALE) for one decimal of
|
||||
# resolution on the display label.
|
||||
self.exposure_time_label = QLabel("Exposure time", parent=self)
|
||||
self.exposure_time_slider = QSlider(Qt.Orientation.Horizontal, parent=self)
|
||||
self.exposure_time_slider.setRange(0, 10000) # placeholder; reseeded on connect
|
||||
self.exposure_time_slider.setEnabled(False) # auto-exposure starts enabled
|
||||
self.exposure_time_value_label = QLabel("-- ms", parent=self)
|
||||
self.exposure_time_value_label.setMinimumWidth(60)
|
||||
self.exposure_time_slider.valueChanged.connect(self._update_exposure_time_value_label)
|
||||
# sliderReleased (fires once, on mouse-up), not valueChanged (fires on
|
||||
# every tick while dragging) -- the throttling choice here, same
|
||||
# "don't hammer the device" concern as editingFinished was for the
|
||||
# spinbox this replaces.
|
||||
self.exposure_time_slider.sliderReleased.connect(self.exposure_time_submitted)
|
||||
|
||||
# separator
|
||||
exposure_grid.addWidget(self.exposure_time_label, 1, 1, _right_vcenter)
|
||||
exposure_grid.addWidget(self.exposure_time_slider, 1, 2, 1, 2)
|
||||
exposure_grid.addWidget(self.exposure_time_value_label, 1, 4, Qt.AlignmentFlag.AlignVCenter)
|
||||
|
||||
self.control_panel_layout.addWidget(self.exposure_grid_widget)
|
||||
|
||||
# separator: below the exposure/gain section, above the alignment
|
||||
# values (2D positioner + zoom)
|
||||
self.control_panel_layout.addWidget(self._create_separator())
|
||||
|
||||
# 2D Positioner (fixed size)
|
||||
@@ -457,18 +499,6 @@ class OMNY_XRayEye(BECWidget, QWidget):
|
||||
# Add form to control panel
|
||||
self.control_panel_layout.addLayout(step_size_form)
|
||||
|
||||
# Exposure time (manual entry, only usable while auto-exposure is off)
|
||||
exposure_form = QGridLayout()
|
||||
self.exposure_time_spin = QDoubleSpinBox(parent=self)
|
||||
self.exposure_time_spin.setRange(0.01, 1000.0) # ms; confirm real driver range
|
||||
self.exposure_time_spin.setDecimals(2)
|
||||
self.exposure_time_spin.setSuffix(" ms")
|
||||
self.exposure_time_spin.setEnabled(False) # auto-exposure starts enabled
|
||||
self.exposure_time_spin.editingFinished.connect(self.exposure_time_submitted)
|
||||
exposure_form.addWidget(QLabel("Exposure time", parent=self), 0, 0)
|
||||
exposure_form.addWidget(self.exposure_time_spin, 0, 1)
|
||||
self.control_panel_layout.addLayout(exposure_form)
|
||||
|
||||
# Push form to bottom
|
||||
self.control_panel_layout.addStretch()
|
||||
|
||||
@@ -934,7 +964,7 @@ class OMNY_XRayEye(BECWidget, QWidget):
|
||||
enabled = bool(auto_exp.get("value"))
|
||||
self.auto_exposure_toggle.blockSignals(True)
|
||||
self.auto_exposure_toggle.checked = enabled
|
||||
self.exposure_time_spin.setEnabled(not enabled)
|
||||
self.exposure_time_slider.setEnabled(not enabled)
|
||||
self.auto_exposure_toggle.blockSignals(False)
|
||||
|
||||
auto_gain = signals.get(f"{CAMERA[0]}_auto_gain_enabled")
|
||||
@@ -943,11 +973,25 @@ class OMNY_XRayEye(BECWidget, QWidget):
|
||||
self.auto_gain_toggle.checked = bool(auto_gain.get("value"))
|
||||
self.auto_gain_toggle.blockSignals(False)
|
||||
|
||||
# Reseed the slider's bounds first (rare -- only changes if the pixel
|
||||
# clock changed), so a value arriving in the same message is clamped
|
||||
# against up-to-date min/max rather than a stale range.
|
||||
exposure_min = signals.get(f"{CAMERA[0]}_exposure_time_min")
|
||||
exposure_max = signals.get(f"{CAMERA[0]}_exposure_time_max")
|
||||
if exposure_min is not None and exposure_max is not None:
|
||||
lo = round(float(exposure_min.get("value")) * self._EXPOSURE_SLIDER_SCALE)
|
||||
hi = round(float(exposure_max.get("value")) * self._EXPOSURE_SLIDER_SCALE)
|
||||
if (lo, hi) != (self.exposure_time_slider.minimum(), self.exposure_time_slider.maximum()):
|
||||
self.exposure_time_slider.setRange(lo, hi)
|
||||
|
||||
exposure_time = signals.get(f"{CAMERA[0]}_exposure_time")
|
||||
if exposure_time is not None:
|
||||
self.exposure_time_spin.blockSignals(True)
|
||||
self.exposure_time_spin.setValue(float(exposure_time.get("value")))
|
||||
self.exposure_time_spin.blockSignals(False)
|
||||
self.exposure_time_slider.blockSignals(True)
|
||||
self.exposure_time_slider.setValue(
|
||||
round(float(exposure_time.get("value")) * self._EXPOSURE_SLIDER_SCALE)
|
||||
)
|
||||
self.exposure_time_slider.blockSignals(False)
|
||||
self._update_exposure_time_value_label(self.exposure_time_slider.value())
|
||||
|
||||
@SafeSlot(bool)
|
||||
def auto_exposure_enabled_changed(self, enabled: bool):
|
||||
@@ -957,7 +1001,7 @@ class OMNY_XRayEye(BECWidget, QWidget):
|
||||
self.auto_exposure_toggle.blockSignals(True)
|
||||
self.dev.get(CAMERA[0]).auto_exposure_enabled.put(enabled)
|
||||
self.auto_exposure_toggle.checked = enabled
|
||||
self.exposure_time_spin.setEnabled(not enabled)
|
||||
self.exposure_time_slider.setEnabled(not enabled)
|
||||
self.auto_exposure_toggle.blockSignals(False)
|
||||
|
||||
@SafeSlot(bool)
|
||||
@@ -970,8 +1014,14 @@ class OMNY_XRayEye(BECWidget, QWidget):
|
||||
self.auto_gain_toggle.checked = enabled
|
||||
self.auto_gain_toggle.blockSignals(False)
|
||||
|
||||
def _update_exposure_time_value_label(self, raw_value: int):
|
||||
self.exposure_time_value_label.setText(
|
||||
f"{raw_value / self._EXPOSURE_SLIDER_SCALE:.1f} ms"
|
||||
)
|
||||
|
||||
def exposure_time_submitted(self):
|
||||
self.dev.get(CAMERA[0]).exposure_time.put(self.exposure_time_spin.value())
|
||||
value_ms = self.exposure_time_slider.value() / self._EXPOSURE_SLIDER_SCALE
|
||||
self.dev.get(CAMERA[0]).exposure_time.put(value_ms)
|
||||
|
||||
@SafeSlot(bool)
|
||||
def opening_shutter(self, enabled: bool):
|
||||
|
||||
Reference in new issue
Block a user