bec-widgets 0.89.0__py3-none-any.whl → 0.90.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 (36) hide show
  1. CHANGELOG.md +30 -34
  2. PKG-INFO +1 -1
  3. bec_widgets/assets/designer_icons/image.png +0 -0
  4. bec_widgets/assets/toolbar_icons/auto_range.svg +1 -1
  5. bec_widgets/assets/toolbar_icons/compare.svg +3 -0
  6. bec_widgets/assets/toolbar_icons/fft.svg +11 -0
  7. bec_widgets/assets/toolbar_icons/image_autorange.svg +3 -0
  8. bec_widgets/assets/toolbar_icons/line_curve.svg +3 -0
  9. bec_widgets/assets/toolbar_icons/lock_aspect_ratio.svg +3 -0
  10. bec_widgets/assets/toolbar_icons/log_scale.png +0 -0
  11. bec_widgets/assets/toolbar_icons/reset_settings.svg +3 -0
  12. bec_widgets/assets/toolbar_icons/rotate_left.svg +3 -0
  13. bec_widgets/assets/toolbar_icons/rotate_right.svg +3 -0
  14. bec_widgets/assets/toolbar_icons/transform.svg +3 -0
  15. bec_widgets/cli/client.py +187 -2
  16. bec_widgets/examples/jupyter_console/jupyter_console_window.py +18 -19
  17. bec_widgets/widgets/figure/figure.py +1 -3
  18. bec_widgets/widgets/figure/plots/axis_settings.ui +86 -100
  19. bec_widgets/widgets/figure/plots/image/image.py +85 -3
  20. bec_widgets/widgets/figure/plots/image/image_item.py +14 -0
  21. bec_widgets/widgets/image/__init__.py +0 -0
  22. bec_widgets/widgets/image/bec_image_widget.pyproject +1 -0
  23. bec_widgets/widgets/image/bec_image_widget_plugin.py +59 -0
  24. bec_widgets/widgets/image/image_widget.py +475 -0
  25. bec_widgets/widgets/image/register_bec_image_widget.py +15 -0
  26. {bec_widgets-0.89.0.dist-info → bec_widgets-0.90.0.dist-info}/METADATA +1 -1
  27. {bec_widgets-0.89.0.dist-info → bec_widgets-0.90.0.dist-info}/RECORD +36 -19
  28. pyproject.toml +1 -1
  29. tests/unit_tests/client_mocks.py +9 -1
  30. tests/unit_tests/test_bec_image_widget.py +218 -0
  31. tests/unit_tests/test_device_input_base.py +1 -1
  32. tests/unit_tests/test_device_input_widgets.py +2 -0
  33. tests/unit_tests/test_waveform_widget.py +1 -1
  34. {bec_widgets-0.89.0.dist-info → bec_widgets-0.90.0.dist-info}/WHEEL +0 -0
  35. {bec_widgets-0.89.0.dist-info → bec_widgets-0.90.0.dist-info}/entry_points.txt +0 -0
  36. {bec_widgets-0.89.0.dist-info → bec_widgets-0.90.0.dist-info}/licenses/LICENSE +0 -0
@@ -26,42 +26,28 @@
26
26
  <string>Form</string>
27
27
  </property>
28
28
  <layout class="QGridLayout" name="gridLayout">
29
- <item row="0" column="0" colspan="3">
30
- <layout class="QHBoxLayout" name="horizontalLayout">
31
- <item>
32
- <widget class="QLabel" name="plot_title_label">
33
- <property name="text">
34
- <string>Plot Title</string>
35
- </property>
36
- </widget>
37
- </item>
38
- <item>
39
- <widget class="QLineEdit" name="plot_title"/>
40
- </item>
41
- </layout>
42
- </item>
43
- <item row="1" column="0" colspan="3">
44
- <widget class="Line" name="line_H">
45
- <property name="orientation">
46
- <enum>Qt::Orientation::Horizontal</enum>
47
- </property>
48
- </widget>
49
- </item>
50
- <item row="2" column="0">
51
- <widget class="QGroupBox" name="x_axis_box">
29
+ <item row="1" column="1">
30
+ <widget class="QGroupBox" name="y_axis_box">
52
31
  <property name="title">
53
- <string>X Axis</string>
32
+ <string>Y Axis</string>
54
33
  </property>
55
- <layout class="QGridLayout" name="gridLayout_4">
56
- <item row="3" column="0">
57
- <widget class="QLabel" name="x_scale_label">
58
- <property name="text">
59
- <string>Scale</string>
60
- </property>
34
+ <layout class="QGridLayout" name="gridLayout_5">
35
+ <item row="3" column="2">
36
+ <widget class="QComboBox" name="y_scale">
37
+ <item>
38
+ <property name="text">
39
+ <string>linear</string>
40
+ </property>
41
+ </item>
42
+ <item>
43
+ <property name="text">
44
+ <string>log</string>
45
+ </property>
46
+ </item>
61
47
  </widget>
62
48
  </item>
63
- <item row="1" column="2">
64
- <widget class="QDoubleSpinBox" name="x_min">
49
+ <item row="2" column="2">
50
+ <widget class="QDoubleSpinBox" name="y_max">
65
51
  <property name="alignment">
66
52
  <set>Qt::AlignmentFlag::AlignCenter</set>
67
53
  </property>
@@ -74,14 +60,14 @@
74
60
  </widget>
75
61
  </item>
76
62
  <item row="1" column="0" colspan="2">
77
- <widget class="QLabel" name="x_min_label">
63
+ <widget class="QLabel" name="y_min_label">
78
64
  <property name="text">
79
65
  <string>Min</string>
80
66
  </property>
81
67
  </widget>
82
68
  </item>
83
- <item row="2" column="2">
84
- <widget class="QDoubleSpinBox" name="x_max">
69
+ <item row="1" column="2">
70
+ <widget class="QDoubleSpinBox" name="y_min">
85
71
  <property name="alignment">
86
72
  <set>Qt::AlignmentFlag::AlignCenter</set>
87
73
  </property>
@@ -93,46 +79,39 @@
93
79
  </property>
94
80
  </widget>
95
81
  </item>
96
- <item row="3" column="2">
97
- <widget class="QComboBox" name="x_scale">
98
- <item>
99
- <property name="text">
100
- <string>linear</string>
101
- </property>
102
- </item>
103
- <item>
104
- <property name="text">
105
- <string>log</string>
106
- </property>
107
- </item>
108
- </widget>
82
+ <item row="0" column="2">
83
+ <widget class="QLineEdit" name="y_label"/>
109
84
  </item>
110
- <item row="2" column="0">
111
- <widget class="QLabel" name="x_max_label">
85
+ <item row="3" column="0">
86
+ <widget class="QLabel" name="y_scale_label">
112
87
  <property name="text">
113
- <string>Max</string>
88
+ <string>Scale</string>
114
89
  </property>
115
90
  </widget>
116
91
  </item>
117
- <item row="0" column="2">
118
- <widget class="QLineEdit" name="x_label"/>
119
- </item>
120
92
  <item row="0" column="0">
121
- <widget class="QLabel" name="x_label_label">
93
+ <widget class="QLabel" name="y_label_label">
122
94
  <property name="text">
123
95
  <string>Label</string>
124
96
  </property>
125
97
  </widget>
126
98
  </item>
99
+ <item row="2" column="0">
100
+ <widget class="QLabel" name="y_max_label">
101
+ <property name="text">
102
+ <string>Max</string>
103
+ </property>
104
+ </widget>
105
+ </item>
127
106
  <item row="4" column="2">
128
- <widget class="QCheckBox" name="x_grid">
107
+ <widget class="QCheckBox" name="y_grid">
129
108
  <property name="text">
130
109
  <string/>
131
110
  </property>
132
111
  </widget>
133
112
  </item>
134
113
  <item row="4" column="0">
135
- <widget class="QLabel" name="x_grid_label">
114
+ <widget class="QLabel" name="y_grid_label">
136
115
  <property name="text">
137
116
  <string>Grid</string>
138
117
  </property>
@@ -141,35 +120,21 @@
141
120
  </layout>
142
121
  </widget>
143
122
  </item>
144
- <item row="2" column="1">
145
- <widget class="Line" name="line_V">
146
- <property name="orientation">
147
- <enum>Qt::Orientation::Vertical</enum>
148
- </property>
149
- </widget>
150
- </item>
151
- <item row="2" column="2">
152
- <widget class="QGroupBox" name="y_axis_box">
123
+ <item row="1" column="0">
124
+ <widget class="QGroupBox" name="x_axis_box">
153
125
  <property name="title">
154
- <string>Y Axis</string>
126
+ <string>X Axis</string>
155
127
  </property>
156
- <layout class="QGridLayout" name="gridLayout_5">
157
- <item row="3" column="2">
158
- <widget class="QComboBox" name="y_scale">
159
- <item>
160
- <property name="text">
161
- <string>linear</string>
162
- </property>
163
- </item>
164
- <item>
165
- <property name="text">
166
- <string>log</string>
167
- </property>
168
- </item>
128
+ <layout class="QGridLayout" name="gridLayout_4">
129
+ <item row="3" column="0">
130
+ <widget class="QLabel" name="x_scale_label">
131
+ <property name="text">
132
+ <string>Scale</string>
133
+ </property>
169
134
  </widget>
170
135
  </item>
171
- <item row="2" column="2">
172
- <widget class="QDoubleSpinBox" name="y_max">
136
+ <item row="1" column="2">
137
+ <widget class="QDoubleSpinBox" name="x_min">
173
138
  <property name="alignment">
174
139
  <set>Qt::AlignmentFlag::AlignCenter</set>
175
140
  </property>
@@ -182,14 +147,14 @@
182
147
  </widget>
183
148
  </item>
184
149
  <item row="1" column="0" colspan="2">
185
- <widget class="QLabel" name="y_min_label">
150
+ <widget class="QLabel" name="x_min_label">
186
151
  <property name="text">
187
152
  <string>Min</string>
188
153
  </property>
189
154
  </widget>
190
155
  </item>
191
- <item row="1" column="2">
192
- <widget class="QDoubleSpinBox" name="y_min">
156
+ <item row="2" column="2">
157
+ <widget class="QDoubleSpinBox" name="x_max">
193
158
  <property name="alignment">
194
159
  <set>Qt::AlignmentFlag::AlignCenter</set>
195
160
  </property>
@@ -201,39 +166,46 @@
201
166
  </property>
202
167
  </widget>
203
168
  </item>
204
- <item row="0" column="2">
205
- <widget class="QLineEdit" name="y_label"/>
169
+ <item row="3" column="2">
170
+ <widget class="QComboBox" name="x_scale">
171
+ <item>
172
+ <property name="text">
173
+ <string>linear</string>
174
+ </property>
175
+ </item>
176
+ <item>
177
+ <property name="text">
178
+ <string>log</string>
179
+ </property>
180
+ </item>
181
+ </widget>
206
182
  </item>
207
- <item row="3" column="0">
208
- <widget class="QLabel" name="y_scale_label">
183
+ <item row="2" column="0">
184
+ <widget class="QLabel" name="x_max_label">
209
185
  <property name="text">
210
- <string>Scale</string>
186
+ <string>Max</string>
211
187
  </property>
212
188
  </widget>
213
189
  </item>
190
+ <item row="0" column="2">
191
+ <widget class="QLineEdit" name="x_label"/>
192
+ </item>
214
193
  <item row="0" column="0">
215
- <widget class="QLabel" name="y_label_label">
194
+ <widget class="QLabel" name="x_label_label">
216
195
  <property name="text">
217
196
  <string>Label</string>
218
197
  </property>
219
198
  </widget>
220
199
  </item>
221
- <item row="2" column="0">
222
- <widget class="QLabel" name="y_max_label">
223
- <property name="text">
224
- <string>Max</string>
225
- </property>
226
- </widget>
227
- </item>
228
200
  <item row="4" column="2">
229
- <widget class="QCheckBox" name="y_grid">
201
+ <widget class="QCheckBox" name="x_grid">
230
202
  <property name="text">
231
203
  <string/>
232
204
  </property>
233
205
  </widget>
234
206
  </item>
235
207
  <item row="4" column="0">
236
- <widget class="QLabel" name="y_grid_label">
208
+ <widget class="QLabel" name="x_grid_label">
237
209
  <property name="text">
238
210
  <string>Grid</string>
239
211
  </property>
@@ -242,6 +214,20 @@
242
214
  </layout>
243
215
  </widget>
244
216
  </item>
217
+ <item row="0" column="0" colspan="2">
218
+ <layout class="QHBoxLayout" name="horizontalLayout">
219
+ <item>
220
+ <widget class="QLabel" name="plot_title_label">
221
+ <property name="text">
222
+ <string>Plot Title</string>
223
+ </property>
224
+ </widget>
225
+ </item>
226
+ <item>
227
+ <widget class="QLineEdit" name="plot_title"/>
228
+ </item>
229
+ </layout>
230
+ </item>
245
231
  </layout>
246
232
  </widget>
247
233
  <resources/>
@@ -32,7 +32,7 @@ class BECImageShow(BECPlotBase):
32
32
  "_rpc_id",
33
33
  "_config_dict",
34
34
  "add_image_by_config",
35
- "add_monitor_image",
35
+ "image",
36
36
  "add_custom_image",
37
37
  "set_vrange",
38
38
  "set_color_map",
@@ -67,6 +67,7 @@ class BECImageShow(BECPlotBase):
67
67
  config: Optional[ImageConfig] = None,
68
68
  client=None,
69
69
  gui_id: Optional[str] = None,
70
+ single_image: bool = True,
70
71
  ):
71
72
  if config is None:
72
73
  config = ImageConfig(widget_class=self.__class__.__name__)
@@ -74,6 +75,7 @@ class BECImageShow(BECPlotBase):
74
75
  parent=parent, parent_figure=parent_figure, config=config, client=client, gui_id=gui_id
75
76
  )
76
77
  # Get bec shortcuts dev, scans, queue, scan_storage, dap
78
+ self.single_image = single_image
77
79
  self.get_bec_shortcuts()
78
80
  self.entry_validator = EntryValidator(self.dev)
79
81
  self._images = defaultdict(dict)
@@ -221,7 +223,7 @@ class BECImageShow(BECPlotBase):
221
223
  """
222
224
  return self._images
223
225
 
224
- def add_monitor_image(
226
+ def image(
225
227
  self,
226
228
  monitor: str,
227
229
  color_map: Optional[str] = "magma",
@@ -232,6 +234,19 @@ class BECImageShow(BECPlotBase):
232
234
  # post_processing: Optional[PostProcessingConfig] = None,
233
235
  **kwargs,
234
236
  ) -> BECImageItem:
237
+ """
238
+ Add an image to the figure. Always access the first image widget in the figure.
239
+
240
+ Args:
241
+ monitor(str): The name of the monitor to display.
242
+ color_bar(Literal["simple","full"]): The type of color bar to display.
243
+ color_map(str): The color map to use for the image.
244
+ data(np.ndarray): Custom data to display.
245
+ vrange(tuple[float, float]): The range of values to display.
246
+
247
+ Returns:
248
+ BECImageItem: The image item.
249
+ """
235
250
  image_source = "device_monitor"
236
251
 
237
252
  image_exits = self._check_image_id(monitor, self._images)
@@ -496,6 +511,7 @@ class BECImageShow(BECPlotBase):
496
511
  data = msg["data"]
497
512
  device = msg["device"]
498
513
  image = self._images["device_monitor"][device]
514
+ image.raw_data = data
499
515
  self.process_image(device, image, data)
500
516
 
501
517
  @pyqtSlot(str, np.ndarray)
@@ -528,7 +544,7 @@ class BECImageShow(BECPlotBase):
528
544
  """
529
545
  for source, images in self._images.items():
530
546
  for image_id, image in images.items():
531
- data = image.get_data()
547
+ data = image.raw_data
532
548
  self.process_image(image_id, image, data)
533
549
 
534
550
  def _connect_device_monitor(self, monitor: str):
@@ -562,6 +578,8 @@ class BECImageShow(BECPlotBase):
562
578
  config.parent_id = self.gui_id
563
579
  image = BECImageItem(config=config, parent_image=self)
564
580
  self.plot_item.addItem(image)
581
+ if self.single_image is True and len(self.images) > 0:
582
+ self.remove_image(0)
565
583
  self._images[source][name] = image
566
584
  if source == "device_monitor":
567
585
  self._connect_device_monitor(config.monitor)
@@ -589,6 +607,70 @@ class BECImageShow(BECPlotBase):
589
607
  return True
590
608
  return False
591
609
 
610
+ def remove_image(self, *identifiers):
611
+ """
612
+ Remove an image from the plot widget.
613
+
614
+ Args:
615
+ *identifiers: Identifier of the image to be removed. Can be either an integer (index) or a string (image_id).
616
+ """
617
+ for identifier in identifiers:
618
+ if isinstance(identifier, int):
619
+ self._remove_image_by_order(identifier)
620
+ elif isinstance(identifier, str):
621
+ self._remove_image_by_id(identifier)
622
+ else:
623
+ raise ValueError(
624
+ "Each identifier must be either an integer (index) or a string (image_id)."
625
+ )
626
+
627
+ def _remove_image_by_id(self, image_id):
628
+ for source, images in self._images.items():
629
+ if image_id in images:
630
+ self._disconnect_monitor(image_id)
631
+ image = images.pop(image_id)
632
+ self.removeItem(image.color_bar)
633
+ self.plot_item.removeItem(image)
634
+ del self.config.images[image_id]
635
+ if image in self.images:
636
+ self.images.remove(image)
637
+ return
638
+ raise KeyError(f"Image with ID '{image_id}' not found.")
639
+
640
+ def _remove_image_by_order(self, N):
641
+ """
642
+ Remove an image by its order from the plot widget.
643
+
644
+ Args:
645
+ N(int): Order of the image to be removed.
646
+ """
647
+ if N < len(self.images):
648
+ image = self.images[N]
649
+ image_id = image.config.monitor
650
+ self._disconnect_monitor(image_id)
651
+ self.removeItem(image.color_bar)
652
+ self.plot_item.removeItem(image)
653
+ del self.config.images[image_id]
654
+ for source, images in self._images.items():
655
+ if image_id in images:
656
+ del images[image_id]
657
+ break
658
+ else:
659
+ raise IndexError(f"Image order {N} out of range.")
660
+
661
+ def _disconnect_monitor(self, image_id):
662
+ """
663
+ Disconnect the monitor from the device.
664
+
665
+ Args:
666
+ image_id(str): The ID of the monitor.
667
+ """
668
+ image = self.find_image_by_monitor(image_id)
669
+ if image:
670
+ self.bec_dispatcher.disconnect_slot(
671
+ self.on_image_update, MessageEndpoints.device_monitor(image.config.monitor)
672
+ )
673
+
592
674
  def cleanup(self):
593
675
  """
594
676
  Clean up the widget.
@@ -71,6 +71,7 @@ class BECImageItem(BECConnector, pg.ImageItem):
71
71
 
72
72
  self.parent_image = parent_image
73
73
  self.colorbar_bar = None
74
+ self._raw_data = None
74
75
 
75
76
  self._add_color_bar(
76
77
  self.config.color_bar, self.config.vrange
@@ -80,6 +81,14 @@ class BECImageItem(BECConnector, pg.ImageItem):
80
81
  self.set(**kwargs)
81
82
  self.connected = False
82
83
 
84
+ @property
85
+ def raw_data(self) -> np.ndarray:
86
+ return self._raw_data
87
+
88
+ @raw_data.setter
89
+ def raw_data(self, data: np.ndarray):
90
+ self._raw_data = data
91
+
83
92
  def apply_config(self):
84
93
  """
85
94
  Apply current configuration.
@@ -315,3 +324,8 @@ class BECImageItem(BECConnector, pg.ImageItem):
315
324
  self.config.color_bar = "full"
316
325
  else:
317
326
  raise ValueError("style should be 'simple' or 'full'")
327
+
328
+ def remove(self):
329
+ """Remove the curve from the plot."""
330
+ self.parent_image.remove_image(self.config.monitor)
331
+ self.rpc_register.remove_rpc(self)
File without changes
@@ -0,0 +1 @@
1
+ {'files': ['image_widget.py']}
@@ -0,0 +1,59 @@
1
+ # Copyright (C) 2022 The Qt Company Ltd.
2
+ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
3
+ import os
4
+
5
+ from qtpy.QtDesigner import QDesignerCustomWidgetInterface
6
+ from qtpy.QtGui import QIcon
7
+
8
+ import bec_widgets
9
+ from bec_widgets.widgets.image.image_widget import BECImageWidget
10
+
11
+ DOM_XML = """
12
+ <ui language='c++'>
13
+ <widget class='BECImageWidget' name='bec_image_widget'>
14
+ </widget>
15
+ </ui>
16
+ """
17
+
18
+ MODULE_PATH = os.path.dirname(bec_widgets.__file__)
19
+
20
+
21
+ class BECImageWidgetPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
22
+ def __init__(self):
23
+ super().__init__()
24
+ self._form_editor = None
25
+
26
+ def createWidget(self, parent):
27
+ t = BECImageWidget(parent)
28
+ return t
29
+
30
+ def domXml(self):
31
+ return DOM_XML
32
+
33
+ def group(self):
34
+ return "BEC Plots"
35
+
36
+ def icon(self):
37
+ icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "image.png")
38
+ return QIcon(icon_path)
39
+
40
+ def includeFile(self):
41
+ return "bec_image_widget"
42
+
43
+ def initialize(self, form_editor):
44
+ self._form_editor = form_editor
45
+
46
+ def isContainer(self):
47
+ return False
48
+
49
+ def isInitialized(self):
50
+ return self._form_editor is not None
51
+
52
+ def name(self):
53
+ return "BECImageWidget"
54
+
55
+ def toolTip(self):
56
+ return "BECImageWidget"
57
+
58
+ def whatsThis(self):
59
+ return self.toolTip()