fix(vision): VisionTuning preview blank until camera switched, silent Apply failures
Two real bugs found interactively: - The preview stayed blank after opening the widget or switching devices, and only appeared after switching camera away and back. Root cause: bec_widgets' Image.image(device=, signal=) subscribes via bec_dispatcher.connect_slot with no newest_only=True (confirmed by reading image.py directly), so it only catches publishes that happen *after* the Redis subscription is actually established -- the immediate refresh() right after subscribing could race it and be silently missed. A deferred refresh (QTimer.singleShot(200, ...)) after .image() reliably lands after the subscription is live; harmless since refresh() just republishes the same last frame. - Apply Edge Params appeared to do nothing on an invalid value (e.g. an even blur_ksize, easy to land on since the spin box steps through every integer) -- set_edge_params()'s VisionInterfaceError was only logger.exception()'d, never shown in the GUI. Added a status label so Apply/Refresh failures are visible instead of indistinguishable from a stalled widget. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015fDXnPuNvm3qUsUb4V4Gfo
This commit is contained in:
1 parent
5d4686f77f
commit
b13e80b916
1 file changed
+25
-4
@@ -140,6 +140,13 @@ class VisionTuning(BECWidget, QWidget):
|
||||
control_row.addStretch()
|
||||
root.addLayout(control_row)
|
||||
|
||||
# Surfaces the last error from an Apply/Refresh action -- e.g. an even blur_ksize rejected
|
||||
# by set_edge_params() -- instead of only logging it (a click that silently does nothing
|
||||
# is indistinguishable from a bug from the operator's side).
|
||||
self.status_label = QLabel("", parent=self)
|
||||
self.status_label.setStyleSheet("color: #c0392b;")
|
||||
root.addWidget(self.status_label)
|
||||
|
||||
self.image = Image(parent=self)
|
||||
root.addWidget(self.image, stretch=1)
|
||||
|
||||
@@ -187,6 +194,14 @@ class VisionTuning(BECWidget, QWidget):
|
||||
self.camera_combo.blockSignals(False)
|
||||
if device_name:
|
||||
self.image.image(device=device_name, signal="diagnostic_image")
|
||||
# The Image widget's subscription (bec_dispatcher.connect_slot, no newest_only) only
|
||||
# catches publishes that happen *after* the Redis subscription is actually established
|
||||
# -- a refresh() published immediately after .image() above can race it and be missed,
|
||||
# leaving the preview blank until some later publish. A short deferred refresh, on top
|
||||
# of the immediate one _on_camera_changed() triggers below, reliably lands after the
|
||||
# subscription is live -- cheap and harmless since refresh() just republishes the same
|
||||
# last frame.
|
||||
QTimer.singleShot(200, self._on_refresh_clicked)
|
||||
if interface is not None:
|
||||
edge_params = interface.get_edge_params()
|
||||
for spin_box, key in (
|
||||
@@ -267,8 +282,8 @@ class VisionTuning(BECWidget, QWidget):
|
||||
interface.set_edge_params(
|
||||
self.edge_low.value(), self.edge_high.value(), self.edge_blur.value()
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("VisionTuning: set_edge_params failed")
|
||||
except Exception as exc:
|
||||
self._set_status(f"set_edge_params failed: {exc}")
|
||||
return
|
||||
self._on_refresh_clicked()
|
||||
|
||||
@@ -284,8 +299,14 @@ class VisionTuning(BECWidget, QWidget):
|
||||
return
|
||||
try:
|
||||
interface.refresh(self._current_camera_name)
|
||||
except Exception:
|
||||
logger.exception("VisionTuning: refresh failed")
|
||||
except Exception as exc:
|
||||
self._set_status(f"refresh failed: {exc}")
|
||||
else:
|
||||
self._set_status("")
|
||||
|
||||
def _set_status(self, text: str):
|
||||
logger.info(f"VisionTuning: {text}") if text else None
|
||||
self.status_label.setText(text)
|
||||
|
||||
@SafeSlot(bool)
|
||||
def _on_live_toggled(self, checked: bool):
|
||||
|
||||
Reference in new issue
Block a user