bec-widgets 0.53.2__py3-none-any.whl → 0.54.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 -25
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +13 -13
- bec_widgets/cli/client_utils.py +0 -4
- bec_widgets/cli/generate_cli.py +7 -5
- bec_widgets/cli/server.py +5 -7
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +7 -3
- bec_widgets/examples/motor_movement/motor_control_compilations.py +17 -16
- bec_widgets/widgets/__init__.py +0 -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-0.53.2.dist-info → bec_widgets-0.54.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.53.2.dist-info → bec_widgets-0.54.0.dist-info}/RECORD +51 -52
- docs/requirements.txt +1 -0
- pyproject.toml +1 -1
- tests/end-2-end/test_bec_dock_rpc_e2e.py +1 -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_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.2.dist-info → bec_widgets-0.54.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.53.2.dist-info → bec_widgets-0.54.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,574 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
|
3
|
-
from pydantic import ValidationError
|
4
|
-
from qtpy import uic
|
5
|
-
from qtpy.QtCore import Signal as pyqtSignal
|
6
|
-
from qtpy.QtWidgets import (
|
7
|
-
QApplication,
|
8
|
-
QLineEdit,
|
9
|
-
QMessageBox,
|
10
|
-
QTableWidget,
|
11
|
-
QTableWidgetItem,
|
12
|
-
QTabWidget,
|
13
|
-
QVBoxLayout,
|
14
|
-
QWidget,
|
15
|
-
)
|
16
|
-
|
17
|
-
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
18
|
-
from bec_widgets.utils.yaml_dialog import load_yaml, save_yaml
|
19
|
-
from bec_widgets.validation import MonitorConfigValidator
|
20
|
-
|
21
|
-
current_path = os.path.dirname(__file__)
|
22
|
-
Ui_Form, BaseClass = uic.loadUiType(os.path.join(current_path, "config_dialog.ui"))
|
23
|
-
Tab_Ui_Form, Tab_BaseClass = uic.loadUiType(os.path.join(current_path, "tab_template.ui"))
|
24
|
-
|
25
|
-
# test configs for demonstration purpose
|
26
|
-
|
27
|
-
# Configuration for default mode when only devices are monitored
|
28
|
-
CONFIG_DEFAULT = {
|
29
|
-
"plot_settings": {
|
30
|
-
"background_color": "black",
|
31
|
-
"num_columns": 1,
|
32
|
-
"colormap": "plasma",
|
33
|
-
"scan_types": False,
|
34
|
-
},
|
35
|
-
"plot_data": [
|
36
|
-
{
|
37
|
-
"plot_name": "BPM4i plots vs samx",
|
38
|
-
"x_label": "Motor Y",
|
39
|
-
"y_label": "bpm4i",
|
40
|
-
"sources": [
|
41
|
-
{
|
42
|
-
"type": "scan_segment",
|
43
|
-
"signals": {
|
44
|
-
"x": [{"name": "samx", "entry": "samx"}],
|
45
|
-
"y": [{"name": "bpm4i", "entry": "bpm4i"}],
|
46
|
-
},
|
47
|
-
}
|
48
|
-
],
|
49
|
-
},
|
50
|
-
{
|
51
|
-
"plot_name": "Gauss plots vs samx",
|
52
|
-
"x_label": "Motor X",
|
53
|
-
"y_label": "Gauss",
|
54
|
-
"sources": [
|
55
|
-
{
|
56
|
-
"type": "scan_segment",
|
57
|
-
"signals": {
|
58
|
-
"x": [{"name": "samx", "entry": "samx"}],
|
59
|
-
"y": [
|
60
|
-
{"name": "gauss_bpm"},
|
61
|
-
{"name": "gauss_adc1"},
|
62
|
-
{"name": "gauss_adc2"},
|
63
|
-
],
|
64
|
-
},
|
65
|
-
}
|
66
|
-
],
|
67
|
-
},
|
68
|
-
],
|
69
|
-
}
|
70
|
-
|
71
|
-
# Configuration which is dynamically changing depending on the scan type
|
72
|
-
CONFIG_SCAN_MODE = {
|
73
|
-
"plot_settings": {
|
74
|
-
"background_color": "white",
|
75
|
-
"num_columns": 3,
|
76
|
-
"colormap": "plasma",
|
77
|
-
"scan_types": True,
|
78
|
-
},
|
79
|
-
"plot_data": {
|
80
|
-
"grid_scan": [
|
81
|
-
{
|
82
|
-
"plot_name": "Grid plot 1",
|
83
|
-
"x_label": "Motor X",
|
84
|
-
"y_label": "BPM",
|
85
|
-
"sources": [
|
86
|
-
{
|
87
|
-
"type": "scan_segment",
|
88
|
-
"signals": {
|
89
|
-
"x": [{"name": "samx", "entry": "samx"}],
|
90
|
-
"y": [{"name": "gauss_bpm"}],
|
91
|
-
},
|
92
|
-
}
|
93
|
-
],
|
94
|
-
},
|
95
|
-
{
|
96
|
-
"plot_name": "Grid plot 2",
|
97
|
-
"x_label": "Motor X",
|
98
|
-
"y_label": "BPM",
|
99
|
-
"sources": [
|
100
|
-
{
|
101
|
-
"type": "scan_segment",
|
102
|
-
"signals": {
|
103
|
-
"x": [{"name": "samx", "entry": "samx"}],
|
104
|
-
"y": [{"name": "gauss_adc1"}],
|
105
|
-
},
|
106
|
-
}
|
107
|
-
],
|
108
|
-
},
|
109
|
-
{
|
110
|
-
"plot_name": "Grid plot 3",
|
111
|
-
"x_label": "Motor X",
|
112
|
-
"y_label": "BPM",
|
113
|
-
"sources": [
|
114
|
-
{
|
115
|
-
"type": "scan_segment",
|
116
|
-
"signals": {"x": [{"name": "samy"}], "y": [{"name": "gauss_adc2"}]},
|
117
|
-
}
|
118
|
-
],
|
119
|
-
},
|
120
|
-
{
|
121
|
-
"plot_name": "Grid plot 4",
|
122
|
-
"x_label": "Motor X",
|
123
|
-
"y_label": "BPM",
|
124
|
-
"sources": [
|
125
|
-
{
|
126
|
-
"type": "scan_segment",
|
127
|
-
"signals": {
|
128
|
-
"x": [{"name": "samy", "entry": "samy"}],
|
129
|
-
"y": [{"name": "gauss_adc3"}],
|
130
|
-
},
|
131
|
-
}
|
132
|
-
],
|
133
|
-
},
|
134
|
-
],
|
135
|
-
"line_scan": [
|
136
|
-
{
|
137
|
-
"plot_name": "BPM plots vs samx",
|
138
|
-
"x_label": "Motor X",
|
139
|
-
"y_label": "Gauss",
|
140
|
-
"sources": [
|
141
|
-
{
|
142
|
-
"type": "scan_segment",
|
143
|
-
"signals": {
|
144
|
-
"x": [{"name": "samx", "entry": "samx"}],
|
145
|
-
"y": [{"name": "bpm4i"}],
|
146
|
-
},
|
147
|
-
}
|
148
|
-
],
|
149
|
-
},
|
150
|
-
{
|
151
|
-
"plot_name": "Gauss plots vs samx",
|
152
|
-
"x_label": "Motor X",
|
153
|
-
"y_label": "Gauss",
|
154
|
-
"sources": [
|
155
|
-
{
|
156
|
-
"type": "scan_segment",
|
157
|
-
"signals": {
|
158
|
-
"x": [{"name": "samx", "entry": "samx"}],
|
159
|
-
"y": [{"name": "gauss_bpm"}, {"name": "gauss_adc1"}],
|
160
|
-
},
|
161
|
-
}
|
162
|
-
],
|
163
|
-
},
|
164
|
-
],
|
165
|
-
},
|
166
|
-
}
|
167
|
-
|
168
|
-
|
169
|
-
class ConfigDialog(QWidget, Ui_Form):
|
170
|
-
config_updated = pyqtSignal(dict)
|
171
|
-
|
172
|
-
def __init__(self, client=None, default_config=None, skip_validation: bool = False):
|
173
|
-
super(ConfigDialog, self).__init__()
|
174
|
-
self.setupUi(self)
|
175
|
-
|
176
|
-
# Client
|
177
|
-
bec_dispatcher = BECDispatcher()
|
178
|
-
self.client = bec_dispatcher.client if client is None else client
|
179
|
-
self.dev = self.client.device_manager.devices
|
180
|
-
|
181
|
-
# Init validator
|
182
|
-
self.skip_validation = skip_validation
|
183
|
-
if self.skip_validation is False:
|
184
|
-
self.validator = MonitorConfigValidator(self.dev)
|
185
|
-
|
186
|
-
# Connect the Ok/Apply/Cancel buttons
|
187
|
-
self.pushButton_ok.clicked.connect(self.apply_and_close)
|
188
|
-
self.pushButton_apply.clicked.connect(self.apply_config)
|
189
|
-
self.pushButton_cancel.clicked.connect(self.close)
|
190
|
-
|
191
|
-
# Hook signals top level
|
192
|
-
self.pushButton_new_scan_type.clicked.connect(
|
193
|
-
lambda: self.generate_empty_scan_tab(
|
194
|
-
self.tabWidget_scan_types, self.lineEdit_scan_type.text()
|
195
|
-
)
|
196
|
-
)
|
197
|
-
|
198
|
-
# Load/save yaml file buttons
|
199
|
-
self.pushButton_import.clicked.connect(self.load_config_from_yaml)
|
200
|
-
self.pushButton_export.clicked.connect(self.save_config_to_yaml)
|
201
|
-
|
202
|
-
# Scan Types changed
|
203
|
-
self.comboBox_scanTypes.currentIndexChanged.connect(self._init_default)
|
204
|
-
|
205
|
-
# Make scan tabs closable
|
206
|
-
self.tabWidget_scan_types.tabCloseRequested.connect(self.handle_tab_close_request)
|
207
|
-
|
208
|
-
# Init functions to make a default dialog
|
209
|
-
if default_config is None:
|
210
|
-
self._init_default()
|
211
|
-
else:
|
212
|
-
self.load_config(default_config)
|
213
|
-
|
214
|
-
def _init_default(self):
|
215
|
-
"""Init default dialog"""
|
216
|
-
|
217
|
-
if self.comboBox_scanTypes.currentText() == "Disabled": # Default mode
|
218
|
-
self.add_new_scan_tab(self.tabWidget_scan_types, "Default")
|
219
|
-
self.add_new_plot_tab(self.tabWidget_scan_types.widget(0))
|
220
|
-
self.pushButton_new_scan_type.setEnabled(False)
|
221
|
-
self.lineEdit_scan_type.setEnabled(False)
|
222
|
-
else: # Scan mode with clear tab
|
223
|
-
self.pushButton_new_scan_type.setEnabled(True)
|
224
|
-
self.lineEdit_scan_type.setEnabled(True)
|
225
|
-
self.tabWidget_scan_types.clear()
|
226
|
-
|
227
|
-
def add_new_scan_tab(
|
228
|
-
self, parent_tab: QTabWidget, scan_name: str, closable: bool = False
|
229
|
-
) -> QWidget:
|
230
|
-
"""
|
231
|
-
Add a new scan tab to the parent tab widget
|
232
|
-
|
233
|
-
Args:
|
234
|
-
parent_tab(QTabWidget): Parent tab widget, where to add scan tab
|
235
|
-
scan_name(str): Scan name
|
236
|
-
closable(bool): If True, the scan tab will be closable
|
237
|
-
|
238
|
-
Returns:
|
239
|
-
scan_tab(QWidget): Scan tab widget
|
240
|
-
"""
|
241
|
-
# Check for an existing tab with the same name
|
242
|
-
for index in range(parent_tab.count()):
|
243
|
-
if parent_tab.tabText(index) == scan_name:
|
244
|
-
print(f'Scan name "{scan_name}" already exists.')
|
245
|
-
return None # or return the existing tab: return parent_tab.widget(index)
|
246
|
-
|
247
|
-
# Create a new scan tab
|
248
|
-
scan_tab = QWidget()
|
249
|
-
scan_tab_layout = QVBoxLayout(scan_tab)
|
250
|
-
|
251
|
-
# Set a tab widget for plots
|
252
|
-
tabWidget_plots = QTabWidget()
|
253
|
-
tabWidget_plots.setObjectName("tabWidget_plots") # TODO decide if needed to give a name
|
254
|
-
tabWidget_plots.setTabsClosable(True)
|
255
|
-
tabWidget_plots.tabCloseRequested.connect(self.handle_tab_close_request)
|
256
|
-
scan_tab_layout.addWidget(tabWidget_plots)
|
257
|
-
|
258
|
-
# Add scan tab
|
259
|
-
parent_tab.addTab(scan_tab, scan_name)
|
260
|
-
|
261
|
-
# Make tabs closable
|
262
|
-
if closable:
|
263
|
-
parent_tab.setTabsClosable(closable)
|
264
|
-
|
265
|
-
return scan_tab
|
266
|
-
|
267
|
-
def add_new_plot_tab(self, scan_tab: QWidget) -> QWidget:
|
268
|
-
"""
|
269
|
-
Add a new plot tab to the scan tab
|
270
|
-
|
271
|
-
Args:
|
272
|
-
scan_tab (QWidget): Scan tab widget
|
273
|
-
|
274
|
-
Returns:
|
275
|
-
plot_tab (QWidget): Plot tab
|
276
|
-
"""
|
277
|
-
|
278
|
-
# Create a new plot tab from .ui template
|
279
|
-
plot_tab = QWidget()
|
280
|
-
plot_tab_ui = Tab_Ui_Form()
|
281
|
-
plot_tab_ui.setupUi(plot_tab)
|
282
|
-
plot_tab.ui = plot_tab_ui
|
283
|
-
|
284
|
-
# Add plot to current scan tab
|
285
|
-
tabWidget_plots = scan_tab.findChild(
|
286
|
-
QTabWidget, "tabWidget_plots"
|
287
|
-
) # TODO decide if putting name is needed
|
288
|
-
plot_name = f"Plot {tabWidget_plots.count() + 1}"
|
289
|
-
tabWidget_plots.addTab(plot_tab, plot_name)
|
290
|
-
|
291
|
-
# Hook signal
|
292
|
-
self.hook_plot_tab_signals(scan_tab=scan_tab, plot_tab=plot_tab.ui)
|
293
|
-
|
294
|
-
return plot_tab
|
295
|
-
|
296
|
-
def hook_plot_tab_signals(self, scan_tab: QTabWidget, plot_tab: Tab_Ui_Form) -> None:
|
297
|
-
"""
|
298
|
-
Hook signals of the plot tab
|
299
|
-
Args:
|
300
|
-
scan_tab(QTabWidget): Scan tab widget
|
301
|
-
plot_tab(Tab_Ui_Form): Plot tab widget
|
302
|
-
"""
|
303
|
-
plot_tab.pushButton_add_new_plot.clicked.connect(
|
304
|
-
lambda: self.add_new_plot_tab(scan_tab=scan_tab)
|
305
|
-
)
|
306
|
-
plot_tab.pushButton_y_new.clicked.connect(
|
307
|
-
lambda: self.add_new_signal(plot_tab.tableWidget_y_signals)
|
308
|
-
)
|
309
|
-
|
310
|
-
def add_new_signal(self, table: QTableWidget) -> None:
|
311
|
-
"""
|
312
|
-
Add a new signal to the table
|
313
|
-
|
314
|
-
Args:
|
315
|
-
table(QTableWidget): Table widget
|
316
|
-
"""
|
317
|
-
|
318
|
-
row_position = table.rowCount()
|
319
|
-
table.insertRow(row_position)
|
320
|
-
table.setItem(row_position, 0, QTableWidgetItem(""))
|
321
|
-
table.setItem(row_position, 1, QTableWidgetItem(""))
|
322
|
-
|
323
|
-
def handle_tab_close_request(self, index: int) -> None:
|
324
|
-
"""
|
325
|
-
Handle tab close request
|
326
|
-
|
327
|
-
Args:
|
328
|
-
index(int): Index of the tab to be closed
|
329
|
-
"""
|
330
|
-
|
331
|
-
parent_tab = self.sender()
|
332
|
-
if parent_tab.count() > 1: # ensure there is at least one tab
|
333
|
-
parent_tab.removeTab(index)
|
334
|
-
|
335
|
-
def generate_empty_scan_tab(self, parent_tab: QTabWidget, scan_name: str):
|
336
|
-
"""
|
337
|
-
Generate an empty scan tab
|
338
|
-
|
339
|
-
Args:
|
340
|
-
parent_tab (QTabWidget): Parent tab widget where to add the scan tab
|
341
|
-
scan_name(str): name of the scan tab
|
342
|
-
"""
|
343
|
-
|
344
|
-
scan_tab = self.add_new_scan_tab(parent_tab, scan_name, closable=True)
|
345
|
-
if scan_tab is not None:
|
346
|
-
self.add_new_plot_tab(scan_tab)
|
347
|
-
|
348
|
-
def get_plot_config(self, plot_tab: QWidget) -> dict:
|
349
|
-
"""
|
350
|
-
Get plot configuration from the plot tab adn send it as dict
|
351
|
-
|
352
|
-
Args:
|
353
|
-
plot_tab(QWidget): Plot tab widget
|
354
|
-
|
355
|
-
Returns:
|
356
|
-
dict: Plot configuration
|
357
|
-
"""
|
358
|
-
|
359
|
-
ui = plot_tab.ui
|
360
|
-
table = ui.tableWidget_y_signals
|
361
|
-
|
362
|
-
x_signals = [
|
363
|
-
{
|
364
|
-
"name": self.safe_text(ui.lineEdit_x_name),
|
365
|
-
"entry": self.safe_text(ui.lineEdit_x_entry),
|
366
|
-
}
|
367
|
-
]
|
368
|
-
|
369
|
-
y_signals = [
|
370
|
-
{
|
371
|
-
"name": self.safe_text(table.item(row, 0)),
|
372
|
-
"entry": self.safe_text(table.item(row, 1)),
|
373
|
-
}
|
374
|
-
for row in range(table.rowCount())
|
375
|
-
]
|
376
|
-
|
377
|
-
plot_data = {
|
378
|
-
"plot_name": self.safe_text(ui.lineEdit_plot_title),
|
379
|
-
"x_label": self.safe_text(ui.lineEdit_x_label),
|
380
|
-
"y_label": self.safe_text(ui.lineEdit_y_label),
|
381
|
-
"sources": [{"type": "scan_segment", "signals": {"x": x_signals, "y": y_signals}}],
|
382
|
-
}
|
383
|
-
|
384
|
-
return plot_data
|
385
|
-
|
386
|
-
def apply_config(self) -> dict:
|
387
|
-
"""
|
388
|
-
Apply configuration from the whole configuration window
|
389
|
-
|
390
|
-
Returns:
|
391
|
-
dict: Current configuration
|
392
|
-
|
393
|
-
"""
|
394
|
-
|
395
|
-
# General settings
|
396
|
-
config = {
|
397
|
-
"plot_settings": {
|
398
|
-
"background_color": self.comboBox_appearance.currentText(),
|
399
|
-
"num_columns": self.spinBox_n_column.value(),
|
400
|
-
"colormap": self.comboBox_colormap.currentText(),
|
401
|
-
"scan_types": True if self.comboBox_scanTypes.currentText() == "Enabled" else False,
|
402
|
-
},
|
403
|
-
"plot_data": {} if self.comboBox_scanTypes.currentText() == "Enabled" else [],
|
404
|
-
}
|
405
|
-
|
406
|
-
# Iterate through the plot tabs - Device monitor mode
|
407
|
-
if config["plot_settings"]["scan_types"] == False:
|
408
|
-
plot_tab = self.tabWidget_scan_types.widget(0).findChild(QTabWidget)
|
409
|
-
for index in range(plot_tab.count()):
|
410
|
-
plot_data = self.get_plot_config(plot_tab.widget(index))
|
411
|
-
config["plot_data"].append(plot_data)
|
412
|
-
|
413
|
-
# Iterate through the scan tabs - Scan mode
|
414
|
-
elif config["plot_settings"]["scan_types"] == True:
|
415
|
-
# Iterate through the scan tabs
|
416
|
-
for index in range(self.tabWidget_scan_types.count()):
|
417
|
-
scan_tab = self.tabWidget_scan_types.widget(index)
|
418
|
-
scan_name = self.tabWidget_scan_types.tabText(index)
|
419
|
-
plot_tab = scan_tab.findChild(QTabWidget)
|
420
|
-
config["plot_data"][scan_name] = []
|
421
|
-
# Iterate through the plot tabs
|
422
|
-
for index in range(plot_tab.count()):
|
423
|
-
plot_data = self.get_plot_config(plot_tab.widget(index))
|
424
|
-
config["plot_data"][scan_name].append(plot_data)
|
425
|
-
|
426
|
-
return config
|
427
|
-
|
428
|
-
def load_config(self, config: dict) -> None:
|
429
|
-
"""
|
430
|
-
Load configuration to the configuration window
|
431
|
-
|
432
|
-
Args:
|
433
|
-
config(dict): Configuration to be loaded
|
434
|
-
"""
|
435
|
-
|
436
|
-
# Plot setting General box
|
437
|
-
plot_settings = config.get("plot_settings", {})
|
438
|
-
|
439
|
-
self.comboBox_appearance.setCurrentText(plot_settings.get("background_color", "black"))
|
440
|
-
self.spinBox_n_column.setValue(plot_settings.get("num_columns", 1))
|
441
|
-
self.comboBox_colormap.setCurrentText(
|
442
|
-
plot_settings.get("colormap", "magma")
|
443
|
-
) # TODO make logic to allow also different colormaps -> validation of incoming dict
|
444
|
-
self.comboBox_scanTypes.setCurrentText(
|
445
|
-
"Enabled" if plot_settings.get("scan_types", False) else "Disabled"
|
446
|
-
)
|
447
|
-
|
448
|
-
# Clear exiting scan tabs
|
449
|
-
self.tabWidget_scan_types.clear()
|
450
|
-
|
451
|
-
# Get what mode is active - scan vs default device monitor
|
452
|
-
scan_mode = plot_settings.get("scan_types", False)
|
453
|
-
|
454
|
-
if scan_mode is False: # default mode:
|
455
|
-
plot_data = config.get("plot_data", [])
|
456
|
-
self.add_new_scan_tab(self.tabWidget_scan_types, "Default")
|
457
|
-
for plot_config in plot_data: # Create plot tab for each plot and populate GUI
|
458
|
-
plot = self.add_new_plot_tab(self.tabWidget_scan_types.widget(0))
|
459
|
-
self.load_plot_setting(plot, plot_config)
|
460
|
-
elif scan_mode is True: # scan mode
|
461
|
-
plot_data = config.get("plot_data", {})
|
462
|
-
for scan_name, scan_config in plot_data.items():
|
463
|
-
scan_tab = self.add_new_scan_tab(self.tabWidget_scan_types, scan_name)
|
464
|
-
for plot_config in scan_config:
|
465
|
-
plot = self.add_new_plot_tab(scan_tab)
|
466
|
-
self.load_plot_setting(plot, plot_config)
|
467
|
-
|
468
|
-
def load_plot_setting(self, plot: QWidget, plot_config: dict) -> None:
|
469
|
-
"""
|
470
|
-
Load plot setting from config
|
471
|
-
|
472
|
-
Args:
|
473
|
-
plot (QWidget): plot tab widget
|
474
|
-
plot_config (dict): config for single plot tab
|
475
|
-
"""
|
476
|
-
sources = plot_config.get("sources", [{}])[0]
|
477
|
-
x_signals = sources.get("signals", {}).get("x", [{}])[0]
|
478
|
-
y_signals = sources.get("signals", {}).get("y", [])
|
479
|
-
|
480
|
-
# LabelBox
|
481
|
-
plot.ui.lineEdit_plot_title.setText(plot_config.get("plot_name", ""))
|
482
|
-
plot.ui.lineEdit_x_label.setText(plot_config.get("x_label", ""))
|
483
|
-
plot.ui.lineEdit_y_label.setText(plot_config.get("y_label", ""))
|
484
|
-
|
485
|
-
# X axis
|
486
|
-
plot.ui.lineEdit_x_name.setText(x_signals.get("name", ""))
|
487
|
-
plot.ui.lineEdit_x_entry.setText(x_signals.get("entry", ""))
|
488
|
-
|
489
|
-
# Y axis
|
490
|
-
for y_signal in y_signals:
|
491
|
-
row_position = plot.ui.tableWidget_y_signals.rowCount()
|
492
|
-
plot.ui.tableWidget_y_signals.insertRow(row_position)
|
493
|
-
plot.ui.tableWidget_y_signals.setItem(
|
494
|
-
row_position, 0, QTableWidgetItem(y_signal.get("name", ""))
|
495
|
-
)
|
496
|
-
plot.ui.tableWidget_y_signals.setItem(
|
497
|
-
row_position, 1, QTableWidgetItem(y_signal.get("entry", ""))
|
498
|
-
)
|
499
|
-
|
500
|
-
def load_config_from_yaml(self):
|
501
|
-
"""
|
502
|
-
Load configuration from yaml file
|
503
|
-
"""
|
504
|
-
config = load_yaml(self)
|
505
|
-
self.load_config(config)
|
506
|
-
|
507
|
-
def save_config_to_yaml(self):
|
508
|
-
"""
|
509
|
-
Save configuration to yaml file
|
510
|
-
"""
|
511
|
-
config = self.apply_config()
|
512
|
-
save_yaml(self, config)
|
513
|
-
|
514
|
-
@staticmethod
|
515
|
-
def safe_text(line_edit: QLineEdit) -> str:
|
516
|
-
"""
|
517
|
-
Get text from a line edit, if it is None, return empty string
|
518
|
-
Args:
|
519
|
-
line_edit(QLineEdit): Line edit widget
|
520
|
-
|
521
|
-
Returns:
|
522
|
-
str: Text from the line edit
|
523
|
-
"""
|
524
|
-
return "" if line_edit is None else line_edit.text()
|
525
|
-
|
526
|
-
def apply_and_close(self):
|
527
|
-
new_config = self.apply_config()
|
528
|
-
if self.skip_validation is True:
|
529
|
-
self.config_updated.emit(new_config)
|
530
|
-
self.close()
|
531
|
-
else:
|
532
|
-
try:
|
533
|
-
validated_config = self.validator.validate_monitor_config(new_config)
|
534
|
-
approved_config = validated_config.model_dump()
|
535
|
-
self.config_updated.emit(approved_config)
|
536
|
-
self.close()
|
537
|
-
except ValidationError as e:
|
538
|
-
error_str = str(e)
|
539
|
-
formatted_error_message = ConfigDialog.format_validation_error(error_str)
|
540
|
-
|
541
|
-
# Display the formatted error message in a popup
|
542
|
-
QMessageBox.critical(self, "Configuration Error", formatted_error_message)
|
543
|
-
|
544
|
-
@staticmethod
|
545
|
-
def format_validation_error(error_str: str) -> str:
|
546
|
-
"""
|
547
|
-
Format the validation error string to be displayed in a popup.
|
548
|
-
Args:
|
549
|
-
error_str(str): Error string from the validation error.
|
550
|
-
"""
|
551
|
-
error_lines = error_str.split("\n")
|
552
|
-
# The first line contains the number of errors.
|
553
|
-
error_header = f"<p><b>{error_lines[0]}</b></p><hr>"
|
554
|
-
|
555
|
-
formatted_error_message = error_header
|
556
|
-
# Skip the first line as it's the header.
|
557
|
-
error_details = error_lines[1:]
|
558
|
-
|
559
|
-
# Iterate through pairs of lines (each error's two lines).
|
560
|
-
for i in range(0, len(error_details), 2):
|
561
|
-
location = error_details[i]
|
562
|
-
message = error_details[i + 1] if i + 1 < len(error_details) else ""
|
563
|
-
|
564
|
-
formatted_error_message += f"<p><b>{location}</b><br>{message}</p><hr>"
|
565
|
-
|
566
|
-
return formatted_error_message
|
567
|
-
|
568
|
-
|
569
|
-
if __name__ == "__main__": # pragma: no cover
|
570
|
-
app = QApplication([])
|
571
|
-
main_app = ConfigDialog()
|
572
|
-
main_app.show()
|
573
|
-
main_app.load_config(CONFIG_SCAN_MODE)
|
574
|
-
app.exec()
|