0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-13 19:21: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
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":
"""
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:
motor_x(str): The name of the motor for the X 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.
Returns:
@ -611,6 +622,12 @@ class BECFigure(RPCBase):
list[BECPlotBase]: List of all widgets in the figure.
"""
@rpc_call
def apply_config(self, config: "dict | FigureConfig"):
"""
None
"""
class BECImageItem(RPCBase):
@property
@ -823,7 +840,7 @@ class BECImageShow(RPCBase):
self,
monitor: "str",
color_map: "Optional[str]" = "magma",
color_bar: "Optional[Literal['simple', 'full']]" = "simple",
color_bar: "Optional[Literal['simple', 'full']]" = "full",
downsample: "Optional[bool]" = True,
opacity: "Optional[float]" = 1.0,
vrange: "Optional[tuple[int, int]]" = None,
@ -839,7 +856,7 @@ class BECImageShow(RPCBase):
name: "str",
data: "Optional[np.ndarray]" = None,
color_map: "Optional[str]" = "magma",
color_bar: "Optional[Literal['simple', 'full']]" = "simple",
color_bar: "Optional[Literal['simple', 'full']]" = "full",
downsample: "Optional[bool]" = True,
opacity: "Optional[float]" = 1.0,
vrange: "Optional[tuple[int, int]]" = None,
@ -1124,6 +1141,16 @@ class BECImageShow(RPCBase):
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):
@property
@ -1222,6 +1249,15 @@ class BECMotorMap(RPCBase):
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):
@property
@ -1702,6 +1738,16 @@ class BECWaveform(RPCBase):
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):
@property

View File

@ -8,7 +8,7 @@ from typing import Literal, Optional
import numpy as np
import pyqtgraph as pg
import qdarktheme
from pydantic import Field
from pydantic import Field, ValidationError, field_validator
from qtpy.QtCore import Signal as pyqtSignal
from qtpy.QtWidgets import QWidget
from typeguard import typechecked
@ -30,6 +30,26 @@ class FigureConfig(ConnectionConfig):
{}, 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:
"""Factory for creating and configuring BEC widgets for BECFigure."""
@ -103,6 +123,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
"clear_all",
"get_all_rpc",
"widget_list",
"apply_config",
]
subplot_map = {
"PlotBase": BECPlotBase,
@ -110,6 +131,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
"BECImageShow": BECImageShow,
"BECMotorMap": BECMotorMap,
}
widget_method_map = {"BECWaveform": "plot", "BECImageShow": "image", "BECMotorMap": "motor_map"}
clean_signal = pyqtSignal()
@ -125,8 +147,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
else:
if isinstance(config, dict):
config = FigureConfig(**config)
self.config = config
super().__init__(client=client, config=config, gui_id=gui_id)
super().__init__(client=client, gui_id=gui_id)
pg.GraphicsLayoutWidget.__init__(self, parent)
self.widget_handler = WidgetHandler()
@ -136,6 +157,8 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
# Container to keep track of the grid
self.grid = []
# Create config and apply it
self.apply_config(config)
def __getitem__(self, key: tuple | str):
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)"
)
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
def widget_list(self) -> list[BECPlotBase]:
"""

View File

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

View File

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

View File

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