Files
AareDAQ/src/aare/gui/gui.py
T
perl_d 83a602da73
CI / lint (push) Skipped
CI / test (3.11) (push) Skipped
CI / test (3.12) (push) Skipped
CI / test (3.13) (push) Skipped
CI / test-with-beamline-plugins (pxi_bec) (push) Skipped
CI / test-with-beamline-plugins (pxii_bec) (push) Skipped
CI / test-with-beamline-plugins (pxiii_bec) (push) Skipped
CI / test (3.11) (pull_request) Failing after 44s
CI / test (3.12) (pull_request) Failing after 41s
CI / test (3.13) (pull_request) Failing after 42s
CI / test-with-beamline-plugins (pxii_bec) (pull_request) Failing after 47s
CI / test-with-beamline-plugins (pxi_bec) (pull_request) Failing after 49s
CI / test-with-beamline-plugins (pxiii_bec) (pull_request) Failing after 1m0s
CI / test-with-coverage (pull_request) Failing after 1m1s
CI / coverage-analysis (pull_request) Skipped
CI / lint (pull_request) Failing after 2m2s
style: british->american spelling
2026-08-24 13:33:14 +02:00

221 lines
9.0 KiB
Python

import os
import sys
from aarecommon.config.beamline import cfg_get, mx_beamline
from aarecommon.config.logger import setup_logger
from aarecommon.models.beamline import MXBeamline
from PySide6 import QtGui
from PySide6.QtCore import QCommandLineOption, QCommandLineParser
from PySide6.QtWidgets import QApplication, QMessageBox
from aare.gui.auth import auth
from aare.gui.constants import LOGGER_NAME
from aare.gui.main_window import MainWindow
from aare.gui.widgets.splash_screen import LoadingSplashScreen
logger = setup_logger(LOGGER_NAME)
def main():
"""Wrapped gui as main function to make tests easier"""
try:
basedir = os.path.dirname(__file__)
icon_path = os.path.join(basedir, "graphics/aaregui_logo.svg")
# SVG looks strange on consoles...only show one line
banner_path = os.path.join(basedir, "graphics/aare_banner.png")
except Exception:
logger.exception("Failed to load resources for splash screen")
sys.exit(1)
try:
# define application
app = QApplication(sys.argv)
splash_pix = QtGui.QPixmap(banner_path)
splash = LoadingSplashScreen(splash_pix)
app.setWindowIcon(QtGui.QIcon(icon_path))
app.setApplicationName("AareGUI")
app.setApplicationVersion("0.3.1")
app.setOrganizationName("PSI")
app.setOrganizationDomain("psi.ch")
splash.show()
splash.set_progress(10, "Initializing Application...")
# Create the parser
parser = QCommandLineParser()
parser.setApplicationDescription("PSI AareGUI")
parser.addHelpOption() # Adds --help option
parser.addVersionOption() # Adds --version option
# TODO if zmq and pred stream come from same source, do not need images from both streams, can combine
match mx_beamline():
case MXBeamline.X06DA:
default_url = cfg_get("gui.daq.daq_url", "https://mx-x06da-queue-01.psi.ch")
default_cert_path = cfg_get("gui.daq.cert_path", "/sls/x06da/misc/.cert/6d.crt")
default_zmq_addr = cfg_get(
"gui.cameras.sample_camera_zmq_url", "tcp://x06da-pserv-01:9089"
)
default_pred_zmq_addr = cfg_get(
"gui.cameras.prediction_zmq_url", "tcp://mx-ml:9091"
)
default_beamline_cam_addr = cfg_get(
"gui.cameras.beamline_camera_url", "x06da-axis-1.psi.ch"
)
default_gonio_cam_addr = cfg_get(
"gui.cameras.gonio_camera_url", "axis-accc8ed2972e.psi.ch"
)
default_gonio_camera_id = int(cfg_get("gui.cameras.gonio_camera_id", 3))
case MXBeamline.X10SA:
default_url = cfg_get("gui.daq.daq_url", "https://mx-x10sa-queue-01.psi.ch")
default_cert_path = cfg_get("gui.daq.cert_path", "/sls/x10sa/misc/.cert/10s.crt")
default_zmq_addr = cfg_get(
"gui.cameras.sample_camera_zmq_url", "tcp://x10sa-spark-01:9091"
)
default_pred_zmq_addr = cfg_get(
"gui.cameras.prediction_zmq_url", "tcp://x10sa-spark-01:9091"
)
default_beamline_cam_addr = cfg_get(
"gui.cameras.beamline_camera_url", "axis-accc8eb02488.psi.ch"
)
default_gonio_cam_addr = cfg_get(
"gui.cameras.gonio_camera_url", "axis-accc8ea5e463.psi.ch"
)
default_gonio_camera_id = int(cfg_get("gui.cameras.gonio_camera_id", 1))
case MXBeamline.X06SA:
default_url = cfg_get("gui.daq.daq_url", "https://mx-x06sa-queue-01.psi.ch")
default_cert_path = cfg_get("gui.daq.cert_path", "/sls/x06sa/misc/.cert/6s.crt")
default_zmq_addr = cfg_get("gui.cameras.sample_camera_zmq_url", "")
default_pred_zmq_addr = cfg_get("gui.cameras.prediction_zmq_url", "")
default_beamline_cam_addr = cfg_get("gui.cameras.beamline_camera_url", "")
default_gonio_cam_addr = cfg_get("gui.cameras.gonio_camera_url", "")
default_gonio_camera_id = int(cfg_get("gui.cameras.gonio_camera_id", 1))
case _:
default_url = cfg_get("gui.daq.daq_url", "")
default_cert_path = cfg_get("gui.daq.cert_path", "")
default_zmq_addr = cfg_get("gui.cameras.sample_camera_zmq_url", "")
default_pred_zmq_addr = cfg_get("gui.cameras.prediction_zmq_url", "")
default_beamline_cam_addr = cfg_get("gui.cameras.beamline_camera_url", "")
default_gonio_cam_addr = cfg_get("gui.cameras.gonio_camera_url", "")
default_gonio_camera_id = int(cfg_get("gui.cameras.gonio_camera_id", 1))
# Add custom options as needed
urlOption = QCommandLineOption(["u", "aaredaq-url"], "Base AareDAQ URL", "url", default_url)
parser.addOption(urlOption)
certPath = QCommandLineOption(
["c", "aaredaq-cert-path"],
"Server Certificate Path (for self-signed certificates)",
"cert",
default_cert_path,
)
parser.addOption(certPath)
defaultImage = QCommandLineOption(
["i", "image"], "Default image to display in absence of the ZMQ stream", "image"
)
parser.addOption(defaultImage)
cameraZeroMQ = QCommandLineOption(
["s", "sample-camera-zmq"],
"Sample camera ZeroMQ URL",
"sample-camera-zmq",
default_zmq_addr,
)
parser.addOption(cameraZeroMQ)
predZmqOption = QCommandLineOption(
["p", "pred-zmq"],
"Prediction ZeroMQ URL (PUB) to subscribe to (e.g. tcp://mx-ml:9091)",
"pred-zmq",
default_pred_zmq_addr,
)
parser.addOption(predZmqOption)
splash.set_progress(30, "Parsing Arguments...")
parser.process(app)
base_url = parser.value(urlOption)
if base_url == "" or base_url.lower() == "none":
base_url = None
cert_path = parser.value(certPath)
if cert_path == "" or cert_path.lower() == "none":
cert_path = None
zmq_addr = parser.value(cameraZeroMQ)
if zmq_addr == "":
zmq_addr = None
pred_zmq_addr = parser.value(predZmqOption)
if pred_zmq_addr == "":
pred_zmq_addr = None
if pred_zmq_addr is None:
pred_zmq_addr = zmq_addr
if parser.isSet(defaultImage):
default_image = parser.value(defaultImage)
else:
default_image = None
try:
splash.set_progress(50, f"Connecting to {base_url or 'backend'}...")
token = auth(base_url, cert_path)
if not token or token.count(".") != 2:
raise RuntimeError(
"Authentication did not return a valid token. "
"Please check the server is running (it may still be initializing)."
)
logger.info("Authentication successful")
splash.set_progress(80, "Authentication successful...")
except Exception as e:
splash.finish(None)
logger.exception("Cannot connect to AareDAQ server. Exiting.")
QMessageBox.critical(
None,
"Authentication Error",
f"Cannot connect to AareDAQ server:\n{e!s}\n\n Please check the server is running and your network connection.",
)
sys.exit(1)
splash.set_progress(90, "Loading Main Window...")
win = MainWindow(
base_url=base_url,
token=token,
default_image=default_image,
zmq_addr=zmq_addr,
pred_zmq_addr=pred_zmq_addr,
beamline_cam_addr=default_beamline_cam_addr,
gonio_cam_addr=default_gonio_cam_addr,
gonio_cam_id=default_gonio_camera_id,
)
splash.set_progress(100, "Ready")
splash.finish(win)
# Start windowed (not maximized) at a fraction of the primary screen,
# centered — sized explicitly because the size hint is wider than the
# monitor. Other panels may still enforce a somewhat larger minimum;
# the window then lands on that minimum instead.
unmax_w, unmax_h = 0.5, 0.7
available = app.primaryScreen().availableGeometry()
win.resize(int(available.width() * unmax_w), int(available.height() * unmax_h))
win.move(available.center() - win.rect().center())
win.show()
sys.exit(app.exec())
except Exception as e:
splash.finish(None)
logger.exception("Error starting GUI")
QMessageBox.critical(
None,
"Fatal Error",
f"An error occurred during startup. See console for details."
f"\nPlease check the server is running and your network connection."
f"\n\n{e!s}\n\n",
)
sys.exit(1)
if __name__ == "__main__":
main()