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:
@ -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
|
||||||
|
@ -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]:
|
||||||
"""
|
"""
|
||||||
|
@ -60,6 +60,7 @@ class BECImageShow(BECPlotBase):
|
|||||||
"lock_aspect_ratio",
|
"lock_aspect_ratio",
|
||||||
"remove",
|
"remove",
|
||||||
"images",
|
"images",
|
||||||
|
"apply_config",
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
Reference in New Issue
Block a user