from __future__ import annotations
import sys
from PyQt5.QtWidgets import (
QApplication,
QGroupBox,
QHBoxLayout,
QLabel,
QMainWindow,
QPushButton,
QSpinBox,
QVBoxLayout,
QWidget,
)
from dlab.boot import ROOT, bootstrap
from dlab.hardware.wrappers.pressure_sensor import PressureMonitorWidget
from dlab.utils.log_panel import LogPanel
[docs]
class DlabControllerWindow(QMainWindow):
"""
Main launcher window for lab instrument control.
To run Grafana with Prometheus:
cd C:\\Prometheus
.\\prometheus.exe --config.file=prometheus.yml
Grafana credentials: user: admin, password: admin
"""
def __init__(self, log_panel: LogPanel):
super().__init__()
self._log = log_panel
self._windows: dict[str, QWidget | None] = {
"andor": None,
"avaspec": None,
"powermeter": None,
"stage_control": None,
"slm": None,
"scan": None,
"phase_lock": None,
}
self._daheng_windows: dict[str, QWidget] = {}
self._camera_controls: dict[str, QSpinBox] = {}
self._setup_ui()
def _setup_ui(self):
self.setWindowTitle("DlabControllerWindow")
self.resize(300, 400)
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
# Windows group
windows_group = QGroupBox("Windows")
view_layout = QVBoxLayout()
# Main instrument buttons
buttons = [
("Open SLM Window", self._open_slm_window),
("Open Andor Window", self._open_andor_window),
("Open Avaspec Window", self._open_avaspec_window),
("Open Powermeter Window", self._open_powermeter_window),
("Open Stage Control Window", self._open_stage_control_window),
("Open Scan Window", self._open_scan_window),
("Open Phase Lock Window", self._open_phase_lock_window),
]
for label, callback in buttons:
btn = QPushButton(label)
btn.clicked.connect(callback)
view_layout.addWidget(btn)
# Daheng camera controls
for i, name in enumerate(
["DahengCam_1", "DahengCam_2", "DahengCam_3"], start=1
):
self._add_daheng_control(view_layout, name, default_index=i)
windows_group.setLayout(view_layout)
main_layout.addWidget(windows_group)
# Grafana dashboard link
dashboard_url = (
"http://localhost:3000/d/ad6bbh8/pressure-dashboard"
"?orgId=1&from=now-30m&to=now&timezone=browser&refresh=auto"
)
path_label = QLabel(f'<a href="{dashboard_url}">Open Pressure Dashboard</a>')
path_label.setOpenExternalLinks(True)
main_layout.addWidget(path_label)
main_layout.addStretch(1)
# Log toggle button
self._log_button = QPushButton("Hide Log")
self._log_button.clicked.connect(self._toggle_log)
main_layout.addWidget(self._log_button)
self._setup_pressure_log()
def _add_daheng_control(self, layout: QVBoxLayout, name: str, default_index: int):
"""Add a Daheng camera control group with index spinbox."""
box = QGroupBox(name)
h_layout = QHBoxLayout()
spinbox = QSpinBox()
spinbox.setRange(1, 5)
spinbox.setValue(default_index)
h_layout.addWidget(QLabel("Index:"))
h_layout.addWidget(spinbox)
button = QPushButton("Open")
button.clicked.connect(
lambda _, n=name, sb=spinbox: self._open_daheng_window(n, sb.value())
)
h_layout.addWidget(button)
box.setLayout(h_layout)
layout.addWidget(box)
self._camera_controls[name] = spinbox
def _setup_pressure_log(self):
self._pressure_monitor = PressureMonitorWidget(self, log_panel=self._log)
def _toggle_log(self):
if self._log.isVisible():
self._log.hide()
self._log_button.setText("Show Log")
else:
self._log.show()
self._log_button.setText("Hide Log")
# -------------------------------------------------------------------------
# Generic window management
# -------------------------------------------------------------------------
def _open_window(
self,
key: str,
window_class: type,
display_name: str,
*args,
use_destroyed_signal: bool = False,
**kwargs,
):
"""
Open a window if not already open, or bring it to front.
Args:
key: Key in self._windows dict
window_class: The window class to instantiate
display_name: Human-readable name for logging
use_destroyed_signal: Use 'destroyed' signal instead of 'closed'
*args, **kwargs: Passed to window_class constructor
"""
win = self._windows[key]
if win is None:
win = window_class(*args, **kwargs)
signal = win.destroyed if use_destroyed_signal else win.closed
signal.connect(
lambda *a, k=key, name=display_name: self._on_window_closed(k, name)
)
self._windows[key] = win
self._log.log("Window opened.", source=display_name)
win.show()
win.raise_()
win.activateWindow()
def _on_window_closed(self, key: str, display_name: str):
"""Handle window close event."""
self._windows[key] = None
self._log.log("Window closed.", source=display_name)
# -------------------------------------------------------------------------
# Individual window openers
# -------------------------------------------------------------------------
def _open_andor_window(self):
from dlab.diagnostics.ui.andor_live_window import AndorLiveWindow
self._open_window("andor", AndorLiveWindow, "Andor", self._log)
def _open_avaspec_window(self):
from dlab.diagnostics.ui.avaspec_live_window import AvaspecLiveWindow
self._open_window("avaspec", AvaspecLiveWindow, "Avaspec", self._log)
def _open_slm_window(self):
from dlab.diagnostics.ui.slm_window import SlmWindow
self._open_window("slm", SlmWindow, "SLM", self._log)
def _open_stage_control_window(self):
from dlab.diagnostics.ui.stage_control_window import StageControlWindow
self._open_window("stage_control", StageControlWindow, "Stage Control", self._log)
def _open_scan_window(self):
from dlab.diagnostics.ui.scans.scan_window import ScanWindow
self._open_window("scan", ScanWindow, "Scan")
def _open_powermeter_window(self):
from dlab.diagnostics.ui.powermeter_live_window import PowermeterLiveWindow
self._open_window("powermeter", PowermeterLiveWindow, "Powermeter", self._log)
def _open_phase_lock_window(self):
from dlab.diagnostics.ui.phase_lock_window import AvaspecPhaseLockWindow
self._open_window(
"phase_lock", AvaspecPhaseLockWindow, "Phase Lock", self._log
)
# -------------------------------------------------------------------------
# Daheng camera windows (multiple instances)
# -------------------------------------------------------------------------
def _open_daheng_window(self, camera_name: str, index: int):
from dlab.diagnostics.ui.daheng_live_window import DahengLiveWindow
if camera_name in self._daheng_windows:
self._log.log("Window already open.", source=camera_name)
win = self._daheng_windows[camera_name]
win.show()
win.raise_()
win.activateWindow()
return
win = DahengLiveWindow(
camera_name=camera_name, fixed_index=index, log_panel=self._log
)
win.closed.connect(lambda name=camera_name: self._on_daheng_window_closed(name))
self._daheng_windows[camera_name] = win
win.show()
win.raise_()
win.activateWindow()
self._log.log("Window opened.", source=camera_name)
def _on_daheng_window_closed(self, camera_name: str):
self._daheng_windows.pop(camera_name, None)
self._log.log("Window closed.", source=camera_name)
[docs]
def main():
CFG = bootstrap(ROOT / "config" / "config.yaml")
app = QApplication(sys.argv)
log_panel = LogPanel()
log_panel.show()
window = DlabControllerWindow(log_panel)
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()