bec-widgets 0.117.1__py3-none-any.whl → 0.119.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 +47 -51
- PKG-INFO +1 -1
- bec_widgets/applications/alignment/alignment_1d/alignment_1d.py +33 -89
- bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui +413 -715
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +1 -1
- bec_widgets/qt_utils/compact_popup.py +68 -23
- bec_widgets/qt_utils/toolbar.py +51 -12
- bec_widgets/widgets/device_line_edit/device_line_edit.py +15 -1
- bec_widgets/widgets/figure/figure.py +10 -1
- bec_widgets/widgets/figure/plots/image/image.py +86 -19
- bec_widgets/widgets/image/image_widget.py +15 -2
- bec_widgets/widgets/positioner_box/positioner_box.py +10 -4
- bec_widgets/widgets/positioner_group/__init__.py +0 -0
- bec_widgets/widgets/positioner_group/positioner_group.py +170 -0
- bec_widgets/widgets/positioner_group/positioner_group.pyproject +1 -0
- bec_widgets/widgets/positioner_group/positioner_group_plugin.py +57 -0
- bec_widgets/widgets/positioner_group/register_positioner_group.py +15 -0
- bec_widgets/widgets/scan_control/scan_control.py +74 -122
- bec_widgets/widgets/scan_control/scan_group_box.py +66 -11
- bec_widgets/widgets/stop_button/stop_button.py +1 -1
- {bec_widgets-0.117.1.dist-info → bec_widgets-0.119.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.117.1.dist-info → bec_widgets-0.119.0.dist-info}/RECORD +26 -21
- pyproject.toml +1 -1
- {bec_widgets-0.117.1.dist-info → bec_widgets-0.119.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.117.1.dist-info → bec_widgets-0.119.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.117.1.dist-info → bec_widgets-0.119.0.dist-info}/licenses/LICENSE +0 -0
@@ -164,7 +164,7 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
|
164
164
|
|
165
165
|
self.d1 = self.dock.add_dock(name="dock_1", position="right")
|
166
166
|
self.im = self.d1.add_widget("BECImageWidget")
|
167
|
-
self.im.image("
|
167
|
+
self.im.image("waveform", "1d")
|
168
168
|
|
169
169
|
self.d2 = self.dock.add_dock(name="dock_2", position="bottom")
|
170
170
|
self.wf = self.d2.add_widget("BECWaveformWidget", row=0, col=0)
|
@@ -1,7 +1,8 @@
|
|
1
|
+
import time
|
1
2
|
from types import SimpleNamespace
|
2
3
|
|
3
4
|
from bec_qthemes import material_icon
|
4
|
-
from qtpy.QtCore import Property, Qt
|
5
|
+
from qtpy.QtCore import Property, Qt, Signal
|
5
6
|
from qtpy.QtGui import QColor
|
6
7
|
from qtpy.QtWidgets import (
|
7
8
|
QDialog,
|
@@ -9,6 +10,7 @@ from qtpy.QtWidgets import (
|
|
9
10
|
QLabel,
|
10
11
|
QPushButton,
|
11
12
|
QSizePolicy,
|
13
|
+
QSpacerItem,
|
12
14
|
QVBoxLayout,
|
13
15
|
QWidget,
|
14
16
|
)
|
@@ -98,6 +100,7 @@ class PopupDialog(QDialog):
|
|
98
100
|
def closeEvent(self, event):
|
99
101
|
self.content_widget.setVisible(False)
|
100
102
|
self.content_widget.setParent(self.parent)
|
103
|
+
self.done(True)
|
101
104
|
|
102
105
|
|
103
106
|
class CompactPopupWidget(QWidget):
|
@@ -107,29 +110,35 @@ class CompactPopupWidget(QWidget):
|
|
107
110
|
In the compact form, a LED-like indicator shows a status indicator.
|
108
111
|
"""
|
109
112
|
|
113
|
+
expand = Signal(bool)
|
114
|
+
|
110
115
|
def __init__(self, parent=None, layout=QVBoxLayout):
|
111
116
|
super().__init__(parent)
|
112
117
|
|
113
118
|
self._popup_window = None
|
119
|
+
self._expand_popup = True
|
114
120
|
|
115
121
|
QVBoxLayout(self)
|
116
|
-
self.
|
117
|
-
self.
|
118
|
-
QHBoxLayout(self.
|
119
|
-
self.
|
120
|
-
self.
|
121
|
-
self.
|
122
|
-
|
123
|
-
|
122
|
+
self.compact_view_widget = QWidget(self)
|
123
|
+
self.compact_view_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
124
|
+
QHBoxLayout(self.compact_view_widget)
|
125
|
+
self.compact_view_widget.layout().setSpacing(0)
|
126
|
+
self.compact_view_widget.layout().setContentsMargins(0, 0, 0, 0)
|
127
|
+
self.compact_view_widget.layout().addSpacerItem(
|
128
|
+
QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Fixed)
|
129
|
+
)
|
130
|
+
self.compact_label = QLabel(self.compact_view_widget)
|
131
|
+
self.compact_status = LedLabel(self.compact_view_widget)
|
132
|
+
self.compact_show_popup = QPushButton(self.compact_view_widget)
|
124
133
|
self.compact_show_popup.setFlat(True)
|
125
134
|
self.compact_show_popup.setIcon(
|
126
|
-
material_icon(icon_name="
|
135
|
+
material_icon(icon_name="expand_content", size=(10, 10), convert_to_pixmap=False)
|
127
136
|
)
|
128
|
-
self.
|
129
|
-
self.
|
130
|
-
self.
|
131
|
-
self.
|
132
|
-
self.layout().addWidget(self.
|
137
|
+
self.compact_view_widget.layout().addWidget(self.compact_label)
|
138
|
+
self.compact_view_widget.layout().addWidget(self.compact_status)
|
139
|
+
self.compact_view_widget.layout().addWidget(self.compact_show_popup)
|
140
|
+
self.compact_view_widget.setVisible(False)
|
141
|
+
self.layout().addWidget(self.compact_view_widget)
|
133
142
|
self.container = QWidget(self)
|
134
143
|
self.layout().addWidget(self.container)
|
135
144
|
self.container.setVisible(True)
|
@@ -148,8 +157,36 @@ class CompactPopupWidget(QWidget):
|
|
148
157
|
|
149
158
|
def show_popup(self):
|
150
159
|
"""Display the contained widgets in a popup dialog"""
|
151
|
-
|
152
|
-
|
160
|
+
if self._expand_popup:
|
161
|
+
# show popup
|
162
|
+
self._popup_window = PopupDialog(self.container)
|
163
|
+
self._popup_window.show()
|
164
|
+
self._popup_window.finished.connect(lambda: self.expand.emit(False))
|
165
|
+
self.expand.emit(True)
|
166
|
+
else:
|
167
|
+
if self.compact_view:
|
168
|
+
# expand in place
|
169
|
+
self.compact_view = False
|
170
|
+
self.compact_view_widget.setVisible(True)
|
171
|
+
self.compact_label.setVisible(False)
|
172
|
+
self.compact_status.setVisible(False)
|
173
|
+
self.compact_show_popup.setIcon(
|
174
|
+
material_icon(
|
175
|
+
icon_name="collapse_content", size=(10, 10), convert_to_pixmap=False
|
176
|
+
)
|
177
|
+
)
|
178
|
+
self.expand.emit(True)
|
179
|
+
else:
|
180
|
+
# back to compact form
|
181
|
+
self.compact_label.setVisible(True)
|
182
|
+
self.compact_status.setVisible(True)
|
183
|
+
self.compact_show_popup.setIcon(
|
184
|
+
material_icon(
|
185
|
+
icon_name="expand_content", size=(10, 10), convert_to_pixmap=False
|
186
|
+
)
|
187
|
+
)
|
188
|
+
self.compact_view = True
|
189
|
+
self.expand.emit(False)
|
153
190
|
|
154
191
|
def setSizePolicy(self, size_policy1, size_policy2=None):
|
155
192
|
# setting size policy on the compact popup widget will set
|
@@ -172,11 +209,11 @@ class CompactPopupWidget(QWidget):
|
|
172
209
|
self.container.layout().addWidget(widget)
|
173
210
|
|
174
211
|
@Property(bool)
|
175
|
-
def
|
176
|
-
return self.
|
212
|
+
def compact_view(self):
|
213
|
+
return self.compact_label.isVisible()
|
177
214
|
|
178
|
-
@
|
179
|
-
def
|
215
|
+
@compact_view.setter
|
216
|
+
def compact_view(self, set_compact: bool):
|
180
217
|
"""Sets the compact form
|
181
218
|
|
182
219
|
If set_compact is True, the compact view is displayed ; otherwise,
|
@@ -184,11 +221,11 @@ class CompactPopupWidget(QWidget):
|
|
184
221
|
the container widget or the compact view widget.
|
185
222
|
"""
|
186
223
|
if set_compact:
|
187
|
-
self.
|
224
|
+
self.compact_view_widget.setVisible(True)
|
188
225
|
self.container.setVisible(False)
|
189
226
|
QWidget.setSizePolicy(self, QSizePolicy.Fixed, QSizePolicy.Fixed)
|
190
227
|
else:
|
191
|
-
self.
|
228
|
+
self.compact_view_widget.setVisible(False)
|
192
229
|
self.container.setVisible(True)
|
193
230
|
QWidget.setSizePolicy(self, self.container.sizePolicy())
|
194
231
|
if self.parentWidget():
|
@@ -215,6 +252,14 @@ class CompactPopupWidget(QWidget):
|
|
215
252
|
self.compact_label.setToolTip(tooltip)
|
216
253
|
self.compact_status.setToolTip(tooltip)
|
217
254
|
|
255
|
+
@Property(bool)
|
256
|
+
def expand_popup(self):
|
257
|
+
return self._expand_popup
|
258
|
+
|
259
|
+
@expand_popup.setter
|
260
|
+
def expand_popup(self, popup: bool):
|
261
|
+
self._expand_popup = popup
|
262
|
+
|
218
263
|
def closeEvent(self, event):
|
219
264
|
# Called by Qt, on closing - since the children widgets can be
|
220
265
|
# BECWidgets, it is good to explicitely call 'close' on them,
|
bec_widgets/qt_utils/toolbar.py
CHANGED
@@ -7,9 +7,18 @@ from collections import defaultdict
|
|
7
7
|
from typing import Literal
|
8
8
|
|
9
9
|
from bec_qthemes._icon.material_icons import material_icon
|
10
|
-
from qtpy.QtCore import QSize
|
10
|
+
from qtpy.QtCore import QSize, Qt
|
11
11
|
from qtpy.QtGui import QAction, QColor, QIcon
|
12
|
-
from qtpy.QtWidgets import
|
12
|
+
from qtpy.QtWidgets import (
|
13
|
+
QComboBox,
|
14
|
+
QHBoxLayout,
|
15
|
+
QLabel,
|
16
|
+
QMenu,
|
17
|
+
QSizePolicy,
|
18
|
+
QToolBar,
|
19
|
+
QToolButton,
|
20
|
+
QWidget,
|
21
|
+
)
|
13
22
|
|
14
23
|
import bec_widgets
|
15
24
|
|
@@ -154,22 +163,52 @@ class WidgetAction(ToolBarAction):
|
|
154
163
|
|
155
164
|
"""
|
156
165
|
|
157
|
-
def __init__(self, label: str | None = None, widget: QWidget = None):
|
158
|
-
super().__init__()
|
166
|
+
def __init__(self, label: str | None = None, widget: QWidget = None, parent=None):
|
167
|
+
super().__init__(parent)
|
159
168
|
self.label = label
|
160
169
|
self.widget = widget
|
161
170
|
|
162
|
-
def add_to_toolbar(self, toolbar, target):
|
163
|
-
|
164
|
-
layout = QHBoxLayout(
|
171
|
+
def add_to_toolbar(self, toolbar: QToolBar, target: QWidget):
|
172
|
+
container = QWidget()
|
173
|
+
layout = QHBoxLayout(container)
|
165
174
|
layout.setContentsMargins(0, 0, 0, 0)
|
175
|
+
layout.setSpacing(5)
|
176
|
+
|
166
177
|
if self.label is not None:
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
178
|
+
label_widget = QLabel(f"{self.label}")
|
179
|
+
label_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
180
|
+
label_widget.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
|
181
|
+
layout.addWidget(label_widget)
|
182
|
+
|
183
|
+
if isinstance(self.widget, QComboBox):
|
184
|
+
self.widget.setSizeAdjustPolicy(QComboBox.AdjustToContents)
|
185
|
+
|
186
|
+
size_policy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
187
|
+
self.widget.setSizePolicy(size_policy)
|
188
|
+
|
189
|
+
self.widget.setMinimumWidth(self.calculate_minimum_width(self.widget))
|
190
|
+
|
191
|
+
else:
|
192
|
+
self.widget.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
193
|
+
|
171
194
|
layout.addWidget(self.widget)
|
172
|
-
|
195
|
+
|
196
|
+
toolbar.addWidget(container)
|
197
|
+
|
198
|
+
@staticmethod
|
199
|
+
def calculate_minimum_width(combo_box: QComboBox) -> int:
|
200
|
+
"""
|
201
|
+
Calculate the minimum width required to display the longest item in the combo box.
|
202
|
+
|
203
|
+
Args:
|
204
|
+
combo_box (QComboBox): The combo box to calculate the width for.
|
205
|
+
|
206
|
+
Returns:
|
207
|
+
int: The calculated minimum width in pixels.
|
208
|
+
"""
|
209
|
+
font_metrics = combo_box.fontMetrics()
|
210
|
+
max_width = max(font_metrics.width(combo_box.itemText(i)) for i in range(combo_box.count()))
|
211
|
+
return max_width + 60
|
173
212
|
|
174
213
|
|
175
214
|
class ExpandableMenuAction(ToolBarAction):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from typing import TYPE_CHECKING
|
2
2
|
|
3
|
-
from qtpy.QtCore import QSize
|
3
|
+
from qtpy.QtCore import QSize, Signal, Slot
|
4
4
|
from qtpy.QtWidgets import QCompleter, QLineEdit, QSizePolicy
|
5
5
|
|
6
6
|
from bec_widgets.utils.bec_widget import BECWidget
|
@@ -24,6 +24,8 @@ class DeviceLineEdit(DeviceInputBase, QLineEdit):
|
|
24
24
|
arg_name: Argument name, can be used for the other widgets which has to call some other function in bec using correct argument names.
|
25
25
|
"""
|
26
26
|
|
27
|
+
device_selected = Signal(str)
|
28
|
+
|
27
29
|
ICON_NAME = "edit_note"
|
28
30
|
|
29
31
|
def __init__(
|
@@ -54,6 +56,18 @@ class DeviceLineEdit(DeviceInputBase, QLineEdit):
|
|
54
56
|
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
55
57
|
self.setMinimumSize(QSize(100, 0))
|
56
58
|
|
59
|
+
self.editingFinished.connect(self.emit_device_selected)
|
60
|
+
|
61
|
+
@Slot()
|
62
|
+
def emit_device_selected(self):
|
63
|
+
"""
|
64
|
+
Editing finished, let's see which device is selected and emit signal
|
65
|
+
"""
|
66
|
+
device_name = self.text().lower()
|
67
|
+
device_obj = getattr(self.dev, device_name, None)
|
68
|
+
if device_obj is not None:
|
69
|
+
self.device_selected.emit(device_name)
|
70
|
+
|
57
71
|
def set_device_filter(self, device_filter: str | list[str]):
|
58
72
|
"""
|
59
73
|
Set the device filter.
|
@@ -321,6 +321,7 @@ class BECFigure(BECWidget, pg.GraphicsLayoutWidget):
|
|
321
321
|
self,
|
322
322
|
image,
|
323
323
|
monitor: str = None,
|
324
|
+
monitor_type: Literal["1d", "2d"] = "2d",
|
324
325
|
color_bar: Literal["simple", "full"] = "full",
|
325
326
|
color_map: str = "magma",
|
326
327
|
data: np.ndarray = None,
|
@@ -337,7 +338,13 @@ class BECFigure(BECWidget, pg.GraphicsLayoutWidget):
|
|
337
338
|
data (np.ndarray): Custom data to display.
|
338
339
|
"""
|
339
340
|
if monitor is not None and data is None:
|
340
|
-
image.image(
|
341
|
+
image.image(
|
342
|
+
monitor=monitor,
|
343
|
+
monitor_type=monitor_type,
|
344
|
+
color_map=color_map,
|
345
|
+
vrange=vrange,
|
346
|
+
color_bar=color_bar,
|
347
|
+
)
|
341
348
|
elif data is not None and monitor is None:
|
342
349
|
image.add_custom_image(
|
343
350
|
name="custom", data=data, color_map=color_map, vrange=vrange, color_bar=color_bar
|
@@ -355,6 +362,7 @@ class BECFigure(BECWidget, pg.GraphicsLayoutWidget):
|
|
355
362
|
def image(
|
356
363
|
self,
|
357
364
|
monitor: str = None,
|
365
|
+
monitor_type: Literal["1d", "2d"] = "2d",
|
358
366
|
color_bar: Literal["simple", "full"] = "full",
|
359
367
|
color_map: str = "magma",
|
360
368
|
data: np.ndarray = None,
|
@@ -393,6 +401,7 @@ class BECFigure(BECWidget, pg.GraphicsLayoutWidget):
|
|
393
401
|
image = self._init_image(
|
394
402
|
image=image,
|
395
403
|
monitor=monitor,
|
404
|
+
monitor_type=monitor_type,
|
396
405
|
color_bar=color_bar,
|
397
406
|
color_map=color_map,
|
398
407
|
data=data,
|
@@ -7,10 +7,10 @@ import numpy as np
|
|
7
7
|
from bec_lib.endpoints import MessageEndpoints
|
8
8
|
from bec_lib.logger import bec_logger
|
9
9
|
from pydantic import BaseModel, Field, ValidationError
|
10
|
-
from qtpy.QtCore import QThread
|
10
|
+
from qtpy.QtCore import QThread, Slot
|
11
11
|
from qtpy.QtWidgets import QWidget
|
12
12
|
|
13
|
-
from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
|
13
|
+
# from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
|
14
14
|
from bec_widgets.utils import EntryValidator
|
15
15
|
from bec_widgets.widgets.figure.plots.image.image_item import BECImageItem, ImageItemConfig
|
16
16
|
from bec_widgets.widgets.figure.plots.image.image_processor import (
|
@@ -80,6 +80,8 @@ class BECImageShow(BECPlotBase):
|
|
80
80
|
)
|
81
81
|
# Get bec shortcuts dev, scans, queue, scan_storage, dap
|
82
82
|
self.single_image = single_image
|
83
|
+
self.image_type = "device_monitor_2d"
|
84
|
+
self.scan_id = None
|
83
85
|
self.get_bec_shortcuts()
|
84
86
|
self.entry_validator = EntryValidator(self.dev)
|
85
87
|
self._images = defaultdict(dict)
|
@@ -105,7 +107,7 @@ class BECImageShow(BECPlotBase):
|
|
105
107
|
|
106
108
|
def find_image_by_monitor(self, item_id: str) -> BECImageItem:
|
107
109
|
"""
|
108
|
-
Find the
|
110
|
+
Find the image item by its gui_id.
|
109
111
|
|
110
112
|
Args:
|
111
113
|
item_id(str): The gui_id of the widget.
|
@@ -230,6 +232,7 @@ class BECImageShow(BECPlotBase):
|
|
230
232
|
def image(
|
231
233
|
self,
|
232
234
|
monitor: str,
|
235
|
+
monitor_type: Literal["1d", "2d"] = "2d",
|
233
236
|
color_map: Optional[str] = "magma",
|
234
237
|
color_bar: Optional[Literal["simple", "full"]] = "full",
|
235
238
|
downsample: Optional[bool] = True,
|
@@ -243,6 +246,7 @@ class BECImageShow(BECPlotBase):
|
|
243
246
|
|
244
247
|
Args:
|
245
248
|
monitor(str): The name of the monitor to display.
|
249
|
+
monitor_type(Literal["1d","2d"]): The type of monitor to display.
|
246
250
|
color_bar(Literal["simple","full"]): The type of color bar to display.
|
247
251
|
color_map(str): The color map to use for the image.
|
248
252
|
data(np.ndarray): Custom data to display.
|
@@ -251,7 +255,12 @@ class BECImageShow(BECPlotBase):
|
|
251
255
|
Returns:
|
252
256
|
BECImageItem: The image item.
|
253
257
|
"""
|
254
|
-
|
258
|
+
if monitor_type == "1d":
|
259
|
+
image_source = "device_monitor_1d"
|
260
|
+
self.image_type = "device_monitor_1d"
|
261
|
+
elif monitor_type == "2d":
|
262
|
+
image_source = "device_monitor_2d"
|
263
|
+
self.image_type = "device_monitor_2d"
|
255
264
|
|
256
265
|
image_exits = self._check_image_id(monitor, self._images)
|
257
266
|
if image_exits:
|
@@ -292,7 +301,6 @@ class BECImageShow(BECPlotBase):
|
|
292
301
|
**kwargs,
|
293
302
|
):
|
294
303
|
image_source = "custom"
|
295
|
-
# image_source = "device_monitor_2d"
|
296
304
|
|
297
305
|
image_exits = self._check_image_id(name, self._images)
|
298
306
|
if image_exits:
|
@@ -516,9 +524,58 @@ class BECImageShow(BECPlotBase):
|
|
516
524
|
"""
|
517
525
|
data = msg["data"]
|
518
526
|
device = msg["device"]
|
519
|
-
|
520
|
-
|
521
|
-
|
527
|
+
if self.image_type == "device_monitor_1d":
|
528
|
+
image = self._images["device_monitor_1d"][device]
|
529
|
+
current_scan_id = metadata.get("scan_id", None)
|
530
|
+
if current_scan_id is None:
|
531
|
+
return
|
532
|
+
if current_scan_id != self.scan_id:
|
533
|
+
self.reset()
|
534
|
+
self.scan_id = current_scan_id
|
535
|
+
image.image_buffer_list = []
|
536
|
+
image.max_len = 0
|
537
|
+
image_buffer = self.adjust_image_buffer(image, data)
|
538
|
+
image.raw_data = image_buffer
|
539
|
+
self.process_image(device, image, image_buffer)
|
540
|
+
elif self.image_type == "device_monitor_2d":
|
541
|
+
image = self._images["device_monitor_2d"][device]
|
542
|
+
image.raw_data = data
|
543
|
+
self.process_image(device, image, data)
|
544
|
+
|
545
|
+
def adjust_image_buffer(self, image: BECImageItem, new_data: np.ndarray) -> np.ndarray:
|
546
|
+
"""
|
547
|
+
Adjusts the image buffer to accommodate the new data, ensuring that all rows have the same length.
|
548
|
+
|
549
|
+
Args:
|
550
|
+
image: The image object (used to store buffer list and max_len).
|
551
|
+
new_data (np.ndarray): The new incoming 1D waveform data.
|
552
|
+
|
553
|
+
Returns:
|
554
|
+
np.ndarray: The updated image buffer with adjusted shapes.
|
555
|
+
"""
|
556
|
+
new_len = new_data.shape[0]
|
557
|
+
if not hasattr(image, "image_buffer_list"):
|
558
|
+
image.image_buffer_list = []
|
559
|
+
image.max_len = 0
|
560
|
+
|
561
|
+
if new_len > image.max_len:
|
562
|
+
image.max_len = new_len
|
563
|
+
for i in range(len(image.image_buffer_list)):
|
564
|
+
wf = image.image_buffer_list[i]
|
565
|
+
pad_width = image.max_len - wf.shape[0]
|
566
|
+
if pad_width > 0:
|
567
|
+
image.image_buffer_list[i] = np.pad(
|
568
|
+
wf, (0, pad_width), mode="constant", constant_values=0
|
569
|
+
)
|
570
|
+
image.image_buffer_list.append(new_data)
|
571
|
+
else:
|
572
|
+
pad_width = image.max_len - new_len
|
573
|
+
if pad_width > 0:
|
574
|
+
new_data = np.pad(new_data, (0, pad_width), mode="constant", constant_values=0)
|
575
|
+
image.image_buffer_list.append(new_data)
|
576
|
+
|
577
|
+
image_buffer = np.array(image.image_buffer_list)
|
578
|
+
return image_buffer
|
522
579
|
|
523
580
|
@Slot(str, np.ndarray)
|
524
581
|
def update_image(self, device: str, data: np.ndarray):
|
@@ -529,7 +586,7 @@ class BECImageShow(BECPlotBase):
|
|
529
586
|
device(str): The name of the device.
|
530
587
|
data(np.ndarray): The data to be updated.
|
531
588
|
"""
|
532
|
-
image_to_update = self._images[
|
589
|
+
image_to_update = self._images[self.image_type][device]
|
533
590
|
image_to_update.updateImage(data, autoLevels=image_to_update.config.autorange)
|
534
591
|
|
535
592
|
@Slot(str, ImageStats)
|
@@ -540,7 +597,7 @@ class BECImageShow(BECPlotBase):
|
|
540
597
|
Args:
|
541
598
|
stats(ImageStats): The statistics of the image.
|
542
599
|
"""
|
543
|
-
image_to_update = self._images[
|
600
|
+
image_to_update = self._images[self.image_type][device]
|
544
601
|
if image_to_update.config.autorange:
|
545
602
|
image_to_update.auto_update_vrange(stats)
|
546
603
|
|
@@ -553,7 +610,7 @@ class BECImageShow(BECPlotBase):
|
|
553
610
|
data = image.raw_data
|
554
611
|
self.process_image(image_id, image, data)
|
555
612
|
|
556
|
-
def
|
613
|
+
def _connect_device_monitor(self, monitor: str):
|
557
614
|
"""
|
558
615
|
Connect to the device monitor.
|
559
616
|
|
@@ -566,29 +623,36 @@ class BECImageShow(BECPlotBase):
|
|
566
623
|
except AttributeError:
|
567
624
|
previous_monitor = None
|
568
625
|
if previous_monitor and image_item.connected is True:
|
626
|
+
self.bec_dispatcher.disconnect_slot(
|
627
|
+
self.on_image_update, MessageEndpoints.device_monitor_1d(previous_monitor)
|
628
|
+
)
|
569
629
|
self.bec_dispatcher.disconnect_slot(
|
570
630
|
self.on_image_update, MessageEndpoints.device_monitor_2d(previous_monitor)
|
571
631
|
)
|
572
632
|
image_item.connected = False
|
573
633
|
if monitor and image_item.connected is False:
|
574
634
|
self.entry_validator.validate_monitor(monitor)
|
575
|
-
self.
|
576
|
-
self.
|
577
|
-
|
635
|
+
if self.image_type == "device_monitor_1d":
|
636
|
+
self.bec_dispatcher.connect_slot(
|
637
|
+
self.on_image_update, MessageEndpoints.device_monitor_1d(monitor)
|
638
|
+
)
|
639
|
+
elif self.image_type == "device_monitor_2d":
|
640
|
+
self.bec_dispatcher.connect_slot(
|
641
|
+
self.on_image_update, MessageEndpoints.device_monitor_2d(monitor)
|
642
|
+
)
|
578
643
|
image_item.set_monitor(monitor)
|
579
644
|
image_item.connected = True
|
580
645
|
|
581
646
|
def _add_image_object(
|
582
647
|
self, source: str, name: str, config: ImageItemConfig, data=None
|
583
|
-
) -> BECImageItem:
|
648
|
+
) -> BECImageItem:
|
584
649
|
config.parent_id = self.gui_id
|
585
650
|
if self.single_image is True and len(self.images) > 0:
|
586
651
|
self.remove_image(0)
|
587
652
|
image = BECImageItem(config=config, parent_image=self)
|
588
653
|
self.plot_item.addItem(image)
|
589
654
|
self._images[source][name] = image
|
590
|
-
|
591
|
-
self._connect_device_monitor_2d(config.monitor)
|
655
|
+
self._connect_device_monitor(config.monitor)
|
592
656
|
self.config.images[name] = config
|
593
657
|
if data is not None:
|
594
658
|
image.setImage(data)
|
@@ -673,6 +737,9 @@ class BECImageShow(BECPlotBase):
|
|
673
737
|
"""
|
674
738
|
image = self.find_image_by_monitor(image_id)
|
675
739
|
if image:
|
740
|
+
self.bec_dispatcher.disconnect_slot(
|
741
|
+
self.on_image_update, MessageEndpoints.device_monitor_1d(image.config.monitor)
|
742
|
+
)
|
676
743
|
self.bec_dispatcher.disconnect_slot(
|
677
744
|
self.on_image_update, MessageEndpoints.device_monitor_2d(image.config.monitor)
|
678
745
|
)
|
@@ -681,9 +748,9 @@ class BECImageShow(BECPlotBase):
|
|
681
748
|
"""
|
682
749
|
Clean up the widget.
|
683
750
|
"""
|
684
|
-
for monitor in self._images[
|
751
|
+
for monitor in self._images[self.image_type]:
|
685
752
|
self.bec_dispatcher.disconnect_slot(
|
686
|
-
self.on_image_update, MessageEndpoints.
|
753
|
+
self.on_image_update, MessageEndpoints.device_monitor_1d(monitor)
|
687
754
|
)
|
688
755
|
self.images.clear()
|
689
756
|
|
@@ -4,7 +4,7 @@ import sys
|
|
4
4
|
from typing import Literal, Optional
|
5
5
|
|
6
6
|
import pyqtgraph as pg
|
7
|
-
from qtpy.QtWidgets import QVBoxLayout, QWidget
|
7
|
+
from qtpy.QtWidgets import QComboBox, QVBoxLayout, QWidget
|
8
8
|
|
9
9
|
from bec_widgets.qt_utils.error_popups import SafeSlot, WarningPopupUtility
|
10
10
|
from bec_widgets.qt_utils.settings_dialog import SettingsDialog
|
@@ -13,6 +13,7 @@ from bec_widgets.qt_utils.toolbar import (
|
|
13
13
|
MaterialIconAction,
|
14
14
|
ModularToolBar,
|
15
15
|
SeparatorAction,
|
16
|
+
WidgetAction,
|
16
17
|
)
|
17
18
|
from bec_widgets.utils.bec_widget import BECWidget
|
18
19
|
from bec_widgets.widgets.device_combobox.device_combobox import DeviceComboBox
|
@@ -63,11 +64,14 @@ class BECImageWidget(BECWidget, QWidget):
|
|
63
64
|
self.layout.setContentsMargins(0, 0, 0, 0)
|
64
65
|
|
65
66
|
self.fig = BECFigure()
|
67
|
+
self.dim_combo_box = QComboBox()
|
68
|
+
self.dim_combo_box.addItems(["1d", "2d"])
|
66
69
|
self.toolbar = ModularToolBar(
|
67
70
|
actions={
|
68
71
|
"monitor": DeviceSelectionAction(
|
69
72
|
"Monitor:", DeviceComboBox(device_filter="Device")
|
70
73
|
),
|
74
|
+
"monitor_type": WidgetAction(widget=self.dim_combo_box),
|
71
75
|
"connect": MaterialIconAction(icon_name="link", tooltip="Connect Device"),
|
72
76
|
"separator_0": SeparatorAction(),
|
73
77
|
"save": MaterialIconAction(icon_name="save", tooltip="Open Export Dialog"),
|
@@ -163,7 +167,8 @@ class BECImageWidget(BECWidget, QWidget):
|
|
163
167
|
def _connect_action(self):
|
164
168
|
monitor_combo = self.toolbar.widgets["monitor"].device_combobox
|
165
169
|
monitor_name = monitor_combo.currentText()
|
166
|
-
self.
|
170
|
+
monitor_type = self.toolbar.widgets["monitor_type"].widget.currentText()
|
171
|
+
self.image(monitor=monitor_name, monitor_type=monitor_type)
|
167
172
|
monitor_combo.setStyleSheet("QComboBox { background-color: " "; }")
|
168
173
|
|
169
174
|
def show_axis_settings(self):
|
@@ -182,6 +187,7 @@ class BECImageWidget(BECWidget, QWidget):
|
|
182
187
|
def image(
|
183
188
|
self,
|
184
189
|
monitor: str,
|
190
|
+
monitor_type: Optional[Literal["1d", "2d"]] = "2d",
|
185
191
|
color_map: Optional[str] = "magma",
|
186
192
|
color_bar: Optional[Literal["simple", "full"]] = "full",
|
187
193
|
downsample: Optional[bool] = True,
|
@@ -195,8 +201,14 @@ class BECImageWidget(BECWidget, QWidget):
|
|
195
201
|
self.toolbar.widgets["monitor"].device_combobox.setStyleSheet(
|
196
202
|
"QComboBox {{ background-color: " "; }}"
|
197
203
|
)
|
204
|
+
if self.toolbar.widgets["monitor_type"].widget.currentText() != monitor_type:
|
205
|
+
self.toolbar.widgets["monitor_type"].widget.setCurrentText(monitor_type)
|
206
|
+
self.toolbar.widgets["monitor_type"].widget.setStyleSheet(
|
207
|
+
"QComboBox {{ background-color: " "; }}"
|
208
|
+
)
|
198
209
|
return self._image.image(
|
199
210
|
monitor=monitor,
|
211
|
+
monitor_type=monitor_type,
|
200
212
|
color_map=color_map,
|
201
213
|
color_bar=color_bar,
|
202
214
|
downsample=downsample,
|
@@ -486,6 +498,7 @@ def main(): # pragma: no cover
|
|
486
498
|
|
487
499
|
app = QApplication(sys.argv)
|
488
500
|
widget = BECImageWidget()
|
501
|
+
widget.image("waveform", "1d")
|
489
502
|
widget.show()
|
490
503
|
sys.exit(app.exec_())
|
491
504
|
|
@@ -14,6 +14,7 @@ from qtpy.QtCore import Property, Signal, Slot
|
|
14
14
|
from qtpy.QtGui import QDoubleValidator
|
15
15
|
from qtpy.QtWidgets import QDialog, QDoubleSpinBox, QPushButton, QVBoxLayout, QWidget
|
16
16
|
|
17
|
+
from bec_widgets.qt_utils.compact_popup import CompactPopupWidget
|
17
18
|
from bec_widgets.utils import UILoader
|
18
19
|
from bec_widgets.utils.bec_widget import BECWidget
|
19
20
|
from bec_widgets.utils.colors import get_accent_colors, set_theme
|
@@ -24,7 +25,7 @@ logger = bec_logger.logger
|
|
24
25
|
MODULE_PATH = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
25
26
|
|
26
27
|
|
27
|
-
class PositionerBox(BECWidget,
|
28
|
+
class PositionerBox(BECWidget, CompactPopupWidget):
|
28
29
|
"""Simple Widget to control a positioner in box form"""
|
29
30
|
|
30
31
|
ui_file = "positioner_box.ui"
|
@@ -44,7 +45,7 @@ class PositionerBox(BECWidget, QWidget):
|
|
44
45
|
device (Positioner): The device to control.
|
45
46
|
"""
|
46
47
|
super().__init__(**kwargs)
|
47
|
-
|
48
|
+
CompactPopupWidget.__init__(self, parent=parent, layout=QVBoxLayout)
|
48
49
|
self.get_bec_shortcuts()
|
49
50
|
self._device = ""
|
50
51
|
self._limits = None
|
@@ -63,8 +64,7 @@ class PositionerBox(BECWidget, QWidget):
|
|
63
64
|
current_path = os.path.dirname(__file__)
|
64
65
|
self.ui = UILoader(self).loader(os.path.join(current_path, self.ui_file))
|
65
66
|
|
66
|
-
self.
|
67
|
-
self.layout.addWidget(self.ui)
|
67
|
+
self.addWidget(self.ui)
|
68
68
|
self.layout.setSpacing(0)
|
69
69
|
self.layout.setContentsMargins(0, 0, 0, 0)
|
70
70
|
|
@@ -135,8 +135,12 @@ class PositionerBox(BECWidget, QWidget):
|
|
135
135
|
"""Setter, checks if device is a string"""
|
136
136
|
if not value or not isinstance(value, str):
|
137
137
|
return
|
138
|
+
if not self._check_device_is_valid(value):
|
139
|
+
return
|
138
140
|
old_device = self._device
|
139
141
|
self._device = value
|
142
|
+
if not self.label:
|
143
|
+
self.label = value
|
140
144
|
self.device_changed.emit(old_device, value)
|
141
145
|
|
142
146
|
@Property(bool)
|
@@ -241,9 +245,11 @@ class PositionerBox(BECWidget, QWidget):
|
|
241
245
|
if is_moving:
|
242
246
|
self.ui.spinner_widget.start()
|
243
247
|
self.ui.spinner_widget.setToolTip("Device is moving")
|
248
|
+
self.set_global_state("warning")
|
244
249
|
else:
|
245
250
|
self.ui.spinner_widget.stop()
|
246
251
|
self.ui.spinner_widget.setToolTip("Device is idle")
|
252
|
+
self.set_global_state("success")
|
247
253
|
|
248
254
|
if readback_val is not None:
|
249
255
|
self.ui.readback.setText(f"{readback_val:.{precision}f}")
|
File without changes
|