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

fix: renamed spiral progress bar to ring progress bar; closes #235

This commit is contained in:
2024-06-24 16:30:32 +02:00
parent 4348ed1bb2
commit e5c0087c9a
14 changed files with 406 additions and 398 deletions

View File

@ -17,8 +17,8 @@ class Widgets(str, enum.Enum):
BECDock = "BECDock"
BECDockArea = "BECDockArea"
BECFigure = "BECFigure"
RingProgressBar = "RingProgressBar"
ScanControl = "ScanControl"
SpiralProgressBar = "SpiralProgressBar"
TextBox = "TextBox"
VSCodeEditor = "VSCodeEditor"
WebsiteWidget = "WebsiteWidget"
@ -1824,25 +1824,7 @@ class Ring(RPCBase):
"""
class ScanControl(RPCBase):
@property
@rpc_call
def config_dict(self) -> "dict":
"""
Get the configuration of the widget.
Returns:
dict: The configuration of the widget.
"""
@rpc_call
def get_all_rpc(self) -> "dict":
"""
Get all registered RPC objects.
"""
class SpiralProgressBar(RPCBase):
class RingProgressBar(RPCBase):
@rpc_call
def get_all_rpc(self) -> "dict":
"""
@ -1874,7 +1856,7 @@ class SpiralProgressBar(RPCBase):
"""
@rpc_call
def update_config(self, config: "SpiralProgressBarConfig | dict"):
def update_config(self, config: "RingProgressBarConfig | dict"):
"""
Update the configuration of the widget.
@ -2021,6 +2003,24 @@ class SpiralProgressBar(RPCBase):
"""
class ScanControl(RPCBase):
@property
@rpc_call
def config_dict(self) -> "dict":
"""
Get the configuration of the widget.
Returns:
dict: The configuration of the widget.
"""
@rpc_call
def get_all_rpc(self) -> "dict":
"""
Get all registered RPC objects.
"""
class StopButton(RPCBase):
@property
@rpc_call

View File

@ -116,7 +116,7 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
self.d2 = self.dock.add_dock(name="dock_2", position="bottom")
self.fig2 = self.d2.add_widget("BECFigure", row=0, col=0)
self.fig2.plot(x_name="samx", y_name="bpm4i")
self.bar = self.d2.add_widget("SpiralProgressBar", row=0, col=1)
self.bar = self.d2.add_widget("RingProgressBar", row=0, col=1)
self.bar.set_diameter(200)
self.dock.save_state()

View File

@ -1,5 +1 @@
# from .buttons import StopButton
# from .dock import BECDock, BECDockArea
# from .figure import BECFigure, FigureConfig
# from .scan_control import ScanControl
# from .spiral_progress_bar import SpiralProgressBar

View File

@ -0,0 +1 @@
from .ring_progress_bar import RingProgressBar

View File

@ -10,12 +10,13 @@ from qtpy import QtGui
from bec_widgets.utils import BECConnector, ConnectionConfig
class RingConnections(BaseModel):
class ProgressbarConnections(BaseModel):
slot: Literal["on_scan_progress", "on_device_readback"] = None
endpoint: EndpointInfo | str = None
model_config: dict = {"validate_assignment": True}
@field_validator("endpoint")
@classmethod
def validate_endpoint(cls, v, values):
slot = values.data["slot"]
v = v.endpoint if isinstance(v, EndpointInfo) else v
@ -36,7 +37,7 @@ class RingConnections(BaseModel):
return v
class RingConfig(ConnectionConfig):
class ProgressbarConfig(ConnectionConfig):
value: int | float | None = Field(0, description="Value for the progress bars.")
direction: int | None = Field(
-1, description="Direction of the progress bars. -1 for clockwise, 1 for counter-clockwise."
@ -62,8 +63,17 @@ class RingConfig(ConnectionConfig):
update_behaviour: Literal["manual", "auto"] | None = Field(
"auto", description="Update behaviour for the progress bars."
)
connections: RingConnections | None = Field(
default_factory=RingConnections, description="Connections for the progress bars."
connections: ProgressbarConnections | None = Field(
default_factory=ProgressbarConnections, description="Connections for the progress bars."
)
class RingConfig(ProgressbarConfig):
index: int | None = Field(0, description="Index of the progress bar. 0 is outer ring.")
start_position: int | None = Field(
90,
description="Start position for the progress bars in degrees. Default is 90 degrees - corespons to "
"the top of the ring.",
)
@ -230,7 +240,7 @@ class Ring(BECConnector):
self.bec_dispatcher.disconnect_slot(
self.config.connections.slot, self.config.connections.endpoint
)
self.config.connections = RingConnections(slot=slot, endpoint=endpoint)
self.config.connections = ProgressbarConnections(slot=slot, endpoint=endpoint)
self.bec_dispatcher.connect_slot(getattr(self, slot), endpoint)
def reset_connection(self):
@ -240,7 +250,7 @@ class Ring(BECConnector):
self.bec_dispatcher.disconnect_slot(
self.config.connections.slot, self.config.connections.endpoint
)
self.config.connections = RingConnections()
self.config.connections = ProgressbarConnections()
def on_scan_progress(self, msg, meta):
"""

View File

@ -11,10 +11,10 @@ from qtpy.QtCore import QSize, Slot
from qtpy.QtWidgets import QSizePolicy, QWidget
from bec_widgets.utils import BECConnector, Colors, ConnectionConfig, EntryValidator
from bec_widgets.widgets.spiral_progress_bar.ring import Ring, RingConfig
from bec_widgets.widgets.ring_progress_bar.ring import Ring, RingConfig
class SpiralProgressBarConfig(ConnectionConfig):
class RingProgressBarConfig(ConnectionConfig):
color_map: Optional[str] = Field(
"magma", description="Color scheme for the progress bars.", validate_default=True
)
@ -32,6 +32,7 @@ class SpiralProgressBarConfig(ConnectionConfig):
rings: list[RingConfig] | None = Field([], description="List of ring configurations.")
@field_validator("num_bars")
@classmethod
def validate_num_bars(cls, v, values):
min_number_of_bars = values.data.get("min_number_of_bars", None)
max_number_of_bars = values.data.get("max_number_of_bars", None)
@ -43,6 +44,7 @@ class SpiralProgressBarConfig(ConnectionConfig):
return v
@field_validator("rings")
@classmethod
def validate_rings(cls, v, values):
if v is not None and v is not []:
num_bars = values.data.get("num_bars", None)
@ -64,7 +66,7 @@ class SpiralProgressBarConfig(ConnectionConfig):
_validate_colormap = field_validator("color_map")(Colors.validate_color_map)
class SpiralProgressBar(BECConnector, QWidget):
class RingProgressBar(BECConnector, QWidget):
USER_ACCESS = [
"get_all_rpc",
"rpc_id",
@ -89,17 +91,17 @@ class SpiralProgressBar(BECConnector, QWidget):
def __init__(
self,
parent=None,
config: SpiralProgressBarConfig | dict | None = None,
config: RingProgressBarConfig | dict | None = None,
client=None,
gui_id: str | None = None,
num_bars: int | None = None,
):
if config is None:
config = SpiralProgressBarConfig(widget_class=self.__class__.__name__)
config = RingProgressBarConfig(widget_class=self.__class__.__name__)
self.config = config
else:
if isinstance(config, dict):
config = SpiralProgressBarConfig(**config, widget_class=self.__class__.__name__)
config = RingProgressBarConfig(**config, widget_class=self.__class__.__name__)
self.config = config
super().__init__(client=client, config=config, gui_id=gui_id)
QWidget.__init__(self, parent=None)
@ -129,7 +131,7 @@ class SpiralProgressBar(BECConnector, QWidget):
def rings(self, value):
self._rings = value
def update_config(self, config: SpiralProgressBarConfig | dict):
def update_config(self, config: RingProgressBarConfig | dict):
"""
Update the configuration of the widget.
@ -137,7 +139,7 @@ class SpiralProgressBar(BECConnector, QWidget):
config(SpiralProgressBarConfig|dict): Configuration to update.
"""
if isinstance(config, dict):
config = SpiralProgressBarConfig(**config, widget_class=self.__class__.__name__)
config = RingProgressBarConfig(**config, widget_class=self.__class__.__name__)
self.config = config
self.clear_all()

View File

@ -1 +0,0 @@
from .spiral_progress_bar import SpiralProgressBar

View File

@ -97,11 +97,11 @@ Note, we chain commands here which is possible since the `add_dock` and `add_wid
cam_widget.set_title("Camera Image Eiger")
cam_widget.set_vrange(vmin=0, vmax=100)
```
As a final step, we can now add also a SpiralProgressBar to a new dock, and perform a grid_scan with the motors *samx* and *samy*.
As a final step, we can now add also a RingProgressBar to a new dock, and perform a grid_scan with the motors *samx* and *samy*.
As you see in the example below, all docks are arranged below each other. This is the default behavior of the `add_dock` method. However, the docks can be freely arranged by drag and drop as desired by the user. We invite you to explore this by yourself following the example in the video, and build your custom GUI with BEC Widgets.
```python
prog_bar = gui.add_dock(name="prog_dock").add_widget('SpiralProgressBar')
prog_bar = gui.add_dock(name="prog_dock").add_widget('RingProgressBar')
prog_bar.set_line_widths(15)
scans.grid_scan(dev.samy, -2, 2, 10, dev.samx, -5, 5, 10, exp_time=0.1, relative=False)
```

View File

@ -1,9 +1,9 @@
(user.widgets.spiral_progress_bar)=
# [Spiral Progress Bar](/api_reference/_autosummary/bec_widgets.cli.client.SpiralProgressBar)
# [Ring Progress Bar](/api_reference/_autosummary/bec_widgets.cli.client.RingProgressBar)
**Purpose:**
The Spiral Progress Bar widget is a circular progress bar that can be used to visualize the progress of a task. The
The ring Progress Bar widget is a circular progress bar that can be used to visualize the progress of a task. The
widget is designed to be used in applications where the progress of a task is represented as a percentage. The Spiral
Progress Bar widget is a part of the BEC Widgets library and can be controlled directly using its API, or hooked up to
the progress of a device readback or scan.
@ -15,22 +15,22 @@ the progress of a device readback or scan.
- multiple progress rings to show different tasks in parallel.
**Example of Use:**
![SpiralProgressBar](./progress_bar.gif)
![RingProgressBar](./progress_bar.gif)
**Code example:**
The following code snipped demonstrates how to create a `SpiralProgressBar` using BEC Widgets within BEC.
The following code snipped demonstrates how to create a `RingProgressBar` using BEC Widgets within BEC.
```python
# adds a new dock with a spiral progress bar
progress = gui.add_dock().add_widget("SpiralProgressBar")
# adds a new dock with a ring progress bar
progress = gui.add_dock().add_widget("RingProgressBar")
# customize the size of the ring
progress.set_line_width(20)
```
By default, the Spiral Progress Bar widget will display a single ring. To add more rings, use the add_ring method:
By default, the Ring Progress Bar widget will display a single ring. To add more rings, use the add_ring method:
```python
# adds a new dock with a spiral progress bar
# adds a new dock with a ring progress bar
progress.add_ring()
```
@ -42,7 +42,7 @@ progress.rings[0].set_line_width(20) # set the width of the first ring
progress.rings[1].set_line_width(10) # set the width of the second ring
```
By default, the `SpiralProgressBar` widget is set with `progress.enable_auto_update(True)`, which will automatically
By default, the `RingProgressBar` widget is set with `progress.enable_auto_update(True)`, which will automatically
update the bars in the widget. To manually set updates for each progress bar, use the set_update method. Note that
manually updating a ring will disable the automatic update for the whole widget:

View File

@ -9,7 +9,7 @@ hidden: false
---
bec_figure/
spiral_progress_bar/
ring_progress_bar/
website/
buttons/
text_box/

View File

@ -151,13 +151,13 @@ def test_dock_manipulations_e2e(rpc_server_dock):
assert len(dock.temp_areas) == 0
def test_spiral_bar(rpc_server_dock):
def test_ring_bar(rpc_server_dock):
dock = BECDockArea(rpc_server_dock)
d0 = dock.add_dock(name="dock_0")
bar = d0.add_widget("SpiralProgressBar")
assert bar.__class__.__name__ == "SpiralProgressBar"
bar = d0.add_widget("RingProgressBar")
assert bar.__class__.__name__ == "RingProgressBar"
bar.set_number_of_bars(5)
bar.set_colors_from_map("viridis")
@ -173,12 +173,12 @@ def test_spiral_bar(rpc_server_dock):
assert bar_colors == expected_colors
def test_spiral_bar_scan_update(bec_client_lib, rpc_server_dock):
def test_ring_bar_scan_update(bec_client_lib, rpc_server_dock):
dock = BECDockArea(rpc_server_dock)
d0 = dock.add_dock("dock_0")
bar = d0.add_widget("SpiralProgressBar")
bar = d0.add_widget("RingProgressBar")
client = bec_client_lib
dev = client.device_manager.devices

View File

@ -0,0 +1,338 @@
# pylint: disable=missing-function-docstring, missing-module-docstring, unused-import
import pytest
from bec_lib.endpoints import MessageEndpoints
from pydantic import ValidationError
from bec_widgets.utils import Colors
from bec_widgets.widgets.ring_progress_bar import RingProgressBar
from bec_widgets.widgets.ring_progress_bar.ring import ProgressbarConnections, RingConfig
from bec_widgets.widgets.ring_progress_bar.ring_progress_bar import RingProgressBarConfig
from .client_mocks import mocked_client
@pytest.fixture
def ring_progress_bar(qtbot, mocked_client):
widget = RingProgressBar(client=mocked_client)
qtbot.addWidget(widget)
qtbot.waitExposed(widget)
yield widget
widget.close()
def test_bar_init(ring_progress_bar):
assert ring_progress_bar is not None
assert ring_progress_bar.client is not None
assert isinstance(ring_progress_bar, RingProgressBar)
assert ring_progress_bar.config.widget_class == "RingProgressBar"
assert ring_progress_bar.config.gui_id is not None
assert ring_progress_bar.gui_id == ring_progress_bar.config.gui_id
def test_config_validation_num_of_bars():
config = RingProgressBarConfig(num_bars=100, min_num_bars=1, max_num_bars=10)
assert config.num_bars == 10
def test_config_validation_num_of_ring_error():
ring_config_0 = RingConfig(index=0)
ring_config_1 = RingConfig(index=1)
with pytest.raises(ValidationError) as excinfo:
RingProgressBarConfig(rings=[ring_config_0, ring_config_1], num_bars=1)
errors = excinfo.value.errors()
assert len(errors) == 1
assert errors[0]["type"] == "different number of configs"
assert "Length of rings configuration (2) does not match the number of bars (1)." in str(
excinfo.value
)
def test_config_validation_ring_indices_wrong_order():
ring_config_0 = RingConfig(index=2)
ring_config_1 = RingConfig(index=5)
with pytest.raises(ValidationError) as excinfo:
RingProgressBarConfig(rings=[ring_config_0, ring_config_1], num_bars=2)
errors = excinfo.value.errors()
assert len(errors) == 1
assert errors[0]["type"] == "wrong indices"
assert (
"Indices of ring configurations must be unique and in order from 0 to num_bars 2."
in str(excinfo.value)
)
def test_config_validation_ring_same_indices():
ring_config_0 = RingConfig(index=0)
ring_config_1 = RingConfig(index=0)
with pytest.raises(ValidationError) as excinfo:
RingProgressBarConfig(rings=[ring_config_0, ring_config_1], num_bars=2)
errors = excinfo.value.errors()
assert len(errors) == 1
assert errors[0]["type"] == "wrong indices"
assert (
"Indices of ring configurations must be unique and in order from 0 to num_bars 2."
in str(excinfo.value)
)
def test_config_validation_invalid_colormap():
with pytest.raises(ValueError) as excinfo:
RingProgressBarConfig(color_map="crazy_colors")
errors = excinfo.value.errors()
assert len(errors) == 1
assert errors[0]["type"] == "unsupported colormap"
assert "Colormap 'crazy_colors' not found in the current installation of pyqtgraph" in str(
excinfo.value
)
def test_ring_connection_endpoint_validation():
with pytest.raises(ValueError) as excinfo:
ProgressbarConnections(slot="on_scan_progress", endpoint="non_existing")
errors = excinfo.value.errors()
assert len(errors) == 1
assert errors[0]["type"] == "unsupported endpoint"
assert (
"For slot 'on_scan_progress', endpoint must be MessageEndpoint.scan_progress or 'scans/scan_progress'."
in str(excinfo.value)
)
with pytest.raises(ValueError) as excinfo:
ProgressbarConnections(slot="on_device_readback", endpoint="non_existing")
errors = excinfo.value.errors()
assert len(errors) == 1
assert errors[0]["type"] == "unsupported endpoint"
assert (
"For slot 'on_device_readback', endpoint must be MessageEndpoint.device_readback(device) or 'internal/devices/readback/{device}'."
in str(excinfo.value)
)
def test_bar_add_number_of_bars(ring_progress_bar):
assert ring_progress_bar.config.num_bars == 1
ring_progress_bar.set_number_of_bars(5)
assert ring_progress_bar.config.num_bars == 5
ring_progress_bar.set_number_of_bars(2)
assert ring_progress_bar.config.num_bars == 2
def test_add_remove_bars_individually(ring_progress_bar):
ring_progress_bar.add_ring()
ring_progress_bar.add_ring()
assert ring_progress_bar.config.num_bars == 3
assert len(ring_progress_bar.config.rings) == 3
ring_progress_bar.remove_ring(1)
assert ring_progress_bar.config.num_bars == 2
assert len(ring_progress_bar.config.rings) == 2
assert ring_progress_bar.rings[0].config.index == 0
assert ring_progress_bar.rings[1].config.index == 1
def test_bar_set_value(ring_progress_bar):
ring_progress_bar.set_number_of_bars(5)
assert ring_progress_bar.config.num_bars == 5
assert len(ring_progress_bar.config.rings) == 5
assert len(ring_progress_bar.rings) == 5
ring_progress_bar.set_value([10, 20, 30, 40, 50])
ring_values = [ring.config.value for ring in ring_progress_bar.rings]
assert ring_values == [10, 20, 30, 40, 50]
# update just one bar
ring_progress_bar.set_value(90, 1)
ring_values = [ring.config.value for ring in ring_progress_bar.rings]
assert ring_values == [10, 90, 30, 40, 50]
def test_bar_set_precision(ring_progress_bar):
ring_progress_bar.set_number_of_bars(3)
assert ring_progress_bar.config.num_bars == 3
assert len(ring_progress_bar.config.rings) == 3
assert len(ring_progress_bar.rings) == 3
ring_progress_bar.set_precision(2)
ring_precision = [ring.config.precision for ring in ring_progress_bar.rings]
assert ring_precision == [2, 2, 2]
ring_progress_bar.set_value([10.1234, 20.1234, 30.1234])
ring_values = [ring.config.value for ring in ring_progress_bar.rings]
assert ring_values == [10.12, 20.12, 30.12]
ring_progress_bar.set_precision(4, 1)
ring_precision = [ring.config.precision for ring in ring_progress_bar.rings]
assert ring_precision == [2, 4, 2]
ring_progress_bar.set_value([10.1234, 20.1234, 30.1234])
ring_values = [ring.config.value for ring in ring_progress_bar.rings]
assert ring_values == [10.12, 20.1234, 30.12]
def test_set_min_max_value(ring_progress_bar):
ring_progress_bar.set_number_of_bars(2)
ring_progress_bar.set_min_max_values(0, 10)
ring_min_values = [ring.config.min_value for ring in ring_progress_bar.rings]
ring_max_values = [ring.config.max_value for ring in ring_progress_bar.rings]
assert ring_min_values == [0, 0]
assert ring_max_values == [10, 10]
ring_progress_bar.set_value([5, 15])
ring_values = [ring.config.value for ring in ring_progress_bar.rings]
assert ring_values == [5, 10]
def test_setup_colors_from_colormap(ring_progress_bar):
ring_progress_bar.set_number_of_bars(5)
ring_progress_bar.set_colors_from_map("viridis", "RGB")
expected_colors = Colors.golden_angle_color("viridis", 5, "RGB")
converted_colors = [ring.color.getRgb() for ring in ring_progress_bar.rings]
ring_config_colors = [ring.config.color for ring in ring_progress_bar.rings]
assert expected_colors == converted_colors
assert ring_config_colors == expected_colors
def get_colors_from_rings(rings):
converted_colors = [ring.color.getRgb() for ring in rings]
ring_config_colors = [ring.config.color for ring in rings]
return converted_colors, ring_config_colors
def test_set_colors_from_colormap_and_change_num_of_bars(ring_progress_bar):
ring_progress_bar.set_number_of_bars(2)
ring_progress_bar.set_colors_from_map("viridis", "RGB")
expected_colors = Colors.golden_angle_color("viridis", 2, "RGB")
converted_colors, ring_config_colors = get_colors_from_rings(ring_progress_bar.rings)
assert expected_colors == converted_colors
assert ring_config_colors == expected_colors
# increase the number of bars to 6
ring_progress_bar.set_number_of_bars(6)
expected_colors = Colors.golden_angle_color("viridis", 6, "RGB")
converted_colors, ring_config_colors = get_colors_from_rings(ring_progress_bar.rings)
assert expected_colors == converted_colors
assert ring_config_colors == expected_colors
# decrease the number of bars to 3
ring_progress_bar.set_number_of_bars(3)
expected_colors = Colors.golden_angle_color("viridis", 3, "RGB")
converted_colors, ring_config_colors = get_colors_from_rings(ring_progress_bar.rings)
assert expected_colors == converted_colors
assert ring_config_colors == expected_colors
def test_set_colors_directly(ring_progress_bar):
ring_progress_bar.set_number_of_bars(3)
# setting as a list of rgb tuples
colors = [(255, 0, 0, 255), (0, 255, 0, 255), (0, 0, 255, 255)]
ring_progress_bar.set_colors_directly(colors)
converted_colors = get_colors_from_rings(ring_progress_bar.rings)[0]
assert colors == converted_colors
ring_progress_bar.set_colors_directly((255, 0, 0, 255), 1)
converted_colors = get_colors_from_rings(ring_progress_bar.rings)[0]
assert converted_colors == [(255, 0, 0, 255), (255, 0, 0, 255), (0, 0, 255, 255)]
def test_set_line_width(ring_progress_bar):
ring_progress_bar.set_number_of_bars(3)
ring_progress_bar.set_line_widths(5)
line_widths = [ring.config.line_width for ring in ring_progress_bar.rings]
assert line_widths == [5, 5, 5]
ring_progress_bar.set_line_widths([10, 20, 30])
line_widths = [ring.config.line_width for ring in ring_progress_bar.rings]
assert line_widths == [10, 20, 30]
ring_progress_bar.set_line_widths(15, 1)
line_widths = [ring.config.line_width for ring in ring_progress_bar.rings]
assert line_widths == [10, 15, 30]
def test_set_gap(ring_progress_bar):
ring_progress_bar.set_number_of_bars(3)
ring_progress_bar.set_gap(20)
assert ring_progress_bar.config.gap == 20
def test_auto_update(ring_progress_bar):
ring_progress_bar.enable_auto_updates(True)
scan_queue_status_scan_progress = {
"queue": {
"primary": {
"info": [{"active_request_block": {"report_instructions": [{"scan_progress": 10}]}}]
}
}
}
meta = {}
ring_progress_bar.on_scan_queue_status(scan_queue_status_scan_progress, meta)
assert ring_progress_bar._auto_updates is True
assert len(ring_progress_bar._rings) == 1
assert ring_progress_bar._rings[0].config.connections == ProgressbarConnections(
slot="on_scan_progress", endpoint=MessageEndpoints.scan_progress()
)
scan_queue_status_device_readback = {
"queue": {
"primary": {
"info": [
{
"active_request_block": {
"report_instructions": [
{
"readback": {
"devices": ["samx", "samy"],
"start": [1, 2],
"end": [10, 20],
}
}
]
}
}
]
}
}
}
ring_progress_bar.on_scan_queue_status(scan_queue_status_device_readback, meta)
assert ring_progress_bar._auto_updates is True
assert len(ring_progress_bar._rings) == 2
assert ring_progress_bar._rings[0].config.connections == ProgressbarConnections(
slot="on_device_readback", endpoint=MessageEndpoints.device_readback("samx")
)
assert ring_progress_bar._rings[1].config.connections == ProgressbarConnections(
slot="on_device_readback", endpoint=MessageEndpoints.device_readback("samy")
)
assert ring_progress_bar._rings[0].config.min_value == 1
assert ring_progress_bar._rings[0].config.max_value == 10
assert ring_progress_bar._rings[1].config.min_value == 2
assert ring_progress_bar._rings[1].config.max_value == 20

View File

@ -4,4 +4,4 @@ from bec_widgets.cli.rpc_wigdet_handler import RPCWidgetHandler
def test_rpc_widget_handler():
handler = RPCWidgetHandler()
assert "BECFigure" in handler.widget_classes
assert "SpiralProgressBar" in handler.widget_classes
assert "RingProgressBar" in handler.widget_classes

View File

@ -1,338 +0,0 @@
# pylint: disable=missing-function-docstring, missing-module-docstring, unused-import
import pytest
from bec_lib.endpoints import MessageEndpoints
from pydantic import ValidationError
from bec_widgets.utils import Colors
from bec_widgets.widgets.spiral_progress_bar import SpiralProgressBar
from bec_widgets.widgets.spiral_progress_bar.ring import RingConfig, RingConnections
from bec_widgets.widgets.spiral_progress_bar.spiral_progress_bar import SpiralProgressBarConfig
from .client_mocks import mocked_client
@pytest.fixture
def spiral_progress_bar(qtbot, mocked_client):
widget = SpiralProgressBar(client=mocked_client)
qtbot.addWidget(widget)
qtbot.waitExposed(widget)
yield widget
widget.close()
def test_bar_init(spiral_progress_bar):
assert spiral_progress_bar is not None
assert spiral_progress_bar.client is not None
assert isinstance(spiral_progress_bar, SpiralProgressBar)
assert spiral_progress_bar.config.widget_class == "SpiralProgressBar"
assert spiral_progress_bar.config.gui_id is not None
assert spiral_progress_bar.gui_id == spiral_progress_bar.config.gui_id
def test_config_validation_num_of_bars():
config = SpiralProgressBarConfig(num_bars=100, min_num_bars=1, max_num_bars=10)
assert config.num_bars == 10
def test_config_validation_num_of_ring_error():
ring_config_0 = RingConfig(index=0)
ring_config_1 = RingConfig(index=1)
with pytest.raises(ValidationError) as excinfo:
SpiralProgressBarConfig(rings=[ring_config_0, ring_config_1], num_bars=1)
errors = excinfo.value.errors()
assert len(errors) == 1
assert errors[0]["type"] == "different number of configs"
assert "Length of rings configuration (2) does not match the number of bars (1)." in str(
excinfo.value
)
def test_config_validation_ring_indices_wrong_order():
ring_config_0 = RingConfig(index=2)
ring_config_1 = RingConfig(index=5)
with pytest.raises(ValidationError) as excinfo:
SpiralProgressBarConfig(rings=[ring_config_0, ring_config_1], num_bars=2)
errors = excinfo.value.errors()
assert len(errors) == 1
assert errors[0]["type"] == "wrong indices"
assert (
"Indices of ring configurations must be unique and in order from 0 to num_bars 2."
in str(excinfo.value)
)
def test_config_validation_ring_same_indices():
ring_config_0 = RingConfig(index=0)
ring_config_1 = RingConfig(index=0)
with pytest.raises(ValidationError) as excinfo:
SpiralProgressBarConfig(rings=[ring_config_0, ring_config_1], num_bars=2)
errors = excinfo.value.errors()
assert len(errors) == 1
assert errors[0]["type"] == "wrong indices"
assert (
"Indices of ring configurations must be unique and in order from 0 to num_bars 2."
in str(excinfo.value)
)
def test_config_validation_invalid_colormap():
with pytest.raises(ValueError) as excinfo:
SpiralProgressBarConfig(color_map="crazy_colors")
errors = excinfo.value.errors()
assert len(errors) == 1
assert errors[0]["type"] == "unsupported colormap"
assert "Colormap 'crazy_colors' not found in the current installation of pyqtgraph" in str(
excinfo.value
)
def test_ring_connection_endpoint_validation():
with pytest.raises(ValueError) as excinfo:
RingConnections(slot="on_scan_progress", endpoint="non_existing")
errors = excinfo.value.errors()
assert len(errors) == 1
assert errors[0]["type"] == "unsupported endpoint"
assert (
"For slot 'on_scan_progress', endpoint must be MessageEndpoint.scan_progress or 'scans/scan_progress'."
in str(excinfo.value)
)
with pytest.raises(ValueError) as excinfo:
RingConnections(slot="on_device_readback", endpoint="non_existing")
errors = excinfo.value.errors()
assert len(errors) == 1
assert errors[0]["type"] == "unsupported endpoint"
assert (
"For slot 'on_device_readback', endpoint must be MessageEndpoint.device_readback(device) or 'internal/devices/readback/{device}'."
in str(excinfo.value)
)
def test_bar_add_number_of_bars(spiral_progress_bar):
assert spiral_progress_bar.config.num_bars == 1
spiral_progress_bar.set_number_of_bars(5)
assert spiral_progress_bar.config.num_bars == 5
spiral_progress_bar.set_number_of_bars(2)
assert spiral_progress_bar.config.num_bars == 2
def test_add_remove_bars_individually(spiral_progress_bar):
spiral_progress_bar.add_ring()
spiral_progress_bar.add_ring()
assert spiral_progress_bar.config.num_bars == 3
assert len(spiral_progress_bar.config.rings) == 3
spiral_progress_bar.remove_ring(1)
assert spiral_progress_bar.config.num_bars == 2
assert len(spiral_progress_bar.config.rings) == 2
assert spiral_progress_bar.rings[0].config.index == 0
assert spiral_progress_bar.rings[1].config.index == 1
def test_bar_set_value(spiral_progress_bar):
spiral_progress_bar.set_number_of_bars(5)
assert spiral_progress_bar.config.num_bars == 5
assert len(spiral_progress_bar.config.rings) == 5
assert len(spiral_progress_bar.rings) == 5
spiral_progress_bar.set_value([10, 20, 30, 40, 50])
ring_values = [ring.config.value for ring in spiral_progress_bar.rings]
assert ring_values == [10, 20, 30, 40, 50]
# update just one bar
spiral_progress_bar.set_value(90, 1)
ring_values = [ring.config.value for ring in spiral_progress_bar.rings]
assert ring_values == [10, 90, 30, 40, 50]
def test_bar_set_precision(spiral_progress_bar):
spiral_progress_bar.set_number_of_bars(3)
assert spiral_progress_bar.config.num_bars == 3
assert len(spiral_progress_bar.config.rings) == 3
assert len(spiral_progress_bar.rings) == 3
spiral_progress_bar.set_precision(2)
ring_precision = [ring.config.precision for ring in spiral_progress_bar.rings]
assert ring_precision == [2, 2, 2]
spiral_progress_bar.set_value([10.1234, 20.1234, 30.1234])
ring_values = [ring.config.value for ring in spiral_progress_bar.rings]
assert ring_values == [10.12, 20.12, 30.12]
spiral_progress_bar.set_precision(4, 1)
ring_precision = [ring.config.precision for ring in spiral_progress_bar.rings]
assert ring_precision == [2, 4, 2]
spiral_progress_bar.set_value([10.1234, 20.1234, 30.1234])
ring_values = [ring.config.value for ring in spiral_progress_bar.rings]
assert ring_values == [10.12, 20.1234, 30.12]
def test_set_min_max_value(spiral_progress_bar):
spiral_progress_bar.set_number_of_bars(2)
spiral_progress_bar.set_min_max_values(0, 10)
ring_min_values = [ring.config.min_value for ring in spiral_progress_bar.rings]
ring_max_values = [ring.config.max_value for ring in spiral_progress_bar.rings]
assert ring_min_values == [0, 0]
assert ring_max_values == [10, 10]
spiral_progress_bar.set_value([5, 15])
ring_values = [ring.config.value for ring in spiral_progress_bar.rings]
assert ring_values == [5, 10]
def test_setup_colors_from_colormap(spiral_progress_bar):
spiral_progress_bar.set_number_of_bars(5)
spiral_progress_bar.set_colors_from_map("viridis", "RGB")
expected_colors = Colors.golden_angle_color("viridis", 5, "RGB")
converted_colors = [ring.color.getRgb() for ring in spiral_progress_bar.rings]
ring_config_colors = [ring.config.color for ring in spiral_progress_bar.rings]
assert expected_colors == converted_colors
assert ring_config_colors == expected_colors
def get_colors_from_rings(rings):
converted_colors = [ring.color.getRgb() for ring in rings]
ring_config_colors = [ring.config.color for ring in rings]
return converted_colors, ring_config_colors
def test_set_colors_from_colormap_and_change_num_of_bars(spiral_progress_bar):
spiral_progress_bar.set_number_of_bars(2)
spiral_progress_bar.set_colors_from_map("viridis", "RGB")
expected_colors = Colors.golden_angle_color("viridis", 2, "RGB")
converted_colors, ring_config_colors = get_colors_from_rings(spiral_progress_bar.rings)
assert expected_colors == converted_colors
assert ring_config_colors == expected_colors
# increase the number of bars to 6
spiral_progress_bar.set_number_of_bars(6)
expected_colors = Colors.golden_angle_color("viridis", 6, "RGB")
converted_colors, ring_config_colors = get_colors_from_rings(spiral_progress_bar.rings)
assert expected_colors == converted_colors
assert ring_config_colors == expected_colors
# decrease the number of bars to 3
spiral_progress_bar.set_number_of_bars(3)
expected_colors = Colors.golden_angle_color("viridis", 3, "RGB")
converted_colors, ring_config_colors = get_colors_from_rings(spiral_progress_bar.rings)
assert expected_colors == converted_colors
assert ring_config_colors == expected_colors
def test_set_colors_directly(spiral_progress_bar):
spiral_progress_bar.set_number_of_bars(3)
# setting as a list of rgb tuples
colors = [(255, 0, 0, 255), (0, 255, 0, 255), (0, 0, 255, 255)]
spiral_progress_bar.set_colors_directly(colors)
converted_colors = get_colors_from_rings(spiral_progress_bar.rings)[0]
assert colors == converted_colors
spiral_progress_bar.set_colors_directly((255, 0, 0, 255), 1)
converted_colors = get_colors_from_rings(spiral_progress_bar.rings)[0]
assert converted_colors == [(255, 0, 0, 255), (255, 0, 0, 255), (0, 0, 255, 255)]
def test_set_line_width(spiral_progress_bar):
spiral_progress_bar.set_number_of_bars(3)
spiral_progress_bar.set_line_widths(5)
line_widths = [ring.config.line_width for ring in spiral_progress_bar.rings]
assert line_widths == [5, 5, 5]
spiral_progress_bar.set_line_widths([10, 20, 30])
line_widths = [ring.config.line_width for ring in spiral_progress_bar.rings]
assert line_widths == [10, 20, 30]
spiral_progress_bar.set_line_widths(15, 1)
line_widths = [ring.config.line_width for ring in spiral_progress_bar.rings]
assert line_widths == [10, 15, 30]
def test_set_gap(spiral_progress_bar):
spiral_progress_bar.set_number_of_bars(3)
spiral_progress_bar.set_gap(20)
assert spiral_progress_bar.config.gap == 20
def test_auto_update(spiral_progress_bar):
spiral_progress_bar.enable_auto_updates(True)
scan_queue_status_scan_progress = {
"queue": {
"primary": {
"info": [{"active_request_block": {"report_instructions": [{"scan_progress": 10}]}}]
}
}
}
meta = {}
spiral_progress_bar.on_scan_queue_status(scan_queue_status_scan_progress, meta)
assert spiral_progress_bar._auto_updates is True
assert len(spiral_progress_bar._rings) == 1
assert spiral_progress_bar._rings[0].config.connections == RingConnections(
slot="on_scan_progress", endpoint=MessageEndpoints.scan_progress()
)
scan_queue_status_device_readback = {
"queue": {
"primary": {
"info": [
{
"active_request_block": {
"report_instructions": [
{
"readback": {
"devices": ["samx", "samy"],
"start": [1, 2],
"end": [10, 20],
}
}
]
}
}
]
}
}
}
spiral_progress_bar.on_scan_queue_status(scan_queue_status_device_readback, meta)
assert spiral_progress_bar._auto_updates is True
assert len(spiral_progress_bar._rings) == 2
assert spiral_progress_bar._rings[0].config.connections == RingConnections(
slot="on_device_readback", endpoint=MessageEndpoints.device_readback("samx")
)
assert spiral_progress_bar._rings[1].config.connections == RingConnections(
slot="on_device_readback", endpoint=MessageEndpoints.device_readback("samy")
)
assert spiral_progress_bar._rings[0].config.min_value == 1
assert spiral_progress_bar._rings[0].config.max_value == 10
assert spiral_progress_bar._rings[1].config.min_value == 2
assert spiral_progress_bar._rings[1].config.max_value == 20