bec-widgets 0.87.0__py3-none-any.whl → 0.88.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 (77) hide show
  1. CHANGELOG.md +66 -70
  2. PKG-INFO +1 -1
  3. bec_widgets/assets/designer_icons/BECWaveformWidget.png +0 -0
  4. bec_widgets/assets/designer_icons/colormap_selector.png +0 -0
  5. bec_widgets/assets/toolbar_icons/add.svg +3 -0
  6. bec_widgets/assets/toolbar_icons/auto_range.svg +3 -0
  7. bec_widgets/assets/toolbar_icons/drag_pan_mode.svg +3 -0
  8. bec_widgets/assets/toolbar_icons/export.svg +9 -0
  9. bec_widgets/assets/toolbar_icons/fitting_parameters.svg +3 -0
  10. bec_widgets/assets/toolbar_icons/import.svg +9 -0
  11. bec_widgets/assets/toolbar_icons/line_axis.svg +3 -0
  12. bec_widgets/assets/toolbar_icons/photo_library.svg +3 -0
  13. bec_widgets/assets/toolbar_icons/rectangle_mode.svg +3 -0
  14. bec_widgets/assets/toolbar_icons/remove.svg +5 -0
  15. bec_widgets/assets/toolbar_icons/save.svg +3 -0
  16. bec_widgets/assets/toolbar_icons/settings.svg +4 -0
  17. bec_widgets/cli/client.py +307 -5
  18. bec_widgets/cli/server.py +2 -1
  19. bec_widgets/examples/jupyter_console/jupyter_console_window.py +13 -7
  20. bec_widgets/examples/plugin_example_pyside/tictactoe.py +1 -0
  21. bec_widgets/utils/bec_connector.py +19 -18
  22. bec_widgets/utils/bec_widget.py +19 -4
  23. bec_widgets/widgets/base_classes/device_input_base.py +3 -5
  24. bec_widgets/widgets/bec_queue/bec_queue.py +3 -2
  25. bec_widgets/widgets/bec_status_box/bec_status_box.py +2 -11
  26. bec_widgets/widgets/{color_map_selector/color_map_selector.py → colormap_selector/colormap_selector.py} +3 -1
  27. bec_widgets/widgets/colormap_selector/colormap_selector.pyproject +1 -0
  28. bec_widgets/widgets/{color_map_selector/color_map_selector_plugin.py → colormap_selector/colormap_selector_plugin.py} +8 -6
  29. bec_widgets/widgets/{color_map_selector/register_color_map_selector.py → colormap_selector/register_colormap_selector.py} +1 -1
  30. bec_widgets/widgets/device_box/device_box.py +2 -2
  31. bec_widgets/widgets/device_combobox/device_combobox.py +1 -8
  32. bec_widgets/widgets/device_line_edit/device_line_edit.py +2 -9
  33. bec_widgets/widgets/dock/dock.py +8 -6
  34. bec_widgets/widgets/dock/dock_area.py +4 -2
  35. bec_widgets/widgets/figure/figure.py +16 -8
  36. bec_widgets/widgets/figure/plots/axis_settings.py +38 -11
  37. bec_widgets/widgets/figure/plots/image/image.py +2 -4
  38. bec_widgets/widgets/figure/plots/motor_map/motor_map.py +1 -1
  39. bec_widgets/widgets/figure/plots/plot_base.py +7 -8
  40. bec_widgets/widgets/figure/plots/waveform/waveform.py +49 -53
  41. bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +10 -1
  42. bec_widgets/widgets/jupyter_console/jupyter_console.py +6 -0
  43. bec_widgets/widgets/motor_map/bec_motor_map_widget_plugin.py +1 -1
  44. bec_widgets/widgets/motor_map/motor_map_widget.py +9 -6
  45. bec_widgets/widgets/ring_progress_bar/ring.py +0 -4
  46. bec_widgets/widgets/ring_progress_bar/ring_progress_bar.py +8 -8
  47. bec_widgets/widgets/scan_control/scan_control.py +2 -6
  48. bec_widgets/widgets/stop_button/stop_button.py +2 -2
  49. bec_widgets/widgets/text_box/text_box.py +3 -2
  50. bec_widgets/widgets/waveform/__init__.py +0 -0
  51. bec_widgets/widgets/waveform/bec_waveform_widget.pyproject +1 -0
  52. bec_widgets/widgets/waveform/bec_waveform_widget_plugin.py +59 -0
  53. bec_widgets/widgets/waveform/register_bec_waveform_widget.py +15 -0
  54. bec_widgets/widgets/waveform/waveform_toolbar/__init__.py +0 -0
  55. bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/__init__.py +0 -0
  56. bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.py +341 -0
  57. bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.ui +358 -0
  58. bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/__init__.py +0 -0
  59. bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/dap_summary.ui +127 -0
  60. bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/dap_summary_dialog.py +69 -0
  61. bec_widgets/widgets/waveform/waveform_toolbar/waveform_toolbar.py +117 -0
  62. bec_widgets/widgets/waveform/waveform_widget.py +571 -0
  63. bec_widgets/widgets/website/website.py +2 -2
  64. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/METADATA +1 -1
  65. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/RECORD +75 -48
  66. pyproject.toml +1 -1
  67. tests/unit_tests/test_color_map_selector.py +1 -1
  68. tests/unit_tests/test_device_input_base.py +10 -4
  69. tests/unit_tests/test_waveform_widget.py +264 -0
  70. bec_widgets/widgets/color_map_selector/assets/color_map_selector_icon.png +0 -0
  71. bec_widgets/widgets/color_map_selector/color_map_selector.pyproject +0 -1
  72. /bec_widgets/assets/{bec_widgets_icon.png → app_icons/bec_widgets_icon.png} +0 -0
  73. /bec_widgets/assets/{terminal_icon.png → app_icons/terminal_icon.png} +0 -0
  74. /bec_widgets/widgets/{color_map_selector → colormap_selector}/__init__.py +0 -0
  75. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/WHEEL +0 -0
  76. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/entry_points.txt +0 -0
  77. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,15 +5,18 @@ import os
5
5
  from qtpy.QtDesigner import QDesignerCustomWidgetInterface
6
6
  from qtpy.QtGui import QIcon
7
7
 
8
- from bec_widgets.widgets.color_map_selector.color_map_selector import ColormapSelector
8
+ import bec_widgets
9
+ from bec_widgets.widgets.colormap_selector.colormap_selector import ColormapSelector
9
10
 
10
11
  DOM_XML = """
11
12
  <ui language='c++'>
12
- <widget class='ColormapSelector' name='color_map_selector'>
13
+ <widget class='ColormapSelector' name='colormap_selector'>
13
14
  </widget>
14
15
  </ui>
15
16
  """
16
17
 
18
+ MODULE_PATH = os.path.dirname(bec_widgets.__file__)
19
+
17
20
 
18
21
  class ColormapSelectorPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
19
22
  def __init__(self):
@@ -31,12 +34,11 @@ class ColormapSelectorPlugin(QDesignerCustomWidgetInterface): # pragma: no cove
31
34
  return "BEC Buttons"
32
35
 
33
36
  def icon(self):
34
- current_path = os.path.dirname(__file__)
35
- icon_path = os.path.join(current_path, "assets", "color_map_selector_icon.png")
37
+ icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "colormap_selector.png")
36
38
  return QIcon(icon_path)
37
39
 
38
40
  def includeFile(self):
39
- return "color_map_selector"
41
+ return "colormap_selector"
40
42
 
41
43
  def initialize(self, form_editor):
42
44
  self._form_editor = form_editor
@@ -51,7 +53,7 @@ class ColormapSelectorPlugin(QDesignerCustomWidgetInterface): # pragma: no cove
51
53
  return "ColormapSelector"
52
54
 
53
55
  def toolTip(self):
54
- return "A custom QComboBox widget for selecting colormaps."
56
+ return ""
55
57
 
56
58
  def whatsThis(self):
57
59
  return self.toolTip()
@@ -6,7 +6,7 @@ def main(): # pragma: no cover
6
6
  return
7
7
  from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
8
8
 
9
- from bec_widgets.widgets.color_map_selector.color_map_selector_plugin import (
9
+ from bec_widgets.widgets.colormap_selector.colormap_selector_plugin import (
10
10
  ColormapSelectorPlugin,
11
11
  )
12
12
 
@@ -8,11 +8,11 @@ from qtpy.QtGui import QDoubleValidator
8
8
  from qtpy.QtWidgets import QDoubleSpinBox, QVBoxLayout, QWidget
9
9
 
10
10
  from bec_widgets.utils import UILoader
11
- from bec_widgets.utils.bec_connector import BECConnector
11
+ from bec_widgets.utils.bec_widget import BECWidget
12
12
  from bec_widgets.utils.colors import apply_theme
13
13
 
14
14
 
15
- class DeviceBox(BECConnector, QWidget):
15
+ class DeviceBox(BECWidget, QWidget):
16
16
  device_changed = Signal(str, str)
17
17
 
18
18
  def __init__(self, parent=None, device=None, *args, **kwargs):
@@ -2,6 +2,7 @@ from typing import TYPE_CHECKING
2
2
 
3
3
  from qtpy.QtWidgets import QComboBox
4
4
 
5
+ from bec_widgets.utils.bec_widget import BECWidget
5
6
  from bec_widgets.widgets.base_classes.device_input_base import DeviceInputBase, DeviceInputConfig
6
7
 
7
8
  if TYPE_CHECKING:
@@ -82,11 +83,3 @@ class DeviceComboBox(DeviceInputBase, QComboBox):
82
83
  if device_obj is None:
83
84
  raise ValueError(f"Device {device_name} is not found.")
84
85
  return device_obj
85
-
86
- def cleanup(self):
87
- """Cleanup the widget."""
88
- super().cleanup()
89
-
90
- def closeEvent(self, event):
91
- super().cleanup()
92
- return QComboBox.closeEvent(self, event)
@@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
3
3
  from qtpy.QtCore import QSize
4
4
  from qtpy.QtWidgets import QCompleter, QLineEdit, QSizePolicy
5
5
 
6
+ from bec_widgets.utils.bec_widget import BECWidget
6
7
  from bec_widgets.widgets.base_classes.device_input_base import DeviceInputBase, DeviceInputConfig
7
8
 
8
9
  if TYPE_CHECKING:
@@ -33,8 +34,8 @@ class DeviceLineEdit(DeviceInputBase, QLineEdit):
33
34
  default: str | None = None,
34
35
  arg_name: str | None = None,
35
36
  ):
37
+ super().__init__(client=client, config=config, gui_id=gui_id)
36
38
  QLineEdit.__init__(self, parent=parent)
37
- DeviceInputBase.__init__(self, client=client, config=config, gui_id=gui_id)
38
39
 
39
40
  self.completer = QCompleter(self)
40
41
  self.setCompleter(self.completer)
@@ -94,11 +95,3 @@ class DeviceLineEdit(DeviceInputBase, QLineEdit):
94
95
  if device_obj is None:
95
96
  raise ValueError(f"Device {device_name} is not found.")
96
97
  return device_obj
97
-
98
- def cleanup(self):
99
- """Cleanup the widget."""
100
- super().cleanup()
101
-
102
- def closeEvent(self, event):
103
- super().cleanup()
104
- return QLineEdit.closeEvent(self, event)
@@ -6,7 +6,8 @@ from pydantic import Field
6
6
  from pyqtgraph.dockarea import Dock
7
7
 
8
8
  from bec_widgets.cli.rpc_wigdet_handler import widget_handler
9
- from bec_widgets.utils import BECConnector, ConnectionConfig, GridLayoutManager
9
+ from bec_widgets.utils import ConnectionConfig, GridLayoutManager
10
+ from bec_widgets.utils.bec_widget import BECWidget
10
11
 
11
12
  if TYPE_CHECKING:
12
13
  from qtpy.QtWidgets import QWidget
@@ -24,7 +25,7 @@ class DockConfig(ConnectionConfig):
24
25
  )
25
26
 
26
27
 
27
- class BECDock(BECConnector, Dock):
28
+ class BECDock(BECWidget, Dock):
28
29
  USER_ACCESS = [
29
30
  "_config_dict",
30
31
  "_rpc_id",
@@ -91,7 +92,7 @@ class BECDock(BECConnector, Dock):
91
92
  super().float()
92
93
 
93
94
  @property
94
- def widget_list(self) -> list[BECConnector]:
95
+ def widget_list(self) -> list[BECWidget]:
95
96
  """
96
97
  Get the widgets in the dock.
97
98
 
@@ -101,7 +102,7 @@ class BECDock(BECConnector, Dock):
101
102
  return self.widgets
102
103
 
103
104
  @widget_list.setter
104
- def widget_list(self, value: list[BECConnector]):
105
+ def widget_list(self, value: list[BECWidget]):
105
106
  self.widgets = value
106
107
 
107
108
  def hide_title_bar(self):
@@ -153,13 +154,13 @@ class BECDock(BECConnector, Dock):
153
154
 
154
155
  def add_widget(
155
156
  self,
156
- widget: BECConnector | str,
157
+ widget: BECWidget | str,
157
158
  row=None,
158
159
  col=0,
159
160
  rowspan=1,
160
161
  colspan=1,
161
162
  shift: Literal["down", "up", "left", "right"] = "down",
162
- ) -> BECConnector:
163
+ ) -> BECWidget:
163
164
  """
164
165
  Add a widget to the dock.
165
166
 
@@ -238,6 +239,7 @@ class BECDock(BECConnector, Dock):
238
239
  for widget in self.widgets:
239
240
  if hasattr(widget, "cleanup"):
240
241
  widget.cleanup()
242
+ self.widgets.clear()
241
243
  super().cleanup()
242
244
 
243
245
  def close(self):
@@ -9,7 +9,8 @@ from qtpy.QtCore import Qt
9
9
  from qtpy.QtGui import QPainter, QPaintEvent
10
10
  from qtpy.QtWidgets import QWidget
11
11
 
12
- from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils
12
+ from bec_widgets.utils import ConnectionConfig, WidgetContainerUtils
13
+ from bec_widgets.utils.bec_widget import BECWidget
13
14
 
14
15
  from .dock import BECDock, DockConfig
15
16
 
@@ -21,7 +22,7 @@ class DockAreaConfig(ConnectionConfig):
21
22
  )
22
23
 
23
24
 
24
- class BECDockArea(BECConnector, DockArea):
25
+ class BECDockArea(BECWidget, DockArea):
25
26
  USER_ACCESS = [
26
27
  "_config_dict",
27
28
  "panels",
@@ -227,6 +228,7 @@ class BECDockArea(BECConnector, DockArea):
227
228
  self.attach_all()
228
229
  for dock in dict(self.docks).values():
229
230
  dock.remove()
231
+ self.docks.clear()
230
232
 
231
233
  def cleanup(self):
232
234
  """
@@ -12,7 +12,8 @@ from qtpy.QtCore import Signal as pyqtSignal
12
12
  from qtpy.QtWidgets import QWidget
13
13
  from typeguard import typechecked
14
14
 
15
- from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils
15
+ from bec_widgets.utils import ConnectionConfig, WidgetContainerUtils
16
+ from bec_widgets.utils.bec_widget import BECWidget
16
17
  from bec_widgets.utils.colors import apply_theme
17
18
  from bec_widgets.widgets.figure.plots.image.image import BECImageShow, ImageConfig
18
19
  from bec_widgets.widgets.figure.plots.motor_map.motor_map import BECMotorMap, MotorMapConfig
@@ -108,7 +109,7 @@ class WidgetHandler:
108
109
  return widget
109
110
 
110
111
 
111
- class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
112
+ class BECFigure(BECWidget, pg.GraphicsLayoutWidget):
112
113
  USER_ACCESS = [
113
114
  "_rpc_id",
114
115
  "_config_dict",
@@ -121,6 +122,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
121
122
  "remove",
122
123
  "change_layout",
123
124
  "change_theme",
125
+ "export",
124
126
  "clear_all",
125
127
  "widget_list",
126
128
  ]
@@ -227,6 +229,17 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
227
229
  """
228
230
  self._widgets = value
229
231
 
232
+ def export(self):
233
+ """Export the plot widget."""
234
+ try:
235
+ plot_item = self.widget_list[0]
236
+ except:
237
+ raise ValueError("No plot widget available to export.")
238
+
239
+ scene = plot_item.scene()
240
+ scene.contextMenuItem = plot_item
241
+ scene.showExportDialog()
242
+
230
243
  @typechecked
231
244
  def plot(
232
245
  self,
@@ -728,14 +741,9 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
728
741
  """Clear all widgets from the figure and reset to default state"""
729
742
  for widget in list(self._widgets.values()):
730
743
  widget.remove()
731
- # self.clear()
732
- self._widgets = defaultdict(dict)
744
+ self._widgets.clear()
733
745
  self.grid = []
734
746
  theme = self.config.theme
735
747
  self.config = FigureConfig(
736
748
  widget_class=self.__class__.__name__, gui_id=self.gui_id, theme=theme
737
749
  )
738
-
739
- # def cleanup(self):
740
- # self.clear_all()
741
- # super().cleanup()
@@ -1,14 +1,14 @@
1
1
  import os
2
2
 
3
3
  from qtpy.QtCore import Slot
4
- from qtpy.QtWidgets import QVBoxLayout, QWidget
4
+ from qtpy.QtWidgets import QVBoxLayout
5
5
 
6
+ from bec_widgets.qt_utils.settings_dialog import SettingWidget
6
7
  from bec_widgets.utils import UILoader
7
- from bec_widgets.utils.colors import apply_theme
8
8
  from bec_widgets.utils.widget_io import WidgetIO
9
9
 
10
10
 
11
- class AxisSettings(QWidget):
11
+ class AxisSettings(SettingWidget):
12
12
  def __init__(self, parent=None, *args, **kwargs):
13
13
  super().__init__(parent=parent, *args, **kwargs)
14
14
 
@@ -25,6 +25,10 @@ class AxisSettings(QWidget):
25
25
 
26
26
  @Slot(dict)
27
27
  def display_current_settings(self, axis_config: dict):
28
+
29
+ if axis_config == {}:
30
+ return
31
+
28
32
  # Top Box
29
33
  WidgetIO.set_value(self.ui.plot_title, axis_config["title"])
30
34
 
@@ -37,6 +41,10 @@ class AxisSettings(QWidget):
37
41
  WidgetIO.check_and_adjust_limits(self.ui.x_max, axis_config["x_lim"][1])
38
42
  WidgetIO.set_value(self.ui.x_min, axis_config["x_lim"][0])
39
43
  WidgetIO.set_value(self.ui.x_max, axis_config["x_lim"][1])
44
+ if axis_config["x_lim"] is None:
45
+ x_range = self.target_widget.fig.widget_list[0].plot_item.viewRange()[0]
46
+ WidgetIO.set_value(self.ui.x_min, x_range[0])
47
+ WidgetIO.set_value(self.ui.x_max, x_range[1])
40
48
 
41
49
  # Y Axis Box
42
50
  WidgetIO.set_value(self.ui.y_label, axis_config["y_label"])
@@ -47,15 +55,34 @@ class AxisSettings(QWidget):
47
55
  WidgetIO.check_and_adjust_limits(self.ui.y_max, axis_config["y_lim"][1])
48
56
  WidgetIO.set_value(self.ui.y_min, axis_config["y_lim"][0])
49
57
  WidgetIO.set_value(self.ui.y_max, axis_config["y_lim"][1])
58
+ if axis_config["y_lim"] is None:
59
+ y_range = self.target_widget.fig.widget_list[0].plot_item.viewRange()[1]
60
+ WidgetIO.set_value(self.ui.y_min, y_range[0])
61
+ WidgetIO.set_value(self.ui.y_max, y_range[1])
50
62
 
63
+ @Slot()
64
+ def accept_changes(self):
65
+ title = WidgetIO.get_value(self.ui.plot_title)
51
66
 
52
- if __name__ == "__main__":
53
- import sys
67
+ # X Axis
68
+ x_label = WidgetIO.get_value(self.ui.x_label)
69
+ x_scale = self.ui.x_scale.currentText()
70
+ x_grid = WidgetIO.get_value(self.ui.x_grid)
71
+ x_lim = (WidgetIO.get_value(self.ui.x_min), WidgetIO.get_value(self.ui.x_max))
54
72
 
55
- from qtpy.QtWidgets import QApplication
73
+ # Y Axis
74
+ y_label = WidgetIO.get_value(self.ui.y_label)
75
+ y_scale = self.ui.y_scale.currentText()
76
+ y_grid = WidgetIO.get_value(self.ui.y_grid)
77
+ y_lim = (WidgetIO.get_value(self.ui.y_min), WidgetIO.get_value(self.ui.y_max))
56
78
 
57
- app = QApplication(sys.argv)
58
- apply_theme("dark")
59
- window = AxisSettings()
60
- window.show()
61
- sys.exit(app.exec_())
79
+ self.target_widget.set(
80
+ title=title,
81
+ x_label=x_label,
82
+ x_scale=x_scale,
83
+ x_lim=x_lim,
84
+ y_label=y_label,
85
+ y_scale=y_scale,
86
+ y_lim=y_lim,
87
+ )
88
+ self.target_widget.set_grid(x_grid, y_grid)
@@ -55,6 +55,7 @@ class BECImageShow(BECPlotBase):
55
55
  "set_y_lim",
56
56
  "set_grid",
57
57
  "lock_aspect_ratio",
58
+ "export",
58
59
  "remove",
59
60
  "images",
60
61
  ]
@@ -596,7 +597,4 @@ class BECImageShow(BECPlotBase):
596
597
  self.bec_dispatcher.disconnect_slot(
597
598
  self.on_image_update, MessageEndpoints.device_monitor(monitor)
598
599
  )
599
- for image in self.images:
600
- image.cleanup()
601
-
602
- super().cleanup()
600
+ self.images.clear()
@@ -58,6 +58,7 @@ class BECMotorMap(BECPlotBase):
58
58
  "set_background_value",
59
59
  "set_scatter_size",
60
60
  "get_data",
61
+ "export",
61
62
  "remove",
62
63
  "reset_history",
63
64
  ]
@@ -518,4 +519,3 @@ class BECMotorMap(BECPlotBase):
518
519
  def cleanup(self):
519
520
  """Cleanup the widget."""
520
521
  self._disconnect_current_motors()
521
- super().cleanup()
@@ -2,11 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Literal, Optional
4
4
 
5
- import numpy as np
6
5
  import pyqtgraph as pg
7
6
  from pydantic import BaseModel, Field
8
- from qtpy import QT_VERSION
9
- from qtpy.QtGui import QFont, QFontDatabase, QFontInfo
10
7
  from qtpy.QtWidgets import QWidget
11
8
 
12
9
  from bec_widgets.utils import BECConnector, ConnectionConfig
@@ -57,6 +54,7 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
57
54
  "set_y_lim",
58
55
  "set_grid",
59
56
  "lock_aspect_ratio",
57
+ "export",
60
58
  "remove",
61
59
  "set_legend_label_size",
62
60
  ]
@@ -293,12 +291,13 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
293
291
  """
294
292
  self.plot_item.setAspectLocked(lock)
295
293
 
294
+ def export(self):
295
+ """Show the Export Dialog of the plot widget."""
296
+ scene = self.plot_item.scene()
297
+ scene.contextMenuItem = self.plot_item
298
+ scene.showExportDialog()
299
+
296
300
  def remove(self):
297
301
  """Remove the plot widget from the figure."""
298
302
  if self.figure is not None:
299
- self.cleanup()
300
303
  self.figure.remove(widget_id=self.gui_id)
301
-
302
- def cleanup(self):
303
- """Cleanup the plot widget."""
304
- super().cleanup()
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import datetime
4
- import time
5
3
  from collections import defaultdict
6
4
  from typing import Any, Literal, Optional
7
5
 
@@ -10,7 +8,8 @@ import pyqtgraph as pg
10
8
  from bec_lib import messages
11
9
  from bec_lib.device import ReadoutPriority
12
10
  from bec_lib.endpoints import MessageEndpoints
13
- from pydantic import Field, ValidationError
11
+ from pydantic import Field, ValidationError, field_validator
12
+ from pyqtgraph.exporters import MatplotlibExporter
14
13
  from qtpy.QtCore import Signal as pyqtSignal
15
14
  from qtpy.QtCore import Slot as pyqtSlot
16
15
  from qtpy.QtWidgets import QWidget
@@ -26,13 +25,16 @@ from bec_widgets.widgets.figure.plots.waveform.waveform_curve import (
26
25
 
27
26
 
28
27
  class Waveform1DConfig(SubplotConfig):
29
- color_palette: Literal["plasma", "viridis", "inferno", "magma"] = Field(
30
- "plasma", description="The color palette of the figure widget."
28
+ color_palette: Optional[str] = Field(
29
+ "plasma", description="The color palette of the figure widget.", validate_default=True
31
30
  )
32
31
  curves: dict[str, CurveConfig] = Field(
33
32
  {}, description="The list of curves to be added to the 1D waveform widget."
34
33
  )
35
34
 
35
+ model_config: dict = {"validate_assignment": True}
36
+ _validate_color_map_z = field_validator("color_palette")(Colors.validate_color_map)
37
+
36
38
 
37
39
  class BECWaveform(BECPlotBase):
38
40
  READOUT_PRIORITY_HANDLER = {
@@ -63,7 +65,9 @@ class BECWaveform(BECPlotBase):
63
65
  "set_x_lim",
64
66
  "set_y_lim",
65
67
  "set_grid",
68
+ "set_colormap",
66
69
  "lock_aspect_ratio",
70
+ "export",
67
71
  "remove",
68
72
  "clear_all",
69
73
  "set_legend_label_size",
@@ -71,6 +75,7 @@ class BECWaveform(BECPlotBase):
71
75
  scan_signal_update = pyqtSignal()
72
76
  async_signal_update = pyqtSignal()
73
77
  dap_params_update = pyqtSignal(dict)
78
+ dap_summary_update = pyqtSignal(dict)
74
79
  autorange_signal = pyqtSignal()
75
80
 
76
81
  def __init__(
@@ -385,7 +390,6 @@ class BECWaveform(BECPlotBase):
385
390
 
386
391
  self.async_signal_update.emit()
387
392
  self.scan_signal_update.emit()
388
- # self.autorange_timer.start(200)
389
393
 
390
394
  @pyqtSlot()
391
395
  def auto_range(self):
@@ -651,6 +655,19 @@ class BECWaveform(BECPlotBase):
651
655
  params[curve_id] = curve.dap_params
652
656
  return params
653
657
 
658
+ @pyqtSlot()
659
+ def get_dap_summary(self) -> dict:
660
+ """
661
+ Get the DAP summary of all DAP curves.
662
+
663
+ Returns:
664
+ dict: DAP summary of all DAP curves.
665
+ """
666
+ summary = {}
667
+ for curve_id, curve in self._curves_data["DAP"].items():
668
+ summary[curve_id] = curve.dap_summary
669
+ return summary
670
+
654
671
  def _add_curve_object(
655
672
  self,
656
673
  name: str,
@@ -954,6 +971,22 @@ class BECWaveform(BECPlotBase):
954
971
  current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
955
972
  self.plot_item.setLabel("bottom", f"{current_label}{self._x_axis_mode['label_suffix']}")
956
973
 
974
+ def set_colormap(self, colormap: str | None = None):
975
+ """
976
+ Set the colormap of the plot widget.
977
+
978
+ Args:
979
+ colormap(str, optional): Scale the colors of curves to colormap. If None, use the default color palette.
980
+ """
981
+ if colormap is not None:
982
+ self.config.color_palette = colormap
983
+
984
+ colors = Colors.golden_angle_color(
985
+ colormap=self.config.color_palette, num=len(self.plot_item.curves) + 1, format="HEX"
986
+ )
987
+ for curve, color in zip(self.curves, colors):
988
+ curve.set_color(color)
989
+
957
990
  def setup_dap(self, old_scan_id: str | None, new_scan_id: str | None):
958
991
  """
959
992
  Setup DAP for the new scan.
@@ -1051,7 +1084,9 @@ class BECWaveform(BECPlotBase):
1051
1084
  y = msg["data"][0]["y"]
1052
1085
  curve.setData(x, y)
1053
1086
  curve.dap_params = msg["data"][1]["fit_parameters"]
1087
+ curve.dap_summary = msg["data"][1]["fit_summary"]
1054
1088
  self.dap_params_update.emit(curve.dap_params)
1089
+ self.dap_summary_update.emit(curve.dap_summary)
1055
1090
  break
1056
1091
 
1057
1092
  @pyqtSlot(dict, dict)
@@ -1180,7 +1215,6 @@ class BECWaveform(BECPlotBase):
1180
1215
  timestamps = self.scan_item.data[y_name][y_entry].timestamps
1181
1216
 
1182
1217
  x_data = timestamps
1183
- print(x_data)
1184
1218
  return x_data
1185
1219
  if self._x_axis_mode["name"] == "index":
1186
1220
  x_data = None
@@ -1214,49 +1248,6 @@ class BECWaveform(BECPlotBase):
1214
1248
  x_data = []
1215
1249
  return x_data
1216
1250
 
1217
- # def _get_x_data(self, curve: BECCurve, y_name: str, y_entry: str) -> list | np.ndarray | None:
1218
- # """
1219
- # Get the x data for the curve with the decision logic based on the curve configuration:
1220
- # - If x is called 'timestamp', use the timestamp data from the scan item.
1221
- # - If x is called 'index', use the rolling index.
1222
- # - If x is a custom signal, use the data from the scan item.
1223
- # - If x is not specified, use the first device from the scan report.
1224
- #
1225
- # Args:
1226
- # curve(BECCurve): The curve object.
1227
- #
1228
- # Returns:
1229
- # list|np.ndarray|None: X data for the curve.
1230
- # """
1231
- # x_data = None
1232
- # if curve.config.signals.x is not None:
1233
- # if curve.config.signals.x.name == "timestamp":
1234
- # timestamps = self.scan_item.data[y_name][y_entry].timestamps
1235
- # x_data = self.convert_timestamps(timestamps)
1236
- # elif curve.config.signals.x.name == "index":
1237
- # x_data = None
1238
- # else:
1239
- # x_name = curve.config.signals.x.name
1240
- # x_entry = curve.config.signals.x.entry
1241
- # try:
1242
- # x_data = self.scan_item.data[x_name][x_entry].val
1243
- # except TypeError:
1244
- # x_data = []
1245
- # else:
1246
- # if len(self._curves_data["async"]) > 0:
1247
- # x_data = None
1248
- # else:
1249
- # x_name = self.scan_item.status_message.info["scan_report_devices"][0]
1250
- # x_entry = self.entry_validator.validate_signal(x_name, None)
1251
- # x_data = self.scan_item.data[x_name][x_entry].val
1252
- # self._x_axis_mode["label_suffix"] = f" [auto: {x_name}-{x_entry}]"
1253
- # current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
1254
- # self.plot_item.setLabel(
1255
- # "bottom", f"{current_label}{self._x_axis_mode['label_suffix']}"
1256
- # )
1257
- #
1258
- # return x_data
1259
-
1260
1251
  def _make_z_gradient(self, data_z: list | np.ndarray, colormap: str) -> list | None:
1261
1252
  """
1262
1253
  Make a gradient color for the z values.
@@ -1349,6 +1340,13 @@ class BECWaveform(BECPlotBase):
1349
1340
  return combined_data
1350
1341
  return data
1351
1342
 
1343
+ def export_to_matplotlib(self):
1344
+ """
1345
+ Export current waveform to matplotlib gui. Available only if matplotlib is installed in the enviroment.
1346
+
1347
+ """
1348
+ MatplotlibExporter(self.plot_item).export()
1349
+
1352
1350
  def clear_all(self):
1353
1351
  curves_data = self._curves_data
1354
1352
  sources = list(curves_data.keys())
@@ -1368,6 +1366,4 @@ class BECWaveform(BECPlotBase):
1368
1366
  self.on_async_readback,
1369
1367
  MessageEndpoints.device_async_readback(self.scan_id, curve_id),
1370
1368
  )
1371
- for curve in self.curves:
1372
- curve.cleanup()
1373
- super().cleanup()
1369
+ self.curves.clear()
@@ -101,6 +101,7 @@ class BECCurve(BECConnector, pg.PlotDataItem):
101
101
  self.parent_item = parent_item
102
102
  self.apply_config()
103
103
  self.dap_params = None
104
+ self.dap_summary = None
104
105
  if kwargs:
105
106
  self.set(**kwargs)
106
107
 
@@ -132,6 +133,14 @@ class BECCurve(BECConnector, pg.PlotDataItem):
132
133
  def dap_params(self, value):
133
134
  self._dap_params = value
134
135
 
136
+ @property
137
+ def dap_summary(self):
138
+ return self._dap_report
139
+
140
+ @dap_summary.setter
141
+ def dap_summary(self, value):
142
+ self._dap_report = value
143
+
135
144
  def set_data(self, x, y):
136
145
  if self.config.source == "custom":
137
146
  self.setData(x, y)
@@ -262,4 +271,4 @@ class BECCurve(BECConnector, pg.PlotDataItem):
262
271
  """Remove the curve from the plot."""
263
272
  # self.parent_item.removeItem(self)
264
273
  self.parent_item.remove_curve(self.name())
265
- self.cleanup()
274
+ self.rpc_register.remove_rpc(self)
@@ -10,6 +10,7 @@ class BECJupyterConsole(RichJupyterWidget): # pragma: no cover:
10
10
  super().__init__()
11
11
 
12
12
  self.inprocess = None
13
+ self.client = None
13
14
 
14
15
  self.kernel_manager, self.kernel_client = self._init_kernel(inprocess=inprocess)
15
16
  self.set_default_style("linux")
@@ -60,6 +61,11 @@ class BECJupyterConsole(RichJupyterWidget): # pragma: no cover:
60
61
  self.kernel_client.stop_channels()
61
62
  self.kernel_manager.shutdown_kernel()
62
63
 
64
+ def closeEvent(self, event):
65
+ self.shutdown_kernel()
66
+ if self.client:
67
+ self.client.shutdown()
68
+
63
69
 
64
70
  if __name__ == "__main__": # pragma: no cover
65
71
  import sys
@@ -26,7 +26,7 @@ class BECMotorMapWidgetPlugin(QDesignerCustomWidgetInterface): # pragma: no cov
26
26
  return DOM_XML
27
27
 
28
28
  def group(self):
29
- return "BEC Visualization Widgets"
29
+ return "BEC Plots"
30
30
 
31
31
  def icon(self):
32
32
  current_path = os.path.dirname(__file__)