bec-widgets 1.25.1__py3-none-any.whl → 2.0.1__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 (196) hide show
  1. .gitlab-ci.yml +3 -5
  2. CHANGELOG.md +639 -0
  3. PKG-INFO +3 -3
  4. bec_widgets/__init__.py +4 -0
  5. bec_widgets/applications/bw_launch.py +23 -0
  6. bec_widgets/applications/launch_window.py +430 -0
  7. bec_widgets/assets/app_icons/auto_update.png +0 -0
  8. bec_widgets/assets/app_icons/ui_loader_tile.png +0 -0
  9. bec_widgets/cli/__init__.py +0 -1
  10. bec_widgets/cli/client.py +1779 -2064
  11. bec_widgets/cli/client_utils.py +346 -174
  12. bec_widgets/cli/generate_cli.py +143 -37
  13. bec_widgets/cli/rpc/rpc_base.py +152 -21
  14. bec_widgets/cli/rpc/rpc_register.py +113 -6
  15. bec_widgets/cli/rpc/rpc_widget_handler.py +13 -11
  16. bec_widgets/cli/server.py +125 -239
  17. bec_widgets/examples/jupyter_console/jupyter_console_window.py +97 -145
  18. bec_widgets/examples/plugin_example_pyside/tictactoetaskmenu.py +1 -1
  19. bec_widgets/utils/bec_connector.py +190 -21
  20. bec_widgets/utils/bec_designer.py +7 -0
  21. bec_widgets/utils/bec_dispatcher.py +71 -4
  22. bec_widgets/utils/bec_plugin_helper.py +89 -0
  23. bec_widgets/utils/bec_signal_proxy.py +1 -1
  24. bec_widgets/utils/bec_widget.py +26 -10
  25. bec_widgets/utils/colors.py +1 -1
  26. bec_widgets/{qt_utils → utils}/compact_popup.py +2 -0
  27. bec_widgets/utils/container_utils.py +37 -12
  28. bec_widgets/utils/crosshair.py +25 -8
  29. bec_widgets/utils/entry_validator.py +3 -1
  30. bec_widgets/{qt_utils → utils}/error_popups.py +18 -0
  31. bec_widgets/{qt_utils → utils}/expandable_frame.py +2 -2
  32. bec_widgets/utils/forms_from_types/forms.py +182 -0
  33. bec_widgets/{widgets/editors/scan_metadata/_metadata_widgets.py → utils/forms_from_types/items.py} +41 -30
  34. bec_widgets/utils/generate_designer_plugin.py +40 -36
  35. bec_widgets/utils/linear_region_selector.py +2 -0
  36. bec_widgets/utils/name_utils.py +16 -0
  37. bec_widgets/{qt_utils → utils}/palette_viewer.py +2 -2
  38. bec_widgets/utils/plot_indicator_items.py +2 -5
  39. bec_widgets/utils/plugin_utils.py +47 -1
  40. bec_widgets/{qt_utils → utils}/round_frame.py +14 -14
  41. bec_widgets/utils/rpc_server.py +277 -0
  42. bec_widgets/utils/serialization.py +44 -0
  43. bec_widgets/{qt_utils → utils}/settings_dialog.py +26 -1
  44. bec_widgets/{qt_utils → utils}/side_panel.py +17 -10
  45. bec_widgets/{qt_utils → utils}/toolbar.py +69 -25
  46. bec_widgets/utils/ui_loader.py +8 -8
  47. bec_widgets/utils/widget_io.py +166 -25
  48. bec_widgets/widgets/containers/auto_update/auto_updates.py +364 -0
  49. bec_widgets/widgets/containers/dock/dock.py +157 -49
  50. bec_widgets/widgets/containers/dock/dock_area.py +188 -138
  51. bec_widgets/widgets/containers/layout_manager/layout_manager.py +2 -1
  52. bec_widgets/widgets/containers/main_window/addons/web_links.py +15 -0
  53. bec_widgets/widgets/containers/main_window/main_window.py +189 -41
  54. bec_widgets/widgets/control/buttons/button_abort/button_abort.py +3 -4
  55. bec_widgets/widgets/control/buttons/button_reset/button_reset.py +3 -4
  56. bec_widgets/widgets/control/buttons/button_resume/button_resume.py +3 -3
  57. bec_widgets/widgets/control/buttons/stop_button/stop_button.py +18 -7
  58. bec_widgets/widgets/control/device_control/position_indicator/position_indicator.py +22 -3
  59. bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py +31 -13
  60. bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.py +3 -1
  61. bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.ui +27 -4
  62. bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.py +5 -2
  63. bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.ui +97 -31
  64. bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.ui +11 -4
  65. bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py +2 -3
  66. bec_widgets/widgets/control/device_input/base_classes/device_input_base.py +29 -4
  67. bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py +1 -0
  68. bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py +2 -2
  69. bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py +2 -2
  70. bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py +1 -2
  71. bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.py +1 -2
  72. bec_widgets/widgets/control/scan_control/scan_control.py +7 -5
  73. bec_widgets/widgets/control/scan_control/scan_group_box.py +28 -5
  74. bec_widgets/widgets/dap/dap_combo_box/dap_combo_box.py +1 -2
  75. bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog.py +3 -4
  76. bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog_vertical.ui +14 -8
  77. bec_widgets/widgets/editors/console/console.py +1 -1
  78. bec_widgets/widgets/editors/{scan_metadata/additional_metadata_table.py → dict_backed_table.py} +29 -6
  79. bec_widgets/widgets/editors/scan_metadata/__init__.py +0 -7
  80. bec_widgets/widgets/editors/scan_metadata/_util.py +1 -1
  81. bec_widgets/widgets/{plots/motor_map/register_bec_motor_map_widget.py → editors/scan_metadata/register_scan_metadata.py} +2 -4
  82. bec_widgets/widgets/editors/scan_metadata/scan_metadata.py +42 -136
  83. bec_widgets/widgets/editors/scan_metadata/scan_metadata.pyproject +1 -0
  84. bec_widgets/widgets/{plots/multi_waveform/bec_multi_waveform_widget_plugin.py → editors/scan_metadata/scan_metadata_plugin.py} +9 -9
  85. bec_widgets/widgets/editors/text_box/text_box.py +2 -3
  86. bec_widgets/widgets/editors/website/website.py +2 -2
  87. bec_widgets/widgets/games/minesweeper.py +3 -2
  88. bec_widgets/widgets/plots/image/image.py +960 -0
  89. bec_widgets/widgets/plots/image/image.pyproject +1 -0
  90. bec_widgets/widgets/plots/image/image_item.py +279 -0
  91. bec_widgets/widgets/plots/{motor_map/bec_motor_map_widget_plugin.py → image/image_plugin.py} +11 -13
  92. bec_widgets/widgets/{containers/figure/plots → plots}/image/image_processor.py +31 -64
  93. bec_widgets/widgets/plots/image/{register_bec_image_widget.py → register_image.py} +2 -2
  94. bec_widgets/widgets/plots/image/toolbar_bundles/image_selection.py +59 -0
  95. bec_widgets/widgets/plots/image/toolbar_bundles/processing.py +79 -0
  96. bec_widgets/widgets/plots/motor_map/motor_map.py +832 -0
  97. bec_widgets/widgets/plots/motor_map/motor_map.pyproject +1 -0
  98. bec_widgets/widgets/plots/motor_map/motor_map_plugin.py +54 -0
  99. bec_widgets/widgets/plots/{multi_waveform/register_bec_multi_waveform_widget.py → motor_map/register_motor_map.py} +2 -4
  100. bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py +129 -0
  101. bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.ui +120 -0
  102. bec_widgets/widgets/plots/motor_map/toolbar_bundles/motor_selection.py +70 -0
  103. bec_widgets/widgets/plots/multi_waveform/multi_waveform.py +508 -0
  104. bec_widgets/widgets/plots/multi_waveform/multi_waveform.pyproject +1 -0
  105. bec_widgets/widgets/plots/multi_waveform/multi_waveform_plugin.py +54 -0
  106. bec_widgets/widgets/plots/multi_waveform/register_multi_waveform.py +15 -0
  107. bec_widgets/widgets/plots/multi_waveform/settings/control_panel.py +144 -0
  108. bec_widgets/widgets/plots/multi_waveform/settings/multi_waveform_controls.ui +164 -0
  109. bec_widgets/widgets/plots/multi_waveform/toolbar_bundles/monitor_selection.py +65 -0
  110. bec_widgets/widgets/{plots_next_gen → plots}/plot_base.py +321 -40
  111. bec_widgets/widgets/plots/{waveform/register_bec_waveform_widget.py → scatter_waveform/register_scatter_waveform.py} +3 -3
  112. bec_widgets/widgets/plots/scatter_waveform/scatter_curve.py +197 -0
  113. bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py +553 -0
  114. bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.pyproject +1 -0
  115. bec_widgets/widgets/plots/{image/bec_image_widget_plugin.py → scatter_waveform/scatter_waveform_plugin.py} +9 -13
  116. bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_setting.py +138 -0
  117. bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_horizontal.ui +195 -0
  118. bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_vertical.ui +204 -0
  119. bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings.py +8 -8
  120. bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/mouse_interactions.py +4 -18
  121. bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/plot_export.py +14 -3
  122. bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/roi_bundle.py +6 -1
  123. bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/save_state.py +2 -2
  124. bec_widgets/widgets/{containers/figure/plots/waveform/waveform_curve.py → plots/waveform/curve.py} +119 -49
  125. bec_widgets/widgets/plots/waveform/register_waveform.py +15 -0
  126. bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_setting.py +125 -0
  127. bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py +576 -0
  128. bec_widgets/widgets/plots/waveform/utils/__init__.py +0 -0
  129. bec_widgets/widgets/plots/waveform/utils/roi_manager.py +84 -0
  130. bec_widgets/widgets/plots/waveform/waveform.py +1794 -0
  131. bec_widgets/widgets/plots/waveform/waveform.pyproject +1 -0
  132. bec_widgets/widgets/plots/waveform/{bec_waveform_widget_plugin.py → waveform_plugin.py} +9 -13
  133. bec_widgets/widgets/progress/bec_progressbar/bec_progressbar.py +1 -2
  134. bec_widgets/widgets/progress/ring_progress_bar/ring.py +11 -10
  135. bec_widgets/widgets/progress/ring_progress_bar/ring_progress_bar.py +24 -14
  136. bec_widgets/widgets/services/bec_queue/bec_queue.py +13 -11
  137. bec_widgets/widgets/services/bec_status_box/bec_status_box.py +3 -4
  138. bec_widgets/widgets/services/device_browser/device_browser.py +5 -2
  139. bec_widgets/widgets/services/device_browser/device_item/device_item.py +1 -1
  140. bec_widgets/widgets/utility/logpanel/logpanel.py +36 -17
  141. bec_widgets/widgets/utility/spinbox/decimal_spinbox.py +3 -3
  142. bec_widgets/widgets/utility/visual/color_button/color_button.py +1 -1
  143. bec_widgets/widgets/utility/visual/colormap_widget/colormap_widget.py +4 -6
  144. bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py +4 -8
  145. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/METADATA +3 -3
  146. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/RECORD +168 -153
  147. pyproject.toml +3 -3
  148. bec_widgets/applications/alignment/alignment_1d/alignment_1d.py +0 -198
  149. bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui +0 -615
  150. bec_widgets/applications/bec_app.py +0 -84
  151. bec_widgets/cli/auto_updates.py +0 -168
  152. bec_widgets/widgets/containers/figure/__init__.py +0 -1
  153. bec_widgets/widgets/containers/figure/figure.py +0 -796
  154. bec_widgets/widgets/containers/figure/plots/axis_settings.py +0 -91
  155. bec_widgets/widgets/containers/figure/plots/axis_settings.ui +0 -256
  156. bec_widgets/widgets/containers/figure/plots/image/image.py +0 -772
  157. bec_widgets/widgets/containers/figure/plots/image/image_item.py +0 -337
  158. bec_widgets/widgets/containers/figure/plots/motor_map/motor_map.py +0 -525
  159. bec_widgets/widgets/containers/figure/plots/multi_waveform/multi_waveform.py +0 -340
  160. bec_widgets/widgets/containers/figure/plots/plot_base.py +0 -505
  161. bec_widgets/widgets/containers/figure/plots/waveform/waveform.py +0 -1563
  162. bec_widgets/widgets/plots/image/bec_image_widget.pyproject +0 -1
  163. bec_widgets/widgets/plots/image/image_widget.py +0 -515
  164. bec_widgets/widgets/plots/motor_map/bec_motor_map_widget.pyproject +0 -1
  165. bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.py +0 -56
  166. bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.ui +0 -108
  167. bec_widgets/widgets/plots/motor_map/motor_map_widget.py +0 -234
  168. bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget.pyproject +0 -1
  169. bec_widgets/widgets/plots/multi_waveform/multi_waveform_controls.ui +0 -99
  170. bec_widgets/widgets/plots/multi_waveform/multi_waveform_widget.py +0 -536
  171. bec_widgets/widgets/plots/waveform/bec_waveform_widget.pyproject +0 -1
  172. bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.py +0 -336
  173. bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.ui +0 -372
  174. bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/dap_summary_dialog.py +0 -25
  175. bec_widgets/widgets/plots/waveform/waveform_widget.py +0 -751
  176. /bec_widgets/{qt_utils → utils}/collapsible_panel_manager.py +0 -0
  177. /bec_widgets/{applications/alignment → utils/forms_from_types}/__init__.py +0 -0
  178. /bec_widgets/{qt_utils → utils}/redis_message_waiter.py +0 -0
  179. /bec_widgets/{applications/alignment/alignment_1d → widgets/containers/auto_update}/__init__.py +0 -0
  180. /bec_widgets/{qt_utils → widgets/containers/main_window/addons}/__init__.py +0 -0
  181. /bec_widgets/widgets/{containers/figure/plots → plots/image/toolbar_bundles}/__init__.py +0 -0
  182. /bec_widgets/widgets/{containers/figure/plots/image → plots/motor_map/settings}/__init__.py +0 -0
  183. /bec_widgets/widgets/{containers/figure/plots/motor_map → plots/motor_map/toolbar_bundles}/__init__.py +0 -0
  184. /bec_widgets/widgets/{containers/figure/plots/multi_waveform → plots/multi_waveform/settings}/__init__.py +0 -0
  185. /bec_widgets/widgets/{containers/figure/plots/waveform → plots/multi_waveform/toolbar_bundles}/__init__.py +0 -0
  186. /bec_widgets/widgets/plots/{motor_map/motor_map_dialog → scatter_waveform}/__init__.py +0 -0
  187. /bec_widgets/widgets/plots/{waveform/waveform_popups → scatter_waveform/settings}/__init__.py +0 -0
  188. /bec_widgets/widgets/plots/{waveform/waveform_popups/curve_dialog → setting_menus}/__init__.py +0 -0
  189. /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_horizontal.ui +0 -0
  190. /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_vertical.ui +0 -0
  191. /bec_widgets/widgets/plots/{waveform/waveform_popups/dap_summary_dialog → toolbar_bundles}/__init__.py +0 -0
  192. /bec_widgets/widgets/{plots_next_gen/setting_menus → plots/waveform/settings}/__init__.py +0 -0
  193. /bec_widgets/widgets/{plots_next_gen/toolbar_bundles → plots/waveform/settings/curve_settings}/__init__.py +0 -0
  194. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/WHEEL +0 -0
  195. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/entry_points.txt +0 -0
  196. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,772 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from collections import defaultdict
4
- from typing import Any, Literal, Optional
5
-
6
- import numpy as np
7
- from bec_lib.endpoints import MessageEndpoints
8
- from bec_lib.logger import bec_logger
9
- from pydantic import Field, ValidationError
10
- from qtpy.QtCore import QThread, Slot
11
- from qtpy.QtWidgets import QWidget
12
-
13
- # from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
14
- from bec_widgets.utils import EntryValidator
15
- from bec_widgets.widgets.containers.figure.plots.image.image_item import (
16
- BECImageItem,
17
- ImageItemConfig,
18
- )
19
- from bec_widgets.widgets.containers.figure.plots.image.image_processor import (
20
- ImageProcessor,
21
- ImageStats,
22
- ProcessorWorker,
23
- )
24
- from bec_widgets.widgets.containers.figure.plots.plot_base import BECPlotBase, SubplotConfig
25
-
26
- logger = bec_logger.logger
27
-
28
-
29
- class ImageConfig(SubplotConfig):
30
- images: dict[str, ImageItemConfig] = Field(
31
- {},
32
- description="The configuration of the images. The key is the name of the image (source).",
33
- )
34
-
35
-
36
- class BECImageShow(BECPlotBase):
37
- USER_ACCESS = [
38
- "_rpc_id",
39
- "_config_dict",
40
- "add_image_by_config",
41
- "image",
42
- "add_custom_image",
43
- "set_vrange",
44
- "set_color_map",
45
- "set_autorange",
46
- "set_autorange_mode",
47
- "set_monitor",
48
- "set_processing",
49
- "set_image_properties",
50
- "set_fft",
51
- "set_log",
52
- "set_rotation",
53
- "set_transpose",
54
- "set",
55
- "set_title",
56
- "set_x_label",
57
- "set_y_label",
58
- "set_x_scale",
59
- "set_y_scale",
60
- "set_x_lim",
61
- "set_y_lim",
62
- "set_grid",
63
- "enable_fps_monitor",
64
- "lock_aspect_ratio",
65
- "export",
66
- "remove",
67
- "images",
68
- ]
69
-
70
- def __init__(
71
- self,
72
- parent: Optional[QWidget] = None,
73
- parent_figure=None,
74
- config: Optional[ImageConfig] = None,
75
- client=None,
76
- gui_id: Optional[str] = None,
77
- single_image: bool = True,
78
- ):
79
- if config is None:
80
- config = ImageConfig(widget_class=self.__class__.__name__)
81
- super().__init__(
82
- parent=parent, parent_figure=parent_figure, config=config, client=client, gui_id=gui_id
83
- )
84
- # Get bec shortcuts dev, scans, queue, scan_storage, dap
85
- self.single_image = single_image
86
- self.image_type = "device_monitor_2d"
87
- self.scan_id = None
88
- self.get_bec_shortcuts()
89
- self.entry_validator = EntryValidator(self.dev)
90
- self._images = defaultdict(dict)
91
- self.apply_config(self.config)
92
- self.processor = ImageProcessor()
93
- self.use_threading = False # TODO WILL be moved to the init method and to figure method
94
-
95
- def _create_thread_worker(self, device: str, image: np.ndarray):
96
- thread = QThread()
97
- worker = ProcessorWorker(self.processor)
98
- worker.moveToThread(thread)
99
-
100
- # Connect signals and slots
101
- thread.started.connect(lambda: worker.process_image(device, image))
102
- worker.processed.connect(self.update_image)
103
- worker.stats.connect(self.update_vrange)
104
- worker.finished.connect(thread.quit)
105
- worker.finished.connect(thread.wait)
106
- worker.finished.connect(worker.deleteLater)
107
- thread.finished.connect(thread.deleteLater)
108
-
109
- thread.start()
110
-
111
- def find_image_by_monitor(self, item_id: str) -> BECImageItem:
112
- """
113
- Find the image item by its gui_id.
114
-
115
- Args:
116
- item_id(str): The gui_id of the widget.
117
-
118
- Returns:
119
- BECImageItem: The widget with the given gui_id.
120
- """
121
- for source, images in self._images.items():
122
- for key, value in images.items():
123
- if key == item_id and isinstance(value, BECImageItem):
124
- return value
125
- elif isinstance(value, dict):
126
- result = self.find_image_by_monitor(item_id)
127
- if result is not None:
128
- return result
129
-
130
- def apply_config(self, config: dict | SubplotConfig):
131
- """
132
- Apply the configuration to the 1D waveform widget.
133
-
134
- Args:
135
- config(dict|SubplotConfig): Configuration settings.
136
- replot_last_scan(bool, optional): If True, replot the last scan. Defaults to False.
137
- """
138
- if isinstance(config, dict):
139
- try:
140
- config = ImageConfig(**config)
141
- except ValidationError as e:
142
- logger.error(f"Validation error when applying config to BECImageShow: {e}")
143
- return
144
- self.config = config
145
- self.plot_item.clear()
146
-
147
- self.apply_axis_config()
148
- self._images = defaultdict(dict)
149
-
150
- for image_id, image_config in config.images.items():
151
- self.add_image_by_config(image_config)
152
-
153
- def change_gui_id(self, new_gui_id: str):
154
- """
155
- Change the GUI ID of the image widget and update the parent_id in all associated curves.
156
-
157
- Args:
158
- new_gui_id (str): The new GUI ID to be set for the image widget.
159
- """
160
- self.gui_id = new_gui_id
161
- self.config.gui_id = new_gui_id
162
-
163
- for source, images in self._images.items():
164
- for id, image_item in images.items():
165
- image_item.config.parent_id = new_gui_id
166
-
167
- def add_image_by_config(self, config: ImageItemConfig | dict) -> BECImageItem:
168
- """
169
- Add an image to the widget by configuration.
170
-
171
- Args:
172
- config(ImageItemConfig|dict): The configuration of the image.
173
-
174
- Returns:
175
- BECImageItem: The image object.
176
- """
177
- if isinstance(config, dict):
178
- config = ImageItemConfig(**config)
179
- config.parent_id = self.gui_id
180
- name = config.monitor if config.monitor is not None else config.gui_id
181
- image = self._add_image_object(source=config.source, name=name, config=config)
182
- return image
183
-
184
- def get_image_config(self, image_id, dict_output: bool = True) -> ImageItemConfig | dict:
185
- """
186
- Get the configuration of the image.
187
-
188
- Args:
189
- image_id(str): The ID of the image.
190
- dict_output(bool): Whether to return the configuration as a dictionary. Defaults to True.
191
-
192
- Returns:
193
- ImageItemConfig|dict: The configuration of the image.
194
- """
195
- for source, images in self._images.items():
196
- for id, image in images.items():
197
- if id == image_id:
198
- if dict_output:
199
- return image.config.dict()
200
- else:
201
- return image.config # TODO check if this works
202
-
203
- @property
204
- def images(self) -> list[BECImageItem]:
205
- """
206
- Get the list of images.
207
- Returns:
208
- list[BECImageItem]: The list of images.
209
- """
210
- images = []
211
- for source, images_dict in self._images.items():
212
- for id, image in images_dict.items():
213
- images.append(image)
214
- return images
215
-
216
- @images.setter
217
- def images(self, value: dict[str, dict[str, BECImageItem]]):
218
- """
219
- Set the images from a dictionary.
220
-
221
- Args:
222
- value (dict[str, dict[str, BECImageItem]]): The images to set, organized by source and id.
223
- """
224
- self._images = value
225
-
226
- def get_image_dict(self) -> dict[str, dict[str, BECImageItem]]:
227
- """
228
- Get all images.
229
-
230
- Returns:
231
- dict[str, dict[str, BECImageItem]]: The dictionary of images.
232
- """
233
- return self._images
234
-
235
- def image(
236
- self,
237
- monitor: str,
238
- monitor_type: Literal["1d", "2d"] = "2d",
239
- color_map: Optional[str] = "magma",
240
- color_bar: Optional[Literal["simple", "full"]] = "full",
241
- downsample: Optional[bool] = True,
242
- opacity: Optional[float] = 1.0,
243
- vrange: Optional[tuple[int, int]] = None,
244
- # post_processing: Optional[PostProcessingConfig] = None,
245
- **kwargs,
246
- ) -> BECImageItem:
247
- """
248
- Add an image to the figure. Always access the first image widget in the figure.
249
-
250
- Args:
251
- monitor(str): The name of the monitor to display.
252
- monitor_type(Literal["1d","2d"]): The type of monitor to display.
253
- color_bar(Literal["simple","full"]): The type of color bar to display.
254
- color_map(str): The color map to use for the image.
255
- data(np.ndarray): Custom data to display.
256
- vrange(tuple[float, float]): The range of values to display.
257
-
258
- Returns:
259
- BECImageItem: The image item.
260
- """
261
- if monitor_type == "1d":
262
- image_source = "device_monitor_1d"
263
- self.image_type = "device_monitor_1d"
264
- elif monitor_type == "2d":
265
- image_source = "device_monitor_2d"
266
- self.image_type = "device_monitor_2d"
267
-
268
- image_exits = self._check_image_id(monitor, self._images)
269
- if image_exits:
270
- # raise ValueError(
271
- # f"Monitor with ID '{monitor}' already exists in widget '{self.gui_id}'."
272
- # )
273
- return
274
-
275
- # monitor = self.entry_validator.validate_monitor(monitor)
276
-
277
- image_config = ImageItemConfig(
278
- widget_class="BECImageItem",
279
- parent_id=self.gui_id,
280
- color_map=color_map,
281
- color_bar=color_bar,
282
- downsample=downsample,
283
- opacity=opacity,
284
- vrange=vrange,
285
- source=image_source,
286
- monitor=monitor,
287
- # post_processing=post_processing,
288
- **kwargs,
289
- )
290
-
291
- image = self._add_image_object(source=image_source, name=monitor, config=image_config)
292
- return image
293
-
294
- def add_custom_image(
295
- self,
296
- name: str,
297
- data: Optional[np.ndarray] = None,
298
- color_map: Optional[str] = "magma",
299
- color_bar: Optional[Literal["simple", "full"]] = "full",
300
- downsample: Optional[bool] = True,
301
- opacity: Optional[float] = 1.0,
302
- vrange: Optional[tuple[int, int]] = None,
303
- # post_processing: Optional[PostProcessingConfig] = None,
304
- **kwargs,
305
- ):
306
- image_source = "custom"
307
-
308
- image_exits = self._check_image_id(name, self._images)
309
- if image_exits:
310
- raise ValueError(f"Monitor with ID '{name}' already exists in widget '{self.gui_id}'.")
311
-
312
- image_config = ImageItemConfig(
313
- widget_class="BECImageItem",
314
- parent_id=self.gui_id,
315
- monitor=name,
316
- color_map=color_map,
317
- color_bar=color_bar,
318
- downsample=downsample,
319
- opacity=opacity,
320
- vrange=vrange,
321
- # post_processing=post_processing,
322
- **kwargs,
323
- )
324
-
325
- image = self._add_image_object(
326
- source=image_source, name=name, config=image_config, data=data
327
- )
328
- return image
329
-
330
- def apply_setting_to_images(
331
- self, setting_method_name: str, args: list, kwargs: dict, image_id: str = None
332
- ):
333
- """
334
- Apply a setting to all images or a specific image by its ID.
335
-
336
- Args:
337
- setting_method_name (str): The name of the method to apply (e.g., 'set_color_map').
338
- args (list): Positional arguments for the setting method.
339
- kwargs (dict): Keyword arguments for the setting method.
340
- image_id (str, optional): The ID of the specific image to apply the setting to. If None, applies to all images.
341
- """
342
- if image_id:
343
- image = self.find_image_by_monitor(image_id)
344
- if image:
345
- getattr(image, setting_method_name)(*args, **kwargs)
346
- else:
347
- for source, images in self._images.items():
348
- for _, image in images.items():
349
- getattr(image, setting_method_name)(*args, **kwargs)
350
- self.refresh_image()
351
-
352
- def set_vrange(self, vmin: float, vmax: float, name: str = None):
353
- """
354
- Set the range of the color bar.
355
- If name is not specified, then set vrange for all images.
356
-
357
- Args:
358
- vmin(float): Minimum value of the color bar.
359
- vmax(float): Maximum value of the color bar.
360
- name(str): The name of the image. If None, apply to all images.
361
- """
362
- self.apply_setting_to_images("set_vrange", args=[vmin, vmax], kwargs={}, image_id=name)
363
-
364
- def set_color_map(self, cmap: str, name: str = None):
365
- """
366
- Set the color map of the image.
367
- If name is not specified, then set color map for all images.
368
-
369
- Args:
370
- cmap(str): The color map of the image.
371
- name(str): The name of the image. If None, apply to all images.
372
- """
373
- self.apply_setting_to_images("set_color_map", args=[cmap], kwargs={}, image_id=name)
374
-
375
- def set_autorange(self, enable: bool = False, name: str = None):
376
- """
377
- Set the autoscale of the image.
378
-
379
- Args:
380
- enable(bool): Whether to autoscale the color bar.
381
- name(str): The name of the image. If None, apply to all images.
382
- """
383
- self.apply_setting_to_images("set_autorange", args=[enable], kwargs={}, image_id=name)
384
-
385
- def set_autorange_mode(self, mode: Literal["max", "mean"], name: str = None):
386
- """
387
- Set the autoscale mode of the image, that decides how the vrange of the color bar is scaled.
388
- Choose betwen 'max' -> min/max of the data, 'mean' -> mean +/- fudge_factor*std of the data (fudge_factor~2).
389
-
390
- Args:
391
- mode(str): The autoscale mode of the image.
392
- name(str): The name of the image. If None, apply to all images.
393
- """
394
- self.apply_setting_to_images("set_autorange_mode", args=[mode], kwargs={}, image_id=name)
395
-
396
- def set_monitor(self, monitor: str, name: str = None):
397
- """
398
- Set the monitor of the image.
399
- If name is not specified, then set monitor for all images.
400
-
401
- Args:
402
- monitor(str): The name of the monitor.
403
- name(str): The name of the image. If None, apply to all images.
404
- """
405
- self.apply_setting_to_images("set_monitor", args=[monitor], kwargs={}, image_id=name)
406
-
407
- def set_processing(self, name: str = None, **kwargs):
408
- """
409
- Set the post processing of the image.
410
- If name is not specified, then set post processing for all images.
411
-
412
- Args:
413
- name(str): The name of the image. If None, apply to all images.
414
- **kwargs: Keyword arguments for the properties to be set.
415
- Possible properties:
416
- - fft: bool
417
- - log: bool
418
- - rot: int
419
- - transpose: bool
420
- """
421
- self.apply_setting_to_images("set", args=[], kwargs=kwargs, image_id=name)
422
-
423
- def set_image_properties(self, name: str = None, **kwargs):
424
- """
425
- Set the properties of the image.
426
-
427
- Args:
428
- name(str): The name of the image. If None, apply to all images.
429
- **kwargs: Keyword arguments for the properties to be set.
430
- Possible properties:
431
- - downsample: bool
432
- - color_map: str
433
- - monitor: str
434
- - opacity: float
435
- - vrange: tuple[int,int]
436
- - fft: bool
437
- - log: bool
438
- - rot: int
439
- - transpose: bool
440
- """
441
- self.apply_setting_to_images("set", args=[], kwargs=kwargs, image_id=name)
442
-
443
- def set_fft(self, enable: bool = False, name: str = None):
444
- """
445
- Set the FFT of the image.
446
- If name is not specified, then set FFT for all images.
447
-
448
- Args:
449
- enable(bool): Whether to perform FFT on the monitor data.
450
- name(str): The name of the image. If None, apply to all images.
451
- """
452
- self.apply_setting_to_images("set_fft", args=[enable], kwargs={}, image_id=name)
453
-
454
- def set_log(self, enable: bool = False, name: str = None):
455
- """
456
- Set the log of the image.
457
- If name is not specified, then set log for all images.
458
-
459
- Args:
460
- enable(bool): Whether to perform log on the monitor data.
461
- name(str): The name of the image. If None, apply to all images.
462
- """
463
- self.apply_setting_to_images("set_log", args=[enable], kwargs={}, image_id=name)
464
-
465
- def set_rotation(self, deg_90: int = 0, name: str = None):
466
- """
467
- Set the rotation of the image.
468
- If name is not specified, then set rotation for all images.
469
-
470
- Args:
471
- deg_90(int): The rotation angle of the monitor data before displaying.
472
- name(str): The name of the image. If None, apply to all images.
473
- """
474
- self.apply_setting_to_images("set_rotation", args=[deg_90], kwargs={}, image_id=name)
475
-
476
- def set_transpose(self, enable: bool = False, name: str = None):
477
- """
478
- Set the transpose of the image.
479
- If name is not specified, then set transpose for all images.
480
-
481
- Args:
482
- enable(bool): Whether to transpose the monitor data before displaying.
483
- name(str): The name of the image. If None, apply to all images.
484
- """
485
- self.apply_setting_to_images("set_transpose", args=[enable], kwargs={}, image_id=name)
486
-
487
- def toggle_threading(self, use_threading: bool):
488
- """
489
- Toggle threading for the widgets postprocessing and updating.
490
-
491
- Args:
492
- use_threading(bool): Whether to use threading.
493
- """
494
- self.use_threading = use_threading
495
- if self.use_threading is False and self.thread.isRunning():
496
- self.cleanup()
497
-
498
- def process_image(self, device: str, image: BECImageItem, data: np.ndarray):
499
- """
500
- Process the image data.
501
-
502
- Args:
503
- device(str): The name of the device - image_id of image.
504
- image(np.ndarray): The image data to be processed.
505
- data(np.ndarray): The image data to be processed.
506
-
507
- Returns:
508
- np.ndarray: The processed image data.
509
- """
510
- processing_config = image.config.processing
511
- self.processor.set_config(processing_config)
512
- if self.use_threading:
513
- self._create_thread_worker(device, data)
514
- else:
515
- data = self.processor.process_image(data)
516
- self.update_image(device, data)
517
- self.update_vrange(device, self.processor.config.stats)
518
-
519
- @Slot(dict, dict)
520
- def on_image_update(self, msg: dict, metadata: dict):
521
- """
522
- Update the image of the device monitor from bec.
523
-
524
- Args:
525
- msg(dict): The message from bec.
526
- metadata(dict): The metadata of the message.
527
- """
528
- data = msg["data"]
529
- device = msg["device"]
530
- if self.image_type == "device_monitor_1d":
531
- image = self._images["device_monitor_1d"][device]
532
- current_scan_id = metadata.get("scan_id", None)
533
- if current_scan_id is None:
534
- return
535
- if current_scan_id != self.scan_id:
536
- self.reset()
537
- self.scan_id = current_scan_id
538
- image.image_buffer_list = []
539
- image.max_len = 0
540
- image_buffer = self.adjust_image_buffer(image, data)
541
- image.raw_data = image_buffer
542
- self.process_image(device, image, image_buffer)
543
- elif self.image_type == "device_monitor_2d":
544
- image = self._images["device_monitor_2d"][device]
545
- image.raw_data = data
546
- self.process_image(device, image, data)
547
-
548
- def adjust_image_buffer(self, image: BECImageItem, new_data: np.ndarray) -> np.ndarray:
549
- """
550
- Adjusts the image buffer to accommodate the new data, ensuring that all rows have the same length.
551
-
552
- Args:
553
- image: The image object (used to store buffer list and max_len).
554
- new_data (np.ndarray): The new incoming 1D waveform data.
555
-
556
- Returns:
557
- np.ndarray: The updated image buffer with adjusted shapes.
558
- """
559
- new_len = new_data.shape[0]
560
- if not hasattr(image, "image_buffer_list"):
561
- image.image_buffer_list = []
562
- image.max_len = 0
563
-
564
- if new_len > image.max_len:
565
- image.max_len = new_len
566
- for i in range(len(image.image_buffer_list)):
567
- wf = image.image_buffer_list[i]
568
- pad_width = image.max_len - wf.shape[0]
569
- if pad_width > 0:
570
- image.image_buffer_list[i] = np.pad(
571
- wf, (0, pad_width), mode="constant", constant_values=0
572
- )
573
- image.image_buffer_list.append(new_data)
574
- else:
575
- pad_width = image.max_len - new_len
576
- if pad_width > 0:
577
- new_data = np.pad(new_data, (0, pad_width), mode="constant", constant_values=0)
578
- image.image_buffer_list.append(new_data)
579
-
580
- image_buffer = np.array(image.image_buffer_list)
581
- return image_buffer
582
-
583
- @Slot(str, np.ndarray)
584
- def update_image(self, device: str, data: np.ndarray):
585
- """
586
- Update the image of the device monitor.
587
-
588
- Args:
589
- device(str): The name of the device.
590
- data(np.ndarray): The data to be updated.
591
- """
592
- image_to_update = self._images[self.image_type][device]
593
- image_to_update.updateImage(data, autoLevels=image_to_update.config.autorange)
594
-
595
- @Slot(str, ImageStats)
596
- def update_vrange(self, device: str, stats: ImageStats):
597
- """
598
- Update the scaling of the image.
599
-
600
- Args:
601
- stats(ImageStats): The statistics of the image.
602
- """
603
- image_to_update = self._images[self.image_type][device]
604
- if image_to_update.config.autorange:
605
- image_to_update.auto_update_vrange(stats)
606
-
607
- def refresh_image(self):
608
- """
609
- Refresh the image.
610
- """
611
- for source, images in self._images.items():
612
- for image_id, image in images.items():
613
- data = image.raw_data
614
- self.process_image(image_id, image, data)
615
-
616
- def _connect_device_monitor(self, monitor: str):
617
- """
618
- Connect to the device monitor.
619
-
620
- Args:
621
- monitor(str): The name of the monitor.
622
- """
623
- image_item = self.find_image_by_monitor(monitor)
624
- try:
625
- previous_monitor = image_item.config.monitor
626
- except AttributeError:
627
- previous_monitor = None
628
- if previous_monitor and image_item.connected is True:
629
- self.bec_dispatcher.disconnect_slot(
630
- self.on_image_update, MessageEndpoints.device_monitor_1d(previous_monitor)
631
- )
632
- self.bec_dispatcher.disconnect_slot(
633
- self.on_image_update, MessageEndpoints.device_monitor_2d(previous_monitor)
634
- )
635
- image_item.connected = False
636
- if monitor and image_item.connected is False:
637
- self.entry_validator.validate_monitor(monitor)
638
- if self.image_type == "device_monitor_1d":
639
- self.bec_dispatcher.connect_slot(
640
- self.on_image_update, MessageEndpoints.device_monitor_1d(monitor)
641
- )
642
- elif self.image_type == "device_monitor_2d":
643
- self.bec_dispatcher.connect_slot(
644
- self.on_image_update, MessageEndpoints.device_monitor_2d(monitor)
645
- )
646
- image_item.set_monitor(monitor)
647
- image_item.connected = True
648
-
649
- def _add_image_object(
650
- self, source: str, name: str, config: ImageItemConfig, data=None
651
- ) -> BECImageItem:
652
- config.parent_id = self.gui_id
653
- if self.single_image is True and len(self.images) > 0:
654
- self.remove_image(0)
655
- image = BECImageItem(config=config, parent_image=self)
656
- self.plot_item.addItem(image)
657
- self._images[source][name] = image
658
- self._connect_device_monitor(config.monitor)
659
- self.config.images[name] = config
660
- if data is not None:
661
- image.setImage(data)
662
- return image
663
-
664
- def _check_image_id(self, val: Any, dict_to_check: dict) -> bool:
665
- """
666
- Check if val is in the values of the dict_to_check or in the values of the nested dictionaries.
667
-
668
- Args:
669
- val(Any): Value to check.
670
- dict_to_check(dict): Dictionary to check.
671
-
672
- Returns:
673
- bool: True if val is in the values of the dict_to_check or in the values of the nested dictionaries, False otherwise.
674
- """
675
- if val in dict_to_check.keys():
676
- return True
677
- for key in dict_to_check:
678
- if isinstance(dict_to_check[key], dict):
679
- if self._check_image_id(val, dict_to_check[key]):
680
- return True
681
- return False
682
-
683
- def remove_image(self, *identifiers):
684
- """
685
- Remove an image from the plot widget.
686
-
687
- Args:
688
- *identifiers: Identifier of the image to be removed. Can be either an integer (index) or a string (image_id).
689
- """
690
- for identifier in identifiers:
691
- if isinstance(identifier, int):
692
- self._remove_image_by_order(identifier)
693
- elif isinstance(identifier, str):
694
- self._remove_image_by_id(identifier)
695
- else:
696
- raise ValueError(
697
- "Each identifier must be either an integer (index) or a string (image_id)."
698
- )
699
-
700
- def _remove_image_by_id(self, image_id):
701
- for source, images in self._images.items():
702
- if image_id in images:
703
- self._disconnect_monitor(image_id)
704
- image = images.pop(image_id)
705
- self.removeItem(image.color_bar)
706
- self.plot_item.removeItem(image)
707
- del self.config.images[image_id]
708
- if image in self.images:
709
- self.images.remove(image)
710
- return
711
- raise KeyError(f"Image with ID '{image_id}' not found.")
712
-
713
- def _remove_image_by_order(self, N):
714
- """
715
- Remove an image by its order from the plot widget.
716
-
717
- Args:
718
- N(int): Order of the image to be removed.
719
- """
720
- if N < len(self.images):
721
- image = self.images[N]
722
- image_id = image.config.monitor
723
- self._disconnect_monitor(image_id)
724
- self.removeItem(image.color_bar)
725
- self.plot_item.removeItem(image)
726
- del self.config.images[image_id]
727
- for source, images in self._images.items():
728
- if image_id in images:
729
- del images[image_id]
730
- break
731
- else:
732
- raise IndexError(f"Image order {N} out of range.")
733
-
734
- def _disconnect_monitor(self, image_id):
735
- """
736
- Disconnect the monitor from the device.
737
-
738
- Args:
739
- image_id(str): The ID of the monitor.
740
- """
741
- image = self.find_image_by_monitor(image_id)
742
- if image:
743
- self.bec_dispatcher.disconnect_slot(
744
- self.on_image_update, MessageEndpoints.device_monitor_1d(image.config.monitor)
745
- )
746
- self.bec_dispatcher.disconnect_slot(
747
- self.on_image_update, MessageEndpoints.device_monitor_2d(image.config.monitor)
748
- )
749
-
750
- def cleanup(self):
751
- """
752
- Clean up the widget.
753
- """
754
- for monitor in self._images[self.image_type]:
755
- self.bec_dispatcher.disconnect_slot(
756
- self.on_image_update, MessageEndpoints.device_monitor_1d(monitor)
757
- )
758
- self.images.clear()
759
-
760
- def cleanup_pyqtgraph(self):
761
- """Cleanup pyqtgraph items."""
762
- super().cleanup_pyqtgraph()
763
- item = self.plot_item
764
- if not item.items:
765
- return
766
- cbar = item.items[0].color_bar
767
- cbar.vb.menu.close()
768
- cbar.vb.menu.deleteLater()
769
- cbar.gradient.menu.close()
770
- cbar.gradient.menu.deleteLater()
771
- cbar.gradient.colorDialog.close()
772
- cbar.gradient.colorDialog.deleteLater()