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.
Files changed (26) hide show
  1. CHANGELOG.md +47 -51
  2. PKG-INFO +1 -1
  3. bec_widgets/applications/alignment/alignment_1d/alignment_1d.py +33 -89
  4. bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui +413 -715
  5. bec_widgets/examples/jupyter_console/jupyter_console_window.py +1 -1
  6. bec_widgets/qt_utils/compact_popup.py +68 -23
  7. bec_widgets/qt_utils/toolbar.py +51 -12
  8. bec_widgets/widgets/device_line_edit/device_line_edit.py +15 -1
  9. bec_widgets/widgets/figure/figure.py +10 -1
  10. bec_widgets/widgets/figure/plots/image/image.py +86 -19
  11. bec_widgets/widgets/image/image_widget.py +15 -2
  12. bec_widgets/widgets/positioner_box/positioner_box.py +10 -4
  13. bec_widgets/widgets/positioner_group/__init__.py +0 -0
  14. bec_widgets/widgets/positioner_group/positioner_group.py +170 -0
  15. bec_widgets/widgets/positioner_group/positioner_group.pyproject +1 -0
  16. bec_widgets/widgets/positioner_group/positioner_group_plugin.py +57 -0
  17. bec_widgets/widgets/positioner_group/register_positioner_group.py +15 -0
  18. bec_widgets/widgets/scan_control/scan_control.py +74 -122
  19. bec_widgets/widgets/scan_control/scan_group_box.py +66 -11
  20. bec_widgets/widgets/stop_button/stop_button.py +1 -1
  21. {bec_widgets-0.117.1.dist-info → bec_widgets-0.119.0.dist-info}/METADATA +1 -1
  22. {bec_widgets-0.117.1.dist-info → bec_widgets-0.119.0.dist-info}/RECORD +26 -21
  23. pyproject.toml +1 -1
  24. {bec_widgets-0.117.1.dist-info → bec_widgets-0.119.0.dist-info}/WHEEL +0 -0
  25. {bec_widgets-0.117.1.dist-info → bec_widgets-0.119.0.dist-info}/entry_points.txt +0 -0
  26. {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("eiger")
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.compact_view = QWidget(self)
117
- self.compact_view.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
118
- QHBoxLayout(self.compact_view)
119
- self.compact_view.layout().setSpacing(0)
120
- self.compact_view.layout().setContentsMargins(0, 0, 0, 0)
121
- self.compact_label = QLabel(self.compact_view)
122
- self.compact_status = LedLabel(self.compact_view)
123
- self.compact_show_popup = QPushButton(self.compact_view)
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="pan_zoom", size=(10, 10), convert_to_pixmap=False)
135
+ material_icon(icon_name="expand_content", size=(10, 10), convert_to_pixmap=False)
127
136
  )
128
- self.compact_view.layout().addWidget(self.compact_label)
129
- self.compact_view.layout().addWidget(self.compact_status)
130
- self.compact_view.layout().addWidget(self.compact_show_popup)
131
- self.compact_view.setVisible(False)
132
- self.layout().addWidget(self.compact_view)
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
- self._popup_window = PopupDialog(self.container)
152
- self._popup_window.show()
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 compact(self):
176
- return self.compact_view.isVisible()
212
+ def compact_view(self):
213
+ return self.compact_label.isVisible()
177
214
 
178
- @compact.setter
179
- def compact(self, set_compact: bool):
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.compact_view.setVisible(True)
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.compact_view.setVisible(False)
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,
@@ -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 QHBoxLayout, QLabel, QMenu, QSizePolicy, QToolBar, QToolButton, QWidget
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
- widget = QWidget()
164
- layout = QHBoxLayout(widget)
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
- label = QLabel(f"{self.label}")
168
- label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
169
- layout.addWidget(label)
170
- self.widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
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
- toolbar.addWidget(widget)
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(monitor=monitor, color_map=color_map, vrange=vrange, color_bar=color_bar)
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 widget by its gui_id.
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
- image_source = "device_monitor_2d"
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
- image = self._images["device_monitor_2d"][device]
520
- image.raw_data = data
521
- self.process_image(device, image, data)
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["device_monitor_2d"][device]
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["device_monitor_2d"][device]
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 _connect_device_monitor_2d(self, monitor: str):
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.bec_dispatcher.connect_slot(
576
- self.on_image_update, MessageEndpoints.device_monitor_2d(monitor)
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: # TODO fix types
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
- if source == "device_monitor_2d":
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["device_monitor_2d"]:
751
+ for monitor in self._images[self.image_type]:
685
752
  self.bec_dispatcher.disconnect_slot(
686
- self.on_image_update, MessageEndpoints.device_monitor_2d(monitor)
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.image(monitor_name)
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, QWidget):
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
- QWidget.__init__(self, parent=parent)
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.layout = QVBoxLayout(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