0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 03:31:50 +02:00

fix(bec_figure): full reconstruction with config from other bec figure

This commit is contained in:
2024-06-28 21:05:56 +02:00
parent 572f2fb811
commit b6e1e20b7c
5 changed files with 96 additions and 6 deletions

View File

@ -538,7 +538,14 @@ class BECFigure(RPCBase):
@rpc_call @rpc_call
def motor_map( def motor_map(
self, motor_x: "str" = None, motor_y: "str" = None, **axis_kwargs self,
motor_x: "str" = None,
motor_y: "str" = None,
new: "bool" = False,
row: "int | None" = None,
col: "int | None" = None,
config: "dict | None" = None,
**axis_kwargs,
) -> "BECMotorMap": ) -> "BECMotorMap":
""" """
Add a motor map to the figure. Always access the first motor map widget in the figure. Add a motor map to the figure. Always access the first motor map widget in the figure.
@ -546,6 +553,10 @@ class BECFigure(RPCBase):
Args: Args:
motor_x(str): The name of the motor for the X axis. motor_x(str): The name of the motor for the X axis.
motor_y(str): The name of the motor for the Y axis. motor_y(str): The name of the motor for the Y axis.
new(bool): If True, create a new plot instead of using the first plot.
row(int): The row coordinate of the widget in the figure. If not provided, the next empty row will be used.
col(int): The column coordinate of the widget in the figure. If not provided, the next empty column will be used.
config(dict): Recreates the whole BECImageShow widget from provided configuration.
**axis_kwargs: Additional axis properties to set on the widget after creation. **axis_kwargs: Additional axis properties to set on the widget after creation.
Returns: Returns:
@ -611,6 +622,12 @@ class BECFigure(RPCBase):
list[BECPlotBase]: List of all widgets in the figure. list[BECPlotBase]: List of all widgets in the figure.
""" """
@rpc_call
def apply_config(self, config: "dict | FigureConfig"):
"""
None
"""
class BECImageItem(RPCBase): class BECImageItem(RPCBase):
@property @property
@ -823,7 +840,7 @@ class BECImageShow(RPCBase):
self, self,
monitor: "str", monitor: "str",
color_map: "Optional[str]" = "magma", color_map: "Optional[str]" = "magma",
color_bar: "Optional[Literal['simple', 'full']]" = "simple", color_bar: "Optional[Literal['simple', 'full']]" = "full",
downsample: "Optional[bool]" = True, downsample: "Optional[bool]" = True,
opacity: "Optional[float]" = 1.0, opacity: "Optional[float]" = 1.0,
vrange: "Optional[tuple[int, int]]" = None, vrange: "Optional[tuple[int, int]]" = None,
@ -839,7 +856,7 @@ class BECImageShow(RPCBase):
name: "str", name: "str",
data: "Optional[np.ndarray]" = None, data: "Optional[np.ndarray]" = None,
color_map: "Optional[str]" = "magma", color_map: "Optional[str]" = "magma",
color_bar: "Optional[Literal['simple', 'full']]" = "simple", color_bar: "Optional[Literal['simple', 'full']]" = "full",
downsample: "Optional[bool]" = True, downsample: "Optional[bool]" = True,
opacity: "Optional[float]" = 1.0, opacity: "Optional[float]" = 1.0,
vrange: "Optional[tuple[int, int]]" = None, vrange: "Optional[tuple[int, int]]" = None,
@ -1124,6 +1141,16 @@ class BECImageShow(RPCBase):
list[BECImageItem]: The list of images. list[BECImageItem]: The list of images.
""" """
@rpc_call
def apply_config(self, config: "dict | SubplotConfig"):
"""
Apply the configuration to the 1D waveform widget.
Args:
config(dict|SubplotConfig): Configuration settings.
replot_last_scan(bool, optional): If True, replot the last scan. Defaults to False.
"""
class BECMotorMap(RPCBase): class BECMotorMap(RPCBase):
@property @property
@ -1222,6 +1249,15 @@ class BECMotorMap(RPCBase):
Remove the plot widget from the figure. Remove the plot widget from the figure.
""" """
@rpc_call
def apply_config(self, config: "dict | MotorMapConfig"):
"""
Apply the config to the motor map.
Args:
config(dict|MotorMapConfig): Config to be applied.
"""
class BECPlotBase(RPCBase): class BECPlotBase(RPCBase):
@property @property
@ -1702,6 +1738,16 @@ class BECWaveform(RPCBase):
size(int): Font size of the legend. size(int): Font size of the legend.
""" """
@rpc_call
def apply_config(self, config: "dict | SubplotConfig", replot_last_scan: "bool" = False):
"""
Apply the configuration to the 1D waveform widget.
Args:
config(dict|SubplotConfig): Configuration settings.
replot_last_scan(bool, optional): If True, replot the last scan. Defaults to False.
"""
class DeviceComboBox(RPCBase): class DeviceComboBox(RPCBase):
@property @property

View File

@ -8,7 +8,7 @@ from typing import Literal, Optional
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
import qdarktheme import qdarktheme
from pydantic import Field from pydantic import Field, ValidationError, field_validator
from qtpy.QtCore import Signal as pyqtSignal from qtpy.QtCore import Signal as pyqtSignal
from qtpy.QtWidgets import QWidget from qtpy.QtWidgets import QWidget
from typeguard import typechecked from typeguard import typechecked
@ -30,6 +30,26 @@ class FigureConfig(ConnectionConfig):
{}, description="The list of widgets to be added to the figure widget." {}, description="The list of widgets to be added to the figure widget."
) )
@field_validator("widgets", mode="before")
@classmethod
def validate_widgets(cls, v):
"""Validate the widgets configuration."""
widget_class_map = {
"BECWaveform": Waveform1DConfig,
"BECImageShow": ImageConfig,
"BECMotorMap": MotorMapConfig,
}
validated_widgets = {}
for key, widget_config in v.items():
if "widget_class" not in widget_config:
raise ValueError(f"Widget config for {key} does not contain 'widget_class'.")
widget_class = widget_config["widget_class"]
if widget_class not in widget_class_map:
raise ValueError(f"Unknown widget_class '{widget_class}' for widget '{key}'.")
config_class = widget_class_map[widget_class]
validated_widgets[key] = config_class(**widget_config)
return validated_widgets
class WidgetHandler: class WidgetHandler:
"""Factory for creating and configuring BEC widgets for BECFigure.""" """Factory for creating and configuring BEC widgets for BECFigure."""
@ -103,6 +123,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
"clear_all", "clear_all",
"get_all_rpc", "get_all_rpc",
"widget_list", "widget_list",
"apply_config",
] ]
subplot_map = { subplot_map = {
"PlotBase": BECPlotBase, "PlotBase": BECPlotBase,
@ -110,6 +131,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
"BECImageShow": BECImageShow, "BECImageShow": BECImageShow,
"BECMotorMap": BECMotorMap, "BECMotorMap": BECMotorMap,
} }
widget_method_map = {"BECWaveform": "plot", "BECImageShow": "image", "BECMotorMap": "motor_map"}
clean_signal = pyqtSignal() clean_signal = pyqtSignal()
@ -125,8 +147,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
else: else:
if isinstance(config, dict): if isinstance(config, dict):
config = FigureConfig(**config) config = FigureConfig(**config)
self.config = config super().__init__(client=client, gui_id=gui_id)
super().__init__(client=client, config=config, gui_id=gui_id)
pg.GraphicsLayoutWidget.__init__(self, parent) pg.GraphicsLayoutWidget.__init__(self, parent)
self.widget_handler = WidgetHandler() self.widget_handler = WidgetHandler()
@ -136,6 +157,8 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
# Container to keep track of the grid # Container to keep track of the grid
self.grid = [] self.grid = []
# Create config and apply it
self.apply_config(config)
def __getitem__(self, key: tuple | str): def __getitem__(self, key: tuple | str):
if isinstance(key, tuple) and len(key) == 2: if isinstance(key, tuple) and len(key) == 2:
@ -150,6 +173,24 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
"Key must be a string (widget id) or a tuple of two integers (grid coordinates)" "Key must be a string (widget id) or a tuple of two integers (grid coordinates)"
) )
def apply_config(self, config: dict | FigureConfig):
if isinstance(config, dict):
try:
config = FigureConfig(**config)
except ValidationError as e:
print(f"Error in applying config: {e}")
return
self.config = config
self.change_theme(self.config.theme)
# widget_config has to be reset for not have each widget config twice when added to the figure
widget_configs = [config for config in self.config.widgets.values()]
self.config.widgets = {}
for widget_config in widget_configs:
getattr(self, self.widget_method_map[widget_config.widget_class])(
config=widget_config.model_dump(), row=widget_config.row, col=widget_config.col
)
@property @property
def widget_list(self) -> list[BECPlotBase]: def widget_list(self) -> list[BECPlotBase]:
""" """

View File

@ -60,6 +60,7 @@ class BECImageShow(BECPlotBase):
"lock_aspect_ratio", "lock_aspect_ratio",
"remove", "remove",
"images", "images",
"apply_config",
] ]
def __init__( def __init__(

View File

@ -62,6 +62,7 @@ class BECMotorMap(BECPlotBase):
"set_scatter_size", "set_scatter_size",
"get_data", "get_data",
"remove", "remove",
"apply_config",
] ]
# QT Signals # QT Signals

View File

@ -59,6 +59,7 @@ class BECWaveform(BECPlotBase):
"lock_aspect_ratio", "lock_aspect_ratio",
"remove", "remove",
"set_legend_label_size", "set_legend_label_size",
"apply_config",
] ]
scan_signal_update = pyqtSignal() scan_signal_update = pyqtSignal()
dap_params_update = pyqtSignal(dict) dap_params_update = pyqtSignal(dict)