1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-04-14 20:50:55 +02:00

Compare commits

..

5 Commits

Author SHA1 Message Date
70fd1b7be4 feat(figure): added support for async device readbacks as curve data 2024-07-07 12:37:31 +02:00
semantic-release
fc3a69bbb0 0.81.0
Automatically generated by python-semantic-release
2024-07-06 12:24:27 +00:00
9594be2606 feat(color_button): can get colors in RGBA or HEX 2024-07-06 12:31:29 +02:00
semantic-release
96fd239608 0.80.1
Automatically generated by python-semantic-release
2024-07-06 10:24:27 +00:00
61de7e9e22 fix(entry_validator): check for entry == "" 2024-07-06 12:16:29 +02:00
7 changed files with 108 additions and 23 deletions

View File

@@ -1,5 +1,17 @@
# CHANGELOG
## v0.81.0 (2024-07-06)
### Feature
* feat(color_button): can get colors in RGBA or HEX ([`9594be2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9594be260680d11c8550ff74ffb8d679e5a5b8f6))
## v0.80.1 (2024-07-06)
### Fix
* fix(entry_validator): check for entry == "" ([`61de7e9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/61de7e9e221c766b9fb3ec23246da6a11c96a986))
## v0.80.0 (2024-07-06)
### Feature
@@ -129,17 +141,3 @@
### Feature
* feat(widgets): added simple bec queue widget ([`3faee98`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3faee98ec80041a27e4c1f1156178de6f9dcdc63))
### Refactor
* refactor(dispatcher): cleanup ([`ca02132`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ca02132c8d18535b37e9192e00459d2aca6ba5cf))
## v0.74.1 (2024-06-26)
### Fix
* fix(rings): rings properties updated right after setting ([`c8b7367`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c8b7367815b095f8e4aa8b819481efb701f2e542))
### Test
* test(bec_figure): tests for removing widgets with rpc e2e ([`a268caa`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a268caaa30711fcc7ece542d24578d74cbf65c77))

View File

@@ -134,7 +134,10 @@ class BECDispatcher:
cls.qapp = None
def connect_slot(
self, slot: Callable, topics: Union[EndpointInfo, str, list[Union[EndpointInfo, str]]]
self,
slot: Callable,
topics: Union[EndpointInfo, str, list[Union[EndpointInfo, str]]],
**kwargs,
) -> None:
"""Connect widget's pyqt slot, so that it is called on new pub/sub topic message.
@@ -144,7 +147,7 @@ class BECDispatcher:
topics (EndpointInfo | str | list): A topic or list of topics that can typically be acquired via bec_lib.MessageEndpoints
"""
slot = QtThreadSafeCallback(slot)
self.client.connector.register(topics, cb=slot)
self.client.connector.register(topics, cb=slot, **kwargs)
topics_str, _ = self.client.connector._convert_endpointinfo(topics)
self._slots[slot].update(set(topics_str))

View File

@@ -19,7 +19,7 @@ class EntryValidator:
device = self.devices[name]
description = device.describe()
if entry is None:
if entry is None or entry == "":
entry = next(iter(device._hints), name) if hasattr(device, "_hints") else name
if entry not in description:
raise ValueError(f"Entry '{entry}' not found in device '{name}' signals")

View File

@@ -1,3 +1,7 @@
from __future__ import annotations
from typing import Literal
import pyqtgraph as pg
@@ -15,3 +19,18 @@ class ColorButton(pg.ColorButton):
self.colorDialog.setCurrentColor(self.color())
self.colorDialog.open()
self.colorDialog.exec()
def get_color(self, format: Literal["RGBA", "HEX"] = "RGBA") -> tuple | str:
"""
Get the color of the button in the specified format.
Args:
format(Literal["RGBA", "HEX"]): The format of the returned color.
Returns:
tuple|str: The color in the specified format.
"""
if format == "RGBA":
return self.color().getRgb()
if format == "HEX":
return self.color().name()

View File

@@ -7,6 +7,7 @@ from typing import Any, Literal, Optional
import numpy as np
import pyqtgraph as pg
from bec_lib import messages
from bec_lib.device import ReadoutPriority
from bec_lib.endpoints import MessageEndpoints
from bec_lib.scan_data import ScanData
from pydantic import Field, ValidationError
@@ -327,7 +328,7 @@ class BECWaveform(BECPlotBase):
color_map_z: Optional[str] = "plasma",
label: Optional[str] = None,
validate_bec: bool = True,
source: str = "scan_segment",
source: Optional[str] = None,
dap: Optional[str] = None,
**kwargs,
) -> BECCurve:
@@ -349,8 +350,6 @@ class BECWaveform(BECPlotBase):
Returns:
BECCurve: The curve object.
"""
# Check if curve already exists
curve_source = source
# Get entry if not provided and validate
x_entry, y_entry, z_entry = self._validate_signal_entries(
@@ -362,6 +361,7 @@ class BECWaveform(BECPlotBase):
else:
label = label or f"{y_name}-{y_entry}"
# Check if curve already exists
curve_exits = self._check_curve_id(label, self._curves_data)
if curve_exits:
raise ValueError(f"Curve with ID '{label}' already exists in widget '{self.gui_id}'.")
@@ -373,6 +373,18 @@ class BECWaveform(BECPlotBase):
)[-1]
)
if source is None:
if validate_bec:
curve_source = (
"async_readback"
if self.dev[y_name].readout_priority == ReadoutPriority.ASYNC
else "scan_segment"
)
else:
curve_source = "scan_segment"
else:
curve_source = source
# Create curve by config
curve_config = CurveConfig(
widget_class="BECCurve",

View File

@@ -1,15 +1,17 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Literal, Optional
from typing import TYPE_CHECKING, Literal, Optional
import pyqtgraph as pg
from bec_lib.endpoints import MessageEndpoints
from pydantic import BaseModel, Field, field_validator
from pydantic_core import PydanticCustomError
from qtpy import QtCore
from bec_widgets.utils import BECConnector, Colors, ConnectionConfig
if TYPE_CHECKING:
import numpy as np
from bec_widgets.widgets.figure.plots.waveform import BECWaveform1D
@@ -103,6 +105,14 @@ class BECCurve(BECConnector, pg.PlotDataItem):
if kwargs:
self.set(**kwargs)
self._async_info = {}
if self.config.source == "async_readback":
# get updates on new scans in order to change the subscription to the correct async readback
self.bec_dispatcher.connect_slot(
self.on_scan_status_update, MessageEndpoints.scan_status()
)
def apply_config(self):
pen_style_map = {
"solid": QtCore.Qt.SolidLine,
@@ -137,6 +147,49 @@ class BECCurve(BECConnector, pg.PlotDataItem):
else:
raise ValueError(f"Source {self.config.source} do not allow custom data setting.")
def on_scan_status_update(self, content: dict, metadata: dict):
"""
Update the async info with the latest scan status.
"""
scan_id = content.get("scan_id")
if scan_id == self._async_info.get("scan_id"):
return
active_subscription = self._async_info.get("active_subscription")
if active_subscription:
self.bec_dispatcher.disconnect_slot(self.on_async_update, active_subscription)
self._async_info["scan_id"] = scan_id
self._async_info["active_subscription"] = MessageEndpoints.device_async_readback(
scan_id, self.config.signals.y.name
)
self._async_info["data"] = []
self.bec_dispatcher.connect_slot(
self.on_async_update,
MessageEndpoints.device_async_readback(scan_id, self.config.signals.y.name),
from_start=True,
)
def on_async_update(self, content: dict, metadata: dict):
"""
Update the curve with the latest async readback data.
"""
async_update = metadata.get("async_update")
if not async_update:
return
signals = content.get("signals")
if not signals:
return
y_data = signals.get(self.config.signals.y.name, {}).get("value", [])
if y_data is None:
return
if async_update == "extend":
self._async_info["data"].extend(y_data)
elif async_update == "replace":
self._async_info["data"] = y_data
else:
print(f"Warning: Unsupported async update type {async_update}.")
self.setData(self._async_info["data"])
def set(self, **kwargs):
"""
Set the properties of the curve.

View File

@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "bec_widgets"
version = "0.80.0"
version = "0.81.0"
description = "BEC Widgets"
requires-python = ">=3.10"
classifiers = [