bec-widgets 0.53.3__py3-none-any.whl → 0.55.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.
- CHANGELOG.md +24 -26
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +265 -13
- bec_widgets/cli/client_utils.py +0 -3
- bec_widgets/cli/generate_cli.py +10 -5
- bec_widgets/cli/rpc_wigdet_handler.py +2 -1
- bec_widgets/cli/server.py +5 -7
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +11 -5
- bec_widgets/examples/motor_movement/motor_control_compilations.py +17 -16
- bec_widgets/widgets/__init__.py +1 -10
- bec_widgets/widgets/figure/figure.py +40 -23
- bec_widgets/widgets/figure/plots/__init__.py +0 -0
- bec_widgets/widgets/figure/plots/image/__init__.py +0 -0
- bec_widgets/widgets/{plots → figure/plots/image}/image.py +6 -416
- bec_widgets/widgets/figure/plots/image/image_item.py +277 -0
- bec_widgets/widgets/figure/plots/image/image_processor.py +152 -0
- bec_widgets/widgets/figure/plots/motor_map/__init__.py +0 -0
- bec_widgets/widgets/{plots → figure/plots/motor_map}/motor_map.py +2 -2
- bec_widgets/widgets/figure/plots/waveform/__init__.py +0 -0
- bec_widgets/widgets/{plots → figure/plots/waveform}/waveform.py +9 -222
- bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +227 -0
- bec_widgets/widgets/motor_control/__init__.py +0 -7
- bec_widgets/widgets/motor_control/motor_control.py +2 -948
- bec_widgets/widgets/motor_control/motor_table/__init__.py +0 -0
- bec_widgets/widgets/motor_control/motor_table/motor_table.py +483 -0
- bec_widgets/widgets/motor_control/movement_absolute/__init__.py +0 -0
- bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +157 -0
- bec_widgets/widgets/motor_control/movement_relative/__init__.py +0 -0
- bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +227 -0
- bec_widgets/widgets/motor_control/selection/__init__.py +0 -0
- bec_widgets/widgets/motor_control/selection/selection.py +110 -0
- bec_widgets/widgets/spiral_progress_bar/__init__.py +1 -0
- bec_widgets/widgets/spiral_progress_bar/ring.py +184 -0
- bec_widgets/widgets/spiral_progress_bar/spiral_progress_bar.py +594 -0
- {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/RECORD +56 -53
- docs/requirements.txt +1 -0
- pyproject.toml +1 -1
- tests/end-2-end/test_bec_dock_rpc_e2e.py +82 -1
- tests/end-2-end/test_bec_figure_rpc_e2e.py +4 -4
- tests/end-2-end/test_rpc_register_e2e.py +1 -1
- tests/unit_tests/test_bec_dock.py +1 -1
- tests/unit_tests/test_bec_figure.py +6 -4
- tests/unit_tests/test_bec_motor_map.py +2 -3
- tests/unit_tests/test_motor_control.py +6 -5
- tests/unit_tests/test_spiral_progress_bar.py +338 -0
- tests/unit_tests/test_waveform1d.py +13 -1
- bec_widgets/validation/__init__.py +0 -2
- bec_widgets/validation/monitor_config_validator.py +0 -258
- bec_widgets/widgets/monitor/__init__.py +0 -1
- bec_widgets/widgets/monitor/config_dialog.py +0 -574
- bec_widgets/widgets/monitor/config_dialog.ui +0 -210
- bec_widgets/widgets/monitor/example_configs/config_device.yaml +0 -60
- bec_widgets/widgets/monitor/example_configs/config_scans.yaml +0 -92
- bec_widgets/widgets/monitor/monitor.py +0 -845
- bec_widgets/widgets/monitor/tab_template.ui +0 -180
- bec_widgets/widgets/motor_map/__init__.py +0 -1
- bec_widgets/widgets/motor_map/motor_map.py +0 -594
- bec_widgets/widgets/plots/__init__.py +0 -4
- tests/unit_tests/test_bec_monitor.py +0 -220
- tests/unit_tests/test_config_dialog.py +0 -178
- tests/unit_tests/test_motor_map.py +0 -171
- tests/unit_tests/test_validator_errors.py +0 -110
- /bec_widgets/{cli → assets}/bec_widgets_icon.png +0 -0
- /bec_widgets/{examples/jupyter_console → assets}/terminal_icon.png +0 -0
- /bec_widgets/widgets/{plots → figure/plots}/plot_base.py +0 -0
- /bec_widgets/widgets/motor_control/{motor_control_table.ui → motor_table/motor_table.ui} +0 -0
- /bec_widgets/widgets/motor_control/{motor_control_absolute.ui → movement_absolute/movement_absolute.ui} +0 -0
- /bec_widgets/widgets/motor_control/{motor_control_relative.ui → movement_relative/movement_relative.ui} +0 -0
- /bec_widgets/widgets/motor_control/{motor_control_selection.ui → selection/selection.ui} +0 -0
- {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,338 @@
|
|
1
|
+
# pylint: disable=missing-function-docstring, missing-module-docstring, unused-import
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
from bec_lib.endpoints import MessageEndpoints
|
5
|
+
from pydantic import ValidationError
|
6
|
+
|
7
|
+
from bec_widgets.utils import Colors
|
8
|
+
from bec_widgets.widgets import SpiralProgressBar
|
9
|
+
from bec_widgets.widgets.spiral_progress_bar.ring import RingConfig, RingConnections
|
10
|
+
from bec_widgets.widgets.spiral_progress_bar.spiral_progress_bar import SpiralProgressBarConfig
|
11
|
+
|
12
|
+
from .client_mocks import mocked_client
|
13
|
+
|
14
|
+
|
15
|
+
@pytest.fixture
|
16
|
+
def spiral_progress_bar(qtbot, mocked_client):
|
17
|
+
widget = SpiralProgressBar(client=mocked_client)
|
18
|
+
qtbot.addWidget(widget)
|
19
|
+
qtbot.waitExposed(widget)
|
20
|
+
yield widget
|
21
|
+
widget.close()
|
22
|
+
|
23
|
+
|
24
|
+
def test_bar_init(spiral_progress_bar):
|
25
|
+
assert spiral_progress_bar is not None
|
26
|
+
assert spiral_progress_bar.client is not None
|
27
|
+
assert isinstance(spiral_progress_bar, SpiralProgressBar)
|
28
|
+
assert spiral_progress_bar.config.widget_class == "SpiralProgressBar"
|
29
|
+
assert spiral_progress_bar.config.gui_id is not None
|
30
|
+
assert spiral_progress_bar.gui_id == spiral_progress_bar.config.gui_id
|
31
|
+
|
32
|
+
|
33
|
+
def test_config_validation_num_of_bars():
|
34
|
+
config = SpiralProgressBarConfig(num_bars=100, min_num_bars=1, max_num_bars=10)
|
35
|
+
|
36
|
+
assert config.num_bars == 10
|
37
|
+
|
38
|
+
|
39
|
+
def test_config_validation_num_of_ring_error():
|
40
|
+
ring_config_0 = RingConfig(index=0)
|
41
|
+
ring_config_1 = RingConfig(index=1)
|
42
|
+
|
43
|
+
with pytest.raises(ValidationError) as excinfo:
|
44
|
+
SpiralProgressBarConfig(rings=[ring_config_0, ring_config_1], num_bars=1)
|
45
|
+
errors = excinfo.value.errors()
|
46
|
+
assert len(errors) == 1
|
47
|
+
assert errors[0]["type"] == "different number of configs"
|
48
|
+
assert "Length of rings configuration (2) does not match the number of bars (1)." in str(
|
49
|
+
excinfo.value
|
50
|
+
)
|
51
|
+
|
52
|
+
|
53
|
+
def test_config_validation_ring_indices_wrong_order():
|
54
|
+
ring_config_0 = RingConfig(index=2)
|
55
|
+
ring_config_1 = RingConfig(index=5)
|
56
|
+
|
57
|
+
with pytest.raises(ValidationError) as excinfo:
|
58
|
+
SpiralProgressBarConfig(rings=[ring_config_0, ring_config_1], num_bars=2)
|
59
|
+
errors = excinfo.value.errors()
|
60
|
+
assert len(errors) == 1
|
61
|
+
assert errors[0]["type"] == "wrong indices"
|
62
|
+
assert (
|
63
|
+
"Indices of ring configurations must be unique and in order from 0 to num_bars 2."
|
64
|
+
in str(excinfo.value)
|
65
|
+
)
|
66
|
+
|
67
|
+
|
68
|
+
def test_config_validation_ring_same_indices():
|
69
|
+
ring_config_0 = RingConfig(index=0)
|
70
|
+
ring_config_1 = RingConfig(index=0)
|
71
|
+
|
72
|
+
with pytest.raises(ValidationError) as excinfo:
|
73
|
+
SpiralProgressBarConfig(rings=[ring_config_0, ring_config_1], num_bars=2)
|
74
|
+
errors = excinfo.value.errors()
|
75
|
+
assert len(errors) == 1
|
76
|
+
assert errors[0]["type"] == "wrong indices"
|
77
|
+
assert (
|
78
|
+
"Indices of ring configurations must be unique and in order from 0 to num_bars 2."
|
79
|
+
in str(excinfo.value)
|
80
|
+
)
|
81
|
+
|
82
|
+
|
83
|
+
def test_config_validation_invalid_colormap():
|
84
|
+
with pytest.raises(ValueError) as excinfo:
|
85
|
+
SpiralProgressBarConfig(color_map="crazy_colors")
|
86
|
+
errors = excinfo.value.errors()
|
87
|
+
assert len(errors) == 1
|
88
|
+
assert errors[0]["type"] == "unsupported colormap"
|
89
|
+
assert "Colormap 'crazy_colors' not found in the current installation of pyqtgraph" in str(
|
90
|
+
excinfo.value
|
91
|
+
)
|
92
|
+
|
93
|
+
|
94
|
+
def test_ring_connection_endpoint_validation():
|
95
|
+
with pytest.raises(ValueError) as excinfo:
|
96
|
+
RingConnections(slot="on_scan_progress", endpoint="non_existing")
|
97
|
+
errors = excinfo.value.errors()
|
98
|
+
assert len(errors) == 1
|
99
|
+
assert errors[0]["type"] == "unsupported endpoint"
|
100
|
+
assert (
|
101
|
+
"For slot 'on_scan_progress', endpoint must be MessageEndpoint.scan_progress or 'scans/scan_progress'."
|
102
|
+
in str(excinfo.value)
|
103
|
+
)
|
104
|
+
|
105
|
+
with pytest.raises(ValueError) as excinfo:
|
106
|
+
RingConnections(slot="on_device_readback", endpoint="non_existing")
|
107
|
+
errors = excinfo.value.errors()
|
108
|
+
assert len(errors) == 1
|
109
|
+
assert errors[0]["type"] == "unsupported endpoint"
|
110
|
+
assert (
|
111
|
+
"For slot 'on_device_readback', endpoint must be MessageEndpoint.device_readback(device) or 'internal/devices/readback/{device}'."
|
112
|
+
in str(excinfo.value)
|
113
|
+
)
|
114
|
+
|
115
|
+
|
116
|
+
def test_bar_add_number_of_bars(spiral_progress_bar):
|
117
|
+
assert spiral_progress_bar.config.num_bars == 1
|
118
|
+
|
119
|
+
spiral_progress_bar.set_number_of_bars(5)
|
120
|
+
assert spiral_progress_bar.config.num_bars == 5
|
121
|
+
|
122
|
+
spiral_progress_bar.set_number_of_bars(2)
|
123
|
+
assert spiral_progress_bar.config.num_bars == 2
|
124
|
+
|
125
|
+
|
126
|
+
def test_add_remove_bars_individually(spiral_progress_bar):
|
127
|
+
spiral_progress_bar.add_ring()
|
128
|
+
spiral_progress_bar.add_ring()
|
129
|
+
|
130
|
+
assert spiral_progress_bar.config.num_bars == 3
|
131
|
+
assert len(spiral_progress_bar.config.rings) == 3
|
132
|
+
|
133
|
+
spiral_progress_bar.remove_ring(1)
|
134
|
+
assert spiral_progress_bar.config.num_bars == 2
|
135
|
+
assert len(spiral_progress_bar.config.rings) == 2
|
136
|
+
assert spiral_progress_bar.rings[0].config.index == 0
|
137
|
+
assert spiral_progress_bar.rings[1].config.index == 1
|
138
|
+
|
139
|
+
|
140
|
+
def test_bar_set_value(spiral_progress_bar):
|
141
|
+
spiral_progress_bar.set_number_of_bars(5)
|
142
|
+
|
143
|
+
assert spiral_progress_bar.config.num_bars == 5
|
144
|
+
assert len(spiral_progress_bar.config.rings) == 5
|
145
|
+
assert len(spiral_progress_bar.rings) == 5
|
146
|
+
|
147
|
+
spiral_progress_bar.set_value([10, 20, 30, 40, 50])
|
148
|
+
ring_values = [ring.value for ring in spiral_progress_bar.rings]
|
149
|
+
assert ring_values == [10, 20, 30, 40, 50]
|
150
|
+
|
151
|
+
# update just one bar
|
152
|
+
spiral_progress_bar.set_value(90, 1)
|
153
|
+
ring_values = [ring.value for ring in spiral_progress_bar.rings]
|
154
|
+
assert ring_values == [10, 90, 30, 40, 50]
|
155
|
+
|
156
|
+
|
157
|
+
def test_bar_set_precision(spiral_progress_bar):
|
158
|
+
spiral_progress_bar.set_number_of_bars(3)
|
159
|
+
|
160
|
+
assert spiral_progress_bar.config.num_bars == 3
|
161
|
+
assert len(spiral_progress_bar.config.rings) == 3
|
162
|
+
assert len(spiral_progress_bar.rings) == 3
|
163
|
+
|
164
|
+
spiral_progress_bar.set_precision(2)
|
165
|
+
ring_precision = [ring.config.precision for ring in spiral_progress_bar.rings]
|
166
|
+
assert ring_precision == [2, 2, 2]
|
167
|
+
|
168
|
+
spiral_progress_bar.set_value([10.1234, 20.1234, 30.1234])
|
169
|
+
ring_values = [ring.value for ring in spiral_progress_bar.rings]
|
170
|
+
assert ring_values == [10.12, 20.12, 30.12]
|
171
|
+
|
172
|
+
spiral_progress_bar.set_precision(4, 1)
|
173
|
+
ring_precision = [ring.config.precision for ring in spiral_progress_bar.rings]
|
174
|
+
assert ring_precision == [2, 4, 2]
|
175
|
+
|
176
|
+
spiral_progress_bar.set_value([10.1234, 20.1234, 30.1234])
|
177
|
+
ring_values = [ring.value for ring in spiral_progress_bar.rings]
|
178
|
+
assert ring_values == [10.12, 20.1234, 30.12]
|
179
|
+
|
180
|
+
|
181
|
+
def test_set_min_max_value(spiral_progress_bar):
|
182
|
+
spiral_progress_bar.set_number_of_bars(2)
|
183
|
+
|
184
|
+
spiral_progress_bar.set_min_max_values(0, 10)
|
185
|
+
ring_min_values = [ring.config.min_value for ring in spiral_progress_bar.rings]
|
186
|
+
ring_max_values = [ring.config.max_value for ring in spiral_progress_bar.rings]
|
187
|
+
|
188
|
+
assert ring_min_values == [0, 0]
|
189
|
+
assert ring_max_values == [10, 10]
|
190
|
+
|
191
|
+
spiral_progress_bar.set_value([5, 15])
|
192
|
+
ring_values = [ring.value for ring in spiral_progress_bar.rings]
|
193
|
+
assert ring_values == [5, 10]
|
194
|
+
|
195
|
+
|
196
|
+
def test_setup_colors_from_colormap(spiral_progress_bar):
|
197
|
+
spiral_progress_bar.set_number_of_bars(5)
|
198
|
+
spiral_progress_bar.set_colors_from_map("viridis", "RGB")
|
199
|
+
|
200
|
+
expected_colors = Colors.golden_angle_color("viridis", 5, "RGB")
|
201
|
+
converted_colors = [ring.color.getRgb() for ring in spiral_progress_bar.rings]
|
202
|
+
ring_config_colors = [ring.config.color for ring in spiral_progress_bar.rings]
|
203
|
+
|
204
|
+
assert expected_colors == converted_colors
|
205
|
+
assert ring_config_colors == expected_colors
|
206
|
+
|
207
|
+
|
208
|
+
def get_colors_from_rings(rings):
|
209
|
+
converted_colors = [ring.color.getRgb() for ring in rings]
|
210
|
+
ring_config_colors = [ring.config.color for ring in rings]
|
211
|
+
return converted_colors, ring_config_colors
|
212
|
+
|
213
|
+
|
214
|
+
def test_set_colors_from_colormap_and_change_num_of_bars(spiral_progress_bar):
|
215
|
+
spiral_progress_bar.set_number_of_bars(2)
|
216
|
+
spiral_progress_bar.set_colors_from_map("viridis", "RGB")
|
217
|
+
|
218
|
+
expected_colors = Colors.golden_angle_color("viridis", 2, "RGB")
|
219
|
+
converted_colors, ring_config_colors = get_colors_from_rings(spiral_progress_bar.rings)
|
220
|
+
|
221
|
+
assert expected_colors == converted_colors
|
222
|
+
assert ring_config_colors == expected_colors
|
223
|
+
|
224
|
+
# increase the number of bars to 6
|
225
|
+
spiral_progress_bar.set_number_of_bars(6)
|
226
|
+
expected_colors = Colors.golden_angle_color("viridis", 6, "RGB")
|
227
|
+
converted_colors, ring_config_colors = get_colors_from_rings(spiral_progress_bar.rings)
|
228
|
+
|
229
|
+
assert expected_colors == converted_colors
|
230
|
+
assert ring_config_colors == expected_colors
|
231
|
+
|
232
|
+
# decrease the number of bars to 3
|
233
|
+
spiral_progress_bar.set_number_of_bars(3)
|
234
|
+
expected_colors = Colors.golden_angle_color("viridis", 3, "RGB")
|
235
|
+
converted_colors, ring_config_colors = get_colors_from_rings(spiral_progress_bar.rings)
|
236
|
+
|
237
|
+
assert expected_colors == converted_colors
|
238
|
+
assert ring_config_colors == expected_colors
|
239
|
+
|
240
|
+
|
241
|
+
def test_set_colors_directly(spiral_progress_bar):
|
242
|
+
spiral_progress_bar.set_number_of_bars(3)
|
243
|
+
|
244
|
+
# setting as a list of rgb tuples
|
245
|
+
colors = [(255, 0, 0, 255), (0, 255, 0, 255), (0, 0, 255, 255)]
|
246
|
+
spiral_progress_bar.set_colors_directly(colors)
|
247
|
+
converted_colors = get_colors_from_rings(spiral_progress_bar.rings)[0]
|
248
|
+
|
249
|
+
assert colors == converted_colors
|
250
|
+
|
251
|
+
spiral_progress_bar.set_colors_directly((255, 0, 0, 255), 1)
|
252
|
+
converted_colors = get_colors_from_rings(spiral_progress_bar.rings)[0]
|
253
|
+
|
254
|
+
assert converted_colors == [(255, 0, 0, 255), (255, 0, 0, 255), (0, 0, 255, 255)]
|
255
|
+
|
256
|
+
|
257
|
+
def test_set_line_width(spiral_progress_bar):
|
258
|
+
spiral_progress_bar.set_number_of_bars(3)
|
259
|
+
|
260
|
+
spiral_progress_bar.set_line_widths(5)
|
261
|
+
line_widths = [ring.config.line_width for ring in spiral_progress_bar.rings]
|
262
|
+
|
263
|
+
assert line_widths == [5, 5, 5]
|
264
|
+
|
265
|
+
spiral_progress_bar.set_line_widths([10, 20, 30])
|
266
|
+
line_widths = [ring.config.line_width for ring in spiral_progress_bar.rings]
|
267
|
+
|
268
|
+
assert line_widths == [10, 20, 30]
|
269
|
+
|
270
|
+
spiral_progress_bar.set_line_widths(15, 1)
|
271
|
+
line_widths = [ring.config.line_width for ring in spiral_progress_bar.rings]
|
272
|
+
|
273
|
+
assert line_widths == [10, 15, 30]
|
274
|
+
|
275
|
+
|
276
|
+
def test_set_gap(spiral_progress_bar):
|
277
|
+
spiral_progress_bar.set_number_of_bars(3)
|
278
|
+
spiral_progress_bar.set_gap(20)
|
279
|
+
|
280
|
+
assert spiral_progress_bar.config.gap == 20
|
281
|
+
|
282
|
+
|
283
|
+
def test_auto_update(spiral_progress_bar):
|
284
|
+
spiral_progress_bar.enable_auto_updates(True)
|
285
|
+
|
286
|
+
scan_queue_status_scan_progress = {
|
287
|
+
"queue": {
|
288
|
+
"primary": {
|
289
|
+
"info": [{"active_request_block": {"report_instructions": [{"scan_progress": 10}]}}]
|
290
|
+
}
|
291
|
+
}
|
292
|
+
}
|
293
|
+
meta = {}
|
294
|
+
|
295
|
+
spiral_progress_bar.on_scan_queue_status(scan_queue_status_scan_progress, meta)
|
296
|
+
|
297
|
+
assert spiral_progress_bar._auto_updates is True
|
298
|
+
assert len(spiral_progress_bar._rings) == 1
|
299
|
+
assert spiral_progress_bar._rings[0].config.connections == RingConnections(
|
300
|
+
slot="on_scan_progress", endpoint=MessageEndpoints.scan_progress()
|
301
|
+
)
|
302
|
+
|
303
|
+
scan_queue_status_device_readback = {
|
304
|
+
"queue": {
|
305
|
+
"primary": {
|
306
|
+
"info": [
|
307
|
+
{
|
308
|
+
"active_request_block": {
|
309
|
+
"report_instructions": [
|
310
|
+
{
|
311
|
+
"readback": {
|
312
|
+
"devices": ["samx", "samy"],
|
313
|
+
"start": [1, 2],
|
314
|
+
"end": [10, 20],
|
315
|
+
}
|
316
|
+
}
|
317
|
+
]
|
318
|
+
}
|
319
|
+
}
|
320
|
+
]
|
321
|
+
}
|
322
|
+
}
|
323
|
+
}
|
324
|
+
spiral_progress_bar.on_scan_queue_status(scan_queue_status_device_readback, meta)
|
325
|
+
|
326
|
+
assert spiral_progress_bar._auto_updates is True
|
327
|
+
assert len(spiral_progress_bar._rings) == 2
|
328
|
+
assert spiral_progress_bar._rings[0].config.connections == RingConnections(
|
329
|
+
slot="on_device_readback", endpoint=MessageEndpoints.device_readback("samx")
|
330
|
+
)
|
331
|
+
assert spiral_progress_bar._rings[1].config.connections == RingConnections(
|
332
|
+
slot="on_device_readback", endpoint=MessageEndpoints.device_readback("samy")
|
333
|
+
)
|
334
|
+
|
335
|
+
assert spiral_progress_bar._rings[0].config.min_value == 1
|
336
|
+
assert spiral_progress_bar._rings[0].config.max_value == 10
|
337
|
+
assert spiral_progress_bar._rings[1].config.min_value == 2
|
338
|
+
assert spiral_progress_bar._rings[1].config.max_value == 20
|
@@ -4,7 +4,7 @@ from unittest.mock import MagicMock
|
|
4
4
|
import numpy as np
|
5
5
|
import pytest
|
6
6
|
|
7
|
-
from bec_widgets.widgets.plots.waveform import CurveConfig, Signal, SignalData
|
7
|
+
from bec_widgets.widgets.figure.plots.waveform.waveform_curve import CurveConfig, Signal, SignalData
|
8
8
|
|
9
9
|
from .client_mocks import mocked_client
|
10
10
|
from .test_bec_figure import bec_figure
|
@@ -304,6 +304,18 @@ def test_set_custom_curve_data(bec_figure, qtbot):
|
|
304
304
|
assert np.array_equal(y_new, [7, 8, 9])
|
305
305
|
|
306
306
|
|
307
|
+
def test_custom_data_2D_array(bec_figure, qtbot):
|
308
|
+
|
309
|
+
data = np.random.rand(10, 2)
|
310
|
+
|
311
|
+
plt = bec_figure.plot(data)
|
312
|
+
|
313
|
+
x, y = plt.curves[0].get_data()
|
314
|
+
|
315
|
+
assert np.array_equal(x, data[:, 0])
|
316
|
+
assert np.array_equal(y, data[:, 1])
|
317
|
+
|
318
|
+
|
307
319
|
def test_get_all_data(bec_figure):
|
308
320
|
w1 = bec_figure.add_plot()
|
309
321
|
|
@@ -1,258 +0,0 @@
|
|
1
|
-
from typing import Literal, Optional, Union
|
2
|
-
|
3
|
-
from pydantic import BaseModel, Field, ValidationError, field_validator, model_validator
|
4
|
-
from pydantic_core import PydanticCustomError
|
5
|
-
|
6
|
-
|
7
|
-
class Signal(BaseModel):
|
8
|
-
"""
|
9
|
-
Represents a signal in a plot configuration.
|
10
|
-
|
11
|
-
Args:
|
12
|
-
name (str): The name of the signal.
|
13
|
-
entry (Optional[str]): The entry point of the signal, optional.
|
14
|
-
"""
|
15
|
-
|
16
|
-
name: str
|
17
|
-
entry: Optional[str] = Field(None, validate_default=True)
|
18
|
-
|
19
|
-
@model_validator(mode="before")
|
20
|
-
@classmethod
|
21
|
-
def validate_fields(cls, values):
|
22
|
-
"""Validate the fields of the model.
|
23
|
-
First validate the 'name' field, then validate the 'entry' field.
|
24
|
-
|
25
|
-
Args:
|
26
|
-
values (dict): The values to be validated."""
|
27
|
-
devices = MonitorConfigValidator.devices
|
28
|
-
|
29
|
-
# Validate 'name'
|
30
|
-
name = values.get("name")
|
31
|
-
|
32
|
-
# Check if device name provided
|
33
|
-
if name is None:
|
34
|
-
raise PydanticCustomError(
|
35
|
-
"no_device_name", "Device name must be provided", {"wrong_value": name}
|
36
|
-
)
|
37
|
-
# Check if device exists in BEC
|
38
|
-
if name not in devices:
|
39
|
-
raise PydanticCustomError(
|
40
|
-
"no_device_bec",
|
41
|
-
'Device "{wrong_value}" not found in current BEC session',
|
42
|
-
{"wrong_value": name},
|
43
|
-
)
|
44
|
-
|
45
|
-
device = devices[name] # get the device to check if it has signals
|
46
|
-
|
47
|
-
# Get device description
|
48
|
-
description = device.describe()
|
49
|
-
|
50
|
-
# Validate 'entry'
|
51
|
-
entry = values.get("entry")
|
52
|
-
|
53
|
-
# Set entry based on hints if not provided
|
54
|
-
if entry is None:
|
55
|
-
entry = next(iter(device._hints), name) if hasattr(device, "_hints") else name
|
56
|
-
if entry not in description:
|
57
|
-
raise PydanticCustomError(
|
58
|
-
"no_entry_for_device",
|
59
|
-
'Entry "{wrong_value}" not found in device "{device_name}" signals',
|
60
|
-
{"wrong_value": entry, "device_name": name},
|
61
|
-
)
|
62
|
-
|
63
|
-
values["entry"] = entry
|
64
|
-
return values
|
65
|
-
|
66
|
-
|
67
|
-
class AxisSignal(BaseModel):
|
68
|
-
"""
|
69
|
-
Configuration signal axis for a single plot.
|
70
|
-
Attributes:
|
71
|
-
x (list): Signal for the X axis.
|
72
|
-
y (list): Signals for the Y axis.
|
73
|
-
"""
|
74
|
-
|
75
|
-
x: list[Signal] = Field(default_factory=list)
|
76
|
-
y: list[Signal] = Field(default_factory=list)
|
77
|
-
|
78
|
-
@field_validator("x")
|
79
|
-
@classmethod
|
80
|
-
def validate_x_signals(cls, v):
|
81
|
-
"""Ensure that there is only one signal for x-axis."""
|
82
|
-
if len(v) != 1:
|
83
|
-
raise PydanticCustomError(
|
84
|
-
"x_axis_multiple_signals",
|
85
|
-
'There must be exactly one signal for x axis. Number of x signals: "{wrong_value}"',
|
86
|
-
{"wrong_value": v},
|
87
|
-
)
|
88
|
-
|
89
|
-
return v
|
90
|
-
|
91
|
-
|
92
|
-
class SourceHistoryValidator(BaseModel):
|
93
|
-
"""History source validator
|
94
|
-
Attributes:
|
95
|
-
type (str): type of source - history
|
96
|
-
scan_id (str): Scan ID for history source.
|
97
|
-
signals (list): Signal for the source.
|
98
|
-
"""
|
99
|
-
|
100
|
-
type: Literal["history"]
|
101
|
-
scan_id: str # TODO can be validated if it is a valid scan_id
|
102
|
-
signals: AxisSignal
|
103
|
-
|
104
|
-
|
105
|
-
class SourceSegmentValidator(BaseModel):
|
106
|
-
"""Scan Segment source validator
|
107
|
-
Attributes:
|
108
|
-
type (str): type of source - scan_segment
|
109
|
-
signals (AxisSignal): Signal for the source.
|
110
|
-
"""
|
111
|
-
|
112
|
-
type: Literal["scan_segment"]
|
113
|
-
signals: AxisSignal
|
114
|
-
|
115
|
-
|
116
|
-
class SourceRedisValidator(BaseModel):
|
117
|
-
"""Scan Segment source validator
|
118
|
-
Attributes:
|
119
|
-
type (str): type of source - scan_segment
|
120
|
-
endpoint (str): Endpoint reference in redis.
|
121
|
-
update (str): Update type.
|
122
|
-
"""
|
123
|
-
|
124
|
-
type: Literal["redis"]
|
125
|
-
endpoint: str
|
126
|
-
update: str
|
127
|
-
signals: dict
|
128
|
-
|
129
|
-
|
130
|
-
class Source(BaseModel): # TODO decide if it should stay for general Source validation
|
131
|
-
"""
|
132
|
-
General source validation, includes all Optional arguments of all other sources.
|
133
|
-
Attributes:
|
134
|
-
type (list): type of source (scan_segment, history)
|
135
|
-
scan_id (Optional[str]): Scan ID for history source.
|
136
|
-
signals (Optional[AxisSignal]): Signal for the source.
|
137
|
-
"""
|
138
|
-
|
139
|
-
type: Literal["scan_segment", "history", "redis"]
|
140
|
-
scan_id: Optional[str] = None
|
141
|
-
signals: Optional[dict] = None
|
142
|
-
|
143
|
-
|
144
|
-
class PlotConfig(BaseModel):
|
145
|
-
"""
|
146
|
-
Configuration for a single plot.
|
147
|
-
|
148
|
-
Attributes:
|
149
|
-
plot_name (Optional[str]): Name of the plot.
|
150
|
-
x_label (Optional[str]): The label for the x-axis.
|
151
|
-
y_label (Optional[str]): The label for the y-axis.
|
152
|
-
sources (list): A list of sources to be plotted on this axis.
|
153
|
-
"""
|
154
|
-
|
155
|
-
plot_name: Optional[str] = None
|
156
|
-
x_label: Optional[str] = None
|
157
|
-
y_label: Optional[str] = None
|
158
|
-
sources: list = Field(default_factory=list)
|
159
|
-
|
160
|
-
@field_validator("sources")
|
161
|
-
@classmethod
|
162
|
-
def validate_sources(cls, values):
|
163
|
-
"""Validate the sources of the plot configuration, based on the type of source."""
|
164
|
-
validated_sources = []
|
165
|
-
for source in values:
|
166
|
-
# Check if source type is supported
|
167
|
-
Source(**source)
|
168
|
-
source_type = source.get("type", None)
|
169
|
-
|
170
|
-
# Validate source based on type
|
171
|
-
if source_type == "scan_segment":
|
172
|
-
validated_sources.append(SourceSegmentValidator(**source))
|
173
|
-
elif source_type == "history":
|
174
|
-
validated_sources.append(SourceHistoryValidator(**source))
|
175
|
-
elif source_type == "redis":
|
176
|
-
validated_sources.append(SourceRedisValidator(**source))
|
177
|
-
return validated_sources
|
178
|
-
|
179
|
-
|
180
|
-
class PlotSettings(BaseModel):
|
181
|
-
"""
|
182
|
-
Global settings for plotting affecting mostly visuals.
|
183
|
-
|
184
|
-
Attributes:
|
185
|
-
background_color (str): Color of the plot background. Default is black.
|
186
|
-
axis_width (Optional[int]): Width of the plot axes. Default is 2.
|
187
|
-
axis_color (Optional[str]): Color of the plot axes. Default is None.
|
188
|
-
num_columns (int): Number of columns in the plot layout. Default is 1.
|
189
|
-
colormap (str): Colormap to be used. Default is magma.
|
190
|
-
scan_types (bool): Indicates if the configuration is for different scan types. Default is False.
|
191
|
-
"""
|
192
|
-
|
193
|
-
background_color: Literal["black", "white"] = "black"
|
194
|
-
axis_width: Optional[int] = 2
|
195
|
-
axis_color: Optional[str] = None
|
196
|
-
num_columns: Optional[int] = 1
|
197
|
-
colormap: Optional[str] = "magma"
|
198
|
-
scan_types: Optional[bool] = False
|
199
|
-
|
200
|
-
|
201
|
-
class DeviceMonitorConfig(BaseModel):
|
202
|
-
"""
|
203
|
-
Configuration model for the device monitor mode.
|
204
|
-
|
205
|
-
Attributes:
|
206
|
-
plot_settings (PlotSettings): Global settings for plotting.
|
207
|
-
plot_data (list[PlotConfig]): List of plot configurations.
|
208
|
-
"""
|
209
|
-
|
210
|
-
plot_settings: PlotSettings
|
211
|
-
plot_data: list[PlotConfig]
|
212
|
-
|
213
|
-
|
214
|
-
class ScanModeConfig(BaseModel):
|
215
|
-
"""
|
216
|
-
Configuration model for scan mode.
|
217
|
-
|
218
|
-
Attributes:
|
219
|
-
plot_settings (PlotSettings): Global settings for plotting.
|
220
|
-
plot_data (dict[str, list[PlotConfig]]): Dictionary of plot configurations,
|
221
|
-
keyed by scan type.
|
222
|
-
"""
|
223
|
-
|
224
|
-
plot_settings: PlotSettings
|
225
|
-
plot_data: dict[str, list[PlotConfig]]
|
226
|
-
|
227
|
-
|
228
|
-
class MonitorConfigValidator:
|
229
|
-
"""Validates the configuration data for the BECMonitor."""
|
230
|
-
|
231
|
-
devices = None
|
232
|
-
|
233
|
-
def __init__(self, devices):
|
234
|
-
# self.device_manager = device_manager
|
235
|
-
MonitorConfigValidator.devices = devices
|
236
|
-
|
237
|
-
def validate_monitor_config(
|
238
|
-
self, config_data: dict
|
239
|
-
) -> Union[DeviceMonitorConfig, ScanModeConfig]:
|
240
|
-
"""
|
241
|
-
Validates the configuration data based on the provided schema.
|
242
|
-
|
243
|
-
Args:
|
244
|
-
config_data (dict): Configuration data to be validated.
|
245
|
-
|
246
|
-
Returns:
|
247
|
-
Union[DeviceMonitorConfig, ScanModeConfig]: Validated configuration object.
|
248
|
-
|
249
|
-
Raises:
|
250
|
-
ValidationError: If the configuration data does not conform to the schema.
|
251
|
-
"""
|
252
|
-
config_type = config_data.get("plot_settings", {}).get("scan_types", False)
|
253
|
-
if config_type:
|
254
|
-
validated_config = ScanModeConfig(**config_data)
|
255
|
-
else:
|
256
|
-
validated_config = DeviceMonitorConfig(**config_data)
|
257
|
-
|
258
|
-
return validated_config
|
@@ -1 +0,0 @@
|
|
1
|
-
from .monitor import BECMonitor
|