mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 11:41:49 +02:00
fix: added hooks to react to incoming config messages and instructions
This commit is contained in:
@ -2,18 +2,17 @@ import os
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
from pydantic import ValidationError
|
|
||||||
|
|
||||||
from bec_lib import MessageEndpoints
|
from bec_lib import MessageEndpoints
|
||||||
from qtpy import QtCore
|
from pydantic import ValidationError
|
||||||
from qtpy.QtCore import Signal as pyqtSignal, Slot as pyqtSlot
|
from pyqtgraph import mkBrush, mkPen
|
||||||
from qtpy.QtWidgets import QApplication, QTableWidgetItem, QWidget, QMessageBox
|
from qtpy import QtCore, uic
|
||||||
from pyqtgraph import mkPen, mkBrush
|
from qtpy.QtCore import Signal as pyqtSignal
|
||||||
from qtpy import uic
|
from qtpy.QtCore import Slot as pyqtSlot
|
||||||
|
from qtpy.QtWidgets import QApplication, QMessageBox, QTableWidgetItem, QWidget
|
||||||
|
|
||||||
from bec_widgets.bec_dispatcher import bec_dispatcher
|
from bec_widgets.bec_dispatcher import bec_dispatcher
|
||||||
from bec_widgets.qt_utils import Crosshair, Colors
|
from bec_widgets.qt_utils import Colors, Crosshair
|
||||||
|
from bec_widgets.qt_utils.yaml_dialog import load_yaml
|
||||||
from bec_widgets.validation import MonitorConfigValidator
|
from bec_widgets.validation import MonitorConfigValidator
|
||||||
|
|
||||||
# just for demonstration purposes if script run directly
|
# just for demonstration purposes if script run directly
|
||||||
@ -51,18 +50,12 @@ config_scan_mode = {
|
|||||||
{
|
{
|
||||||
"plot_name": "Grid plot 3",
|
"plot_name": "Grid plot 3",
|
||||||
"x": {"label": "Motor Y", "signals": [{"name": "samx", "entry": "samx"}]},
|
"x": {"label": "Motor Y", "signals": [{"name": "samx", "entry": "samx"}]},
|
||||||
"y": {
|
"y": {"label": "BPM", "signals": [{"name": "gauss_bpm", "entry": "gauss_bpm"}]},
|
||||||
"label": "BPM",
|
|
||||||
"signals": [{"name": "gauss_bpm", "entry": "gauss_bpm"}],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"plot_name": "Grid plot 4",
|
"plot_name": "Grid plot 4",
|
||||||
"x": {"label": "Motor Y", "signals": [{"name": "samx", "entry": "samx"}]},
|
"x": {"label": "Motor Y", "signals": [{"name": "samx", "entry": "samx"}]},
|
||||||
"y": {
|
"y": {"label": "BPM", "signals": [{"name": "gauss_adc3", "entry": "gauss_adc3"}]},
|
||||||
"label": "BPM",
|
|
||||||
"signals": [{"name": "gauss_adc3", "entry": "gauss_adc3"}],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"line_scan": [
|
"line_scan": [
|
||||||
@ -108,24 +101,15 @@ config_simple = {
|
|||||||
# "signals": [{"name": "samx", "entry": "samx"}],
|
# "signals": [{"name": "samx", "entry": "samx"}],
|
||||||
"signals": [{"name": "samy"}],
|
"signals": [{"name": "samy"}],
|
||||||
},
|
},
|
||||||
"y": {
|
"y": {"label": "bpm4i", "signals": [{"name": "bpm4i", "entry": "bpm4i"}]},
|
||||||
"label": "bpm4i",
|
|
||||||
"signals": [{"name": "bpm4i", "entry": "bpm4i"}],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"plot_name": "Gauss plots vs samx",
|
"plot_name": "Gauss plots vs samx",
|
||||||
"x": {
|
"x": {"label": "Motor X", "signals": [{"name": "samx", "entry": "samx"}]},
|
||||||
"label": "Motor X",
|
|
||||||
"signals": [{"name": "samx", "entry": "samx"}],
|
|
||||||
},
|
|
||||||
"y": {
|
"y": {
|
||||||
"label": "Gauss",
|
"label": "Gauss",
|
||||||
# "signals": [{"name": "gauss_bpm", "entry": "gauss_bpm"}],
|
# "signals": [{"name": "gauss_bpm", "entry": "gauss_bpm"}],
|
||||||
"signals": [
|
"signals": [{"name": "gauss_bpm"}, {"name": "samy", "entry": "samy"}],
|
||||||
{"name": "gauss_bpm"},
|
|
||||||
{"name": "samy", "entry": "samy"},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -152,6 +136,23 @@ config_no_entry = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_config = {
|
||||||
|
"plot_settings": {
|
||||||
|
"background_color": "white",
|
||||||
|
"axis_width": 2,
|
||||||
|
"num_columns": 5,
|
||||||
|
"colormap": "plasma",
|
||||||
|
"scan_types": False,
|
||||||
|
},
|
||||||
|
"plot_data": [
|
||||||
|
{
|
||||||
|
"plot_name": "BPM4i plots vs samx",
|
||||||
|
"x": {"label": "Motor Y", "signals": [{"name": "samx"}]},
|
||||||
|
"y": {"label": "bpm4i", "signals": [{"name": "bpm4i"}]},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class BECMonitor(pg.GraphicsLayoutWidget):
|
class BECMonitor(pg.GraphicsLayoutWidget):
|
||||||
update_signal = pyqtSignal()
|
update_signal = pyqtSignal()
|
||||||
@ -161,7 +162,7 @@ class BECMonitor(pg.GraphicsLayoutWidget):
|
|||||||
parent=None,
|
parent=None,
|
||||||
client=None,
|
client=None,
|
||||||
config: dict = None,
|
config: dict = None,
|
||||||
enable_crosshair: bool = False,
|
enable_crosshair: bool = True,
|
||||||
gui_id=None,
|
gui_id=None,
|
||||||
):
|
):
|
||||||
super(BECMonitor, self).__init__(parent=parent)
|
super(BECMonitor, self).__init__(parent=parent)
|
||||||
@ -172,13 +173,17 @@ class BECMonitor(pg.GraphicsLayoutWidget):
|
|||||||
self.queue = self.client.queue
|
self.queue = self.client.queue
|
||||||
|
|
||||||
self.validator = MonitorConfigValidator(self.dev)
|
self.validator = MonitorConfigValidator(self.dev)
|
||||||
|
self.gui_id = gui_id
|
||||||
|
|
||||||
if gui_id is None:
|
if self.gui_id is None:
|
||||||
self.gui_id = self.__class__.__name__ + str(time.time()) # TODO still in discussion
|
self.gui_id = self.__class__.__name__ + str(time.time()) # TODO still in discussion
|
||||||
|
|
||||||
# Connect slots dispatcher
|
# Connect slots dispatcher
|
||||||
bec_dispatcher.connect_slot(self.on_scan_segment, MessageEndpoints.scan_segment())
|
bec_dispatcher.connect_slot(self.on_scan_segment, MessageEndpoints.scan_segment())
|
||||||
# bec_dispatcher.connect_slot(self.on_config_update, MessageEndpoints.gui_config(self.gui_id)) #TODO connect when ready
|
bec_dispatcher.connect_slot(self.on_config_update, MessageEndpoints.gui_config(self.gui_id))
|
||||||
|
bec_dispatcher.connect_slot(
|
||||||
|
self.on_instruction, MessageEndpoints.gui_instructions(self.gui_id)
|
||||||
|
)
|
||||||
|
|
||||||
# Current configuration
|
# Current configuration
|
||||||
self.config = config
|
self.config = config
|
||||||
@ -224,8 +229,6 @@ class BECMonitor(pg.GraphicsLayoutWidget):
|
|||||||
else: # without incoming data setup the first configuration to the first scan type sorted alphabetically by name
|
else: # without incoming data setup the first configuration to the first scan type sorted alphabetically by name
|
||||||
self.plot_data = self.plot_data_config[min(list(self.plot_data_config.keys()))]
|
self.plot_data = self.plot_data_config[min(list(self.plot_data_config.keys()))]
|
||||||
|
|
||||||
# TODO init plot background -> so far not used, I don't like how it is done in extreme.py
|
|
||||||
|
|
||||||
# Initialize the UI
|
# Initialize the UI
|
||||||
self._init_ui(self.plot_settings["num_columns"])
|
self._init_ui(self.plot_settings["num_columns"])
|
||||||
|
|
||||||
@ -252,7 +255,8 @@ class BECMonitor(pg.GraphicsLayoutWidget):
|
|||||||
num_columns = num_plots
|
num_columns = num_plots
|
||||||
self.plot_settings["num_columns"] = num_columns # Update the settings
|
self.plot_settings["num_columns"] = num_columns # Update the settings
|
||||||
print(
|
print(
|
||||||
f"Warning: num_columns in the YAML file was greater than the number of plots. Resetting num_columns to number of plots:{num_columns}."
|
"Warning: num_columns in the YAML file was greater than the number of plots."
|
||||||
|
f" Resetting num_columns to number of plots:{num_columns}."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.plot_settings["num_columns"] = num_columns # Update the settings
|
self.plot_settings["num_columns"] = num_columns # Update the settings
|
||||||
@ -281,12 +285,49 @@ class BECMonitor(pg.GraphicsLayoutWidget):
|
|||||||
plot.setLabel("bottom", x_label)
|
plot.setLabel("bottom", x_label)
|
||||||
plot.setLabel("left", y_label)
|
plot.setLabel("left", y_label)
|
||||||
plot.addLegend()
|
plot.addLegend()
|
||||||
|
self._set_plot_colors(plot, self.plot_settings)
|
||||||
|
|
||||||
self.plots[plot_name] = plot
|
self.plots[plot_name] = plot
|
||||||
self.grid_coordinates.append((row, col))
|
self.grid_coordinates.append((row, col))
|
||||||
|
|
||||||
self.init_curves()
|
self.init_curves()
|
||||||
|
|
||||||
|
def _set_plot_colors(self, plot: pg.PlotItem, plot_settings: dict) -> None:
|
||||||
|
"""
|
||||||
|
Set the plot colors based on the plot config.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plot (pg.PlotItem): Plot object to set the colors.
|
||||||
|
plot_settings (dict): Plot settings dictionary.
|
||||||
|
"""
|
||||||
|
if plot_settings.get("show_grid", False):
|
||||||
|
plot.showGrid(x=True, y=True, alpha=0.5)
|
||||||
|
pen_width = plot_settings.get("axis_width", 2)
|
||||||
|
color = plot_settings.get("axis_color")
|
||||||
|
if color is None:
|
||||||
|
if plot_settings["background_color"].lower() == "black":
|
||||||
|
color = "w"
|
||||||
|
self.setBackground("k")
|
||||||
|
elif plot_settings["background_color"].lower() == "white":
|
||||||
|
color = "k"
|
||||||
|
self.setBackground("w")
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid background color {plot_settings['background_color']}. Allowed values"
|
||||||
|
" are 'white' or 'black'."
|
||||||
|
)
|
||||||
|
print(plot_settings)
|
||||||
|
pen = pg.mkPen(color=color, width=pen_width)
|
||||||
|
x_axis = plot.getAxis("bottom") # 'bottom' corresponds to the x-axis
|
||||||
|
x_axis.setPen(pen)
|
||||||
|
x_axis.setTextPen(pen)
|
||||||
|
x_axis.setTickPen(pen)
|
||||||
|
|
||||||
|
y_axis = plot.getAxis("left") # 'left' corresponds to the y-axis
|
||||||
|
y_axis.setPen(pen)
|
||||||
|
y_axis.setTextPen(pen)
|
||||||
|
y_axis.setTickPen(pen)
|
||||||
|
|
||||||
def init_curves(self) -> None:
|
def init_curves(self) -> None:
|
||||||
"""
|
"""
|
||||||
Initialize curve data and properties, and update table row labels.
|
Initialize curve data and properties, and update table row labels.
|
||||||
@ -382,6 +423,27 @@ class BECMonitor(pg.GraphicsLayoutWidget):
|
|||||||
self.client = client
|
self.client = client
|
||||||
self.dev = self.client.device_manager.devices
|
self.dev = self.client.device_manager.devices
|
||||||
|
|
||||||
|
@pyqtSlot(dict)
|
||||||
|
def on_instruction(self, msg_content: dict) -> None:
|
||||||
|
"""
|
||||||
|
Handle instructions sent to the GUI.
|
||||||
|
Possible actions are:
|
||||||
|
- clear: Clear the plots
|
||||||
|
- close: Close the GUI
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg_content (dict): Message content with the instruction and parameters.
|
||||||
|
"""
|
||||||
|
action = msg_content.get("action", None)
|
||||||
|
parameters = msg_content.get("parameters", None)
|
||||||
|
|
||||||
|
if action == "clear":
|
||||||
|
self.flush()
|
||||||
|
elif action == "close":
|
||||||
|
self.close()
|
||||||
|
else:
|
||||||
|
print(f"Unknown instruction received: {msg_content}")
|
||||||
|
|
||||||
@pyqtSlot(dict)
|
@pyqtSlot(dict)
|
||||||
def on_config_update(self, config: dict) -> None:
|
def on_config_update(self, config: dict) -> None:
|
||||||
"""
|
"""
|
||||||
@ -389,6 +451,8 @@ class BECMonitor(pg.GraphicsLayoutWidget):
|
|||||||
Args:
|
Args:
|
||||||
config(dict): Configuration settings
|
config(dict): Configuration settings
|
||||||
"""
|
"""
|
||||||
|
if "config" in config:
|
||||||
|
config = config["config"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validated_config = self.validator.validate_monitor_config(config)
|
validated_config = self.validator.validate_monitor_config(config)
|
||||||
@ -399,6 +463,11 @@ class BECMonitor(pg.GraphicsLayoutWidget):
|
|||||||
print(error_message)
|
print(error_message)
|
||||||
# QMessageBox.critical(self, "Configuration Error", error_message) #TODO do better error popups
|
# QMessageBox.critical(self, "Configuration Error", error_message) #TODO do better error popups
|
||||||
|
|
||||||
|
def flush(self) -> None:
|
||||||
|
"""Flush the data dictionary."""
|
||||||
|
self.data = {}
|
||||||
|
self.init_curves()
|
||||||
|
|
||||||
@pyqtSlot(dict, dict)
|
@pyqtSlot(dict, dict)
|
||||||
def on_scan_segment(self, msg, metadata):
|
def on_scan_segment(self, msg, metadata):
|
||||||
"""
|
"""
|
||||||
@ -420,22 +489,21 @@ class BECMonitor(pg.GraphicsLayoutWidget):
|
|||||||
currentName = metadata.get("scan_name")
|
currentName = metadata.get("scan_name")
|
||||||
if currentName is None:
|
if currentName is None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Scan name not found in metadata. Please check the scan_name in the YAML config or in bec "
|
f"Scan name not found in metadata. Please check the scan_name in the YAML"
|
||||||
f"configuration."
|
f" config or in bec configuration."
|
||||||
)
|
)
|
||||||
self.plot_data = self.plot_data_config.get(currentName, [])
|
self.plot_data = self.plot_data_config.get(currentName, [])
|
||||||
if self.plot_data == []:
|
if self.plot_data == []:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Scan name {currentName} not found in the YAML config. Please check the scan_name in the "
|
f"Scan name {currentName} not found in the YAML config. Please check the"
|
||||||
f"YAML config or in bec configuration."
|
" scan_name in the YAML config or in bec configuration."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Init UI
|
# Init UI
|
||||||
self._init_ui(self.plot_settings["num_columns"])
|
self._init_ui(self.plot_settings["num_columns"])
|
||||||
|
|
||||||
self.scanID = current_scanID
|
self.scanID = current_scanID
|
||||||
self.data = {}
|
self.flush()
|
||||||
self.init_curves()
|
|
||||||
|
|
||||||
for plot_config in self.plot_data:
|
for plot_config in self.plot_data:
|
||||||
x_config = plot_config["x"]
|
x_config = plot_config["x"]
|
||||||
@ -464,13 +532,30 @@ class BECMonitor(pg.GraphicsLayoutWidget):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": # pragma: no cover
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from bec_widgets.bec_dispatcher import bec_dispatcher
|
from bec_widgets.bec_dispatcher import bec_dispatcher
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--config_file", help="Path to the config file.")
|
||||||
|
parser.add_argument("--config", help="Path to the config file.")
|
||||||
|
parser.add_argument("--id", help="GUI ID.")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.config is not None:
|
||||||
|
# Load config from file
|
||||||
|
config = json.loads(args.config)
|
||||||
|
elif args.config_file is not None:
|
||||||
|
# Load config from file
|
||||||
|
config = load_yaml(args.config_file)
|
||||||
|
else:
|
||||||
|
config = test_config
|
||||||
|
|
||||||
client = bec_dispatcher.client
|
client = bec_dispatcher.client
|
||||||
client.start()
|
client.start()
|
||||||
|
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
monitor = BECMonitor(config=config_simple)
|
monitor = BECMonitor(config=config, gui_id=args.id)
|
||||||
monitor.show()
|
monitor.show()
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec())
|
||||||
|
Reference in New Issue
Block a user