|
|
|
@@ -140,6 +140,7 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
"enable_move_buttons",
|
|
|
|
|
"enable_move_buttons.setter",
|
|
|
|
|
"switch_tab",
|
|
|
|
|
"set_dap_params_forwarding",
|
|
|
|
|
"submit_fit_array",
|
|
|
|
|
]
|
|
|
|
|
PLUGIN = True
|
|
|
|
@@ -147,6 +148,12 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
def __init__(self, parent=None, **kwargs):
|
|
|
|
|
super().__init__(parent=parent, **kwargs)
|
|
|
|
|
self._connected_motor = None
|
|
|
|
|
self._dap_params_forwarding_connected = False
|
|
|
|
|
self._queue_busy = False
|
|
|
|
|
self._queue_idle_timer = QTimer(self)
|
|
|
|
|
self._queue_idle_timer.setSingleShot(True)
|
|
|
|
|
self._queue_idle_timer.setInterval(800)
|
|
|
|
|
self._queue_idle_timer.timeout.connect(self._release_queue_toggles)
|
|
|
|
|
self.get_bec_shortcuts()
|
|
|
|
|
|
|
|
|
|
self._init_ui()
|
|
|
|
@@ -159,9 +166,13 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
self.bec_dispatcher.connect_slot(
|
|
|
|
|
self.getting_camera_status, MessageEndpoints.device_read_configuration(CAMERA[0])
|
|
|
|
|
)
|
|
|
|
|
self.bec_dispatcher.connect_slot(
|
|
|
|
|
self.on_queue_status_update, MessageEndpoints.scan_queue_status()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.connect_motors()
|
|
|
|
|
self.resize(800, 600)
|
|
|
|
|
QTimer.singleShot(0, self._init_queue_status)
|
|
|
|
|
QTimer.singleShot(0, self._init_gui_trigger)
|
|
|
|
|
|
|
|
|
|
def _init_ui(self):
|
|
|
|
@@ -203,7 +214,8 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
header_row.addWidget(self.live_preview_toggle, 0, Qt.AlignmentFlag.AlignVCenter)
|
|
|
|
|
self.control_panel_layout.addLayout(header_row)
|
|
|
|
|
|
|
|
|
|
switch_row = QHBoxLayout()
|
|
|
|
|
self.switch_row_widget = QWidget(parent=self)
|
|
|
|
|
switch_row = QHBoxLayout(self.switch_row_widget)
|
|
|
|
|
switch_row.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
switch_row.setSpacing(8)
|
|
|
|
|
switch_row.addStretch()
|
|
|
|
@@ -219,7 +231,7 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
switch_row.addWidget(self.shutter_toggle, 0, Qt.AlignmentFlag.AlignVCenter)
|
|
|
|
|
switch_row.addWidget(self.camera_running_label, 0, Qt.AlignmentFlag.AlignVCenter)
|
|
|
|
|
switch_row.addWidget(self.camera_running_toggle, 0, Qt.AlignmentFlag.AlignVCenter)
|
|
|
|
|
self.control_panel_layout.addLayout(switch_row)
|
|
|
|
|
self.control_panel_layout.addWidget(self.switch_row_widget)
|
|
|
|
|
|
|
|
|
|
# separator
|
|
|
|
|
self.control_panel_layout.addWidget(self._create_separator())
|
|
|
|
@@ -308,9 +320,6 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
self.fit_x = self.waveform_x.curves[0]
|
|
|
|
|
self.fit_y = self.waveform_y.curves[0]
|
|
|
|
|
|
|
|
|
|
self.waveform_x.dap_params_update.connect(self.on_dap_params)
|
|
|
|
|
self.waveform_y.dap_params_update.connect(self.on_dap_params)
|
|
|
|
|
|
|
|
|
|
for wave in (self.waveform_x, self.waveform_y):
|
|
|
|
|
wave.x_label = "Angle (deg)"
|
|
|
|
|
wave.x_grid = True
|
|
|
|
@@ -389,6 +398,70 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
def enable_move_buttons(self, enabled: bool):
|
|
|
|
|
self.motor_control_2d.setEnabled(enabled)
|
|
|
|
|
|
|
|
|
|
def _queue_guarded_toggles(self) -> tuple[ToggleSwitch, ToggleSwitch, ToggleSwitch]:
|
|
|
|
|
return (self.live_preview_toggle, self.shutter_toggle, self.camera_running_toggle)
|
|
|
|
|
|
|
|
|
|
def _set_queue_toggles_blocked(self, blocked: bool):
|
|
|
|
|
if blocked == self._queue_busy:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
self._queue_busy = blocked
|
|
|
|
|
self._refresh_queue_toggle_availability()
|
|
|
|
|
|
|
|
|
|
def _refresh_queue_toggle_availability(self):
|
|
|
|
|
tooltip = "Disabled while scan queue is busy." if self._queue_busy else ""
|
|
|
|
|
for toggle in self._queue_guarded_toggles():
|
|
|
|
|
toggle.setEnabled(not self._queue_busy)
|
|
|
|
|
toggle.setToolTip(tooltip)
|
|
|
|
|
|
|
|
|
|
def _manual_toggle_blocked_by_queue(self) -> bool:
|
|
|
|
|
return self._queue_busy and self.sender() in self._queue_guarded_toggles()
|
|
|
|
|
|
|
|
|
|
def _update_queue_toggles_from_busy_state(self, busy: bool):
|
|
|
|
|
if busy:
|
|
|
|
|
self._queue_idle_timer.stop()
|
|
|
|
|
self._set_queue_toggles_blocked(True)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if self._queue_busy and not self._queue_idle_timer.isActive():
|
|
|
|
|
self._queue_idle_timer.start()
|
|
|
|
|
|
|
|
|
|
def _release_queue_toggles(self):
|
|
|
|
|
self._set_queue_toggles_blocked(False)
|
|
|
|
|
|
|
|
|
|
def _init_queue_status(self):
|
|
|
|
|
try:
|
|
|
|
|
msg = self.client.connector.get(MessageEndpoints.scan_queue_status())
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
logger.warning(f"Failed to fetch initial scan queue status for XRayEye: {exc}")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if msg is None:
|
|
|
|
|
return
|
|
|
|
|
self._update_queue_toggles_from_busy_state(self._is_queue_busy(msg.content))
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _is_queue_busy(msg_content: dict) -> bool:
|
|
|
|
|
queues = msg_content.get("queue", {}) if isinstance(msg_content, dict) else {}
|
|
|
|
|
primary_queue = queues.get("primary") if isinstance(queues, dict) else None
|
|
|
|
|
if primary_queue is None:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
queue_info = getattr(primary_queue, "info", None)
|
|
|
|
|
if queue_info is None and isinstance(primary_queue, dict):
|
|
|
|
|
queue_info = primary_queue.get("info", [])
|
|
|
|
|
if not queue_info:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
idle_statuses = {"STOPPED", "COMPLETED", "IDLE"}
|
|
|
|
|
for item in queue_info:
|
|
|
|
|
status = getattr(item, "status", None)
|
|
|
|
|
if status is None and isinstance(item, dict):
|
|
|
|
|
status = item.get("status")
|
|
|
|
|
if str(status).upper() not in idle_statuses:
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def active_roi(self) -> BaseROI | None:
|
|
|
|
|
"""Return the currently active ROI, or None if no ROI is active."""
|
|
|
|
|
return self.roi_manager.single_active_roi
|
|
|
|
@@ -418,6 +491,9 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
@SafeSlot(bool)
|
|
|
|
|
@rpc_timeout(20)
|
|
|
|
|
def on_live_view_enabled(self, enabled: bool):
|
|
|
|
|
if self._manual_toggle_blocked_by_queue():
|
|
|
|
|
logger.warning("Ignoring live-preview toggle while scan queue is busy.")
|
|
|
|
|
return
|
|
|
|
|
logger.info(f"Live view is enabled: {enabled}")
|
|
|
|
|
self.live_preview_toggle.blockSignals(True)
|
|
|
|
|
if enabled:
|
|
|
|
@@ -432,6 +508,9 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
|
|
|
|
|
@SafeSlot(bool)
|
|
|
|
|
def camera_running_enabled(self, enabled: bool):
|
|
|
|
|
if self._manual_toggle_blocked_by_queue():
|
|
|
|
|
logger.warning("Ignoring camera live-mode toggle while scan queue is busy.")
|
|
|
|
|
return
|
|
|
|
|
logger.info(f"Camera running: {enabled}")
|
|
|
|
|
self.camera_running_toggle.blockSignals(True)
|
|
|
|
|
self.dev.get(CAMERA[0]).live_mode_enabled.put(enabled)
|
|
|
|
@@ -448,6 +527,9 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
|
|
|
|
|
@SafeSlot(bool)
|
|
|
|
|
def opening_shutter(self, enabled: bool):
|
|
|
|
|
if self._manual_toggle_blocked_by_queue():
|
|
|
|
|
logger.warning("Ignoring shutter toggle while scan queue is busy.")
|
|
|
|
|
return
|
|
|
|
|
logger.info(f"Shutter changed from GUI to: {enabled}")
|
|
|
|
|
self.shutter_toggle.blockSignals(True)
|
|
|
|
|
if enabled:
|
|
|
|
@@ -464,6 +546,11 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
self.shutter_toggle.checked = shutter_open
|
|
|
|
|
self.shutter_toggle.blockSignals(False)
|
|
|
|
|
|
|
|
|
|
@SafeSlot(dict, dict)
|
|
|
|
|
def on_queue_status_update(self, data, meta):
|
|
|
|
|
_ = meta
|
|
|
|
|
self._update_queue_toggles_from_busy_state(self._is_queue_busy(data))
|
|
|
|
|
|
|
|
|
|
@SafeSlot(bool, bool)
|
|
|
|
|
@rpc_timeout(20)
|
|
|
|
|
def on_motors_enable(self, x_enable: bool, y_enable: bool):
|
|
|
|
@@ -490,6 +577,31 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
else:
|
|
|
|
|
self.submit_button.setEnabled(False)
|
|
|
|
|
|
|
|
|
|
@SafeSlot(bool)
|
|
|
|
|
@rpc_timeout(20)
|
|
|
|
|
def set_dap_params_forwarding(self, enabled: bool):
|
|
|
|
|
"""
|
|
|
|
|
Connect or disconnect DAP fit parameter forwarding to omny_xray_gui.
|
|
|
|
|
"""
|
|
|
|
|
if enabled == self._dap_params_forwarding_connected:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
signals = (self.waveform_x.dap_params_update, self.waveform_y.dap_params_update)
|
|
|
|
|
if enabled:
|
|
|
|
|
for signal in signals:
|
|
|
|
|
signal.connect(self.on_dap_params)
|
|
|
|
|
self._dap_params_forwarding_connected = True
|
|
|
|
|
logger.info("Enabled XRayEye DAP parameter forwarding.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
for signal in signals:
|
|
|
|
|
try:
|
|
|
|
|
signal.disconnect(self.on_dap_params)
|
|
|
|
|
except (TypeError, RuntimeError):
|
|
|
|
|
pass
|
|
|
|
|
self._dap_params_forwarding_connected = False
|
|
|
|
|
logger.info("Disabled XRayEye DAP parameter forwarding.")
|
|
|
|
|
|
|
|
|
|
@SafeSlot(dict, dict)
|
|
|
|
|
def on_dap_params(self, data, meta):
|
|
|
|
|
print("#######################################")
|
|
|
|
@@ -502,10 +614,10 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
curve_id = meta.get("curve_id")
|
|
|
|
|
|
|
|
|
|
if curve_id == "fit-x-SineModel+LinearModel":
|
|
|
|
|
self.dev.omny_xray_gui.fit_params_x.set(data).wait()
|
|
|
|
|
self.dev.omny_xray_gui.fit_params_x.set(data)
|
|
|
|
|
print(f"setting x data to {data}")
|
|
|
|
|
else:
|
|
|
|
|
self.dev.omny_xray_gui.fit_params_y.set(data).wait()
|
|
|
|
|
self.dev.omny_xray_gui.fit_params_y.set(data)
|
|
|
|
|
print(f"setting y data to {data}")
|
|
|
|
|
# self.bec_dispatcher.connect_slot(self.device_updates, MessageEndpoints.device_readback("omny_xray_gui"))
|
|
|
|
|
|
|
|
|
@@ -561,6 +673,7 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
|
|
|
|
|
def cleanup(self):
|
|
|
|
|
"""Cleanup connections on widget close -> disconnect slots and stop live mode of camera."""
|
|
|
|
|
self._queue_idle_timer.stop()
|
|
|
|
|
if self._connected_motor is not None:
|
|
|
|
|
self.bec_dispatcher.disconnect_slot(
|
|
|
|
|
self.on_tomo_angle_readback, MessageEndpoints.device_readback(self._connected_motor)
|
|
|
|
@@ -572,6 +685,9 @@ class XRayEye(BECWidget, QWidget):
|
|
|
|
|
self.bec_dispatcher.disconnect_slot(
|
|
|
|
|
self.getting_camera_status, MessageEndpoints.device_read_configuration(CAMERA[0])
|
|
|
|
|
)
|
|
|
|
|
self.bec_dispatcher.disconnect_slot(
|
|
|
|
|
self.on_queue_status_update, MessageEndpoints.scan_queue_status()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
getattr(self.dev, CAMERA[0]).stop_live_mode()
|
|
|
|
|
super().cleanup()
|
|
|
|
|