1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-04-18 06:15:37 +02:00

Compare commits

..

2 Commits

Author SHA1 Message Date
de7eaf7826 feat: added websitewidget 2024-04-23 09:23:17 +02:00
1694215c06 feat: added simple vscode widget 2024-04-21 10:08:44 +02:00
23 changed files with 171 additions and 406 deletions

View File

@@ -2,12 +2,6 @@
<!--next-version-placeholder-->
## v0.46.7 (2024-04-21)
### Fix
* **plot/image:** Monitors are now validated with current bec session ([`67a99a1`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/67a99a1a19c261f9a1f09635f274cd9fbfe53639))
## v0.46.6 (2024-04-19)
### Fix

View File

@@ -248,11 +248,11 @@ class BECWaveform(RPCBase):
"""
@rpc_call
def apply_config(self, config: "dict | SubplotConfig", replot_last_scan: "bool" = False):
def apply_config(self, config: "dict | WidgetConfig", replot_last_scan: "bool" = False):
"""
Apply the configuration to the 1D waveform widget.
Args:
config(dict|SubplotConfig): Configuration settings.
config(dict|WidgetConfig): Configuration settings.
replot_last_scan(bool, optional): If True, replot the last scan. Defaults to False.
"""
@@ -614,13 +614,6 @@ class BECFigure(RPCBase, BECFigureClientMixin):
Clear all widgets from the figure and reset to default state
"""
@property
@rpc_call
def containers(self) -> "dict":
"""
None
"""
class BECCurve(RPCBase):
@property

View File

@@ -1,12 +1,10 @@
import inspect
from typing import Literal
from bec_lib import MessageEndpoints, messages
from qtpy.QtCore import QTimer
from bec_widgets.utils import BECDispatcher
from bec_widgets.utils.bec_connector import BECConnector
from bec_widgets.widgets.dock.dock_area import BECDockArea
from bec_widgets.widgets.figure import BECFigure
from bec_widgets.widgets.plots import BECCurve, BECImageShow, BECWaveform
@@ -14,18 +12,12 @@ from bec_widgets.widgets.plots import BECCurve, BECImageShow, BECWaveform
class BECWidgetsCLIServer:
WIDGETS = [BECWaveform, BECFigure, BECCurve, BECImageShow]
def __init__(
self,
gui_id: str = None,
dispatcher: BECDispatcher = None,
client=None,
gui_class: BECFigure | BECDockArea = BECFigure,
) -> None:
def __init__(self, gui_id: str = None, dispatcher: BECDispatcher = None, client=None) -> None:
self.dispatcher = BECDispatcher() if dispatcher is None else dispatcher
self.client = self.dispatcher.client if client is None else client
self.client.start()
self.gui_id = gui_id
self.gui = gui_class(gui_id=self.gui_id)
self.fig = BECFigure(gui_id=self.gui_id)
self.dispatcher.connect_slot(
self.on_rpc_update, MessageEndpoints.gui_instructions(self.gui_id)
@@ -61,14 +53,14 @@ class BECWidgetsCLIServer:
def get_object_from_config(self, config: dict):
gui_id = config.get("gui_id")
# check if the object is the figure
if gui_id == self.gui.gui_id:
return self.gui
if gui_id == self.fig.gui_id:
return self.fig
# check if the object is a widget
if gui_id in self.gui.containers:
obj = self.gui.containers[config["gui_id"]]
if gui_id in self.fig._widgets:
obj = self.fig._widgets[config["gui_id"]]
return obj
if self.gui.containers:
for widget in self.gui.containers.values():
if self.fig._widgets:
for widget in self.fig._widgets.values():
item = widget.find_widget_by_id(gui_id)
if item:
return item
@@ -131,29 +123,13 @@ if __name__ == "__main__": # pragma: no cover
parser = argparse.ArgumentParser(description="BEC Widgets CLI Server")
parser.add_argument("--id", type=str, help="The id of the server")
parser.add_argument(
"--gui_class",
type=str,
help="Name of the gui class to be rendered. Possible values: \n- BECFigure\n- BECDockArea",
)
args = parser.parse_args()
if args.gui_class == "BECFigure":
gui_class = BECFigure
elif args.gui_class == "BECDockArea":
gui_class = BECDockArea
else:
print(
"Please specify a valid gui_class to run. Use -h for help."
"\n Starting with default gui_class BECFigure."
)
gui_class = BECFigure
server = BECWidgetsCLIServer(gui_id=args.id)
# server = BECWidgetsCLIServer(gui_id="test")
# server = BECWidgetsCLIServer(gui_id=args.id, gui_class=gui_class)
server = BECWidgetsCLIServer(gui_id="test", gui_class=gui_class)
fig = server.gui
fig = server.fig
win.setCentralWidget(fig)
win.show()

View File

@@ -2,16 +2,13 @@ import os
import numpy as np
import pyqtgraph as pg
from PyQt6.QtCore import QSize
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QMainWindow
from pyqtgraph.Qt import QtWidgets, uic
from pyqtgraph.Qt import uic
from qtconsole.inprocess import QtInProcessKernelManager
from qtconsole.rich_jupyter_widget import RichJupyterWidget
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
from bec_widgets.utils import BECDispatcher
from bec_widgets.widgets import BECDockArea, BECFigure
from bec_widgets.widgets import BECFigure
class JupyterConsoleWidget(RichJupyterWidget): # pragma: no cover:
@@ -50,14 +47,9 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
self.console.kernel_manager.kernel.shell.push(
{
"fig": self.figure,
"dock": self.dock,
"w1": self.w1,
"w2": self.w2,
"w3": self.w3,
"d1": self.d1,
"d2": self.d2,
"d3": self.d3,
"label_2": self.label_2,
"bec": self.figure.client,
"scans": self.figure.client.scans,
"dev": self.figure.client.device_manager.devices,
@@ -70,16 +62,9 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
self.figure = BECFigure(parent=self, gui_id="remote") # Create a new BECDeviceMonitor
self.glw_1_layout.addWidget(self.figure) # Add BECDeviceMonitor to the layout
self.dock_layout = QVBoxLayout(self.dock_placeholder)
self.dock = BECDockArea(gui_id="remote")
self.dock_layout.addWidget(self.dock)
# add stuff to figure
self._init_figure()
# init dock for testing
self._init_dock()
self.console_layout = QVBoxLayout(self.widget_console)
self.console = JupyterConsoleWidget()
self.console_layout.addWidget(self.console)
@@ -101,19 +86,6 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
self.w1.add_curve_scan("samx", "samy", "bpm3a", pen_style="dash")
self.c1 = self.w1.get_config()
def _init_dock(self):
self.button_1 = QtWidgets.QPushButton("Button 1 ")
self.label_1 = QtWidgets.QLabel("some scan info label with useful information")
self.label_2 = QtWidgets.QLabel("label which is added separately")
self.d1 = self.dock.add_dock(widget=self.button_1, position="left")
self.d2 = self.dock.add_dock(widget=self.label_1, position="right")
self.d3 = self.dock.plot(x_name="samx", y_name="bpm4d")
self.d4 = self.dock.image(monitor="eiger")
self.d4.set_vrange(0, 100)
if __name__ == "__main__": # pragma: no cover
import sys
@@ -124,10 +96,6 @@ if __name__ == "__main__": # pragma: no cover
app = QApplication(sys.argv)
app.setApplicationName("Jupyter Console")
app.setApplicationDisplayName("Jupyter Console")
icon = QIcon()
icon.addFile("terminal_icon.png", size=QSize(48, 48))
app.setWindowIcon(icon)
win = JupyterConsoleWindow()
win.show()

View File

@@ -13,37 +13,13 @@
<property name="windowTitle">
<string>Plotting Console</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_1">
<attribute name="title">
<string>BECDock</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QWidget" name="dock_placeholder" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>BECFigure</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QWidget" name="glw" native="true"/>
</item>
</layout>
</widget>
</widget>
<widget class="QWidget" name="glw" native="true"/>
<widget class="QWidget" name="widget_console" native="true"/>
</widget>
</item>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -2,7 +2,6 @@ from .bec_connector import BECConnector, ConnectionConfig
from .bec_dispatcher import BECDispatcher
from .bec_table import BECTable
from .colors import Colors
from .container_utils import WidgetContainerUtils
from .crosshair import Crosshair
from .entry_validator import EntryValidator
from .rpc_decorator import register_rpc_methods, rpc_public

View File

@@ -1,45 +0,0 @@
import itertools
from typing import Type
from qtpy.QtWidgets import QWidget
class WidgetContainerUtils:
@staticmethod
def generate_unique_widget_id(container: dict, prefix: str = "widget") -> str:
"""
Generate a unique widget ID.
Args:
container(dict): The container of widgets.
prefix(str): The prefix of the widget ID.
Returns:
widget_id(str): The unique widget ID.
"""
existing_ids = set(container.keys())
for i in itertools.count(1):
widget_id = f"{prefix}_{i}"
if widget_id not in existing_ids:
return widget_id
@staticmethod
def find_first_widget_by_class(
container: dict, widget_class: Type[QWidget], can_fail: bool = True
) -> QWidget | None:
"""
Find the first widget of a given class in the figure.
Args:
container(dict): The container of widgets.
widget_class(Type): The class of the widget to find.
can_fail(bool): If True, the method will return None if no widget is found. If False, it will raise an error.
Returns:
widget: The widget of the given class.
"""
for widget_id, widget in container.items():
if isinstance(widget, widget_class):
return widget
if can_fail:
return None
else:
raise ValueError(f"No widget of class {widget_class} found.")

View File

@@ -3,15 +3,6 @@ class EntryValidator:
self.devices = devices
def validate_signal(self, name: str, entry: str = None) -> str:
"""
Validate a signal entry for a given device. If the entry is not provided, the first signal entry will be used from the device hints.
Args:
name(str): Device name
entry(str): Signal entry
Returns:
str: Signal entry
"""
if name not in self.devices:
raise ValueError(f"Device '{name}' not found in current BEC session")
@@ -24,17 +15,3 @@ class EntryValidator:
raise ValueError(f"Entry '{entry}' not found in device '{name}' signals")
return entry
def validate_monitor(self, monitor: str) -> str:
"""
Validate a monitor entry for a given device.
Args:
monitor(str): Monitor entry
Returns:
str: Monitor entry
"""
if monitor not in self.devices:
raise ValueError(f"Device '{monitor}' not found in current BEC session")
return monitor

View File

@@ -1,50 +0,0 @@
from typing import Literal, Optional
from pydantic import Field
from pyqtgraph.dockarea import Dock
from qtpy.QtWidgets import QWidget
from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils
class DockConfig(ConnectionConfig):
widgets: dict[str, ConnectionConfig] = Field({}, description="The widgets in the dock.")
position: Literal["bottom", "top", "left", "right", "above", "below"] = Field(
"bottom", description="The position of the dock."
)
parent_dock_area: Optional[str] = Field(
None, description="The GUI ID of parent dock area of the dock."
)
class BECDock(BECConnector, Dock):
def __init__(
self,
parent: Optional[QWidget] = None,
parent_dock_area: Optional["BECDockArea"] = None,
config: Optional[
DockConfig
] = None, # TODO ATM connection config -> will be changed when I will know what I want to use there
name: Optional[str] = None,
client=None,
gui_id: Optional[str] = None,
**kwargs,
) -> None:
if config is None:
config = DockConfig(
widget_class=self.__class__.__name__, parent_dock_area=parent_dock_area.gui_id
)
else:
if isinstance(config, dict):
config = DockConfig(**config)
self.config = config
super().__init__(client=client, config=config, gui_id=gui_id)
Dock.__init__(self, name=name, **kwargs)
self.parent_dock_area = parent_dock_area
self.sigClosed.connect(self._remove_from_dock_area)
def _remove_from_dock_area(self):
"""Remove this dock from the DockArea it lives inside."""
self.parent_dock_area.docks.pop(self.name())

View File

@@ -1,107 +0,0 @@
from collections import defaultdict
from typing import Literal, Optional
import pyqtgraph as pg
from pydantic import Field
from pyqtgraph.dockarea.DockArea import Dock, DockArea
from qtpy.QtWidgets import QWidget
from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils
from bec_widgets.widgets import BECFigure, BECMotorMap, BECWaveform
from bec_widgets.widgets.plots import BECImageShow
from .dock import BECDock, DockConfig
class DockAreaConfig(ConnectionConfig):
docks: dict[str, DockConfig] = Field({}, description="The docks in the dock area.")
class BECDockArea(BECConnector, DockArea):
USER_ACCESS = ["figure", "plot", "image", "motor_map", "add_dock", "remove_dock_by_id", "clear"]
def __init__(
self,
parent: Optional[QWidget] = None,
config: Optional[DockAreaConfig] = None,
client=None,
gui_id: Optional[str] = None,
) -> None:
if config is None:
config = DockAreaConfig(widget_class=self.__class__.__name__)
else:
if isinstance(config, dict):
config = DockAreaConfig(**config)
self.config = config
super().__init__(client=client, config=config, gui_id=gui_id)
DockArea.__init__(self, parent=parent)
self._last_state = None # TODO not sure if this will ever work
def figure(self, name: str = None) -> BECFigure:
figure = BECFigure(gui_id="remote")
self.add_dock(name=name, widget=figure, prefix="figure")
return figure
def plot(
self,
x_name: str = None,
y_name: str = None,
name: str = None,
) -> BECWaveform:
figure = BECFigure(gui_id="remote")
self.add_dock(name=name, widget=figure, prefix="plot")
plot = figure.plot(x_name, y_name)
return plot
def image(self, monitor: str = "eiger", name: str = None) -> BECImageShow:
figure = BECFigure(gui_id="remote")
self.add_dock(name=name, widget=figure, prefix="image")
image = figure.image(monitor)
return image
def motor_map(self, x_name: str = None, y_name: str = None, name: str = None) -> BECMotorMap:
figure = BECFigure(gui_id="remote")
self.add_dock(name=name, widget=figure, prefix="motor_map")
motor_map = figure.motor_map(x_name, y_name)
return motor_map
def add_dock(
self,
name: str = None,
widget: QWidget = None,
position: Literal["bottom", "top", "left", "right", "above", "below"] = None,
relative_to: Optional[BECDock] = None, # TODO implement relative_to
prefix: str = "dock",
) -> BECDock:
if name is None:
name = WidgetContainerUtils.generate_unique_widget_id(
container=self.docks, prefix=prefix
)
if name in set(self.docks.keys()):
raise ValueError(f"Dock with name {name} already exists.")
if position is None:
position = "bottom"
dock = BECDock(name=name, parent_dock_area=self, closable=True)
dock.config.position = position
self.config.docks[name] = dock.config
self.addDock(dock, position)
if widget is not None:
dock.addWidget(widget)
return dock
def remove_dock_by_id(self, dock_id: str):
if dock_id in self.docks:
dock_to_remove = self.docks[dock_id]
dock_to_remove.close()
else:
raise ValueError(f"Dock with id {dock_id} does not exist.")

View File

@@ -0,0 +1,32 @@
from qtpy.QtCore import QUrl
from qtpy.QtWebEngineWidgets import QWebEngineView
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
class WebsiteWidget(QWidget):
def __init__(self, url):
super().__init__()
self.editor = QWebEngineView(self)
layout = QVBoxLayout()
layout.addWidget(self.editor)
self.setLayout(layout)
self.editor.setUrl(QUrl(url))
class VSCodeEditor(WebsiteWidget):
token = "bec"
host = "localhost"
port = 7000
def __init__(self):
super().__init__(f"http://{self.host}:{self.port}?tkn={self.token}")
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
mainWin = WebsiteWidget("https://scilog.psi.ch")
mainWin.show()
sys.exit(app.exec())

View File

@@ -0,0 +1,59 @@
"""
Module to handle the vscode server
"""
import subprocess
class VSCodeServer:
"""
Class to handle the vscode server
"""
_instance = None
def __init__(self, port=7000, token="bec"):
self.started = False
self._server = None
self.port = port
self.token = token
def __new__(cls, *args, forced=False, **kwargs):
if cls._instance is None or forced:
cls._instance = super(VSCodeServer, cls).__new__(cls)
return cls._instance
def start_server(self):
"""
Start the vscode server in a subprocess
"""
if self.started:
return
self._server = subprocess.Popen(
f"code serve-web --port {self.port} --connection-token={self.token} --accept-server-license-terms",
shell=True,
)
self.started = True
def wait(self):
"""
Wait for the server to finish
"""
if not self.started:
return
if not self._server:
return
self._server.wait()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Start the vscode server")
parser.add_argument("--port", type=int, default=7000, help="Port to start the server")
parser.add_argument("--token", type=str, default="bec", help="Token to start the server")
args = parser.parse_args()
server = VSCodeServer(port=args.port, token=args.token)
server.start_server()
server.wait()

View File

@@ -14,14 +14,14 @@ from pyqtgraph.Qt import uic
from qtpy.QtCore import Signal as pyqtSignal
from qtpy.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
from bec_widgets.utils import BECConnector, BECDispatcher, ConnectionConfig, WidgetContainerUtils
from bec_widgets.utils import BECConnector, BECDispatcher, ConnectionConfig
from bec_widgets.widgets.plots import (
BECImageShow,
BECMotorMap,
BECPlotBase,
BECWaveform,
SubplotConfig,
Waveform1DConfig,
WidgetConfig,
)
from bec_widgets.widgets.plots.image import ImageConfig
from bec_widgets.widgets.plots.motor_map import MotorMapConfig
@@ -33,7 +33,7 @@ class FigureConfig(ConnectionConfig):
theme: Literal["dark", "light"] = Field("dark", description="The theme of the figure widget.")
num_cols: int = Field(1, description="The number of columns in the figure widget.")
num_rows: int = Field(1, description="The number of rows in the figure widget.")
widgets: dict[str, SubplotConfig] = Field(
widgets: dict[str, WidgetConfig] = Field(
{}, description="The list of widgets to be added to the figure widget."
)
@@ -43,7 +43,7 @@ class WidgetHandler:
def __init__(self):
self.widget_factory = {
"PlotBase": (BECPlotBase, SubplotConfig),
"PlotBase": (BECPlotBase, WidgetConfig),
"Waveform1D": (BECWaveform, Waveform1DConfig),
"ImShow": (BECImageShow, ImageConfig),
"MotorMap": (BECMotorMap, MotorMapConfig),
@@ -110,7 +110,6 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
"change_layout",
"change_theme",
"clear_all",
"containers",
]
clean_signal = pyqtSignal()
@@ -161,14 +160,6 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
def widgets(self, value: dict):
self._widgets = value
@property
def containers(self) -> dict:
return self._widgets
@containers.setter
def containers(self, value: dict):
self._widgets = value
def add_plot(
self,
x_name: str = None,
@@ -197,7 +188,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
config(dict): Additional configuration for the widget.
**axis_kwargs(dict): Additional axis properties to set on the widget after creation.
"""
widget_id = WidgetContainerUtils.generate_unique_widget_id(self._widgets)
widget_id = self._generate_unique_widget_id()
waveform = self.add_widget(
widget_type="Waveform1D",
widget_id=widget_id,
@@ -287,9 +278,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
Returns:
BECWaveform: The waveform plot widget.
"""
waveform = WidgetContainerUtils.find_first_widget_by_class(
self._widgets, BECWaveform, can_fail=True
)
waveform = self._find_first_widget_by_class(BECWaveform, can_fail=True)
if waveform is not None:
if axis_kwargs:
waveform.set(**axis_kwargs)
@@ -366,9 +355,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
Returns:
BECImageShow: The image widget.
"""
image = WidgetContainerUtils.find_first_widget_by_class(
self._widgets, BECImageShow, can_fail=True
)
image = self._find_first_widget_by_class(BECImageShow, can_fail=True)
if image is not None:
if axis_kwargs:
image.set(**axis_kwargs)
@@ -423,7 +410,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
BECImageShow: The image widget.
"""
widget_id = WidgetContainerUtils.generate_unique_widget_id(self._widgets)
widget_id = self._generate_unique_widget_id()
if config is None:
config = ImageConfig(
widget_class="BECImageShow",
@@ -470,9 +457,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
Returns:
BECMotorMap: The motor map widget.
"""
motor_map = WidgetContainerUtils.find_first_widget_by_class(
self._widgets, BECMotorMap, can_fail=True
)
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)
@@ -506,7 +491,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
Returns:
BECMotorMap: The motor map widget.
"""
widget_id = WidgetContainerUtils.generate_unique_widget_id(self._widgets)
widget_id = self._generate_unique_widget_id()
if config is None:
config = MotorMapConfig(
widget_class="BECMotorMap",
@@ -547,7 +532,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
**axis_kwargs(dict): Additional axis properties to set on the widget after creation.
"""
if not widget_id:
widget_id = WidgetContainerUtils.generate_unique_widget_id(self._widgets)
widget_id = self._generate_unique_widget_id()
if widget_id in self._widgets:
raise ValueError(f"Widget with ID '{widget_id}' already exists.")
@@ -625,6 +610,25 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
self.setBackground("k" if theme == "dark" else "w")
self.config.theme = theme
def _find_first_widget_by_class(
self, widget_class: Type[BECPlotBase], can_fail: bool = True
) -> BECPlotBase | None:
"""
Find the first widget of a given class in the figure.
Args:
widget_class(Type[BECPlotBase]): The class of the widget to find.
can_fail(bool): If True, the method will return None if no widget is found. If False, it will raise an error.
Returns:
BECPlotBase: The widget of the given class.
"""
for widget_id, widget in self._widgets.items():
if isinstance(widget, widget_class):
return widget
if can_fail:
return None
else:
raise ValueError(f"No widget of class {widget_class} found.")
def _remove_by_coordinates(self, row: int, col: int) -> None:
"""
Remove a widget from the figure by its coordinates.
@@ -691,6 +695,14 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
row += 1
return row, col
def _generate_unique_widget_id(self):
"""Generate a unique widget ID."""
existing_ids = set(self._widgets.keys())
for i in itertools.count(1):
widget_id = f"widget_{i}"
if widget_id not in existing_ids:
return widget_id
def _change_grid(self, widget_id: str, row: int, col: int):
"""
Change the grid to reflect the new position of the widget.

View File

@@ -1,4 +1,4 @@
from .image import BECImageItem, BECImageShow, ImageItemConfig
from .motor_map import BECMotorMap, MotorMapConfig
from .plot_base import AxisConfig, BECPlotBase, SubplotConfig
from .plot_base import AxisConfig, BECPlotBase, WidgetConfig
from .waveform import BECCurve, BECWaveform, Waveform1DConfig

View File

@@ -12,9 +12,9 @@ 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, ConnectionConfig, EntryValidator
from bec_widgets.utils import BECConnector, ConnectionConfig
from .plot_base import BECPlotBase, SubplotConfig
from .plot_base import BECPlotBase, WidgetConfig
class ProcessingConfig(BaseModel):
@@ -50,7 +50,7 @@ class ImageItemConfig(ConnectionConfig):
)
class ImageConfig(SubplotConfig):
class ImageConfig(WidgetConfig):
images: dict[str, ImageItemConfig] = Field(
{},
description="The configuration of the images. The key is the name of the image (source).",
@@ -335,9 +335,7 @@ class BECImageShow(BECPlotBase):
super().__init__(
parent=parent, parent_figure=parent_figure, config=config, client=client, gui_id=gui_id
)
# Get bec shortcuts dev, scans, queue, scan_storage, dap
self.get_bec_shortcuts()
self.entry_validator = EntryValidator(self.dev)
self._images = defaultdict(dict)
self.apply_config(self.config)
self.processor = ImageProcessor()
@@ -358,7 +356,7 @@ class BECImageShow(BECPlotBase):
thread.start()
def find_widget_by_id(self, item_id: str) -> BECImageItem: # todo can be done in mixin
def find_widget_by_id(self, item_id: str) -> BECImageItem:
"""
Find the widget by its gui_id.
Args:
@@ -390,11 +388,11 @@ class BECImageShow(BECPlotBase):
if result is not None:
return result
def apply_config(self, config: dict | SubplotConfig):
def apply_config(self, config: dict | WidgetConfig):
"""
Apply the configuration to the 1D waveform widget.
Args:
config(dict|SubplotConfig): Configuration settings.
config(dict|WidgetConfig): Configuration settings.
replot_last_scan(bool, optional): If True, replot the last scan. Defaults to False.
"""
if isinstance(config, dict):
@@ -509,8 +507,6 @@ class BECImageShow(BECPlotBase):
f"Monitor with ID '{monitor}' already exists in widget '{self.gui_id}'."
)
monitor = self.entry_validator.validate_monitor(monitor)
image_config = ImageItemConfig(
widget_class="BECImageItem",
parent_id=self.gui_id,
@@ -719,8 +715,10 @@ class BECImageShow(BECPlotBase):
processing_config = image_to_update.config.processing
self.processor.set_config(processing_config)
if self.use_threading:
print("using threaded version")
self._create_thread_worker(device, data)
else:
print("using NON-threaded version")
data = self.processor.process_image(data)
self.update_image(device, data)
@@ -787,22 +785,6 @@ class BECImageShow(BECPlotBase):
return True
return False
def _validate_monitor(self, monitor: str, validate_bec: bool = True):
"""
Validate the monitor name.
Args:
monitor(str): The name of the monitor.
validate_bec(bool): Whether to validate the monitor name with BEC.
Returns:
bool: True if the monitor name is valid, False otherwise.
"""
if not monitor or monitor == "":
return False
if validate_bec:
return monitor in self.dev
return True
def cleanup(self):
"""
Clean up the widget.

View File

@@ -13,11 +13,11 @@ from qtpy.QtCore import Slot as pyqtSlot
from qtpy.QtWidgets import QWidget
from bec_widgets.utils import EntryValidator
from bec_widgets.widgets.plots.plot_base import BECPlotBase, SubplotConfig
from bec_widgets.widgets.plots.plot_base import BECPlotBase, WidgetConfig
from bec_widgets.widgets.plots.waveform import Signal, SignalData
class MotorMapConfig(SubplotConfig):
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."

View File

@@ -22,7 +22,7 @@ class AxisConfig(BaseModel):
y_grid: bool = Field(False, description="Show grid on the y-axis.")
class SubplotConfig(ConnectionConfig):
class WidgetConfig(ConnectionConfig):
parent_id: Optional[str] = Field(None, description="The parent figure of the plot.")
# Coordinates in the figure
@@ -56,12 +56,12 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
self,
parent: Optional[QWidget] = None, # TODO decide if needed for this class
parent_figure=None,
config: Optional[SubplotConfig] = None,
config: Optional[WidgetConfig] = None,
client=None,
gui_id: Optional[str] = None,
):
if config is None:
config = SubplotConfig(widget_class=self.__class__.__name__)
config = WidgetConfig(widget_class=self.__class__.__name__)
super().__init__(client=client, config=config, gui_id=gui_id)
pg.GraphicsLayout.__init__(self, parent)

View File

@@ -15,7 +15,7 @@ from qtpy.QtCore import Slot as pyqtSlot
from qtpy.QtWidgets import QWidget
from bec_widgets.utils import BECConnector, Colors, ConnectionConfig, EntryValidator
from bec_widgets.widgets.plots.plot_base import BECPlotBase, SubplotConfig
from bec_widgets.widgets.plots.plot_base import BECPlotBase, WidgetConfig
class SignalData(BaseModel):
@@ -53,7 +53,7 @@ class CurveConfig(ConnectionConfig):
colormap: Optional[str] = Field("plasma", description="The colormap of the curves z gradient.")
class Waveform1DConfig(SubplotConfig):
class Waveform1DConfig(WidgetConfig):
color_palette: Literal["plasma", "viridis", "inferno", "magma"] = Field(
"plasma", description="The color palette of the figure widget."
) # TODO can be extended to all colormaps from current pyqtgraph session
@@ -299,11 +299,11 @@ class BECWaveform(BECPlotBase):
if curve.gui_id == item_id:
return curve
def apply_config(self, config: dict | SubplotConfig, replot_last_scan: bool = False):
def apply_config(self, config: dict | WidgetConfig, replot_last_scan: bool = False):
"""
Apply the configuration to the 1D waveform widget.
Args:
config(dict|SubplotConfig): Configuration settings.
config(dict|WidgetConfig): Configuration settings.
replot_last_scan(bool, optional): If True, replot the last scan. Defaults to False.
"""
if isinstance(config, dict):

View File

@@ -1,7 +1,7 @@
# pylint: disable= missing-module-docstring
from setuptools import find_packages, setup
__version__ = "0.46.7"
__version__ = "0.46.6"
# Default to PyQt6 if no other Qt binding is installed
QT_DEPENDENCY = "PyQt6>=6.0"

View File

@@ -11,8 +11,8 @@ from bec_widgets.utils import BECDispatcher
def rpc_server(qtbot, bec_client_lib, threads_check):
dispatcher = BECDispatcher(client=bec_client_lib) # Has to init singleton with fixture client
server = BECWidgetsCLIServer(gui_id="id_test")
qtbot.addWidget(server.gui)
qtbot.waitExposed(server.gui)
qtbot.addWidget(server.fig)
qtbot.waitExposed(server.fig)
qtbot.wait(1000) # 1s long to wait until gui is ready
yield server
dispatcher.disconnect_all()
@@ -23,7 +23,7 @@ def rpc_server(qtbot, bec_client_lib, threads_check):
def test_rpc_waveform1d_custom_curve(rpc_server, qtbot):
fig = BECFigure(rpc_server.gui_id)
fig_server = rpc_server.gui
fig_server = rpc_server.fig
ax = fig.add_plot()
curve = ax.add_curve_custom([1, 2, 3], [1, 2, 3])
@@ -37,7 +37,7 @@ def test_rpc_waveform1d_custom_curve(rpc_server, qtbot):
def test_rpc_plotting_shortcuts_init_configs(rpc_server, qtbot):
fig = BECFigure(rpc_server.gui_id)
fig_server = rpc_server.gui
fig_server = rpc_server.fig
plt = fig.plot("samx", "bpm4i")
im = fig.image("eiger")
@@ -151,7 +151,7 @@ def test_rpc_image(rpc_server, qtbot):
def test_rpc_motor_map(rpc_server, qtbot):
fig = BECFigure(rpc_server.gui_id)
fig_server = rpc_server.gui
fig_server = rpc_server.fig
motor_map = fig.motor_map("samx", "samy")

View File

@@ -91,7 +91,6 @@ DEVICES = [
FakeDevice("bpm4i"),
FakeDevice("bpm3a"),
FakeDevice("bpm3i"),
FakeDevice("eiger"),
]