bec-widgets 0.49.1__py3-none-any.whl → 0.50.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bec_widgets/cli/client.py +69 -4
- bec_widgets/cli/client_utils.py +18 -11
- bec_widgets/cli/rpc_register.py +76 -0
- bec_widgets/cli/server.py +8 -16
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +5 -0
- bec_widgets/utils/bec_connector.py +21 -1
- bec_widgets/widgets/figure/figure.py +23 -22
- bec_widgets/widgets/plots/image.py +14 -24
- bec_widgets/widgets/plots/motor_map.py +12 -19
- bec_widgets/widgets/plots/plot_base.py +1 -0
- bec_widgets/widgets/plots/waveform.py +18 -14
- {bec_widgets-0.49.1.dist-info → bec_widgets-0.50.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.49.1.dist-info → bec_widgets-0.50.0.dist-info}/RECORD +22 -16
- tests/end-2-end/__init__.py +0 -0
- tests/end-2-end/conftest.py +25 -0
- tests/end-2-end/test_bec_figure_rpc_e2e.py +165 -0
- tests/end-2-end/test_rpc_register_e2e.py +50 -0
- tests/unit_tests/conftest.py +7 -0
- tests/unit_tests/test_rpc_register.py +52 -0
- {bec_widgets-0.49.1.dist-info → bec_widgets-0.50.0.dist-info}/LICENSE +0 -0
- {bec_widgets-0.49.1.dist-info → bec_widgets-0.50.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.49.1.dist-info → bec_widgets-0.50.0.dist-info}/top_level.txt +0 -0
bec_widgets/cli/client.py
CHANGED
@@ -136,6 +136,13 @@ class BECPlotBase(RPCBase):
|
|
136
136
|
|
137
137
|
|
138
138
|
class BECWaveform(RPCBase):
|
139
|
+
@property
|
140
|
+
@rpc_call
|
141
|
+
def rpc_id(self) -> "str":
|
142
|
+
"""
|
143
|
+
Get the RPC ID of the widget.
|
144
|
+
"""
|
145
|
+
|
139
146
|
@property
|
140
147
|
@rpc_call
|
141
148
|
def config_dict(self) -> "dict":
|
@@ -387,6 +394,13 @@ class BECWaveform(RPCBase):
|
|
387
394
|
|
388
395
|
|
389
396
|
class BECFigure(RPCBase, BECFigureClientMixin):
|
397
|
+
@property
|
398
|
+
@rpc_call
|
399
|
+
def rpc_id(self) -> "str":
|
400
|
+
"""
|
401
|
+
Get the RPC ID of the widget.
|
402
|
+
"""
|
403
|
+
|
390
404
|
@property
|
391
405
|
@rpc_call
|
392
406
|
def config_dict(self) -> "dict":
|
@@ -396,13 +410,16 @@ class BECFigure(RPCBase, BECFigureClientMixin):
|
|
396
410
|
dict: The configuration of the widget.
|
397
411
|
"""
|
398
412
|
|
399
|
-
@property
|
400
413
|
@rpc_call
|
401
|
-
def axes(self) -> "
|
414
|
+
def axes(self, row: "int", col: "int") -> "BECPlotBase":
|
402
415
|
"""
|
403
|
-
|
416
|
+
Get widget by its coordinates in the figure.
|
417
|
+
Args:
|
418
|
+
row(int): the row coordinate
|
419
|
+
col(int): the column coordinate
|
420
|
+
|
404
421
|
Returns:
|
405
|
-
|
422
|
+
BECPlotBase: the widget at the given coordinates
|
406
423
|
"""
|
407
424
|
|
408
425
|
@property
|
@@ -614,8 +631,36 @@ class BECFigure(RPCBase, BECFigureClientMixin):
|
|
614
631
|
Clear all widgets from the figure and reset to default state
|
615
632
|
"""
|
616
633
|
|
634
|
+
@rpc_call
|
635
|
+
def get_all_rpc(self) -> "dict":
|
636
|
+
"""
|
637
|
+
Get all registered RPC objects.
|
638
|
+
"""
|
639
|
+
|
640
|
+
@property
|
641
|
+
@rpc_call
|
642
|
+
def widget_list(self) -> "list[BECPlotBase]":
|
643
|
+
"""
|
644
|
+
Access all widget in BECFigure as a list
|
645
|
+
Returns:
|
646
|
+
list[BECPlotBase]: List of all widgets in the figure.
|
647
|
+
"""
|
648
|
+
|
617
649
|
|
618
650
|
class BECCurve(RPCBase):
|
651
|
+
@rpc_call
|
652
|
+
def remove(self):
|
653
|
+
"""
|
654
|
+
Remove the curve from the plot.
|
655
|
+
"""
|
656
|
+
|
657
|
+
@property
|
658
|
+
@rpc_call
|
659
|
+
def rpc_id(self) -> "str":
|
660
|
+
"""
|
661
|
+
Get the RPC ID of the widget.
|
662
|
+
"""
|
663
|
+
|
619
664
|
@property
|
620
665
|
@rpc_call
|
621
666
|
def config_dict(self) -> "dict":
|
@@ -713,6 +758,13 @@ class BECCurve(RPCBase):
|
|
713
758
|
|
714
759
|
|
715
760
|
class BECImageShow(RPCBase):
|
761
|
+
@property
|
762
|
+
@rpc_call
|
763
|
+
def rpc_id(self) -> "str":
|
764
|
+
"""
|
765
|
+
Get the RPC ID of the widget.
|
766
|
+
"""
|
767
|
+
|
716
768
|
@property
|
717
769
|
@rpc_call
|
718
770
|
def config_dict(self) -> "dict":
|
@@ -1045,8 +1097,21 @@ class BECConnector(RPCBase):
|
|
1045
1097
|
dict: The configuration of the widget.
|
1046
1098
|
"""
|
1047
1099
|
|
1100
|
+
@rpc_call
|
1101
|
+
def get_all_rpc(self) -> "dict":
|
1102
|
+
"""
|
1103
|
+
Get all registered RPC objects.
|
1104
|
+
"""
|
1105
|
+
|
1048
1106
|
|
1049
1107
|
class BECImageItem(RPCBase):
|
1108
|
+
@property
|
1109
|
+
@rpc_call
|
1110
|
+
def rpc_id(self) -> "str":
|
1111
|
+
"""
|
1112
|
+
Get the RPC ID of the widget.
|
1113
|
+
"""
|
1114
|
+
|
1050
1115
|
@property
|
1051
1116
|
@rpc_call
|
1052
1117
|
def config_dict(self) -> "dict":
|
bec_widgets/cli/client_utils.py
CHANGED
@@ -157,17 +157,24 @@ class BECFigureClientMixin:
|
|
157
157
|
self.stderr_output.clear()
|
158
158
|
|
159
159
|
def _get_output(self) -> str:
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
160
|
+
try:
|
161
|
+
os.set_blocking(self._process.stdout.fileno(), False)
|
162
|
+
os.set_blocking(self._process.stderr.fileno(), False)
|
163
|
+
while self._process.poll() is None:
|
164
|
+
readylist, _, _ = select.select(
|
165
|
+
[self._process.stdout, self._process.stderr], [], [], 1
|
166
|
+
)
|
167
|
+
if self._process.stdout in readylist:
|
168
|
+
output = self._process.stdout.read(1024)
|
169
|
+
if output:
|
170
|
+
print(output, end="")
|
171
|
+
if self._process.stderr in readylist:
|
172
|
+
error_output = self._process.stderr.read(1024)
|
173
|
+
if error_output:
|
174
|
+
print(error_output, end="", file=sys.stderr)
|
175
|
+
self.stderr_output.append(error_output)
|
176
|
+
except Exception as e:
|
177
|
+
print(f"Error reading process output: {str(e)}")
|
171
178
|
|
172
179
|
|
173
180
|
class RPCResponseTimeoutError(Exception):
|
@@ -0,0 +1,76 @@
|
|
1
|
+
from threading import Lock
|
2
|
+
from weakref import WeakValueDictionary
|
3
|
+
|
4
|
+
from qtpy.QtCore import QObject
|
5
|
+
|
6
|
+
|
7
|
+
class RPCRegister:
|
8
|
+
"""
|
9
|
+
A singleton class that keeps track of all the RPC objects registered in the system for CLI usage.
|
10
|
+
"""
|
11
|
+
|
12
|
+
_instance = None
|
13
|
+
_initialized = False
|
14
|
+
_lock = Lock()
|
15
|
+
|
16
|
+
def __new__(cls, *args, **kwargs):
|
17
|
+
if cls._instance is None:
|
18
|
+
cls._instance = super(RPCRegister, cls).__new__(cls)
|
19
|
+
cls._initialized = False
|
20
|
+
return cls._instance
|
21
|
+
|
22
|
+
def __init__(self):
|
23
|
+
if self._initialized:
|
24
|
+
return
|
25
|
+
self._rpc_register = WeakValueDictionary()
|
26
|
+
self._initialized = True
|
27
|
+
|
28
|
+
def add_rpc(self, rpc: QObject):
|
29
|
+
"""
|
30
|
+
Add an RPC object to the register.
|
31
|
+
Args:
|
32
|
+
rpc(QObject): The RPC object to be added to the register.
|
33
|
+
"""
|
34
|
+
if not hasattr(rpc, "gui_id"):
|
35
|
+
raise ValueError("RPC object must have a 'gui_id' attribute.")
|
36
|
+
self._rpc_register[rpc.gui_id] = rpc
|
37
|
+
|
38
|
+
def remove_rpc(self, rpc: str):
|
39
|
+
"""
|
40
|
+
Remove an RPC object from the register.
|
41
|
+
Args:
|
42
|
+
rpc(str): The RPC object to be removed from the register.
|
43
|
+
"""
|
44
|
+
if not hasattr(rpc, "gui_id"):
|
45
|
+
raise ValueError(f"RPC object {rpc} must have a 'gui_id' attribute.")
|
46
|
+
self._rpc_register.pop(rpc.gui_id, None)
|
47
|
+
|
48
|
+
def get_rpc_by_id(self, gui_id: str) -> QObject:
|
49
|
+
"""
|
50
|
+
Get an RPC object by its ID.
|
51
|
+
Args:
|
52
|
+
gui_id(str): The ID of the RPC object to be retrieved.
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
QObject: The RPC object with the given ID.
|
56
|
+
"""
|
57
|
+
rpc_object = self._rpc_register.get(gui_id, None)
|
58
|
+
return rpc_object
|
59
|
+
|
60
|
+
def list_all_connections(self) -> dict:
|
61
|
+
"""
|
62
|
+
List all the registered RPC objects.
|
63
|
+
Returns:
|
64
|
+
dict: A dictionary containing all the registered RPC objects.
|
65
|
+
"""
|
66
|
+
with self._lock:
|
67
|
+
connections = dict(self._rpc_register)
|
68
|
+
return connections
|
69
|
+
|
70
|
+
@classmethod
|
71
|
+
def reset_singleton(cls):
|
72
|
+
"""
|
73
|
+
Reset the singleton instance.
|
74
|
+
"""
|
75
|
+
cls._instance = None
|
76
|
+
cls._initialized = False
|
bec_widgets/cli/server.py
CHANGED
@@ -5,6 +5,7 @@ import time
|
|
5
5
|
from bec_lib import MessageEndpoints, messages
|
6
6
|
from qtpy.QtCore import QTimer
|
7
7
|
|
8
|
+
from bec_widgets.cli.rpc_register import RPCRegister
|
8
9
|
from bec_widgets.utils import BECDispatcher
|
9
10
|
from bec_widgets.utils.bec_connector import BECConnector
|
10
11
|
from bec_widgets.widgets.figure import BECFigure
|
@@ -20,6 +21,8 @@ class BECWidgetsCLIServer:
|
|
20
21
|
self.client.start()
|
21
22
|
self.gui_id = gui_id
|
22
23
|
self.fig = BECFigure(gui_id=self.gui_id)
|
24
|
+
self.rpc_register = RPCRegister()
|
25
|
+
self.rpc_register.add_rpc(self.fig)
|
23
26
|
|
24
27
|
self.dispatcher.connect_slot(
|
25
28
|
self.on_rpc_update, MessageEndpoints.gui_instructions(self.gui_id)
|
@@ -34,10 +37,10 @@ class BECWidgetsCLIServer:
|
|
34
37
|
def on_rpc_update(self, msg: dict, metadata: dict):
|
35
38
|
request_id = metadata.get("request_id")
|
36
39
|
try:
|
40
|
+
obj = self.get_object_from_config(msg["parameter"])
|
37
41
|
method = msg["action"]
|
38
42
|
args = msg["parameter"].get("args", [])
|
39
43
|
kwargs = msg["parameter"].get("kwargs", {})
|
40
|
-
obj = self.get_object_from_config(msg["parameter"])
|
41
44
|
res = self.run_rpc(obj, method, args, kwargs)
|
42
45
|
except Exception as e:
|
43
46
|
print(e)
|
@@ -54,20 +57,10 @@ class BECWidgetsCLIServer:
|
|
54
57
|
|
55
58
|
def get_object_from_config(self, config: dict):
|
56
59
|
gui_id = config.get("gui_id")
|
57
|
-
|
58
|
-
if
|
59
|
-
|
60
|
-
|
61
|
-
if gui_id in self.fig._widgets:
|
62
|
-
obj = self.fig._widgets[config["gui_id"]]
|
63
|
-
return obj
|
64
|
-
if self.fig._widgets:
|
65
|
-
for widget in self.fig._widgets.values():
|
66
|
-
item = widget.find_widget_by_id(gui_id)
|
67
|
-
if item:
|
68
|
-
return item
|
69
|
-
|
70
|
-
raise ValueError(f"Object with gui_id {gui_id} not found")
|
60
|
+
obj = self.rpc_register.get_rpc_by_id(gui_id)
|
61
|
+
if obj is None:
|
62
|
+
raise ValueError(f"Object with gui_id {gui_id} not found")
|
63
|
+
return obj
|
71
64
|
|
72
65
|
def run_rpc(self, obj, method, args, kwargs):
|
73
66
|
method_obj = getattr(obj, method)
|
@@ -106,7 +99,6 @@ class BECWidgetsCLIServer:
|
|
106
99
|
messages.StatusMessage(name=self.gui_id, status=1, info={}),
|
107
100
|
expire=10,
|
108
101
|
)
|
109
|
-
print("Heartbeat emitted")
|
110
102
|
|
111
103
|
def shutdown(self):
|
112
104
|
self._shutdown_event = True
|
@@ -7,6 +7,7 @@ from qtconsole.inprocess import QtInProcessKernelManager
|
|
7
7
|
from qtconsole.rich_jupyter_widget import RichJupyterWidget
|
8
8
|
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
|
9
9
|
|
10
|
+
from bec_widgets.cli.rpc_register import RPCRegister
|
10
11
|
from bec_widgets.utils import BECDispatcher
|
11
12
|
from bec_widgets.widgets import BECFigure
|
12
13
|
|
@@ -43,10 +44,14 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
|
43
44
|
self.safe_close = False
|
44
45
|
# self.figure.clean_signal.connect(self.confirm_close)
|
45
46
|
|
47
|
+
self.register = RPCRegister()
|
48
|
+
self.register.add_rpc(self.figure)
|
49
|
+
print("Registered objects:", dict(self.register.list_all_connections()))
|
46
50
|
# console push
|
47
51
|
self.console.kernel_manager.kernel.shell.push(
|
48
52
|
{
|
49
53
|
"fig": self.figure,
|
54
|
+
"register": self.register,
|
50
55
|
"w1": self.w1,
|
51
56
|
"w2": self.w2,
|
52
57
|
"w3": self.w3,
|
@@ -7,6 +7,7 @@ from typing import Optional, Type
|
|
7
7
|
from pydantic import BaseModel, Field, field_validator
|
8
8
|
from qtpy.QtCore import Slot as pyqtSlot
|
9
9
|
|
10
|
+
from bec_widgets.cli.rpc_register import RPCRegister
|
10
11
|
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
11
12
|
|
12
13
|
|
@@ -31,7 +32,7 @@ class ConnectionConfig(BaseModel):
|
|
31
32
|
class BECConnector:
|
32
33
|
"""Connection mixin class for all BEC widgets, to handle BEC client and device manager"""
|
33
34
|
|
34
|
-
USER_ACCESS = ["config_dict"]
|
35
|
+
USER_ACCESS = ["config_dict", "get_all_rpc"]
|
35
36
|
|
36
37
|
def __init__(self, client=None, config: ConnectionConfig = None, gui_id: str = None):
|
37
38
|
# BEC related connections
|
@@ -54,6 +55,25 @@ class BECConnector:
|
|
54
55
|
else:
|
55
56
|
self.gui_id = self.config.gui_id
|
56
57
|
|
58
|
+
# register widget to rpc register
|
59
|
+
self.rpc_register = RPCRegister()
|
60
|
+
self.rpc_register.add_rpc(self)
|
61
|
+
|
62
|
+
def get_all_rpc(self) -> dict:
|
63
|
+
"""Get all registered RPC objects."""
|
64
|
+
all_connections = self.rpc_register.list_all_connections()
|
65
|
+
return dict(all_connections)
|
66
|
+
|
67
|
+
@property
|
68
|
+
def rpc_id(self) -> str:
|
69
|
+
"""Get the RPC ID of the widget."""
|
70
|
+
return self.gui_id
|
71
|
+
|
72
|
+
@rpc_id.setter
|
73
|
+
def rpc_id(self, rpc_id: str) -> None:
|
74
|
+
"""Set the RPC ID of the widget."""
|
75
|
+
self.gui_id = rpc_id
|
76
|
+
|
57
77
|
@property
|
58
78
|
def config_dict(self) -> dict:
|
59
79
|
"""
|
@@ -97,6 +97,7 @@ class WidgetHandler:
|
|
97
97
|
|
98
98
|
class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
99
99
|
USER_ACCESS = [
|
100
|
+
"rpc_id",
|
100
101
|
"config_dict",
|
101
102
|
"axes",
|
102
103
|
"widgets",
|
@@ -110,6 +111,8 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
|
110
111
|
"change_layout",
|
111
112
|
"change_theme",
|
112
113
|
"clear_all",
|
114
|
+
"get_all_rpc",
|
115
|
+
"widget_list",
|
113
116
|
]
|
114
117
|
|
115
118
|
clean_signal = pyqtSignal()
|
@@ -138,8 +141,21 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
|
138
141
|
# Container to keep track of the grid
|
139
142
|
self.grid = []
|
140
143
|
|
144
|
+
def __getitem__(self, key: tuple | str):
|
145
|
+
if isinstance(key, tuple) and len(key) == 2:
|
146
|
+
return self.axes(*key)
|
147
|
+
elif isinstance(key, str):
|
148
|
+
widget = self._widgets.get(key)
|
149
|
+
if widget is None:
|
150
|
+
raise KeyError(f"No widget with ID {key}")
|
151
|
+
return self._widgets.get(key)
|
152
|
+
else:
|
153
|
+
raise TypeError(
|
154
|
+
"Key must be a string (widget id) or a tuple of two integers (grid coordinates)"
|
155
|
+
)
|
156
|
+
|
141
157
|
@property
|
142
|
-
def
|
158
|
+
def widget_list(self) -> list[BECPlotBase]:
|
143
159
|
"""
|
144
160
|
Access all widget in BECFigure as a list
|
145
161
|
Returns:
|
@@ -148,8 +164,8 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
|
148
164
|
axes = [value for value in self._widgets.values() if isinstance(value, BECPlotBase)]
|
149
165
|
return axes
|
150
166
|
|
151
|
-
@
|
152
|
-
def
|
167
|
+
@widget_list.setter
|
168
|
+
def widget_list(self, value: list[BECPlotBase]):
|
153
169
|
self._axes = value
|
154
170
|
|
155
171
|
@property
|
@@ -623,7 +639,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
|
623
639
|
row(int): The row coordinate of the widget to remove.
|
624
640
|
col(int): The column coordinate of the widget to remove.
|
625
641
|
"""
|
626
|
-
widget = self.
|
642
|
+
widget = self.axes(row, col)
|
627
643
|
if widget:
|
628
644
|
widget_id = widget.config.gui_id
|
629
645
|
if widget_id in self._widgets:
|
@@ -643,24 +659,10 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
|
643
659
|
self._reindex_grid()
|
644
660
|
if widget_id in self.config.widgets:
|
645
661
|
self.config.widgets.pop(widget_id)
|
646
|
-
print(f"Removed widget {widget_id}.")
|
647
662
|
else:
|
648
663
|
raise ValueError(f"Widget with ID '{widget_id}' does not exist.")
|
649
664
|
|
650
|
-
def
|
651
|
-
if isinstance(key, tuple) and len(key) == 2:
|
652
|
-
return self._get_widget_by_coordinates(*key)
|
653
|
-
elif isinstance(key, str):
|
654
|
-
widget = self._widgets.get(key)
|
655
|
-
if widget is None:
|
656
|
-
raise KeyError(f"No widget with ID {key}")
|
657
|
-
return self._widgets.get(key)
|
658
|
-
else:
|
659
|
-
raise TypeError(
|
660
|
-
"Key must be a string (widget id) or a tuple of two integers (grid coordinates)"
|
661
|
-
)
|
662
|
-
|
663
|
-
def _get_widget_by_coordinates(self, row: int, col: int) -> BECPlotBase:
|
665
|
+
def axes(self, row: int, col: int) -> BECPlotBase:
|
664
666
|
"""
|
665
667
|
Get widget by its coordinates in the figure.
|
666
668
|
Args:
|
@@ -699,7 +701,6 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
|
699
701
|
|
700
702
|
def _reindex_grid(self):
|
701
703
|
"""Reindex the grid to remove empty rows and columns."""
|
702
|
-
print(f"old grid: {self.grid}")
|
703
704
|
new_grid = []
|
704
705
|
for row in self.grid:
|
705
706
|
new_row = [widget for widget in row if widget is not None]
|
@@ -766,8 +767,8 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
|
766
767
|
|
767
768
|
def clear_all(self):
|
768
769
|
"""Clear all widgets from the figure and reset to default state"""
|
769
|
-
|
770
|
-
|
770
|
+
for widget in self._widgets.values():
|
771
|
+
widget.cleanup()
|
771
772
|
self.clear()
|
772
773
|
self._widgets = defaultdict(dict)
|
773
774
|
self.grid = []
|
@@ -59,6 +59,7 @@ class ImageConfig(WidgetConfig):
|
|
59
59
|
|
60
60
|
class BECImageItem(BECConnector, pg.ImageItem):
|
61
61
|
USER_ACCESS = [
|
62
|
+
"rpc_id",
|
62
63
|
"config_dict",
|
63
64
|
"set",
|
64
65
|
"set_fft",
|
@@ -287,9 +288,14 @@ class BECImageItem(BECConnector, pg.ImageItem):
|
|
287
288
|
else:
|
288
289
|
raise ValueError("style should be 'simple' or 'full'")
|
289
290
|
|
291
|
+
def cleanup(self):
|
292
|
+
"""Clean up widget."""
|
293
|
+
self.rpc_register.remove_rpc(self)
|
294
|
+
|
290
295
|
|
291
296
|
class BECImageShow(BECPlotBase):
|
292
297
|
USER_ACCESS = [
|
298
|
+
"rpc_id",
|
293
299
|
"config_dict",
|
294
300
|
"add_image_by_config",
|
295
301
|
"get_image_config",
|
@@ -358,20 +364,6 @@ class BECImageShow(BECPlotBase):
|
|
358
364
|
|
359
365
|
thread.start()
|
360
366
|
|
361
|
-
def find_widget_by_id(self, item_id: str) -> BECImageItem:
|
362
|
-
"""
|
363
|
-
Find the widget by its gui_id.
|
364
|
-
Args:
|
365
|
-
item_id(str): The gui_id of the widget.
|
366
|
-
|
367
|
-
Returns:
|
368
|
-
BECImageItem: The widget with the given gui_id.
|
369
|
-
"""
|
370
|
-
for source, images in self._images.items():
|
371
|
-
for monitor, image_item in images.items():
|
372
|
-
if image_item.gui_id == item_id:
|
373
|
-
return image_item
|
374
|
-
|
375
367
|
def find_image_by_monitor(self, item_id: str) -> BECImageItem:
|
376
368
|
"""
|
377
369
|
Find the widget by its gui_id.
|
@@ -719,10 +711,8 @@ class BECImageShow(BECPlotBase):
|
|
719
711
|
processing_config = image_to_update.config.processing
|
720
712
|
self.processor.set_config(processing_config)
|
721
713
|
if self.use_threading:
|
722
|
-
print("using threaded version")
|
723
714
|
self._create_thread_worker(device, data)
|
724
715
|
else:
|
725
|
-
print("using NON-threaded version")
|
726
716
|
data = self.processor.process_image(data)
|
727
717
|
self.update_image(device, data)
|
728
718
|
|
@@ -809,14 +799,14 @@ class BECImageShow(BECPlotBase):
|
|
809
799
|
"""
|
810
800
|
Clean up the widget.
|
811
801
|
"""
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
802
|
+
for monitor in self._images["device_monitor"]:
|
803
|
+
self.bec_dispatcher.disconnect_slot(
|
804
|
+
self.on_image_update, MessageEndpoints.device_monitor(monitor)
|
805
|
+
)
|
806
|
+
for image in self.images:
|
807
|
+
image.cleanup()
|
808
|
+
|
809
|
+
self.rpc_register.remove_rpc(self)
|
820
810
|
|
821
811
|
|
822
812
|
class ImageProcessor:
|
@@ -182,14 +182,18 @@ class BECMotorMap(BECPlotBase):
|
|
182
182
|
"""
|
183
183
|
self.config.scatter_size = scatter_size
|
184
184
|
|
185
|
-
def
|
186
|
-
"""
|
185
|
+
def _disconnect_current_motors(self):
|
186
|
+
"""Disconnect the current motors from the slots."""
|
187
187
|
if self.motor_x is not None and self.motor_y is not None:
|
188
|
-
|
188
|
+
endpoints = [
|
189
189
|
MessageEndpoints.device_readback(self.motor_x),
|
190
190
|
MessageEndpoints.device_readback(self.motor_y),
|
191
191
|
]
|
192
|
-
self.bec_dispatcher.disconnect_slot(self.on_device_readback,
|
192
|
+
self.bec_dispatcher.disconnect_slot(self.on_device_readback, endpoints)
|
193
|
+
|
194
|
+
def _connect_motor_to_slots(self):
|
195
|
+
"""Connect motors to slots."""
|
196
|
+
self._disconnect_current_motors()
|
193
197
|
|
194
198
|
self.motor_x = self.config.signals.x.name
|
195
199
|
self.motor_y = self.config.signals.y.name
|
@@ -418,18 +422,7 @@ class BECMotorMap(BECPlotBase):
|
|
418
422
|
|
419
423
|
self.update_signal.emit()
|
420
424
|
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
import pyqtgraph as pg
|
426
|
-
from qtpy.QtWidgets import QApplication
|
427
|
-
|
428
|
-
app = QApplication(sys.argv)
|
429
|
-
glw = pg.GraphicsLayoutWidget()
|
430
|
-
motor_map = BECMotorMap()
|
431
|
-
motor_map.change_motors("samx", "samy")
|
432
|
-
glw.addItem(motor_map)
|
433
|
-
widget = glw
|
434
|
-
widget.show()
|
435
|
-
sys.exit(app.exec_())
|
425
|
+
def cleanup(self):
|
426
|
+
"""Cleanup the widget."""
|
427
|
+
self._disconnect_current_motors()
|
428
|
+
self.rpc_register.remove_rpc(self)
|
@@ -64,6 +64,8 @@ class Waveform1DConfig(WidgetConfig):
|
|
64
64
|
|
65
65
|
class BECCurve(BECConnector, pg.PlotDataItem):
|
66
66
|
USER_ACCESS = [
|
67
|
+
"remove",
|
68
|
+
"rpc_id",
|
67
69
|
"config_dict",
|
68
70
|
"set",
|
69
71
|
"set_data",
|
@@ -82,6 +84,7 @@ class BECCurve(BECConnector, pg.PlotDataItem):
|
|
82
84
|
name: Optional[str] = None,
|
83
85
|
config: Optional[CurveConfig] = None,
|
84
86
|
gui_id: Optional[str] = None,
|
87
|
+
parent_item: Optional[pg.PlotItem] = None,
|
85
88
|
**kwargs,
|
86
89
|
):
|
87
90
|
if config is None:
|
@@ -93,6 +96,7 @@ class BECCurve(BECConnector, pg.PlotDataItem):
|
|
93
96
|
super().__init__(config=config, gui_id=gui_id)
|
94
97
|
pg.PlotDataItem.__init__(self, name=name)
|
95
98
|
|
99
|
+
self.parent_item = parent_item
|
96
100
|
self.apply_config()
|
97
101
|
if kwargs:
|
98
102
|
self.set(**kwargs)
|
@@ -225,9 +229,19 @@ class BECCurve(BECConnector, pg.PlotDataItem):
|
|
225
229
|
x_data, y_data = self.getData()
|
226
230
|
return x_data, y_data
|
227
231
|
|
232
|
+
def cleanup(self):
|
233
|
+
"""Cleanup the curve."""
|
234
|
+
self.rpc_register.remove_rpc(self)
|
235
|
+
|
236
|
+
def remove(self):
|
237
|
+
"""Remove the curve from the plot."""
|
238
|
+
self.cleanup()
|
239
|
+
self.parent_item.removeItem(self)
|
240
|
+
|
228
241
|
|
229
242
|
class BECWaveform(BECPlotBase):
|
230
243
|
USER_ACCESS = [
|
244
|
+
"rpc_id",
|
231
245
|
"config_dict",
|
232
246
|
"add_curve_scan",
|
233
247
|
"add_curve_custom",
|
@@ -286,19 +300,6 @@ class BECWaveform(BECPlotBase):
|
|
286
300
|
self.add_legend()
|
287
301
|
self.apply_config(self.config)
|
288
302
|
|
289
|
-
def find_widget_by_id(self, item_id: str) -> BECCurve:
|
290
|
-
"""
|
291
|
-
Find the curve by its ID.
|
292
|
-
Args:
|
293
|
-
item_id(str): ID of the curve.
|
294
|
-
|
295
|
-
Returns:
|
296
|
-
BECCurve: The curve object.
|
297
|
-
"""
|
298
|
-
for curve in self.plot_item.curves:
|
299
|
-
if curve.gui_id == item_id:
|
300
|
-
return curve
|
301
|
-
|
302
303
|
def apply_config(self, config: dict | WidgetConfig, replot_last_scan: bool = False):
|
303
304
|
"""
|
304
305
|
Apply the configuration to the 1D waveform widget.
|
@@ -468,7 +469,7 @@ class BECWaveform(BECPlotBase):
|
|
468
469
|
Returns:
|
469
470
|
BECCurve: The curve object.
|
470
471
|
"""
|
471
|
-
curve = BECCurve(config=config, name=name)
|
472
|
+
curve = BECCurve(config=config, name=name, parent_item=self.plot_item)
|
472
473
|
self._curves_data[source][name] = curve
|
473
474
|
self.plot_item.addItem(curve)
|
474
475
|
self.config.curves[name] = curve.config
|
@@ -796,3 +797,6 @@ class BECWaveform(BECPlotBase):
|
|
796
797
|
def cleanup(self):
|
797
798
|
"""Cleanup the widget connection from BECDispatcher."""
|
798
799
|
self.bec_dispatcher.disconnect_slot(self.on_scan_segment, MessageEndpoints.scan_segment())
|
800
|
+
for curve in self.curves:
|
801
|
+
curve.cleanup()
|
802
|
+
self.rpc_register.remove_rpc(self)
|
@@ -2,16 +2,17 @@ bec_widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
bec_widgets/cli/__init__.py,sha256=ULoNTVnv2UeSSjhFq3uCQJ-0JTJf9oU76l27aRiizL4,78
|
3
3
|
bec_widgets/cli/auto_updates.py,sha256=ptZeBKr13o9THc8oKLn93K_16i6G3pxzw8hZ4MUgjW4,3845
|
4
4
|
bec_widgets/cli/bec_widgets_icon.png,sha256=pRCGpoOtwyZl97fBV_CHcGppliErzd0qQkCXLxjbp-s,5760
|
5
|
-
bec_widgets/cli/client.py,sha256=
|
6
|
-
bec_widgets/cli/client_utils.py,sha256=
|
5
|
+
bec_widgets/cli/client.py,sha256=AvkaEDKB8cZFm2WS5JWIusMXtcqErEeP2Ayk9l7iAp8,39163
|
6
|
+
bec_widgets/cli/client_utils.py,sha256=FBuU0LTi1lQrzRIH_4ued_isC_iknYMAaNxnSTsJvTw,10118
|
7
7
|
bec_widgets/cli/generate_cli.py,sha256=JLqUlUgfz_f_4KHPRUAN-Xli-K7uNOc8-F-LkAC7Scw,4004
|
8
|
-
bec_widgets/cli/
|
8
|
+
bec_widgets/cli/rpc_register.py,sha256=OZOWX0IKGXqDsnrUYi0Irl_zpPS4Q_JPCV9JQfN6YYw,2212
|
9
|
+
bec_widgets/cli/server.py,sha256=Xru-gLqDhTRIu6EnYIsFXeinMDW_NbtKYtPivlfdBY8,4772
|
9
10
|
bec_widgets/examples/__init__.py,sha256=WWQ0cu7m8sA4Ehy-DWdTIqSISjaHsbxhsNmNrMnhDZU,202
|
10
11
|
bec_widgets/examples/eiger_plot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
12
|
bec_widgets/examples/eiger_plot/eiger_plot.py,sha256=Uxl2Usf8jEzaX7AT8zVqa1x8ZIEgI1HmazSlb-tRFWE,10359
|
12
13
|
bec_widgets/examples/eiger_plot/eiger_plot.ui,sha256=grHfnO3OG_lelJhdRsnA0badCvRdDunPrIMIyNQ5N-w,5809
|
13
14
|
bec_widgets/examples/jupyter_console/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
-
bec_widgets/examples/jupyter_console/jupyter_console_window.py,sha256=
|
15
|
+
bec_widgets/examples/jupyter_console/jupyter_console_window.py,sha256=aEwwOHNW4CJab41hbEB3hvWEEAPNtGjFQEwMo8GkEmM,3581
|
15
16
|
bec_widgets/examples/jupyter_console/jupyter_console_window.ui,sha256=GodXBvBvs5QAUsHbo3pcxR4o51Tvce4DTqpTluk3hOs,742
|
16
17
|
bec_widgets/examples/mca_readout/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
18
|
bec_widgets/examples/mca_readout/mca_plot.py,sha256=do7mSK_nzHtojRiMi8JoN_Rckg9yfjYYWz2S_Nl3xbE,5079
|
@@ -28,7 +29,7 @@ bec_widgets/examples/stream_plot/line_plot.ui,sha256=rgNfhOXu1AcWF0P6wnOlmJKDjS-
|
|
28
29
|
bec_widgets/examples/stream_plot/stream_plot.py,sha256=vHii1p9JxSyGQ_VcCjnk9SHJ41Q6Oi1GGd6swVVHLRM,12177
|
29
30
|
bec_widgets/simulations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
31
|
bec_widgets/utils/__init__.py,sha256=xytx86Yosjkta0PU4rHfoeO7FCPcimS15xjMPQUgIXc,403
|
31
|
-
bec_widgets/utils/bec_connector.py,sha256=
|
32
|
+
bec_widgets/utils/bec_connector.py,sha256=U_quQy7p1ISEpTnvwKsnDw5rdCc3jEoATfPVez2K7eI,4867
|
32
33
|
bec_widgets/utils/bec_dispatcher.py,sha256=sLv9CmJ3GKGDhvCXCDmuKtNRlI4w1oWxuQu_Mq2mDDY,4840
|
33
34
|
bec_widgets/utils/bec_table.py,sha256=Xy5qM343K8EvEpB4g_129b63yo1wdEvEY3wqxB_p_Iw,716
|
34
35
|
bec_widgets/utils/colors.py,sha256=JsLxzkxbw-I8GIuvnIKyiM83n0edhyMG2Fa4Ffm62ww,2392
|
@@ -45,7 +46,7 @@ bec_widgets/validation/__init__.py,sha256=ismd1bU5FhFb0zFPwNKuq7oT48G4Y2GfaMZOdN
|
|
45
46
|
bec_widgets/validation/monitor_config_validator.py,sha256=M9p8K_nvxicnqJB4X7j90R377WHYVH4wMCtSXsRI51M,8150
|
46
47
|
bec_widgets/widgets/__init__.py,sha256=HBzIWJYX4dp2iDZl_qIuyy-X5IWRMhGwQ-4UisP8wqE,353
|
47
48
|
bec_widgets/widgets/figure/__init__.py,sha256=3hGx_KOV7QHCYAV06aNuUgKq4QIYCjUTad-DrwkUaBM,44
|
48
|
-
bec_widgets/widgets/figure/figure.py,sha256=
|
49
|
+
bec_widgets/widgets/figure/figure.py,sha256=IOj5rz3dXHOThtdLRPHx0lpoIQCLg-QbPGO1ECM-CKo,28484
|
49
50
|
bec_widgets/widgets/monitor/__init__.py,sha256=afXuZcBOxNAuYdCkIQXX5J60R5A3Q_86lNEW2vpFtPI,32
|
50
51
|
bec_widgets/widgets/monitor/config_dialog.py,sha256=Z1a4WRIVlfEGdwC-QG25kba2EHCZWi5J843tBVZlWiI,20275
|
51
52
|
bec_widgets/widgets/monitor/config_dialog.ui,sha256=ISMcF7CLTAMXhfZh2Yv5yezzAjMtb9fxY1pmX4B_jCg,5932
|
@@ -60,18 +61,22 @@ bec_widgets/widgets/motor_control/motor_control_table.ui,sha256=t6aRKiSmutMfp0Ay
|
|
60
61
|
bec_widgets/widgets/motor_map/__init__.py,sha256=K3c-3A_LbxK0UJ0_bV3opL-wGLTwBLendsJXsg8GAqE,32
|
61
62
|
bec_widgets/widgets/motor_map/motor_map.py,sha256=vJlLWa0BXv5KMZ7UVT0q3I1I5CXgsDiXoM_ptsAsG3c,21860
|
62
63
|
bec_widgets/widgets/plots/__init__.py,sha256=kGQTORTr-2M9vmVCK-T7AFre4bY5LVVzGxcIzT81-ZU,237
|
63
|
-
bec_widgets/widgets/plots/image.py,sha256=
|
64
|
-
bec_widgets/widgets/plots/motor_map.py,sha256=
|
65
|
-
bec_widgets/widgets/plots/plot_base.py,sha256=
|
66
|
-
bec_widgets/widgets/plots/waveform.py,sha256=
|
64
|
+
bec_widgets/widgets/plots/image.py,sha256=uanFs3YoAefLgfQnAW5oiQtssRLfhKmWHMlOMpb9HqQ,32504
|
65
|
+
bec_widgets/widgets/plots/motor_map.py,sha256=wbD95aIA4K3BLDZHZmHLblvKea7jPA7u4yw_tFaA4OE,15298
|
66
|
+
bec_widgets/widgets/plots/plot_base.py,sha256=ZieahcTwz0tynszdYe4r0rrOlWcRmXblplIYuuTX1ic,8474
|
67
|
+
bec_widgets/widgets/plots/waveform.py,sha256=CzPPq_9ZgBr4pZkLNSDqPbP8AJXXkan221-Fe8rw31c,28693
|
67
68
|
bec_widgets/widgets/scan_control/__init__.py,sha256=IOfHl15vxb_uC6KN62-PeUzbBha_vQyqkkXbJ2HU674,38
|
68
69
|
bec_widgets/widgets/scan_control/scan_control.py,sha256=tbO9tbVynRvs4VCxTZ4ZFBDTVAojIr-zkl70vuHbWgw,17116
|
69
70
|
bec_widgets/widgets/toolbar/__init__.py,sha256=d-TP4_cr_VbpwreMM4ePnfZ5YXsEPQ45ibEf75nuGoE,36
|
70
71
|
bec_widgets/widgets/toolbar/toolbar.py,sha256=sxz7rbc8XNPS6n2WMObF4-2PqdYfPxVtsOZEGV6mqa0,5124
|
71
72
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
73
|
+
tests/end-2-end/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
74
|
+
tests/end-2-end/conftest.py,sha256=jksYsc3Vm15LDyke-fmc80qCKBUnYuEjYi_QikeSOkE,756
|
75
|
+
tests/end-2-end/test_bec_figure_rpc_e2e.py,sha256=-eMCKkj6sh_OkuVRhVTGMw5KJOyNhzDR783jdi5hNM4,5350
|
76
|
+
tests/end-2-end/test_rpc_register_e2e.py,sha256=zqatLWjM_tYxiB3GgupYHHGTorGUFa0jvDFaqJpkZyA,1560
|
72
77
|
tests/unit_tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
73
78
|
tests/unit_tests/client_mocks.py,sha256=LNUgI9Ccv5Ol7_pmybIhoVqZZem1RPIsTDk7ZTARNls,4128
|
74
|
-
tests/unit_tests/conftest.py,sha256=
|
79
|
+
tests/unit_tests/conftest.py,sha256=KrnktXPWmZhnKNue-xGWOLD1XGEvdz9Vf7V2eO3XQ3A,596
|
75
80
|
tests/unit_tests/test_bec_connector.py,sha256=f2XXGGw3NoZLIUrDuZuEWwF_ttOYmmquCgUrV5XkIOY,1951
|
76
81
|
tests/unit_tests/test_bec_dispatcher.py,sha256=MtNyfC7-Y4na-Fwf1ny9raHBqE45eSnQNWSqqAx79FU,1857
|
77
82
|
tests/unit_tests/test_bec_figure.py,sha256=T4k-E1D3sjTTDTFZGdTFDQv0EYNQ_R-QbWOM7pQwFw4,7926
|
@@ -85,6 +90,7 @@ tests/unit_tests/test_generate_cli_client.py,sha256=BdpTZMNUFOBJa2e-rme9AJUoXfue
|
|
85
90
|
tests/unit_tests/test_motor_control.py,sha256=jdTG35z3jOL9XCAIDNIGfdv60vcwGLHa3KJjKqJkoZw,20322
|
86
91
|
tests/unit_tests/test_motor_map.py,sha256=UEjmtIYI2mxq9BUeopqoqNNy7UiPJEts9h45ufsFcrA,5979
|
87
92
|
tests/unit_tests/test_plot_base.py,sha256=bOdlgAxh9oKk5PwiQ_MSFmzr44uJ61Tlg242RCIhl5c,2610
|
93
|
+
tests/unit_tests/test_rpc_register.py,sha256=hECjZEimd440mwRrO0rg7L3PKN7__3DgjmESN6wx3bo,1179
|
88
94
|
tests/unit_tests/test_scan_control.py,sha256=7dtGpE0g4FqUhhQeCkyJl-9o7NH3DFZJgEaqDmBYbBc,7551
|
89
95
|
tests/unit_tests/test_stream_plot.py,sha256=LNCYIj9CafremGaz-DwDktCRJRrjgfOdVewCUwwZE5s,5843
|
90
96
|
tests/unit_tests/test_validator_errors.py,sha256=NFxyv0TIOXeZKZRRUBfVQ7bpunwY4KkG95yTUdQmvns,3532
|
@@ -93,8 +99,8 @@ tests/unit_tests/test_widget_io.py,sha256=FeL3ZYSBQnRt6jxj8VGYw1cmcicRQyHKleahw7
|
|
93
99
|
tests/unit_tests/test_yaml_dialog.py,sha256=HNrqferkdg02-9ieOhhI2mr2Qvt7GrYgXmQ061YCTbg,5794
|
94
100
|
tests/unit_tests/test_msgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
95
101
|
tests/unit_tests/test_msgs/available_scans_message.py,sha256=m_z97hIrjHXXMa2Ex-UvsPmTxOYXfjxyJaGkIY6StTY,46532
|
96
|
-
bec_widgets-0.
|
97
|
-
bec_widgets-0.
|
98
|
-
bec_widgets-0.
|
99
|
-
bec_widgets-0.
|
100
|
-
bec_widgets-0.
|
102
|
+
bec_widgets-0.50.0.dist-info/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
103
|
+
bec_widgets-0.50.0.dist-info/METADATA,sha256=2i8dBEDDsAAbm4Hgxf_Vt5h2weKOK-dQ0KKtb75wlG0,3719
|
104
|
+
bec_widgets-0.50.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
105
|
+
bec_widgets-0.50.0.dist-info/top_level.txt,sha256=EXCwhJYmXmd1DjYYL3hrGsddX-97IwYSiIHrf27FFVk,18
|
106
|
+
bec_widgets-0.50.0.dist-info/RECORD,,
|
File without changes
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
from bec_widgets.cli.rpc_register import RPCRegister
|
4
|
+
from bec_widgets.cli.server import BECWidgetsCLIServer
|
5
|
+
from bec_widgets.utils import BECDispatcher
|
6
|
+
|
7
|
+
|
8
|
+
@pytest.fixture(autouse=True)
|
9
|
+
def rpc_register():
|
10
|
+
yield RPCRegister()
|
11
|
+
RPCRegister.reset_singleton()
|
12
|
+
|
13
|
+
|
14
|
+
@pytest.fixture
|
15
|
+
def rpc_server(qtbot, bec_client_lib, threads_check):
|
16
|
+
dispatcher = BECDispatcher(client=bec_client_lib) # Has to init singleton with fixture client
|
17
|
+
server = BECWidgetsCLIServer(gui_id="figure")
|
18
|
+
qtbot.addWidget(server.fig)
|
19
|
+
qtbot.waitExposed(server.fig)
|
20
|
+
qtbot.wait(1000) # 1s long to wait until gui is ready
|
21
|
+
yield server
|
22
|
+
dispatcher.disconnect_all()
|
23
|
+
server.client.shutdown()
|
24
|
+
server.shutdown()
|
25
|
+
dispatcher.reset_singleton()
|
@@ -0,0 +1,165 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import pytest
|
3
|
+
from bec_lib import MessageEndpoints
|
4
|
+
|
5
|
+
from bec_widgets.cli.client import BECFigure, BECImageShow, BECMotorMap, BECWaveform
|
6
|
+
|
7
|
+
|
8
|
+
def test_rpc_waveform1d_custom_curve(rpc_server, qtbot):
|
9
|
+
fig = BECFigure(rpc_server.gui_id)
|
10
|
+
fig_server = rpc_server.fig
|
11
|
+
|
12
|
+
ax = fig.add_plot()
|
13
|
+
curve = ax.add_curve_custom([1, 2, 3], [1, 2, 3])
|
14
|
+
curve.set_color("red")
|
15
|
+
curve = ax.curves[0]
|
16
|
+
curve.set_color("blue")
|
17
|
+
|
18
|
+
assert len(fig_server.widgets) == 1
|
19
|
+
assert len(fig_server.widgets["widget_1"].curves) == 1
|
20
|
+
|
21
|
+
|
22
|
+
def test_rpc_plotting_shortcuts_init_configs(rpc_server, qtbot):
|
23
|
+
fig = BECFigure(rpc_server.gui_id)
|
24
|
+
fig_server = rpc_server.fig
|
25
|
+
|
26
|
+
plt = fig.plot("samx", "bpm4i")
|
27
|
+
im = fig.image("eiger")
|
28
|
+
motor_map = fig.motor_map("samx", "samy")
|
29
|
+
plt_z = fig.add_plot("samx", "samy", "bpm4i")
|
30
|
+
|
31
|
+
# Checking if classes are correctly initialised
|
32
|
+
assert len(fig_server.widgets) == 4
|
33
|
+
assert plt.__class__.__name__ == "BECWaveform"
|
34
|
+
assert plt.__class__ == BECWaveform
|
35
|
+
assert im.__class__.__name__ == "BECImageShow"
|
36
|
+
assert im.__class__ == BECImageShow
|
37
|
+
assert motor_map.__class__.__name__ == "BECMotorMap"
|
38
|
+
assert motor_map.__class__ == BECMotorMap
|
39
|
+
|
40
|
+
# check if the correct devices are set
|
41
|
+
# plot
|
42
|
+
assert plt.config_dict["curves"]["bpm4i-bpm4i"]["signals"] == {
|
43
|
+
"source": "scan_segment",
|
44
|
+
"x": {"name": "samx", "entry": "samx", "unit": None, "modifier": None, "limits": None},
|
45
|
+
"y": {"name": "bpm4i", "entry": "bpm4i", "unit": None, "modifier": None, "limits": None},
|
46
|
+
"z": None,
|
47
|
+
}
|
48
|
+
# image
|
49
|
+
assert im.config_dict["images"]["eiger"]["monitor"] == "eiger"
|
50
|
+
# motor map
|
51
|
+
assert motor_map.config_dict["signals"] == {
|
52
|
+
"source": "device_readback",
|
53
|
+
"x": {
|
54
|
+
"name": "samx",
|
55
|
+
"entry": "samx",
|
56
|
+
"unit": None,
|
57
|
+
"modifier": None,
|
58
|
+
"limits": [-50.0, 50.0],
|
59
|
+
},
|
60
|
+
"y": {
|
61
|
+
"name": "samy",
|
62
|
+
"entry": "samy",
|
63
|
+
"unit": None,
|
64
|
+
"modifier": None,
|
65
|
+
"limits": [-50.0, 50.0],
|
66
|
+
},
|
67
|
+
"z": None,
|
68
|
+
}
|
69
|
+
# plot with z scatter
|
70
|
+
assert plt_z.config_dict["curves"]["bpm4i-bpm4i"]["signals"] == {
|
71
|
+
"source": "scan_segment",
|
72
|
+
"x": {"name": "samx", "entry": "samx", "unit": None, "modifier": None, "limits": None},
|
73
|
+
"y": {"name": "samy", "entry": "samy", "unit": None, "modifier": None, "limits": None},
|
74
|
+
"z": {"name": "bpm4i", "entry": "bpm4i", "unit": None, "modifier": None, "limits": None},
|
75
|
+
}
|
76
|
+
|
77
|
+
|
78
|
+
def test_rpc_waveform_scan(rpc_server, qtbot):
|
79
|
+
fig = BECFigure(rpc_server.gui_id)
|
80
|
+
|
81
|
+
# add 3 different curves to track
|
82
|
+
plt = fig.plot("samx", "bpm4i")
|
83
|
+
fig.plot("samx", "bpm3a")
|
84
|
+
fig.plot("samx", "bpm4d")
|
85
|
+
|
86
|
+
client = rpc_server.client
|
87
|
+
dev = client.device_manager.devices
|
88
|
+
scans = client.scans
|
89
|
+
queue = client.queue
|
90
|
+
|
91
|
+
status = scans.line_scan(dev.samx, -5, 5, steps=10, exp_time=0.05, relative=False)
|
92
|
+
|
93
|
+
# wait for scan to finish
|
94
|
+
while not status.status == "COMPLETED":
|
95
|
+
qtbot.wait(200)
|
96
|
+
|
97
|
+
last_scan_data = queue.scan_storage.storage[-1].data
|
98
|
+
|
99
|
+
# get data from curves
|
100
|
+
plt_data = plt.get_all_data()
|
101
|
+
|
102
|
+
# check plotted data
|
103
|
+
assert plt_data["bpm4i-bpm4i"]["x"] == last_scan_data["samx"]["samx"].val
|
104
|
+
assert plt_data["bpm4i-bpm4i"]["y"] == last_scan_data["bpm4i"]["bpm4i"].val
|
105
|
+
assert plt_data["bpm3a-bpm3a"]["x"] == last_scan_data["samx"]["samx"].val
|
106
|
+
assert plt_data["bpm3a-bpm3a"]["y"] == last_scan_data["bpm3a"]["bpm3a"].val
|
107
|
+
assert plt_data["bpm4d-bpm4d"]["x"] == last_scan_data["samx"]["samx"].val
|
108
|
+
assert plt_data["bpm4d-bpm4d"]["y"] == last_scan_data["bpm4d"]["bpm4d"].val
|
109
|
+
|
110
|
+
|
111
|
+
def test_rpc_image(rpc_server, qtbot):
|
112
|
+
fig = BECFigure(rpc_server.gui_id)
|
113
|
+
|
114
|
+
im = fig.image("eiger")
|
115
|
+
|
116
|
+
client = rpc_server.client
|
117
|
+
dev = client.device_manager.devices
|
118
|
+
scans = client.scans
|
119
|
+
|
120
|
+
status = scans.line_scan(dev.samx, -5, 5, steps=10, exp_time=0.05, relative=False)
|
121
|
+
|
122
|
+
# wait for scan to finish
|
123
|
+
while not status.status == "COMPLETED":
|
124
|
+
qtbot.wait(200)
|
125
|
+
|
126
|
+
last_image_device = client.connector.get_last(MessageEndpoints.device_monitor("eiger"))[
|
127
|
+
"data"
|
128
|
+
].data
|
129
|
+
qtbot.wait(500)
|
130
|
+
last_image_plot = im.images[0].get_data()
|
131
|
+
|
132
|
+
# check plotted data
|
133
|
+
np.testing.assert_equal(last_image_device, last_image_plot)
|
134
|
+
|
135
|
+
|
136
|
+
def test_rpc_motor_map(rpc_server, qtbot):
|
137
|
+
fig = BECFigure(rpc_server.gui_id)
|
138
|
+
fig_server = rpc_server.fig
|
139
|
+
|
140
|
+
motor_map = fig.motor_map("samx", "samy")
|
141
|
+
|
142
|
+
client = rpc_server.client
|
143
|
+
dev = client.device_manager.devices
|
144
|
+
scans = client.scans
|
145
|
+
|
146
|
+
initial_pos_x = dev.samx.read()["samx"]["value"]
|
147
|
+
initial_pos_y = dev.samy.read()["samy"]["value"]
|
148
|
+
|
149
|
+
status = scans.mv(dev.samx, 1, dev.samy, 2, relative=True)
|
150
|
+
|
151
|
+
# wait for scan to finish
|
152
|
+
while not status.status == "COMPLETED":
|
153
|
+
qtbot.wait(200)
|
154
|
+
final_pos_x = dev.samx.read()["samx"]["value"]
|
155
|
+
final_pos_y = dev.samy.read()["samy"]["value"]
|
156
|
+
|
157
|
+
# check plotted data
|
158
|
+
motor_map_data = motor_map.get_data()
|
159
|
+
|
160
|
+
np.testing.assert_equal(
|
161
|
+
[motor_map_data["x"][0], motor_map_data["y"][0]], [initial_pos_x, initial_pos_y]
|
162
|
+
)
|
163
|
+
np.testing.assert_equal(
|
164
|
+
[motor_map_data["x"][-1], motor_map_data["y"][-1]], [final_pos_x, final_pos_y]
|
165
|
+
)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
from bec_widgets.cli.client import BECFigure, BECImageShow, BECMotorMap, BECWaveform
|
4
|
+
|
5
|
+
|
6
|
+
def find_deepest_value(d: dict):
|
7
|
+
"""
|
8
|
+
Recursively find the deepest value in a dictionary
|
9
|
+
Args:
|
10
|
+
d(dict): Dictionary to search
|
11
|
+
|
12
|
+
Returns:
|
13
|
+
The deepest value in the dictionary.
|
14
|
+
"""
|
15
|
+
if isinstance(d, dict):
|
16
|
+
if d:
|
17
|
+
return find_deepest_value(next(iter(d.values())))
|
18
|
+
return d
|
19
|
+
|
20
|
+
|
21
|
+
def test_rpc_register_list_connections(rpc_server, rpc_register, qtbot):
|
22
|
+
fig = BECFigure(rpc_server.gui_id)
|
23
|
+
fig_server = rpc_server.fig
|
24
|
+
|
25
|
+
plt = fig.plot("samx", "bpm4i")
|
26
|
+
im = fig.image("eiger")
|
27
|
+
motor_map = fig.motor_map("samx", "samy")
|
28
|
+
plt_z = fig.add_plot("samx", "samy", "bpm4i")
|
29
|
+
|
30
|
+
all_connections = rpc_register.list_all_connections()
|
31
|
+
|
32
|
+
# Construct dict of all rpc items manually
|
33
|
+
all_subwidgets_expected = dict(fig_server.widgets)
|
34
|
+
curve_1D = find_deepest_value(fig_server.widgets[plt.rpc_id]._curves_data)
|
35
|
+
curve_2D = find_deepest_value(fig_server.widgets[plt_z.rpc_id]._curves_data)
|
36
|
+
curves_expected = {curve_1D.rpc_id: curve_1D, curve_2D.rpc_id: curve_2D}
|
37
|
+
fig_expected = {fig.rpc_id: fig_server}
|
38
|
+
image_item_expected = {
|
39
|
+
fig_server.widgets[im.rpc_id].images[0].rpc_id: fig_server.widgets[im.rpc_id].images[0]
|
40
|
+
}
|
41
|
+
|
42
|
+
all_connections_expected = {
|
43
|
+
**all_subwidgets_expected,
|
44
|
+
**curves_expected,
|
45
|
+
**fig_expected,
|
46
|
+
**image_item_expected,
|
47
|
+
}
|
48
|
+
|
49
|
+
assert len(all_connections) == 8
|
50
|
+
assert all_connections == all_connections_expected
|
tests/unit_tests/conftest.py
CHANGED
@@ -1,8 +1,15 @@
|
|
1
1
|
import pytest
|
2
2
|
|
3
|
+
from bec_widgets.cli.rpc_register import RPCRegister
|
3
4
|
from bec_widgets.utils import bec_dispatcher as bec_dispatcher_module
|
4
5
|
|
5
6
|
|
7
|
+
@pytest.fixture(autouse=True)
|
8
|
+
def rpc_register():
|
9
|
+
yield RPCRegister()
|
10
|
+
RPCRegister.reset_singleton()
|
11
|
+
|
12
|
+
|
6
13
|
@pytest.fixture(autouse=True)
|
7
14
|
def bec_dispatcher(threads_check):
|
8
15
|
bec_dispatcher = bec_dispatcher_module.BECDispatcher()
|
@@ -0,0 +1,52 @@
|
|
1
|
+
from bec_widgets.cli.rpc_register import RPCRegister
|
2
|
+
|
3
|
+
|
4
|
+
class FakeObject:
|
5
|
+
def __init__(self, gui_id):
|
6
|
+
self.gui_id = gui_id
|
7
|
+
|
8
|
+
|
9
|
+
def test_add_connection(rpc_register):
|
10
|
+
obj1 = FakeObject("id1")
|
11
|
+
obj2 = FakeObject("id2")
|
12
|
+
|
13
|
+
rpc_register.add_rpc(obj1)
|
14
|
+
rpc_register.add_rpc(obj2)
|
15
|
+
|
16
|
+
all_connections = rpc_register.list_all_connections()
|
17
|
+
|
18
|
+
assert len(all_connections) == 2
|
19
|
+
assert all_connections["id1"] == obj1
|
20
|
+
assert all_connections["id2"] == obj2
|
21
|
+
|
22
|
+
|
23
|
+
def test_remove_connection(rpc_register):
|
24
|
+
|
25
|
+
obj1 = FakeObject("id1")
|
26
|
+
obj2 = FakeObject("id2")
|
27
|
+
|
28
|
+
rpc_register.add_rpc(obj1)
|
29
|
+
rpc_register.add_rpc(obj2)
|
30
|
+
|
31
|
+
rpc_register.remove_rpc(obj1)
|
32
|
+
|
33
|
+
all_connections = rpc_register.list_all_connections()
|
34
|
+
|
35
|
+
assert len(all_connections) == 1
|
36
|
+
assert all_connections["id2"] == obj2
|
37
|
+
|
38
|
+
|
39
|
+
def test_reset_singleton(rpc_register):
|
40
|
+
obj1 = FakeObject("id1")
|
41
|
+
obj2 = FakeObject("id2")
|
42
|
+
|
43
|
+
rpc_register.add_rpc(obj1)
|
44
|
+
rpc_register.add_rpc(obj2)
|
45
|
+
|
46
|
+
rpc_register.reset_singleton()
|
47
|
+
rpc_register = RPCRegister()
|
48
|
+
|
49
|
+
all_connections = rpc_register.list_all_connections()
|
50
|
+
|
51
|
+
assert len(all_connections) == 0
|
52
|
+
assert all_connections == {}
|
File without changes
|
File without changes
|
File without changes
|