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

feat(plots/bec_figure): Motor Map integrated to BECFigure

This commit is contained in:
2024-03-26 20:31:45 +01:00
parent 0f69c346cd
commit b8519e8770
5 changed files with 192 additions and 13 deletions

View File

@ -450,12 +450,35 @@ class BECFigure(RPCBase, BECFigureClientMixin):
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): Additional configuration for the widget.
**axis_kwargs:
**axis_kwargs: Additional axis properties to set on the widget after creation.
Returns:
BECImageShow: The image widget.
"""
@rpc_call
def add_motor_map(
self,
motor_x: "str" = None,
motor_y: "str" = None,
row: "int" = None,
col: "int" = None,
config=None,
**axis_kwargs
) -> "BECMotorMap":
"""
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.
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): Additional configuration for the widget.
**axis_kwargs:
Returns:
BECMotorMap: The motor map widget.
"""
@rpc_call
def plot(
self,
@ -512,6 +535,21 @@ class BECFigure(RPCBase, BECFigureClientMixin):
BECImageShow: The image widget.
"""
@rpc_call
def motor_map(
self, motor_x: "str" = None, motor_y: "str" = None, **axis_kwargs
) -> "BECMotorMap":
"""
Add a motor map to the figure. Always access the first motor map widget in the figure.
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.
**axis_kwargs: Additional axis properties to set on the widget after creation.
Returns:
BECMotorMap: The motor map widget.
"""
@rpc_call
def remove(
self,
@ -1091,3 +1129,64 @@ class BECImageItem(RPCBase):
Returns:
dict: The configuration of the plot widget.
"""
class BECMotorMap(RPCBase):
@rpc_call
def change_motors(
self,
motor_x: "str",
motor_y: "str",
motor_x_entry: "str" = None,
motor_y_entry: "str" = None,
validate_bec: "bool" = True,
) -> "None":
"""
Change the active motors for the plot.
Args:
motor_x(str): Motor name for the X axis.
motor_y(str): Motor name for the Y axis.
motor_x_entry(str): Motor entry for the X axis.
motor_y_entry(str): Motor entry for the Y axis.
validate_bec(bool, optional): If True, validate the signal with BEC. Defaults to True.
"""
@rpc_call
def set_max_points(self, max_points: "int") -> "None":
"""
Set the maximum number of points to display.
Args:
max_points(int): Maximum number of points to display.
"""
@rpc_call
def set_precision(self, precision: "int") -> "None":
"""
Set the decimal precision of the motor position.
Args:
precision(int): Decimal precision of the motor position.
"""
@rpc_call
def set_num_dim_points(self, num_dim_points: "int") -> "None":
"""
Set the number of dim points for the motor map.
Args:
num_dim_points(int): Number of dim points.
"""
@rpc_call
def set_background_value(self, background_value: "int") -> "None":
"""
Set the background value of the motor map.
Args:
background_value(int): Background value of the motor map.
"""
@rpc_call
def set_scatter_size(self, scatter_size: "int") -> "None":
"""
Set the scatter size of the motor map plot.
Args:
scatter_size(int): Size of the scatter points.
"""

View File

@ -109,7 +109,7 @@ if __name__ == "__main__": # pragma: no cover
from bec_widgets.utils import BECConnector
from bec_widgets.widgets.figure import BECFigure
from bec_widgets.widgets.plots import BECImageShow, BECPlotBase, BECWaveform1D
from bec_widgets.widgets.plots import BECImageShow, BECMotorMap, BECPlotBase, BECWaveform1D
from bec_widgets.widgets.plots.image import BECImageItem
from bec_widgets.widgets.plots.waveform1d import BECCurve
@ -123,6 +123,7 @@ if __name__ == "__main__": # pragma: no cover
BECImageShow,
BECConnector,
BECImageItem,
BECMotorMap,
]
generator = ClientGenerator()
generator.generate_client(clss)

View File

@ -17,12 +17,14 @@ from qtpy.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
from bec_widgets.utils import BECConnector, BECDispatcher, ConnectionConfig
from bec_widgets.widgets.plots import (
BECImageShow,
BECMotorMap,
BECPlotBase,
BECWaveform1D,
Waveform1DConfig,
WidgetConfig,
)
from bec_widgets.widgets.plots.image import ImageConfig
from bec_widgets.widgets.plots.motor_map import MotorMapConfig
class FigureConfig(ConnectionConfig):
@ -44,6 +46,7 @@ class WidgetHandler:
"PlotBase": (BECPlotBase, WidgetConfig),
"Waveform1D": (BECWaveform1D, Waveform1DConfig),
"ImShow": (BECImageShow, ImageConfig),
"MotorMap": (BECMotorMap, MotorMapConfig),
}
def create_widget(
@ -98,8 +101,10 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
"widgets",
"add_plot",
"add_image",
"add_motor_map",
"plot",
"image",
"motor_map",
"remove",
"change_layout",
"change_theme",
@ -347,7 +352,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
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): Additional configuration for the widget.
**axis_kwargs:
**axis_kwargs: Additional axis properties to set on the widget after creation.
Returns:
BECImageShow: The image widget.
@ -389,6 +394,72 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
return image
def motor_map(self, motor_x: str = None, motor_y: str = None, **axis_kwargs) -> BECMotorMap:
"""
Add a motor map to the figure. Always access the first motor map widget in the figure.
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.
**axis_kwargs: Additional axis properties to set on the widget after creation.
Returns:
BECMotorMap: The motor map widget.
"""
motor_map = self._find_first_widget_by_class(BECMotorMap, can_fail=True)
if motor_map is not None:
if axis_kwargs:
motor_map.set(**axis_kwargs)
else:
motor_map = self.add_motor_map(**axis_kwargs)
if motor_x is not None and motor_y is not None:
motor_map.change_motors(motor_x, motor_y)
return motor_map
def add_motor_map(
self,
motor_x: str = None,
motor_y: str = None,
row: int = None,
col: int = None,
config=None,
**axis_kwargs,
) -> BECMotorMap:
"""
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.
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): Additional configuration for the widget.
**axis_kwargs:
Returns:
BECMotorMap: The motor map widget.
"""
widget_id = self._generate_unique_widget_id()
if config is None:
config = MotorMapConfig(
widget_class="BECMotorMap",
gui_id=widget_id,
parent_id=self.gui_id,
)
motor_map = self.add_widget(
widget_type="MotorMap",
widget_id=widget_id,
row=row,
col=col,
config=config,
**axis_kwargs,
)
if motor_x is not None and motor_y is not None:
motor_map.change_motors(motor_x, motor_y)
return motor_map
def add_widget(
self,
widget_type: Literal["PlotBase", "Waveform1D", "ImShow"] = "PlotBase",

View File

@ -1,20 +1,18 @@
from __future__ import annotations
from collections import defaultdict
from typing import Any, Literal, Optional, Union
from typing import Optional, Union
import numpy as np
import pyqtgraph as pg
from bec_lib import MessageEndpoints
from bec_lib.scan_data import ScanData
from pydantic import BaseModel, Field, ValidationError
from pyqtgraph import mkBrush
from pydantic import Field
from qtpy import QtCore, QtGui
from qtpy.QtCore import Signal as pyqtSignal
from qtpy.QtCore import Slot as pyqtSlot
from qtpy.QtWidgets import QWidget
from bec_widgets.utils import BECConnector, Colors, ConnectionConfig, EntryValidator
from bec_widgets.utils import EntryValidator
from bec_widgets.widgets.plots.plot_base import BECPlotBase, WidgetConfig
from bec_widgets.widgets.plots.waveform1d import Signal, SignalData
@ -23,7 +21,7 @@ class MotorMapConfig(WidgetConfig):
signals: Optional[Signal] = Field(None, description="Signals of the motor map")
color_map: Optional[str] = Field(
"Greys", description="Color scheme of the motor position gradient."
)
) # TODO decide if useful for anything, or just keep GREYS always
scatter_size: Optional[int] = Field(5, description="Size of the scatter points.")
max_points: Optional[int] = Field(1000, description="Maximum number of points to display.")
num_dim_points: Optional[int] = Field(
@ -37,7 +35,14 @@ class MotorMapConfig(WidgetConfig):
class BECMotorMap(BECPlotBase):
USER_ACCESS = []
USER_ACCESS = [
"change_motors",
"set_max_points",
"set_precision",
"set_num_dim_points",
"set_background_value",
"set_scatter_size",
]
# QT Signals
update_signal = pyqtSignal()
@ -220,6 +225,9 @@ class BECMotorMap(BECPlotBase):
self.plot_components["scatter"].setData([initial_position_x], [initial_position_y])
self._add_coordinantes_crosshair(initial_position_x, initial_position_y)
# Set default labels for the plot
self.set(x_label=f"Motor X ({self.motor_x})", y_label=f"Motor Y ({self.motor_y})")
def _add_coordinantes_crosshair(self, x: float, y: float) -> None:
"""
Add crosshair to the plot to highlight the current position.
@ -399,11 +407,11 @@ class BECMotorMap(BECPlotBase):
self.update_signal.emit()
if __name__ == "__main__":
if __name__ == "__main__": # pragma: no cover
import sys
import pyqtgraph as pg
from qtpy.QtWidgets import QApplication, QMainWindow
from qtpy.QtWidgets import QApplication
app = QApplication(sys.argv)
glw = pg.GraphicsLayoutWidget()

View File

@ -66,7 +66,7 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
pg.GraphicsLayout.__init__(self, parent)
self.figure = parent_figure
self.plot_item = self.addPlot()
self.plot_item = self.addPlot(row=0, col=0)
self.add_legend()