mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-04-18 06:15:37 +02:00
Compare commits
2 Commits
feat/dock_
...
feature/vs
| Author | SHA1 | Date | |
|---|---|---|---|
| de7eaf7826 | |||
| 1694215c06 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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 |
@@ -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
|
||||
|
||||
@@ -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.")
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
@@ -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.")
|
||||
32
bec_widgets/widgets/editor/vscode.py
Normal file
32
bec_widgets/widgets/editor/vscode.py
Normal 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())
|
||||
59
bec_widgets/widgets/editor/vscode_server.py
Normal file
59
bec_widgets/widgets/editor/vscode_server.py
Normal 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()
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
2
setup.py
2
setup.py
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -91,7 +91,6 @@ DEVICES = [
|
||||
FakeDevice("bpm4i"),
|
||||
FakeDevice("bpm3a"),
|
||||
FakeDevice("bpm3i"),
|
||||
FakeDevice("eiger"),
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user