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
@@ -1,220 +0,0 @@
|
|
1
|
-
# pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
|
2
|
-
import os
|
3
|
-
from unittest.mock import MagicMock
|
4
|
-
|
5
|
-
import pytest
|
6
|
-
import yaml
|
7
|
-
|
8
|
-
from bec_widgets.widgets import BECMonitor
|
9
|
-
|
10
|
-
from .client_mocks import mocked_client
|
11
|
-
|
12
|
-
|
13
|
-
def load_test_config(config_name):
|
14
|
-
"""Helper function to load config from yaml file."""
|
15
|
-
config_path = os.path.join(os.path.dirname(__file__), "test_configs", f"{config_name}.yaml")
|
16
|
-
with open(config_path, "r") as f:
|
17
|
-
config = yaml.safe_load(f)
|
18
|
-
return config
|
19
|
-
|
20
|
-
|
21
|
-
@pytest.fixture(scope="function")
|
22
|
-
def monitor(bec_dispatcher, qtbot, mocked_client):
|
23
|
-
# client = MagicMock()
|
24
|
-
widget = BECMonitor(client=mocked_client)
|
25
|
-
qtbot.addWidget(widget)
|
26
|
-
qtbot.waitExposed(widget)
|
27
|
-
yield widget
|
28
|
-
|
29
|
-
|
30
|
-
@pytest.mark.parametrize(
|
31
|
-
"config_name, scan_type, number_of_plots",
|
32
|
-
[
|
33
|
-
("config_device", False, 2),
|
34
|
-
("config_device_no_entry", False, 2),
|
35
|
-
# ("config_scan", True, 4),
|
36
|
-
],
|
37
|
-
)
|
38
|
-
def test_initialization_with_device_config(monitor, config_name, scan_type, number_of_plots):
|
39
|
-
config = load_test_config(config_name)
|
40
|
-
monitor.on_config_update(config)
|
41
|
-
assert isinstance(monitor, BECMonitor)
|
42
|
-
assert monitor.client is not None
|
43
|
-
assert len(monitor.plot_data) == number_of_plots
|
44
|
-
assert monitor.scan_types == scan_type
|
45
|
-
|
46
|
-
|
47
|
-
@pytest.mark.parametrize(
|
48
|
-
"config_initial,config_update",
|
49
|
-
[("config_device", "config_scan"), ("config_scan", "config_device")],
|
50
|
-
)
|
51
|
-
def test_on_config_update(monitor, config_initial, config_update):
|
52
|
-
config_initial = load_test_config(config_initial)
|
53
|
-
config_update = load_test_config(config_update)
|
54
|
-
# validated config has to be compared
|
55
|
-
config_initial_validated = monitor.validator.validate_monitor_config(
|
56
|
-
config_initial
|
57
|
-
).model_dump()
|
58
|
-
config_update_validated = monitor.validator.validate_monitor_config(config_update).model_dump()
|
59
|
-
monitor.on_config_update(config_initial)
|
60
|
-
assert monitor.config == config_initial_validated
|
61
|
-
monitor.on_config_update(config_update)
|
62
|
-
assert monitor.config == config_update_validated
|
63
|
-
|
64
|
-
|
65
|
-
@pytest.mark.parametrize(
|
66
|
-
"config_name, expected_num_columns, expected_plot_names, expected_coordinates",
|
67
|
-
[
|
68
|
-
("config_device", 1, ["BPM4i plots vs samx", "Gauss plots vs samx"], [(0, 0), (1, 0)]),
|
69
|
-
(
|
70
|
-
"config_scan",
|
71
|
-
3,
|
72
|
-
["Grid plot 1", "Grid plot 2", "Grid plot 3", "Grid plot 4"],
|
73
|
-
[(0, 0), (0, 1), (0, 2), (1, 0)],
|
74
|
-
),
|
75
|
-
],
|
76
|
-
)
|
77
|
-
def test_render_initial_plots(
|
78
|
-
monitor, config_name, expected_num_columns, expected_plot_names, expected_coordinates
|
79
|
-
):
|
80
|
-
config = load_test_config(config_name)
|
81
|
-
monitor.on_config_update(config)
|
82
|
-
|
83
|
-
# Validate number of columns
|
84
|
-
assert monitor.plot_settings["num_columns"] == expected_num_columns
|
85
|
-
|
86
|
-
# Validate the plots are created correctly
|
87
|
-
for expected_name in expected_plot_names:
|
88
|
-
assert expected_name in monitor.plots.keys()
|
89
|
-
|
90
|
-
# Validate the grid_coordinates
|
91
|
-
assert monitor.grid_coordinates == expected_coordinates
|
92
|
-
|
93
|
-
|
94
|
-
def mock_getitem(dev_name):
|
95
|
-
"""Helper function to mock the __getitem__ method of the 'dev'."""
|
96
|
-
mock_instance = MagicMock()
|
97
|
-
if dev_name == "samx":
|
98
|
-
mock_instance._hints = "samx"
|
99
|
-
elif dev_name == "bpm4i":
|
100
|
-
mock_instance._hints = "bpm4i"
|
101
|
-
elif dev_name == "gauss_bpm":
|
102
|
-
mock_instance._hints = "gauss_bpm"
|
103
|
-
|
104
|
-
return mock_instance
|
105
|
-
|
106
|
-
|
107
|
-
def mock_get_scan_storage(scan_id, data):
|
108
|
-
"""Helper function to mock the __getitem__ method of the 'dev'."""
|
109
|
-
mock_instance = MagicMock()
|
110
|
-
mock_instance.get_scan_storage.return_value = data
|
111
|
-
return mock_instance
|
112
|
-
|
113
|
-
|
114
|
-
# mocked messages and metadata
|
115
|
-
msg_1 = {
|
116
|
-
"data": {
|
117
|
-
"samx": {"samx": {"value": 10}},
|
118
|
-
"bpm4i": {"bpm4i": {"value": 5}},
|
119
|
-
"gauss_bpm": {"gauss_bpm": {"value": 6}},
|
120
|
-
"gauss_adc1": {"gauss_adc1": {"value": 8}},
|
121
|
-
"gauss_adc2": {"gauss_adc2": {"value": 9}},
|
122
|
-
},
|
123
|
-
"scan_id": 1,
|
124
|
-
}
|
125
|
-
metadata_grid = {"scan_name": "grid_scan"}
|
126
|
-
metadata_line = {"scan_name": "line_scan"}
|
127
|
-
|
128
|
-
|
129
|
-
@pytest.mark.parametrize(
|
130
|
-
"config_name, msg, metadata, expected_data",
|
131
|
-
[
|
132
|
-
# case: msg does not have 'scan_id'
|
133
|
-
(
|
134
|
-
"config_device",
|
135
|
-
{"data": {}},
|
136
|
-
{},
|
137
|
-
{
|
138
|
-
"scan_segment": {
|
139
|
-
"bpm4i": {"bpm4i": []},
|
140
|
-
"gauss_adc1": {"gauss_adc1": []},
|
141
|
-
"gauss_adc2": {"gauss_adc2": []},
|
142
|
-
"samx": {"samx": []},
|
143
|
-
}
|
144
|
-
},
|
145
|
-
),
|
146
|
-
# case: scan_types is false, msg contains all valid fields, and entry is present in config
|
147
|
-
(
|
148
|
-
"config_device",
|
149
|
-
msg_1,
|
150
|
-
{},
|
151
|
-
{
|
152
|
-
"scan_segment": {
|
153
|
-
"bpm4i": {"bpm4i": [5]},
|
154
|
-
"gauss_adc1": {"gauss_adc1": [8]},
|
155
|
-
"gauss_adc2": {"gauss_adc2": [9]},
|
156
|
-
"samx": {"samx": [10]},
|
157
|
-
}
|
158
|
-
},
|
159
|
-
),
|
160
|
-
# case: scan_types is false, msg contains all valid fields and entry is missing in config, should use hints
|
161
|
-
(
|
162
|
-
"config_device_no_entry",
|
163
|
-
msg_1,
|
164
|
-
{},
|
165
|
-
{
|
166
|
-
"scan_segment": {
|
167
|
-
"bpm4i": {"bpm4i": [5]},
|
168
|
-
"gauss_bpm": {"gauss_bpm": [6]},
|
169
|
-
"samx": {"samx": [10]},
|
170
|
-
}
|
171
|
-
},
|
172
|
-
),
|
173
|
-
# case: scan_types is true, msg contains all valid fields, metadata contains scan "line_scan:"
|
174
|
-
(
|
175
|
-
"config_scan",
|
176
|
-
msg_1,
|
177
|
-
metadata_line,
|
178
|
-
{
|
179
|
-
"scan_segment": {
|
180
|
-
"bpm4i": {"bpm4i": [5]},
|
181
|
-
"gauss_adc1": {"gauss_adc1": [8]},
|
182
|
-
"gauss_adc2": {"gauss_adc2": [9]},
|
183
|
-
"gauss_bpm": {"gauss_bpm": [6]},
|
184
|
-
"samx": {"samx": [10]},
|
185
|
-
}
|
186
|
-
},
|
187
|
-
),
|
188
|
-
(
|
189
|
-
"config_scan",
|
190
|
-
msg_1,
|
191
|
-
metadata_grid,
|
192
|
-
{
|
193
|
-
"scan_segment": {
|
194
|
-
"bpm4i": {"bpm4i": [5]},
|
195
|
-
"gauss_adc1": {"gauss_adc1": [8]},
|
196
|
-
"gauss_adc2": {"gauss_adc2": [9]},
|
197
|
-
"gauss_bpm": {"gauss_bpm": [6]},
|
198
|
-
"samx": {"samx": [10]},
|
199
|
-
}
|
200
|
-
},
|
201
|
-
),
|
202
|
-
],
|
203
|
-
)
|
204
|
-
def test_on_scan_segment(monitor, config_name, msg, metadata, expected_data):
|
205
|
-
config = load_test_config(config_name)
|
206
|
-
monitor.on_config_update(config)
|
207
|
-
|
208
|
-
# Mock scan_storage.find_scan_by_ID
|
209
|
-
mock_scan_data = MagicMock()
|
210
|
-
mock_scan_data.data = {
|
211
|
-
device_name: {
|
212
|
-
entry: MagicMock(val=[msg["data"][device_name][entry]["value"]])
|
213
|
-
for entry in msg["data"][device_name]
|
214
|
-
}
|
215
|
-
for device_name in msg["data"]
|
216
|
-
}
|
217
|
-
monitor.queue.scan_storage.find_scan_by_ID.return_value = mock_scan_data
|
218
|
-
|
219
|
-
monitor.on_scan_segment(msg, metadata)
|
220
|
-
assert monitor.database == expected_data
|
@@ -1,178 +0,0 @@
|
|
1
|
-
# pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
|
2
|
-
import os
|
3
|
-
from unittest.mock import MagicMock
|
4
|
-
|
5
|
-
import pytest
|
6
|
-
import yaml
|
7
|
-
from qtpy.QtWidgets import QTableWidgetItem, QTabWidget
|
8
|
-
|
9
|
-
from bec_widgets.widgets.monitor.config_dialog import ConfigDialog
|
10
|
-
|
11
|
-
from .client_mocks import mocked_client
|
12
|
-
|
13
|
-
|
14
|
-
def load_test_config(config_name):
|
15
|
-
"""Helper function to load config from yaml file."""
|
16
|
-
config_path = os.path.join(os.path.dirname(__file__), "test_configs", f"{config_name}.yaml")
|
17
|
-
with open(config_path, "r") as f:
|
18
|
-
config = yaml.safe_load(f)
|
19
|
-
return config
|
20
|
-
|
21
|
-
|
22
|
-
@pytest.fixture(scope="function")
|
23
|
-
def config_dialog(qtbot, mocked_client):
|
24
|
-
client = mocked_client
|
25
|
-
widget = ConfigDialog(client=client)
|
26
|
-
qtbot.addWidget(widget)
|
27
|
-
qtbot.waitExposed(widget)
|
28
|
-
yield widget
|
29
|
-
|
30
|
-
|
31
|
-
@pytest.mark.parametrize("config_name", ["config_device", "config_scan"])
|
32
|
-
def test_load_config(config_dialog, config_name):
|
33
|
-
config = load_test_config(config_name)
|
34
|
-
config_dialog.load_config(config)
|
35
|
-
|
36
|
-
assert (
|
37
|
-
config_dialog.comboBox_appearance.currentText()
|
38
|
-
== config["plot_settings"]["background_color"]
|
39
|
-
)
|
40
|
-
assert config_dialog.spinBox_n_column.value() == config["plot_settings"]["num_columns"]
|
41
|
-
assert config_dialog.comboBox_colormap.currentText() == config["plot_settings"]["colormap"]
|
42
|
-
|
43
|
-
|
44
|
-
@pytest.mark.parametrize(
|
45
|
-
"config_name, scan_mode",
|
46
|
-
[("config_device", False), ("config_scan", True), ("config_device_no_entry", False)],
|
47
|
-
)
|
48
|
-
def test_initialization(config_dialog, config_name, scan_mode):
|
49
|
-
config = load_test_config(config_name)
|
50
|
-
config_dialog.load_config(config)
|
51
|
-
|
52
|
-
assert isinstance(config_dialog, ConfigDialog)
|
53
|
-
assert (
|
54
|
-
config_dialog.comboBox_appearance.currentText()
|
55
|
-
== config["plot_settings"]["background_color"]
|
56
|
-
)
|
57
|
-
assert config_dialog.spinBox_n_column.value() == config["plot_settings"]["num_columns"]
|
58
|
-
assert (config_dialog.comboBox_scanTypes.currentText() == "Enabled") == scan_mode
|
59
|
-
assert (
|
60
|
-
config_dialog.tabWidget_scan_types.count() > 0
|
61
|
-
) # Ensures there's at least one tab created
|
62
|
-
|
63
|
-
# If there's a need to check the contents of the first tab (there has to be always at least one tab)
|
64
|
-
first_tab = config_dialog.tabWidget_scan_types.widget(0)
|
65
|
-
if scan_mode:
|
66
|
-
assert (
|
67
|
-
first_tab.findChild(QTabWidget, "tabWidget_plots") is not None
|
68
|
-
) # Ensures plot tab widget exists in scan mode
|
69
|
-
else:
|
70
|
-
assert (
|
71
|
-
first_tab.findChild(QTabWidget) is not None
|
72
|
-
) # Ensures plot tab widget exists in default mode
|
73
|
-
|
74
|
-
|
75
|
-
def test_edit_and_apply_config(config_dialog):
|
76
|
-
config_device = load_test_config("config_device")
|
77
|
-
config_dialog.load_config(config_device)
|
78
|
-
|
79
|
-
config_dialog.comboBox_appearance.setCurrentText("white")
|
80
|
-
config_dialog.spinBox_n_column.setValue(2)
|
81
|
-
config_dialog.comboBox_colormap.setCurrentText("viridis")
|
82
|
-
|
83
|
-
applied_config = config_dialog.apply_config()
|
84
|
-
|
85
|
-
assert applied_config["plot_settings"]["background_color"] == "white"
|
86
|
-
assert applied_config["plot_settings"]["num_columns"] == 2
|
87
|
-
assert applied_config["plot_settings"]["colormap"] == "viridis"
|
88
|
-
|
89
|
-
|
90
|
-
def test_edit_and_apply_config_scan_mode(config_dialog):
|
91
|
-
config_scan = load_test_config("config_scan")
|
92
|
-
config_dialog.load_config(config_scan)
|
93
|
-
|
94
|
-
config_dialog.comboBox_appearance.setCurrentText("white")
|
95
|
-
config_dialog.spinBox_n_column.setValue(2)
|
96
|
-
config_dialog.comboBox_colormap.setCurrentText("viridis")
|
97
|
-
config_dialog.comboBox_scanTypes.setCurrentText("Enabled")
|
98
|
-
|
99
|
-
applied_config = config_dialog.apply_config()
|
100
|
-
|
101
|
-
assert applied_config["plot_settings"]["background_color"] == "white"
|
102
|
-
assert applied_config["plot_settings"]["num_columns"] == 2
|
103
|
-
assert applied_config["plot_settings"]["colormap"] == "viridis"
|
104
|
-
assert applied_config["plot_settings"]["scan_types"] is True
|
105
|
-
|
106
|
-
|
107
|
-
def test_add_new_scan(config_dialog):
|
108
|
-
# Ensure the tab count is initially 1 (from the default config)
|
109
|
-
assert config_dialog.tabWidget_scan_types.count() == 1
|
110
|
-
|
111
|
-
# Add a new scan tab
|
112
|
-
config_dialog.add_new_scan_tab(config_dialog.tabWidget_scan_types, "Test Scan Tab")
|
113
|
-
|
114
|
-
# Ensure the tab count is now 2
|
115
|
-
assert config_dialog.tabWidget_scan_types.count() == 2
|
116
|
-
|
117
|
-
# Ensure the new tab has the correct name
|
118
|
-
assert config_dialog.tabWidget_scan_types.tabText(1) == "Test Scan Tab"
|
119
|
-
|
120
|
-
|
121
|
-
def test_add_new_plot_and_modify(config_dialog):
|
122
|
-
# Ensure the tab count is initially 1 and it is called "Default"
|
123
|
-
assert config_dialog.tabWidget_scan_types.count() == 1
|
124
|
-
assert config_dialog.tabWidget_scan_types.tabText(0) == "Default"
|
125
|
-
|
126
|
-
# Get the first tab (which should be a scan tab)
|
127
|
-
scan_tab = config_dialog.tabWidget_scan_types.widget(0)
|
128
|
-
|
129
|
-
# Ensure the plot tab count is initially 1 and it is called "Plot 1"
|
130
|
-
tabWidget_plots = scan_tab.findChild(QTabWidget)
|
131
|
-
assert tabWidget_plots.count() == 1
|
132
|
-
assert tabWidget_plots.tabText(0) == "Plot 1"
|
133
|
-
|
134
|
-
# Add a new plot tab
|
135
|
-
config_dialog.add_new_plot_tab(scan_tab)
|
136
|
-
|
137
|
-
# Ensure the plot tab count is now 2
|
138
|
-
assert tabWidget_plots.count() == 2
|
139
|
-
|
140
|
-
# Ensure the new tab has the correct name
|
141
|
-
assert tabWidget_plots.tabText(1) == "Plot 2"
|
142
|
-
|
143
|
-
# Access the new plot tab
|
144
|
-
new_plot_tab = tabWidget_plots.widget(1)
|
145
|
-
|
146
|
-
# Modify the line edits within the new plot tab
|
147
|
-
new_plot_tab.ui.lineEdit_plot_title.setText("Modified Plot Title")
|
148
|
-
new_plot_tab.ui.lineEdit_x_label.setText("Modified X Label")
|
149
|
-
new_plot_tab.ui.lineEdit_y_label.setText("Modified Y Label")
|
150
|
-
new_plot_tab.ui.lineEdit_x_name.setText("Modified X Name")
|
151
|
-
new_plot_tab.ui.lineEdit_x_entry.setText("Modified X Entry")
|
152
|
-
|
153
|
-
# Modify the table for signals
|
154
|
-
config_dialog.add_new_signal(new_plot_tab.ui.tableWidget_y_signals)
|
155
|
-
|
156
|
-
table = new_plot_tab.ui.tableWidget_y_signals
|
157
|
-
assert table.rowCount() == 1 # Ensure the new row is added
|
158
|
-
|
159
|
-
row_position = table.rowCount() - 1
|
160
|
-
|
161
|
-
# Modify the first row
|
162
|
-
table.setItem(row_position, 0, QTableWidgetItem("New Signal Name"))
|
163
|
-
table.setItem(row_position, 1, QTableWidgetItem("New Signal Entry"))
|
164
|
-
|
165
|
-
# Apply the configuration
|
166
|
-
config = config_dialog.apply_config()
|
167
|
-
|
168
|
-
# Check if the modifications are reflected in the configuration
|
169
|
-
modified_plot_config = config["plot_data"][1] # Access the second plot in the plot_data list
|
170
|
-
sources = modified_plot_config["sources"][0] # Access the first source in the sources list
|
171
|
-
|
172
|
-
assert modified_plot_config["plot_name"] == "Modified Plot Title"
|
173
|
-
assert modified_plot_config["x_label"] == "Modified X Label"
|
174
|
-
assert modified_plot_config["y_label"] == "Modified Y Label"
|
175
|
-
assert sources["signals"]["x"][0]["name"] == "Modified X Name"
|
176
|
-
assert sources["signals"]["x"][0]["entry"] == "Modified X Entry"
|
177
|
-
assert sources["signals"]["y"][0]["name"] == "New Signal Name"
|
178
|
-
assert sources["signals"]["y"][0]["entry"] == "New Signal Entry"
|
@@ -1,171 +0,0 @@
|
|
1
|
-
# pylint: disable = no-name-in-module,missing-module-docstring, missing-function-docstring
|
2
|
-
from unittest.mock import MagicMock
|
3
|
-
|
4
|
-
import pytest
|
5
|
-
|
6
|
-
from bec_widgets.widgets import MotorMap
|
7
|
-
|
8
|
-
from .client_mocks import mocked_client
|
9
|
-
|
10
|
-
CONFIG_DEFAULT = {
|
11
|
-
"plot_settings": {
|
12
|
-
"colormap": "Greys",
|
13
|
-
"scatter_size": 5,
|
14
|
-
"max_points": 1000,
|
15
|
-
"num_dim_points": 100,
|
16
|
-
"precision": 2,
|
17
|
-
"num_columns": 1,
|
18
|
-
"background_value": 25,
|
19
|
-
},
|
20
|
-
"motors": [
|
21
|
-
{
|
22
|
-
"plot_name": "Motor Map",
|
23
|
-
"x_label": "Motor X",
|
24
|
-
"y_label": "Motor Y",
|
25
|
-
"signals": {
|
26
|
-
"x": [{"name": "samx", "entry": "samx"}],
|
27
|
-
"y": [{"name": "samy", "entry": "samy"}],
|
28
|
-
},
|
29
|
-
},
|
30
|
-
{
|
31
|
-
"plot_name": "Motor Map 2 ",
|
32
|
-
"x_label": "Motor X",
|
33
|
-
"y_label": "Motor Y",
|
34
|
-
"signals": {
|
35
|
-
"x": [{"name": "aptrx", "entry": "aptrx"}],
|
36
|
-
"y": [{"name": "aptry", "entry": "aptry"}],
|
37
|
-
},
|
38
|
-
},
|
39
|
-
],
|
40
|
-
}
|
41
|
-
|
42
|
-
CONFIG_ONE_DEVICE = {
|
43
|
-
"plot_settings": {
|
44
|
-
"colormap": "Greys",
|
45
|
-
"scatter_size": 5,
|
46
|
-
"max_points": 1000,
|
47
|
-
"num_dim_points": 100,
|
48
|
-
"precision": 2,
|
49
|
-
"num_columns": 1,
|
50
|
-
"background_value": 25,
|
51
|
-
},
|
52
|
-
"motors": [
|
53
|
-
{
|
54
|
-
"plot_name": "Motor Map",
|
55
|
-
"x_label": "Motor X",
|
56
|
-
"y_label": "Motor Y",
|
57
|
-
"signals": {
|
58
|
-
"x": [{"name": "samx", "entry": "samx"}],
|
59
|
-
"y": [{"name": "samy", "entry": "samy"}],
|
60
|
-
},
|
61
|
-
}
|
62
|
-
],
|
63
|
-
}
|
64
|
-
|
65
|
-
|
66
|
-
@pytest.fixture(scope="function")
|
67
|
-
def motor_map(qtbot, mocked_client):
|
68
|
-
widget = MotorMap(client=mocked_client)
|
69
|
-
qtbot.addWidget(widget)
|
70
|
-
qtbot.waitExposed(widget)
|
71
|
-
yield widget
|
72
|
-
|
73
|
-
|
74
|
-
def test_motor_limits_initialization(motor_map):
|
75
|
-
# Example test to check if motor limits are correctly initialized
|
76
|
-
expected_limits = {"samx": [-10, 10], "samy": [-5, 5]}
|
77
|
-
for motor_name, expected_limit in expected_limits.items():
|
78
|
-
actual_limit = motor_map._get_motor_limit(motor_name)
|
79
|
-
assert actual_limit == expected_limit
|
80
|
-
|
81
|
-
|
82
|
-
def test_motor_initial_position(motor_map):
|
83
|
-
motor_map.precision = 2
|
84
|
-
|
85
|
-
motor_map_dev = motor_map.client.device_manager.devices
|
86
|
-
|
87
|
-
# Example test to check if motor initial positions are correctly initialized
|
88
|
-
expected_positions = {
|
89
|
-
("samx", "samx"): motor_map_dev["samx"].read()["samx"]["value"],
|
90
|
-
("samy", "samy"): motor_map_dev["samy"].read()["samy"]["value"],
|
91
|
-
("aptrx", "aptrx"): motor_map_dev["aptrx"].read()["aptrx"]["value"],
|
92
|
-
("aptry", "aptry"): motor_map_dev["aptry"].read()["aptry"]["value"],
|
93
|
-
}
|
94
|
-
for (motor_name, entry), expected_position in expected_positions.items():
|
95
|
-
actual_position = motor_map._get_motor_init_position(motor_name, entry)
|
96
|
-
assert actual_position == expected_position
|
97
|
-
|
98
|
-
|
99
|
-
@pytest.mark.parametrize("config, number_of_plots", [(CONFIG_DEFAULT, 2), (CONFIG_ONE_DEVICE, 1)])
|
100
|
-
def test_initialization(motor_map, config, number_of_plots):
|
101
|
-
config_load = config
|
102
|
-
motor_map.on_config_update(config_load)
|
103
|
-
assert isinstance(motor_map, MotorMap)
|
104
|
-
assert motor_map.client is not None
|
105
|
-
assert motor_map.config == config_load
|
106
|
-
assert len(motor_map.plot_data) == number_of_plots
|
107
|
-
|
108
|
-
|
109
|
-
def test_motor_movement_updates_position_and_database(motor_map):
|
110
|
-
motor_map.on_config_update(CONFIG_DEFAULT)
|
111
|
-
|
112
|
-
# Initial positions
|
113
|
-
initial_position_samx = 2.0
|
114
|
-
initial_position_samy = 3.0
|
115
|
-
|
116
|
-
# Set initial positions in the mocked database
|
117
|
-
motor_map.database["samx"]["samx"] = [initial_position_samx]
|
118
|
-
motor_map.database["samy"]["samy"] = [initial_position_samy]
|
119
|
-
|
120
|
-
# Simulate motor movement for 'samx' only
|
121
|
-
new_position_samx = 4.0
|
122
|
-
motor_map.on_device_readback({"signals": {"samx": {"value": new_position_samx}}})
|
123
|
-
|
124
|
-
# Verify database update for 'samx'
|
125
|
-
assert motor_map.database["samx"]["samx"] == [initial_position_samx, new_position_samx]
|
126
|
-
|
127
|
-
# Verify 'samy' retains its last known position
|
128
|
-
assert motor_map.database["samy"]["samy"] == [initial_position_samy, initial_position_samy]
|
129
|
-
|
130
|
-
|
131
|
-
def test_scatter_plot_rendering(motor_map):
|
132
|
-
motor_map.on_config_update(CONFIG_DEFAULT)
|
133
|
-
# Set initial positions
|
134
|
-
initial_position_samx = 2.0
|
135
|
-
initial_position_samy = 3.0
|
136
|
-
motor_map.database["samx"]["samx"] = [initial_position_samx]
|
137
|
-
motor_map.database["samy"]["samy"] = [initial_position_samy]
|
138
|
-
|
139
|
-
# Simulate motor movement for 'samx' only
|
140
|
-
new_position_samx = 4.0
|
141
|
-
motor_map.on_device_readback({"signals": {"samx": {"value": new_position_samx}}})
|
142
|
-
motor_map._update_plots()
|
143
|
-
|
144
|
-
# Get the scatter plot item
|
145
|
-
plot_name = "Motor Map" # Update as per your actual plot name
|
146
|
-
scatter_plot_item = motor_map.curves_data[plot_name]["pos"]
|
147
|
-
|
148
|
-
# Check the scatter plot item properties
|
149
|
-
assert len(scatter_plot_item.data) > 0, "Scatter plot data is empty"
|
150
|
-
x_data = scatter_plot_item.data["x"]
|
151
|
-
y_data = scatter_plot_item.data["y"]
|
152
|
-
assert x_data[-1] == new_position_samx, "Scatter plot X data not updated correctly"
|
153
|
-
assert (
|
154
|
-
y_data[-1] == initial_position_samy
|
155
|
-
), "Scatter plot Y data should retain last known position"
|
156
|
-
|
157
|
-
|
158
|
-
def test_plot_visualization_consistency(motor_map):
|
159
|
-
motor_map.on_config_update(CONFIG_DEFAULT)
|
160
|
-
# Simulate updating the plot with new data
|
161
|
-
motor_map.on_device_readback({"signals": {"samx": {"value": 5}}})
|
162
|
-
motor_map.on_device_readback({"signals": {"samy": {"value": 9}}})
|
163
|
-
motor_map._update_plots()
|
164
|
-
|
165
|
-
plot_name = "Motor Map"
|
166
|
-
scatter_plot_item = motor_map.curves_data[plot_name]["pos"]
|
167
|
-
|
168
|
-
# Check if the scatter plot reflects the new data correctly
|
169
|
-
assert (
|
170
|
-
scatter_plot_item.data["x"][-1] == 5 and scatter_plot_item.data["y"][-1] == 9
|
171
|
-
), "Plot not updated correctly with new data"
|
@@ -1,110 +0,0 @@
|
|
1
|
-
# pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
|
2
|
-
import pytest
|
3
|
-
from pydantic import ValidationError
|
4
|
-
|
5
|
-
from bec_widgets.validation.monitor_config_validator import (
|
6
|
-
AxisSignal,
|
7
|
-
MonitorConfigValidator,
|
8
|
-
PlotConfig,
|
9
|
-
Signal,
|
10
|
-
)
|
11
|
-
|
12
|
-
from .test_bec_monitor import mocked_client
|
13
|
-
|
14
|
-
|
15
|
-
@pytest.fixture(scope="function")
|
16
|
-
def setup_devices(mocked_client):
|
17
|
-
MonitorConfigValidator.devices = mocked_client.device_manager.devices
|
18
|
-
|
19
|
-
|
20
|
-
def test_signal_validation_name_missing(setup_devices):
|
21
|
-
with pytest.raises(ValidationError) as excinfo:
|
22
|
-
Signal(name=None)
|
23
|
-
errors = excinfo.value.errors()
|
24
|
-
assert len(errors) == 1
|
25
|
-
assert errors[0]["type"] == "no_device_name"
|
26
|
-
assert "Device name must be provided" in str(excinfo.value)
|
27
|
-
|
28
|
-
|
29
|
-
def test_signal_validation_name_not_in_bec(setup_devices):
|
30
|
-
with pytest.raises(ValidationError) as excinfo:
|
31
|
-
Signal(name="non_existent_device")
|
32
|
-
errors = excinfo.value.errors()
|
33
|
-
assert len(errors) == 1
|
34
|
-
assert errors[0]["type"] == "no_device_bec"
|
35
|
-
assert 'Device "non_existent_device" not found in current BEC session' in str(excinfo.value)
|
36
|
-
|
37
|
-
|
38
|
-
def test_signal_validation_entry_not_in_device(setup_devices):
|
39
|
-
with pytest.raises(ValidationError) as excinfo:
|
40
|
-
Signal(name="samx", entry="non_existent_entry")
|
41
|
-
|
42
|
-
errors = excinfo.value.errors()
|
43
|
-
assert len(errors) == 1
|
44
|
-
assert errors[0]["type"] == "no_entry_for_device"
|
45
|
-
assert 'Entry "non_existent_entry" not found in device "samx" signals' in errors[0]["msg"]
|
46
|
-
|
47
|
-
|
48
|
-
def test_signal_validation_success(setup_devices):
|
49
|
-
signal = Signal(name="samx")
|
50
|
-
assert signal.name == "samx"
|
51
|
-
|
52
|
-
|
53
|
-
def test_plot_config_x_axis_signal_validation(setup_devices):
|
54
|
-
# Setup a valid signal
|
55
|
-
valid_signal = Signal(name="samx")
|
56
|
-
|
57
|
-
with pytest.raises(ValidationError) as excinfo:
|
58
|
-
AxisSignal(x=[valid_signal, valid_signal], y=[valid_signal, valid_signal])
|
59
|
-
|
60
|
-
errors = excinfo.value.errors()
|
61
|
-
assert len(errors) == 1
|
62
|
-
assert errors[0]["type"] == "x_axis_multiple_signals"
|
63
|
-
assert "There must be exactly one signal for x axis" in errors[0]["msg"]
|
64
|
-
|
65
|
-
|
66
|
-
def test_plot_config_unsupported_source_type(setup_devices):
|
67
|
-
with pytest.raises(ValidationError) as excinfo:
|
68
|
-
PlotConfig(sources=[{"type": "unsupported_type", "signals": {}}])
|
69
|
-
|
70
|
-
errors = excinfo.value.errors()
|
71
|
-
print(errors)
|
72
|
-
assert len(errors) == 1
|
73
|
-
assert errors[0]["type"] == "literal_error"
|
74
|
-
|
75
|
-
|
76
|
-
def test_plot_config_no_source_type_provided(setup_devices):
|
77
|
-
with pytest.raises(ValidationError) as excinfo:
|
78
|
-
PlotConfig(sources=[{"signals": {}}])
|
79
|
-
|
80
|
-
errors = excinfo.value.errors()
|
81
|
-
assert len(errors) == 1
|
82
|
-
assert errors[0]["type"] == "missing"
|
83
|
-
|
84
|
-
|
85
|
-
def test_plot_config_history_source_type(setup_devices):
|
86
|
-
history_source = {
|
87
|
-
"type": "history",
|
88
|
-
"scan_id": "valid_scan_id",
|
89
|
-
"signals": {"x": [{"name": "samx"}], "y": [{"name": "samx"}]},
|
90
|
-
}
|
91
|
-
|
92
|
-
plot_config = PlotConfig(sources=[history_source])
|
93
|
-
|
94
|
-
assert len(plot_config.sources) == 1
|
95
|
-
assert plot_config.sources[0].type == "history"
|
96
|
-
assert plot_config.sources[0].scan_id == "valid_scan_id"
|
97
|
-
|
98
|
-
|
99
|
-
def test_plot_config_redis_source_type(setup_devices):
|
100
|
-
history_source = {
|
101
|
-
"type": "redis",
|
102
|
-
"endpoint": "valid_endpoint",
|
103
|
-
"update": "append",
|
104
|
-
"signals": {"x": [{"name": "samx"}], "y": [{"name": "samx"}]},
|
105
|
-
}
|
106
|
-
|
107
|
-
plot_config = PlotConfig(sources=[history_source])
|
108
|
-
|
109
|
-
assert len(plot_config.sources) == 1
|
110
|
-
assert plot_config.sources[0].type == "redis"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|