bec-widgets 0.112.1__py3-none-any.whl → 0.114.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 +52 -48
- PKG-INFO +1 -1
- bec_widgets/applications/__init__.py +0 -0
- bec_widgets/applications/alignment/__init__.py +0 -0
- bec_widgets/applications/alignment/alignment_1d/__init__.py +0 -0
- bec_widgets/applications/alignment/alignment_1d/alignment_1d.py +265 -0
- bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui +887 -0
- bec_widgets/assets/app_icons/alignment_1d.png +0 -0
- bec_widgets/qt_utils/toolbar.py +3 -1
- bec_widgets/utils/bec_signal_proxy.py +54 -0
- bec_widgets/utils/linear_region_selector.py +15 -3
- bec_widgets/utils/plot_indicator_items.py +247 -0
- bec_widgets/widgets/bec_status_box/bec_status_box.py +5 -9
- bec_widgets/widgets/bec_status_box/status_item.py +18 -9
- bec_widgets/widgets/dap_combo_box/dap_combo_box.py +2 -0
- bec_widgets/widgets/figure/plots/plot_base.py +5 -0
- bec_widgets/widgets/figure/plots/waveform/waveform.py +28 -15
- bec_widgets/widgets/lmfit_dialog/lmfit_dialog.py +151 -16
- bec_widgets/widgets/lmfit_dialog/lmfit_dialog_vertical.ui +47 -14
- bec_widgets/widgets/positioner_box/positioner_box.py +4 -1
- bec_widgets/widgets/scan_control/scan_control.py +42 -1
- bec_widgets/widgets/stop_button/stop_button.py +14 -2
- {bec_widgets-0.112.1.dist-info → bec_widgets-0.114.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.112.1.dist-info → bec_widgets-0.114.0.dist-info}/RECORD +28 -25
- pyproject.toml +1 -1
- bec_widgets/assets/status_icons/error.svg +0 -3
- bec_widgets/assets/status_icons/not_connected.svg +0 -3
- bec_widgets/assets/status_icons/refresh.svg +0 -3
- bec_widgets/assets/status_icons/running.svg +0 -3
- bec_widgets/assets/status_icons/warning.svg +0 -3
- {bec_widgets-0.112.1.dist-info → bec_widgets-0.114.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.112.1.dist-info → bec_widgets-0.114.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.112.1.dist-info → bec_widgets-0.114.0.dist-info}/licenses/LICENSE +0 -0
Binary file
|
bec_widgets/qt_utils/toolbar.py
CHANGED
@@ -9,7 +9,7 @@ from typing import Literal
|
|
9
9
|
from bec_qthemes._icon.material_icons import material_icon
|
10
10
|
from qtpy.QtCore import QSize
|
11
11
|
from qtpy.QtGui import QAction, QColor, QIcon
|
12
|
-
from qtpy.QtWidgets import QHBoxLayout, QLabel, QMenu, QToolBar, QToolButton, QWidget
|
12
|
+
from qtpy.QtWidgets import QHBoxLayout, QLabel, QMenu, QSizePolicy, QToolBar, QToolButton, QWidget
|
13
13
|
|
14
14
|
import bec_widgets
|
15
15
|
|
@@ -165,7 +165,9 @@ class WidgetAction(ToolBarAction):
|
|
165
165
|
layout.setContentsMargins(0, 0, 0, 0)
|
166
166
|
if self.label is not None:
|
167
167
|
label = QLabel(f"{self.label}")
|
168
|
+
label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
168
169
|
layout.addWidget(label)
|
170
|
+
self.widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
169
171
|
layout.addWidget(self.widget)
|
170
172
|
toolbar.addWidget(widget)
|
171
173
|
|
@@ -0,0 +1,54 @@
|
|
1
|
+
""" This custom class is a thin wrapper around the SignalProxy class to allow signal calls to be blocked.
|
2
|
+
Unblocking the proxy needs to be done through the slot unblock_proxy. The most likely use case for this class is
|
3
|
+
when the callback function is potentially initiating a slower progress, i.e. requesting a data analysis routine to
|
4
|
+
analyse data. Requesting a new fit may lead to request piling up and an overall slow done of performance. This proxy
|
5
|
+
will allow you to decide by yourself when to unblock and execute the callback again."""
|
6
|
+
|
7
|
+
from pyqtgraph import SignalProxy
|
8
|
+
from qtpy.QtCore import Signal, Slot
|
9
|
+
|
10
|
+
|
11
|
+
class BECSignalProxy(SignalProxy):
|
12
|
+
"""Thin wrapper around the SignalProxy class to allow signal calls to be blocked, but args still being stored
|
13
|
+
|
14
|
+
Args:
|
15
|
+
*args: Arguments to pass to the SignalProxy class
|
16
|
+
rateLimit (int): The rateLimit of the proxy
|
17
|
+
**kwargs: Keyword arguments to pass to the SignalProxy class
|
18
|
+
|
19
|
+
Example:
|
20
|
+
>>> proxy = BECSignalProxy(signal, rate_limit=25, slot=callback)"""
|
21
|
+
|
22
|
+
is_blocked = Signal(bool)
|
23
|
+
|
24
|
+
def __init__(self, *args, rateLimit=25, **kwargs):
|
25
|
+
super().__init__(*args, rateLimit=rateLimit, **kwargs)
|
26
|
+
self._blocking = False
|
27
|
+
self.old_args = None
|
28
|
+
self.new_args = None
|
29
|
+
|
30
|
+
@property
|
31
|
+
def blocked(self):
|
32
|
+
"""Returns if the proxy is blocked"""
|
33
|
+
return self._blocking
|
34
|
+
|
35
|
+
@blocked.setter
|
36
|
+
def blocked(self, value: bool):
|
37
|
+
self._blocking = value
|
38
|
+
self.is_blocked.emit(value)
|
39
|
+
|
40
|
+
def signalReceived(self, *args):
|
41
|
+
"""Receive signal, store the args and call signalReceived from the parent class if not blocked"""
|
42
|
+
self.new_args = args
|
43
|
+
if self.blocked is True:
|
44
|
+
return
|
45
|
+
self.blocked = True
|
46
|
+
self.old_args = args
|
47
|
+
super().signalReceived(*args)
|
48
|
+
|
49
|
+
@Slot()
|
50
|
+
def unblock_proxy(self):
|
51
|
+
"""Unblock the proxy, and call the signalReceived method in case there was an update of the args."""
|
52
|
+
self.blocked = False
|
53
|
+
if self.new_args != self.old_args:
|
54
|
+
self.signalReceived(*self.new_args)
|
@@ -23,11 +23,14 @@ class LinearRegionWrapper(QObject):
|
|
23
23
|
self, plot_item: pg.PlotItem, color: QColor = None, hover_color: QColor = None, parent=None
|
24
24
|
):
|
25
25
|
super().__init__(parent)
|
26
|
+
self.is_log_x = None
|
26
27
|
self._edge_width = 2
|
27
28
|
self.plot_item = plot_item
|
28
29
|
self.linear_region_selector = pg.LinearRegionItem()
|
29
30
|
self.proxy = None
|
30
31
|
self.change_roi_color((color, hover_color))
|
32
|
+
self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
|
33
|
+
self.plot_item.ctrl.logYCheck.checkStateChanged.connect(self.check_log)
|
31
34
|
|
32
35
|
# Slot for changing the color of the region selector (edge and fill)
|
33
36
|
@Slot(tuple)
|
@@ -63,9 +66,18 @@ class LinearRegionWrapper(QObject):
|
|
63
66
|
self.plot_item.removeItem(self.linear_region_selector)
|
64
67
|
|
65
68
|
def _region_change_proxy(self):
|
66
|
-
"""Emit the region change signal"""
|
67
|
-
|
68
|
-
self.
|
69
|
+
"""Emit the region change signal. If the plot is in log mode, convert the region to log."""
|
70
|
+
x_low, x_high = self.linear_region_selector.getRegion()
|
71
|
+
if self.is_log_x:
|
72
|
+
x_low = 10**x_low
|
73
|
+
x_high = 10**x_high
|
74
|
+
self.region_changed.emit((x_low, x_high))
|
75
|
+
|
76
|
+
@Slot()
|
77
|
+
def check_log(self):
|
78
|
+
"""Check if the plot is in log mode."""
|
79
|
+
self.is_log_x = self.plot_item.ctrl.logXCheck.isChecked()
|
80
|
+
self.is_log_y = self.plot_item.ctrl.logYCheck.isChecked()
|
69
81
|
|
70
82
|
def cleanup(self):
|
71
83
|
"""Cleanup the widget."""
|
@@ -0,0 +1,247 @@
|
|
1
|
+
"""Module to create an arrow item for a pyqtgraph plot"""
|
2
|
+
|
3
|
+
import numpy as np
|
4
|
+
import pyqtgraph as pg
|
5
|
+
from bec_lib.logger import bec_logger
|
6
|
+
from qtpy.QtCore import QObject, QPointF, Signal, Slot
|
7
|
+
|
8
|
+
from bec_widgets.utils.colors import get_accent_colors
|
9
|
+
|
10
|
+
logger = bec_logger.logger
|
11
|
+
|
12
|
+
|
13
|
+
class BECIndicatorItem(QObject):
|
14
|
+
|
15
|
+
def __init__(self, plot_item: pg.PlotItem = None, parent=None):
|
16
|
+
super().__init__(parent=parent)
|
17
|
+
self.accent_colors = get_accent_colors()
|
18
|
+
self.plot_item = plot_item
|
19
|
+
self._item_on_plot = False
|
20
|
+
self._pos = None
|
21
|
+
self.is_log_x = False
|
22
|
+
self.is_log_y = False
|
23
|
+
|
24
|
+
@property
|
25
|
+
def item_on_plot(self) -> bool:
|
26
|
+
"""Returns if the item is on the plot"""
|
27
|
+
return self._item_on_plot
|
28
|
+
|
29
|
+
@item_on_plot.setter
|
30
|
+
def item_on_plot(self, value: bool) -> None:
|
31
|
+
self._item_on_plot = value
|
32
|
+
|
33
|
+
def add_to_plot(self) -> None:
|
34
|
+
"""Add the item to the plot"""
|
35
|
+
raise NotImplementedError("Method add_to_plot not implemented")
|
36
|
+
|
37
|
+
def remove_from_plot(self) -> None:
|
38
|
+
"""Remove the item from the plot"""
|
39
|
+
raise NotImplementedError("Method remove_from_plot not implemented")
|
40
|
+
|
41
|
+
def set_position(self, pos) -> None:
|
42
|
+
"""This method should implement the logic to set the position of the
|
43
|
+
item on the plot. Depending on the child class, the position can be
|
44
|
+
a tuple (x,y) or a single value, i.e. x position where y position is fixed.
|
45
|
+
"""
|
46
|
+
raise NotImplementedError("Method set_position not implemented")
|
47
|
+
|
48
|
+
def check_log(self):
|
49
|
+
"""Checks if the x or y axis is in log scale and updates the internal state accordingly."""
|
50
|
+
self.is_log_x = self.plot_item.ctrl.logXCheck.isChecked()
|
51
|
+
self.is_log_y = self.plot_item.ctrl.logYCheck.isChecked()
|
52
|
+
self.set_position(self._pos)
|
53
|
+
|
54
|
+
|
55
|
+
class BECTickItem(BECIndicatorItem):
|
56
|
+
"""Class to create a tick item which can be added to a pyqtgraph plot.
|
57
|
+
The tick item will be added to the layout of the plot item and can be used to indicate
|
58
|
+
a position"""
|
59
|
+
|
60
|
+
position_changed = Signal(float)
|
61
|
+
position_changed_str = Signal(str)
|
62
|
+
|
63
|
+
def __init__(self, plot_item: pg.PlotItem = None, parent=None):
|
64
|
+
super().__init__(plot_item=plot_item, parent=parent)
|
65
|
+
self.tick_item = pg.TickSliderItem(
|
66
|
+
parent=parent, allowAdd=False, allowRemove=False, orientation="bottom"
|
67
|
+
)
|
68
|
+
self.tick_item.skip_auto_range = True
|
69
|
+
self.tick = None
|
70
|
+
self._pos = 0.0
|
71
|
+
self._range = [0, 1]
|
72
|
+
|
73
|
+
@Slot(float)
|
74
|
+
def set_position(self, pos: float) -> None:
|
75
|
+
"""Set the x position of the tick item
|
76
|
+
|
77
|
+
Args:
|
78
|
+
pos (float): The position of the tick item.
|
79
|
+
"""
|
80
|
+
if self.is_log_x is True:
|
81
|
+
pos = pos if pos > 0 else 1e-10
|
82
|
+
pos = np.log10(pos)
|
83
|
+
self._pos = pos
|
84
|
+
view_box = self.plot_item.getViewBox() # Ensure you're accessing the correct view box
|
85
|
+
view_range = view_box.viewRange()[0]
|
86
|
+
self.update_range(self.plot_item.vb, view_range)
|
87
|
+
self.position_changed.emit(pos)
|
88
|
+
self.position_changed_str.emit(str(pos))
|
89
|
+
|
90
|
+
@Slot()
|
91
|
+
def update_range(self, _, view_range: tuple[float, float]) -> None:
|
92
|
+
"""Update the range of the tick item
|
93
|
+
|
94
|
+
Args:
|
95
|
+
vb (pg.ViewBox): The view box.
|
96
|
+
viewRange (tuple): The view range.
|
97
|
+
"""
|
98
|
+
if self._pos < view_range[0] or self._pos > view_range[1]:
|
99
|
+
self.tick_item.setVisible(False)
|
100
|
+
else:
|
101
|
+
self.tick_item.setVisible(True)
|
102
|
+
|
103
|
+
if self.tick_item.isVisible():
|
104
|
+
origin = self.tick_item.tickSize / 2.0
|
105
|
+
length = self.tick_item.length
|
106
|
+
|
107
|
+
length_with_padding = length + self.tick_item.tickSize + 2
|
108
|
+
|
109
|
+
self._range = view_range
|
110
|
+
tick_with_padding = (self._pos - view_range[0]) / (view_range[1] - view_range[0])
|
111
|
+
tick_value = (tick_with_padding * length_with_padding - origin) / length
|
112
|
+
self.tick_item.setTickValue(self.tick, tick_value)
|
113
|
+
|
114
|
+
def add_to_plot(self):
|
115
|
+
"""Add the tick item to the view box or plot item."""
|
116
|
+
if self.plot_item is None:
|
117
|
+
return
|
118
|
+
|
119
|
+
self.plot_item.layout.addItem(self.tick_item, 2, 1)
|
120
|
+
self.tick_item.setOrientation("top")
|
121
|
+
self.tick = self.tick_item.addTick(0, movable=False, color=self.accent_colors.highlight)
|
122
|
+
self.update_tick_pos_y()
|
123
|
+
self.plot_item.vb.sigXRangeChanged.connect(self.update_range)
|
124
|
+
self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
|
125
|
+
self.plot_item.ctrl.logYCheck.checkStateChanged.connect(self.check_log)
|
126
|
+
self.plot_item.vb.geometryChanged.connect(self.update_tick_pos_y)
|
127
|
+
self.item_on_plot = True
|
128
|
+
|
129
|
+
@Slot()
|
130
|
+
def update_tick_pos_y(self):
|
131
|
+
"""Update tick position, while respecting the tick_item coordinates"""
|
132
|
+
pos = self.tick.pos()
|
133
|
+
pos = self.tick_item.mapToParent(pos)
|
134
|
+
new_pos = self.plot_item.vb.geometry().bottom()
|
135
|
+
new_pos = self.tick_item.mapFromParent(QPointF(pos.x(), new_pos))
|
136
|
+
self.tick.setPos(new_pos)
|
137
|
+
|
138
|
+
def remove_from_plot(self):
|
139
|
+
"""Remove the tick item from the view box or plot item."""
|
140
|
+
if self.plot_item is not None and self.item_on_plot is True:
|
141
|
+
self.plot_item.vb.sigXRangeChanged.disconnect(self.update_range)
|
142
|
+
self.plot_item.ctrl.logXCheck.checkStateChanged.disconnect(self.check_log)
|
143
|
+
self.plot_item.ctrl.logYCheck.checkStateChanged.disconnect(self.check_log)
|
144
|
+
if self.plot_item.layout is not None:
|
145
|
+
self.plot_item.layout.removeItem(self.tick_item)
|
146
|
+
self.item_on_plot = False
|
147
|
+
|
148
|
+
def cleanup(self) -> None:
|
149
|
+
"""Cleanup the item"""
|
150
|
+
self.remove_from_plot()
|
151
|
+
if self.tick_item is not None:
|
152
|
+
self.tick_item.close()
|
153
|
+
self.tick_item.deleteLater()
|
154
|
+
self.tick_item = None
|
155
|
+
|
156
|
+
|
157
|
+
class BECArrowItem(BECIndicatorItem):
|
158
|
+
"""Class to create an arrow item which can be added to a pyqtgraph plot.
|
159
|
+
It can be either added directly to a view box or a plot item.
|
160
|
+
To add the arrow item to a view box or plot item, use the add_to_plot method.
|
161
|
+
|
162
|
+
Args:
|
163
|
+
view_box (pg.ViewBox | pg.PlotItem): The view box or plot item to which the arrow item should be added.
|
164
|
+
parent (QObject): The parent object.
|
165
|
+
|
166
|
+
Signals:
|
167
|
+
position_changed (tuple[float, float]): Signal emitted when the position of the arrow item has changed.
|
168
|
+
position_changed_str (tuple[str, str]): Signal emitted when the position of the arrow item has changed.
|
169
|
+
"""
|
170
|
+
|
171
|
+
# Signal to emit if the position of the arrow item has changed
|
172
|
+
position_changed = Signal(tuple)
|
173
|
+
position_changed_str = Signal(tuple)
|
174
|
+
|
175
|
+
def __init__(self, plot_item: pg.PlotItem = None, parent=None):
|
176
|
+
super().__init__(plot_item=plot_item, parent=parent)
|
177
|
+
self.arrow_item = pg.ArrowItem(parent=parent)
|
178
|
+
self.arrow_item.skip_auto_range = True
|
179
|
+
self._pos = (0, 0)
|
180
|
+
self.arrow_item.setVisible(False)
|
181
|
+
|
182
|
+
@Slot(dict)
|
183
|
+
def set_style(self, style: dict) -> None:
|
184
|
+
"""Set the style of the arrow item
|
185
|
+
|
186
|
+
Args:
|
187
|
+
style (dict): The style of the arrow item. Dictionary with key,
|
188
|
+
value pairs which are accepted from the pg.ArrowItem.setStyle method.
|
189
|
+
"""
|
190
|
+
self.arrow_item.setStyle(**style)
|
191
|
+
|
192
|
+
@Slot(tuple)
|
193
|
+
def set_position(self, pos: tuple[float, float]) -> None:
|
194
|
+
"""Set the position of the arrow item
|
195
|
+
|
196
|
+
Args:
|
197
|
+
pos (tuple): The position of the arrow item as a tuple (x, y).
|
198
|
+
"""
|
199
|
+
self._pos = pos
|
200
|
+
pos_x = pos[0]
|
201
|
+
pos_y = pos[1]
|
202
|
+
if self.is_log_x is True:
|
203
|
+
pos_x = np.log10(pos_x) if pos_x > 0 else 1e-10
|
204
|
+
view_box = self.plot_item.getViewBox() # Ensure you're accessing the correct view box
|
205
|
+
view_range = view_box.viewRange()[0]
|
206
|
+
# Avoid values outside the view range in the negative direction. Otherwise, there is
|
207
|
+
# a buggy behaviour of the arrow item and it appears at the wrong position.
|
208
|
+
if pos_x < view_range[0]:
|
209
|
+
pos_x = view_range[0]
|
210
|
+
if self.is_log_y is True:
|
211
|
+
pos_y = np.log10(pos_y) if pos_y > 0 else 1e-10
|
212
|
+
|
213
|
+
self.arrow_item.setPos(pos_x, pos_y)
|
214
|
+
self.position_changed.emit(self._pos)
|
215
|
+
self.position_changed_str.emit((str(self._pos[0]), str(self._pos[1])))
|
216
|
+
|
217
|
+
def add_to_plot(self):
|
218
|
+
"""Add the arrow item to the view box or plot item."""
|
219
|
+
if not self.arrow_item:
|
220
|
+
logger.warning(f"Arrow item was already destroyed, cannot be created")
|
221
|
+
return
|
222
|
+
|
223
|
+
self.arrow_item.setStyle(
|
224
|
+
angle=-90,
|
225
|
+
pen=pg.mkPen(self.accent_colors.emergency, width=1),
|
226
|
+
brush=pg.mkBrush(self.accent_colors.highlight),
|
227
|
+
headLen=20,
|
228
|
+
)
|
229
|
+
self.arrow_item.setVisible(True)
|
230
|
+
if self.plot_item is not None:
|
231
|
+
self.plot_item.addItem(self.arrow_item)
|
232
|
+
self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
|
233
|
+
self.plot_item.ctrl.logYCheck.checkStateChanged.connect(self.check_log)
|
234
|
+
self.item_on_plot = True
|
235
|
+
|
236
|
+
def remove_from_plot(self):
|
237
|
+
"""Remove the arrow item from the view box or plot item."""
|
238
|
+
if self.plot_item is not None and self.item_on_plot is True:
|
239
|
+
self.plot_item.ctrl.logXCheck.checkStateChanged.disconnect(self.check_log)
|
240
|
+
self.plot_item.ctrl.logYCheck.checkStateChanged.disconnect(self.check_log)
|
241
|
+
self.plot_item.removeItem(self.arrow_item)
|
242
|
+
self.item_on_plot = False
|
243
|
+
|
244
|
+
def cleanup(self) -> None:
|
245
|
+
"""Cleanup the item"""
|
246
|
+
self.remove_from_plot()
|
247
|
+
self.arrow_item = None
|
@@ -14,7 +14,7 @@ from qtpy.QtCore import QObject, QTimer, Signal, Slot
|
|
14
14
|
from qtpy.QtWidgets import QHBoxLayout, QTreeWidget, QTreeWidgetItem, QWidget
|
15
15
|
|
16
16
|
from bec_widgets.utils.bec_widget import BECWidget
|
17
|
-
from bec_widgets.utils.colors import
|
17
|
+
from bec_widgets.utils.colors import set_theme
|
18
18
|
from bec_widgets.widgets.bec_status_box.status_item import StatusItem
|
19
19
|
|
20
20
|
if TYPE_CHECKING:
|
@@ -303,17 +303,13 @@ class BECStatusBox(BECWidget, QWidget):
|
|
303
303
|
return super().cleanup()
|
304
304
|
|
305
305
|
|
306
|
-
|
307
|
-
|
308
|
-
|
306
|
+
if __name__ == "__main__": # pragma: no cover
|
307
|
+
import sys
|
308
|
+
|
309
309
|
from qtpy.QtWidgets import QApplication
|
310
310
|
|
311
311
|
app = QApplication(sys.argv)
|
312
|
-
|
312
|
+
set_theme("dark")
|
313
313
|
main_window = BECStatusBox()
|
314
314
|
main_window.show()
|
315
315
|
sys.exit(app.exec())
|
316
|
-
|
317
|
-
|
318
|
-
if __name__ == "__main__":
|
319
|
-
main()
|
@@ -6,11 +6,13 @@ import os
|
|
6
6
|
from datetime import datetime
|
7
7
|
|
8
8
|
from bec_lib.utils.import_utils import lazy_import_from
|
9
|
+
from bec_qthemes import material_icon
|
9
10
|
from qtpy.QtCore import Qt, Slot
|
10
|
-
from qtpy.QtGui import QIcon
|
11
|
+
from qtpy.QtGui import QIcon, QPainter
|
11
12
|
from qtpy.QtWidgets import QDialog, QHBoxLayout, QLabel, QVBoxLayout, QWidget
|
12
13
|
|
13
14
|
import bec_widgets
|
15
|
+
from bec_widgets.utils.colors import get_accent_colors
|
14
16
|
|
15
17
|
# TODO : Put normal imports back when Pydantic gets faster
|
16
18
|
BECStatus = lazy_import_from("bec_lib.messages", ("BECStatus",))
|
@@ -21,11 +23,11 @@ MODULE_PATH = os.path.dirname(bec_widgets.__file__)
|
|
21
23
|
class IconsEnum(enum.Enum):
|
22
24
|
"""Enum class for icons in the status item widget."""
|
23
25
|
|
24
|
-
RUNNING =
|
25
|
-
BUSY =
|
26
|
-
IDLE =
|
27
|
-
ERROR =
|
28
|
-
NOTCONNECTED =
|
26
|
+
RUNNING = "done_outline"
|
27
|
+
BUSY = "progress_activity"
|
28
|
+
IDLE = "progress_activity"
|
29
|
+
ERROR = "emergency_home"
|
30
|
+
NOTCONNECTED = "signal_disconnected"
|
29
31
|
|
30
32
|
|
31
33
|
class StatusItem(QWidget):
|
@@ -43,13 +45,13 @@ class StatusItem(QWidget):
|
|
43
45
|
raise ValueError(
|
44
46
|
"Please initialize the StatusItem with a BECServiceInfoContainer for config, received None."
|
45
47
|
)
|
48
|
+
self.accent_colors = get_accent_colors()
|
46
49
|
self.config = config
|
47
50
|
self.parent = parent
|
48
51
|
self.layout = None
|
49
52
|
self._label = None
|
50
53
|
self._icon = None
|
51
54
|
self.icon_size = (24, 24)
|
52
|
-
|
53
55
|
self._popup_label_ref = {}
|
54
56
|
self.init_ui()
|
55
57
|
|
@@ -97,8 +99,15 @@ class StatusItem(QWidget):
|
|
97
99
|
|
98
100
|
def set_status(self) -> None:
|
99
101
|
"""Set the status icon for the status item widget."""
|
100
|
-
|
101
|
-
|
102
|
+
status = self.config.status
|
103
|
+
if status in ["RUNNING", "BUSY"]:
|
104
|
+
color = self.accent_colors.success
|
105
|
+
elif status == "IDLE":
|
106
|
+
color = self.accent_colors.warning
|
107
|
+
elif status in ["ERROR", "NOTCONNECTED"]:
|
108
|
+
color = self.accent_colors.emergency
|
109
|
+
icon = QIcon(material_icon(IconsEnum[status].value, filled=True, color=color))
|
110
|
+
|
102
111
|
self._icon.setPixmap(icon.pixmap(*self.icon_size))
|
103
112
|
self._icon.setAlignment(Qt.AlignmentFlag.AlignRight)
|
104
113
|
|
@@ -124,6 +124,7 @@ class DapComboBox(BECWidget, QWidget):
|
|
124
124
|
x_axis(str): X axis.
|
125
125
|
"""
|
126
126
|
self.x_axis = x_axis
|
127
|
+
self._update_current_fit(self.fit_model_combobox.currentText())
|
127
128
|
|
128
129
|
@Slot(str)
|
129
130
|
def select_y_axis(self, y_axis: str):
|
@@ -133,6 +134,7 @@ class DapComboBox(BECWidget, QWidget):
|
|
133
134
|
y_axis(str): Y axis.
|
134
135
|
"""
|
135
136
|
self.y_axis = y_axis
|
137
|
+
self._update_current_fit(self.fit_model_combobox.currentText())
|
136
138
|
|
137
139
|
@Slot(str)
|
138
140
|
def select_fit_model(self, fit_name: str | None):
|
@@ -12,6 +12,7 @@ from qtpy.QtWidgets import QApplication, QWidget
|
|
12
12
|
|
13
13
|
from bec_widgets.utils import BECConnector, ConnectionConfig
|
14
14
|
from bec_widgets.utils.crosshair import Crosshair
|
15
|
+
from bec_widgets.utils.plot_indicator_items import BECArrowItem, BECTickItem
|
15
16
|
|
16
17
|
logger = bec_logger.logger
|
17
18
|
|
@@ -105,6 +106,8 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
|
|
105
106
|
|
106
107
|
self.add_legend()
|
107
108
|
self.crosshair = None
|
109
|
+
self.tick_item = BECTickItem(parent=self, plot_item=self.plot_item)
|
110
|
+
self.arrow_item = BECArrowItem(parent=self, plot_item=self.plot_item)
|
108
111
|
self._connect_to_theme_change()
|
109
112
|
|
110
113
|
def _connect_to_theme_change(self):
|
@@ -428,6 +431,8 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
|
|
428
431
|
def cleanup_pyqtgraph(self):
|
429
432
|
"""Cleanup pyqtgraph items."""
|
430
433
|
self.unhook_crosshair()
|
434
|
+
self.tick_item.cleanup()
|
435
|
+
self.arrow_item.cleanup()
|
431
436
|
item = self.plot_item
|
432
437
|
item.vb.menu.close()
|
433
438
|
item.vb.menu.deleteLater()
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# pylint: disable=too_many_lines
|
1
2
|
from __future__ import annotations
|
2
3
|
|
3
4
|
from collections import defaultdict
|
@@ -12,7 +13,7 @@ from bec_lib.logger import bec_logger
|
|
12
13
|
from pydantic import Field, ValidationError, field_validator
|
13
14
|
from pyqtgraph.exporters import MatplotlibExporter
|
14
15
|
from qtpy.QtCore import Signal as pyqtSignal
|
15
|
-
from qtpy.QtWidgets import
|
16
|
+
from qtpy.QtWidgets import QWidget
|
16
17
|
|
17
18
|
from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
|
18
19
|
from bec_widgets.utils import Colors, EntryValidator
|
@@ -165,7 +166,7 @@ class BECWaveform(BECPlotBase):
|
|
165
166
|
self.roi_select.linear_region_selector.setRegion(region)
|
166
167
|
except Exception as e:
|
167
168
|
logger.error(f"Error setting region {tuple}; Exception raised: {e}")
|
168
|
-
raise ValueError(f"Error setting region {tuple}; Exception raised: {e}")
|
169
|
+
raise ValueError(f"Error setting region {tuple}; Exception raised: {e}") from e
|
169
170
|
|
170
171
|
def _hook_roi(self):
|
171
172
|
"""Hook the linear region selector to the plot."""
|
@@ -219,7 +220,7 @@ class BECWaveform(BECPlotBase):
|
|
219
220
|
# Reset curves
|
220
221
|
self._curves_data = defaultdict(dict)
|
221
222
|
self._curves = self.plot_item.curves
|
222
|
-
for
|
223
|
+
for curve_config in self.config.curves.values():
|
223
224
|
self.add_curve_by_config(curve_config)
|
224
225
|
if replot_last_scan:
|
225
226
|
self.scan_history(scan_index=-1)
|
@@ -342,7 +343,7 @@ class BECWaveform(BECPlotBase):
|
|
342
343
|
Returns:
|
343
344
|
CurveConfig|dict: Configuration of the curve.
|
344
345
|
"""
|
345
|
-
for
|
346
|
+
for curves in self._curves_data.values():
|
346
347
|
if curve_id in curves:
|
347
348
|
if dict_output:
|
348
349
|
return curves[curve_id].config.model_dump()
|
@@ -362,7 +363,7 @@ class BECWaveform(BECPlotBase):
|
|
362
363
|
if isinstance(identifier, int):
|
363
364
|
return self.plot_item.curves[identifier]
|
364
365
|
elif isinstance(identifier, str):
|
365
|
-
for
|
366
|
+
for curves in self._curves_data.values():
|
366
367
|
if identifier in curves:
|
367
368
|
return curves[identifier]
|
368
369
|
raise ValueError(f"Curve with ID '{identifier}' not found.")
|
@@ -466,6 +467,10 @@ class BECWaveform(BECPlotBase):
|
|
466
467
|
- Custom signal name of device from BEC.
|
467
468
|
x_entry(str): Entry of the x signal.
|
468
469
|
"""
|
470
|
+
if not x_name:
|
471
|
+
# this can happen, if executed by a signal from a widget
|
472
|
+
return
|
473
|
+
|
469
474
|
curve_configs = self.config.curves
|
470
475
|
curve_ids = list(curve_configs.keys())
|
471
476
|
curve_configs = list(curve_configs.values())
|
@@ -513,6 +518,7 @@ class BECWaveform(BECPlotBase):
|
|
513
518
|
|
514
519
|
@Slot()
|
515
520
|
def auto_range(self):
|
521
|
+
"""Manually set auto range of the plotitem"""
|
516
522
|
self.plot_item.autoRange()
|
517
523
|
|
518
524
|
def set_auto_range(self, enabled: bool, axis: str = "xy"):
|
@@ -551,7 +557,6 @@ class BECWaveform(BECPlotBase):
|
|
551
557
|
Returns:
|
552
558
|
BECCurve: The curve object.
|
553
559
|
"""
|
554
|
-
curve_source = curve_source
|
555
560
|
curve_id = label or f"Curve {len(self.plot_item.curves) + 1}"
|
556
561
|
|
557
562
|
curve_exits = self._check_curve_id(curve_id, self._curves_data)
|
@@ -630,6 +635,13 @@ class BECWaveform(BECPlotBase):
|
|
630
635
|
x_name = mode if mode is not None else "best_effort"
|
631
636
|
self.x_axis_mode["name"] = x_name
|
632
637
|
|
638
|
+
if not x_name or not y_name:
|
639
|
+
# can happen if executed from a signal from a widget ;
|
640
|
+
# the code above has to be executed to set some other
|
641
|
+
# variables, but it cannot continue if both names are
|
642
|
+
# not set properly -> exit here
|
643
|
+
return
|
644
|
+
|
633
645
|
# 3. Check - Get entry if not provided and validate
|
634
646
|
x_entry, y_entry, z_entry = self._validate_signal_entries(
|
635
647
|
x_name, y_name, z_name, x_entry, y_entry, z_entry, validate_bec
|
@@ -725,7 +737,7 @@ class BECWaveform(BECPlotBase):
|
|
725
737
|
|
726
738
|
if self.x_axis_mode["readout_priority"] == "async":
|
727
739
|
raise ValueError(
|
728
|
-
|
740
|
+
"Async signals cannot be fitted at the moment. Please switch to 'monitored' or 'baseline' signals."
|
729
741
|
)
|
730
742
|
|
731
743
|
if validate_bec is True:
|
@@ -999,7 +1011,7 @@ class BECWaveform(BECPlotBase):
|
|
999
1011
|
Args:
|
1000
1012
|
curve_id(str): ID of the curve to be removed.
|
1001
1013
|
"""
|
1002
|
-
for
|
1014
|
+
for curves in self._curves_data.values():
|
1003
1015
|
if curve_id in curves:
|
1004
1016
|
curve = curves.pop(curve_id)
|
1005
1017
|
self.plot_item.removeItem(curve)
|
@@ -1022,7 +1034,7 @@ class BECWaveform(BECPlotBase):
|
|
1022
1034
|
self.plot_item.removeItem(curve)
|
1023
1035
|
del self.config.curves[curve_id]
|
1024
1036
|
# Remove from self.curve_data
|
1025
|
-
for
|
1037
|
+
for curves in self._curves_data.values():
|
1026
1038
|
if curve_id in curves:
|
1027
1039
|
del curves[curve_id]
|
1028
1040
|
break
|
@@ -1052,7 +1064,7 @@ class BECWaveform(BECPlotBase):
|
|
1052
1064
|
if self._curves_data["DAP"]:
|
1053
1065
|
self.setup_dap(self.old_scan_id, self.scan_id)
|
1054
1066
|
if self._curves_data["async"]:
|
1055
|
-
for
|
1067
|
+
for curve in self._curves_data["async"].values():
|
1056
1068
|
self.setup_async(
|
1057
1069
|
name=curve.config.signals.y.name, entry=curve.config.signals.y.entry
|
1058
1070
|
)
|
@@ -1184,7 +1196,9 @@ class BECWaveform(BECPlotBase):
|
|
1184
1196
|
|
1185
1197
|
@Slot(dict, dict)
|
1186
1198
|
def update_dap(self, msg, metadata):
|
1187
|
-
|
1199
|
+
"""Callback for DAP response message."""
|
1200
|
+
|
1201
|
+
# pylint: disable=unused-variable
|
1188
1202
|
scan_id, x_name, x_entry, y_name, y_entry = msg["dap_request"].content["config"]["args"]
|
1189
1203
|
model = msg["dap_request"].content["config"]["class_kwargs"]["model"]
|
1190
1204
|
|
@@ -1213,7 +1227,7 @@ class BECWaveform(BECPlotBase):
|
|
1213
1227
|
metadata(dict): Metadata of the message.
|
1214
1228
|
"""
|
1215
1229
|
instruction = metadata.get("async_update")
|
1216
|
-
for
|
1230
|
+
for curve in self._curves_data["async"].values():
|
1217
1231
|
y_name = curve.config.signals.y.name
|
1218
1232
|
y_entry = curve.config.signals.y.entry
|
1219
1233
|
x_name = self._x_axis_mode["name"]
|
@@ -1337,7 +1351,7 @@ class BECWaveform(BECPlotBase):
|
|
1337
1351
|
if self._x_axis_mode["name"] is None or self._x_axis_mode["name"] == "best_effort":
|
1338
1352
|
if len(self._curves_data["async"]) > 0:
|
1339
1353
|
x_data = None
|
1340
|
-
self._x_axis_mode["label_suffix"] =
|
1354
|
+
self._x_axis_mode["label_suffix"] = " [auto: index]"
|
1341
1355
|
current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
|
1342
1356
|
self.plot_item.setLabel(
|
1343
1357
|
"bottom", f"{current_label}{self._x_axis_mode['label_suffix']}"
|
@@ -1414,7 +1428,6 @@ class BECWaveform(BECPlotBase):
|
|
1414
1428
|
self.scan_signal_update.emit()
|
1415
1429
|
self.async_signal_update.emit()
|
1416
1430
|
|
1417
|
-
# pylint: ignore: undefined-variable
|
1418
1431
|
def get_all_data(self, output: Literal["dict", "pandas"] = "dict") -> dict: # | pd.DataFrame:
|
1419
1432
|
"""
|
1420
1433
|
Extract all curve data into a dictionary or a pandas DataFrame.
|
@@ -1484,7 +1497,7 @@ class BECWaveform(BECPlotBase):
|
|
1484
1497
|
self.bec_dispatcher.disconnect_slot(
|
1485
1498
|
self.update_dap, MessageEndpoints.dap_response(self.scan_id)
|
1486
1499
|
)
|
1487
|
-
for curve_id
|
1500
|
+
for curve_id in self._curves_data["async"]:
|
1488
1501
|
self.bec_dispatcher.disconnect_slot(
|
1489
1502
|
self.on_async_readback,
|
1490
1503
|
MessageEndpoints.device_async_readback(self.scan_id, curve_id),
|