bec-widgets 0.112.1__py3-none-any.whl → 0.113.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 +34 -36
- 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 +869 -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 +17 -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 +38 -0
- bec_widgets/widgets/stop_button/stop_button.py +14 -2
- {bec_widgets-0.112.1.dist-info → bec_widgets-0.113.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.112.1.dist-info → bec_widgets-0.113.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.113.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.112.1.dist-info → bec_widgets-0.113.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.112.1.dist-info → bec_widgets-0.113.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.")
|
@@ -513,6 +514,7 @@ class BECWaveform(BECPlotBase):
|
|
513
514
|
|
514
515
|
@Slot()
|
515
516
|
def auto_range(self):
|
517
|
+
"""Manually set auto range of the plotitem"""
|
516
518
|
self.plot_item.autoRange()
|
517
519
|
|
518
520
|
def set_auto_range(self, enabled: bool, axis: str = "xy"):
|
@@ -551,7 +553,6 @@ class BECWaveform(BECPlotBase):
|
|
551
553
|
Returns:
|
552
554
|
BECCurve: The curve object.
|
553
555
|
"""
|
554
|
-
curve_source = curve_source
|
555
556
|
curve_id = label or f"Curve {len(self.plot_item.curves) + 1}"
|
556
557
|
|
557
558
|
curve_exits = self._check_curve_id(curve_id, self._curves_data)
|
@@ -725,7 +726,7 @@ class BECWaveform(BECPlotBase):
|
|
725
726
|
|
726
727
|
if self.x_axis_mode["readout_priority"] == "async":
|
727
728
|
raise ValueError(
|
728
|
-
|
729
|
+
"Async signals cannot be fitted at the moment. Please switch to 'monitored' or 'baseline' signals."
|
729
730
|
)
|
730
731
|
|
731
732
|
if validate_bec is True:
|
@@ -999,7 +1000,7 @@ class BECWaveform(BECPlotBase):
|
|
999
1000
|
Args:
|
1000
1001
|
curve_id(str): ID of the curve to be removed.
|
1001
1002
|
"""
|
1002
|
-
for
|
1003
|
+
for curves in self._curves_data.values():
|
1003
1004
|
if curve_id in curves:
|
1004
1005
|
curve = curves.pop(curve_id)
|
1005
1006
|
self.plot_item.removeItem(curve)
|
@@ -1022,7 +1023,7 @@ class BECWaveform(BECPlotBase):
|
|
1022
1023
|
self.plot_item.removeItem(curve)
|
1023
1024
|
del self.config.curves[curve_id]
|
1024
1025
|
# Remove from self.curve_data
|
1025
|
-
for
|
1026
|
+
for curves in self._curves_data.values():
|
1026
1027
|
if curve_id in curves:
|
1027
1028
|
del curves[curve_id]
|
1028
1029
|
break
|
@@ -1052,7 +1053,7 @@ class BECWaveform(BECPlotBase):
|
|
1052
1053
|
if self._curves_data["DAP"]:
|
1053
1054
|
self.setup_dap(self.old_scan_id, self.scan_id)
|
1054
1055
|
if self._curves_data["async"]:
|
1055
|
-
for
|
1056
|
+
for curve in self._curves_data["async"].values():
|
1056
1057
|
self.setup_async(
|
1057
1058
|
name=curve.config.signals.y.name, entry=curve.config.signals.y.entry
|
1058
1059
|
)
|
@@ -1184,7 +1185,9 @@ class BECWaveform(BECPlotBase):
|
|
1184
1185
|
|
1185
1186
|
@Slot(dict, dict)
|
1186
1187
|
def update_dap(self, msg, metadata):
|
1187
|
-
|
1188
|
+
"""Callback for DAP response message."""
|
1189
|
+
|
1190
|
+
# pylint: disable=unused-variable
|
1188
1191
|
scan_id, x_name, x_entry, y_name, y_entry = msg["dap_request"].content["config"]["args"]
|
1189
1192
|
model = msg["dap_request"].content["config"]["class_kwargs"]["model"]
|
1190
1193
|
|
@@ -1213,7 +1216,7 @@ class BECWaveform(BECPlotBase):
|
|
1213
1216
|
metadata(dict): Metadata of the message.
|
1214
1217
|
"""
|
1215
1218
|
instruction = metadata.get("async_update")
|
1216
|
-
for
|
1219
|
+
for curve in self._curves_data["async"].values():
|
1217
1220
|
y_name = curve.config.signals.y.name
|
1218
1221
|
y_entry = curve.config.signals.y.entry
|
1219
1222
|
x_name = self._x_axis_mode["name"]
|
@@ -1337,7 +1340,7 @@ class BECWaveform(BECPlotBase):
|
|
1337
1340
|
if self._x_axis_mode["name"] is None or self._x_axis_mode["name"] == "best_effort":
|
1338
1341
|
if len(self._curves_data["async"]) > 0:
|
1339
1342
|
x_data = None
|
1340
|
-
self._x_axis_mode["label_suffix"] =
|
1343
|
+
self._x_axis_mode["label_suffix"] = " [auto: index]"
|
1341
1344
|
current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
|
1342
1345
|
self.plot_item.setLabel(
|
1343
1346
|
"bottom", f"{current_label}{self._x_axis_mode['label_suffix']}"
|
@@ -1414,7 +1417,6 @@ class BECWaveform(BECPlotBase):
|
|
1414
1417
|
self.scan_signal_update.emit()
|
1415
1418
|
self.async_signal_update.emit()
|
1416
1419
|
|
1417
|
-
# pylint: ignore: undefined-variable
|
1418
1420
|
def get_all_data(self, output: Literal["dict", "pandas"] = "dict") -> dict: # | pd.DataFrame:
|
1419
1421
|
"""
|
1420
1422
|
Extract all curve data into a dictionary or a pandas DataFrame.
|
@@ -1484,7 +1486,7 @@ class BECWaveform(BECPlotBase):
|
|
1484
1486
|
self.bec_dispatcher.disconnect_slot(
|
1485
1487
|
self.update_dap, MessageEndpoints.dap_response(self.scan_id)
|
1486
1488
|
)
|
1487
|
-
for curve_id
|
1489
|
+
for curve_id in self._curves_data["async"]:
|
1488
1490
|
self.bec_dispatcher.disconnect_slot(
|
1489
1491
|
self.on_async_readback,
|
1490
1492
|
MessageEndpoints.device_async_readback(self.scan_id, curve_id),
|