bec-widgets 0.53.2__py3-none-any.whl → 0.54.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 (67) hide show
  1. CHANGELOG.md +24 -25
  2. PKG-INFO +1 -1
  3. bec_widgets/cli/client.py +13 -13
  4. bec_widgets/cli/client_utils.py +0 -4
  5. bec_widgets/cli/generate_cli.py +7 -5
  6. bec_widgets/cli/server.py +5 -7
  7. bec_widgets/examples/jupyter_console/jupyter_console_window.py +7 -3
  8. bec_widgets/examples/motor_movement/motor_control_compilations.py +17 -16
  9. bec_widgets/widgets/__init__.py +0 -10
  10. bec_widgets/widgets/figure/figure.py +40 -23
  11. bec_widgets/widgets/figure/plots/__init__.py +0 -0
  12. bec_widgets/widgets/figure/plots/image/__init__.py +0 -0
  13. bec_widgets/widgets/{plots → figure/plots/image}/image.py +6 -416
  14. bec_widgets/widgets/figure/plots/image/image_item.py +277 -0
  15. bec_widgets/widgets/figure/plots/image/image_processor.py +152 -0
  16. bec_widgets/widgets/figure/plots/motor_map/__init__.py +0 -0
  17. bec_widgets/widgets/{plots → figure/plots/motor_map}/motor_map.py +2 -2
  18. bec_widgets/widgets/figure/plots/waveform/__init__.py +0 -0
  19. bec_widgets/widgets/{plots → figure/plots/waveform}/waveform.py +9 -222
  20. bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +227 -0
  21. bec_widgets/widgets/motor_control/__init__.py +0 -7
  22. bec_widgets/widgets/motor_control/motor_control.py +2 -948
  23. bec_widgets/widgets/motor_control/motor_table/__init__.py +0 -0
  24. bec_widgets/widgets/motor_control/motor_table/motor_table.py +483 -0
  25. bec_widgets/widgets/motor_control/movement_absolute/__init__.py +0 -0
  26. bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +157 -0
  27. bec_widgets/widgets/motor_control/movement_relative/__init__.py +0 -0
  28. bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +227 -0
  29. bec_widgets/widgets/motor_control/selection/__init__.py +0 -0
  30. bec_widgets/widgets/motor_control/selection/selection.py +110 -0
  31. {bec_widgets-0.53.2.dist-info → bec_widgets-0.54.0.dist-info}/METADATA +1 -1
  32. {bec_widgets-0.53.2.dist-info → bec_widgets-0.54.0.dist-info}/RECORD +51 -52
  33. docs/requirements.txt +1 -0
  34. pyproject.toml +1 -1
  35. tests/end-2-end/test_bec_dock_rpc_e2e.py +1 -1
  36. tests/end-2-end/test_bec_figure_rpc_e2e.py +4 -4
  37. tests/end-2-end/test_rpc_register_e2e.py +1 -1
  38. tests/unit_tests/test_bec_dock.py +1 -1
  39. tests/unit_tests/test_bec_figure.py +6 -4
  40. tests/unit_tests/test_bec_motor_map.py +2 -3
  41. tests/unit_tests/test_motor_control.py +6 -5
  42. tests/unit_tests/test_waveform1d.py +13 -1
  43. bec_widgets/validation/__init__.py +0 -2
  44. bec_widgets/validation/monitor_config_validator.py +0 -258
  45. bec_widgets/widgets/monitor/__init__.py +0 -1
  46. bec_widgets/widgets/monitor/config_dialog.py +0 -574
  47. bec_widgets/widgets/monitor/config_dialog.ui +0 -210
  48. bec_widgets/widgets/monitor/example_configs/config_device.yaml +0 -60
  49. bec_widgets/widgets/monitor/example_configs/config_scans.yaml +0 -92
  50. bec_widgets/widgets/monitor/monitor.py +0 -845
  51. bec_widgets/widgets/monitor/tab_template.ui +0 -180
  52. bec_widgets/widgets/motor_map/__init__.py +0 -1
  53. bec_widgets/widgets/motor_map/motor_map.py +0 -594
  54. bec_widgets/widgets/plots/__init__.py +0 -4
  55. tests/unit_tests/test_bec_monitor.py +0 -220
  56. tests/unit_tests/test_config_dialog.py +0 -178
  57. tests/unit_tests/test_motor_map.py +0 -171
  58. tests/unit_tests/test_validator_errors.py +0 -110
  59. /bec_widgets/{cli → assets}/bec_widgets_icon.png +0 -0
  60. /bec_widgets/{examples/jupyter_console → assets}/terminal_icon.png +0 -0
  61. /bec_widgets/widgets/{plots → figure/plots}/plot_base.py +0 -0
  62. /bec_widgets/widgets/motor_control/{motor_control_table.ui → motor_table/motor_table.ui} +0 -0
  63. /bec_widgets/widgets/motor_control/{motor_control_absolute.ui → movement_absolute/movement_absolute.ui} +0 -0
  64. /bec_widgets/widgets/motor_control/{motor_control_relative.ui → movement_relative/movement_relative.ui} +0 -0
  65. /bec_widgets/widgets/motor_control/{motor_control_selection.ui → selection/selection.ui} +0 -0
  66. {bec_widgets-0.53.2.dist-info → bec_widgets-0.54.0.dist-info}/WHEEL +0 -0
  67. {bec_widgets-0.53.2.dist-info → bec_widgets-0.54.0.dist-info}/licenses/LICENSE +0 -0
@@ -4,50 +4,16 @@ from collections import defaultdict
4
4
  from typing import Any, Literal, Optional
5
5
 
6
6
  import numpy as np
7
- import pyqtgraph as pg
8
7
  from bec_lib.endpoints import MessageEndpoints
9
- from pydantic import BaseModel, Field, ValidationError
10
- from qtpy.QtCore import QObject, QThread
11
- from qtpy.QtCore import Signal as pyqtSignal
8
+ from pydantic import Field, ValidationError
9
+ from qtpy.QtCore import QThread
12
10
  from qtpy.QtCore import Slot as pyqtSlot
13
11
  from qtpy.QtWidgets import QWidget
14
12
 
15
- from bec_widgets.utils import BECConnector, ConnectionConfig, EntryValidator
16
-
17
- from .plot_base import BECPlotBase, SubplotConfig
18
-
19
-
20
- class ProcessingConfig(BaseModel):
21
- fft: Optional[bool] = Field(False, description="Whether to perform FFT on the monitor data.")
22
- log: Optional[bool] = Field(False, description="Whether to perform log on the monitor data.")
23
- center_of_mass: Optional[bool] = Field(
24
- False, description="Whether to calculate the center of mass of the monitor data."
25
- )
26
- transpose: Optional[bool] = Field(
27
- False, description="Whether to transpose the monitor data before displaying."
28
- )
29
- rotation: Optional[int] = Field(
30
- None, description="The rotation angle of the monitor data before displaying."
31
- )
32
-
33
-
34
- class ImageItemConfig(ConnectionConfig):
35
- parent_id: Optional[str] = Field(None, description="The parent plot of the image.")
36
- monitor: Optional[str] = Field(None, description="The name of the monitor.")
37
- source: Optional[str] = Field(None, description="The source of the curve.")
38
- color_map: Optional[str] = Field("magma", description="The color map of the image.")
39
- downsample: Optional[bool] = Field(True, description="Whether to downsample the image.")
40
- opacity: Optional[float] = Field(1.0, description="The opacity of the image.")
41
- vrange: Optional[tuple[int, int]] = Field(
42
- None, description="The range of the color bar. If None, the range is automatically set."
43
- )
44
- color_bar: Optional[Literal["simple", "full"]] = Field(
45
- "simple", description="The type of the color bar."
46
- )
47
- autorange: Optional[bool] = Field(True, description="Whether to autorange the color bar.")
48
- processing: ProcessingConfig = Field(
49
- default_factory=ProcessingConfig, description="The post processing of the image."
50
- )
13
+ from bec_widgets.utils import EntryValidator
14
+ from bec_widgets.widgets.figure.plots.image.image_item import BECImageItem, ImageItemConfig
15
+ from bec_widgets.widgets.figure.plots.image.image_processor import ImageProcessor, ProcessorWorker
16
+ from bec_widgets.widgets.figure.plots.plot_base import BECPlotBase, SubplotConfig
51
17
 
52
18
 
53
19
  class ImageConfig(SubplotConfig):
@@ -57,251 +23,6 @@ class ImageConfig(SubplotConfig):
57
23
  )
58
24
 
59
25
 
60
- class BECImageItem(BECConnector, pg.ImageItem):
61
- USER_ACCESS = [
62
- "rpc_id",
63
- "config_dict",
64
- "set",
65
- "set_fft",
66
- "set_log",
67
- "set_rotation",
68
- "set_transpose",
69
- "set_opacity",
70
- "set_autorange",
71
- "set_color_map",
72
- "set_auto_downsample",
73
- "set_monitor",
74
- "set_vrange",
75
- "get_data",
76
- ]
77
-
78
- def __init__(
79
- self,
80
- config: Optional[ImageItemConfig] = None,
81
- gui_id: Optional[str] = None,
82
- parent_image: Optional[BECImageItem] = None,
83
- **kwargs,
84
- ):
85
- if config is None:
86
- config = ImageItemConfig(widget_class=self.__class__.__name__)
87
- self.config = config
88
- else:
89
- self.config = config
90
- super().__init__(config=config, gui_id=gui_id)
91
- pg.ImageItem.__init__(self)
92
-
93
- self.parent_image = parent_image
94
- self.colorbar_bar = None
95
-
96
- self._add_color_bar(
97
- self.config.color_bar, self.config.vrange
98
- ) # TODO can also support None to not have any colorbar
99
- self.apply_config()
100
- if kwargs:
101
- self.set(**kwargs)
102
-
103
- def apply_config(self):
104
- """
105
- Apply current configuration.
106
- """
107
- self.set_color_map(self.config.color_map)
108
- self.set_auto_downsample(self.config.downsample)
109
- if self.config.vrange is not None:
110
- self.set_vrange(vrange=self.config.vrange)
111
-
112
- def set(self, **kwargs):
113
- """
114
- Set the properties of the image.
115
-
116
- Args:
117
- **kwargs: Keyword arguments for the properties to be set.
118
-
119
- Possible properties:
120
- - downsample
121
- - color_map
122
- - monitor
123
- - opacity
124
- - vrange
125
- - fft
126
- - log
127
- - rot
128
- - transpose
129
- """
130
- method_map = {
131
- "downsample": self.set_auto_downsample,
132
- "color_map": self.set_color_map,
133
- "monitor": self.set_monitor,
134
- "opacity": self.set_opacity,
135
- "vrange": self.set_vrange,
136
- "fft": self.set_fft,
137
- "log": self.set_log,
138
- "rot": self.set_rotation,
139
- "transpose": self.set_transpose,
140
- }
141
- for key, value in kwargs.items():
142
- if key in method_map:
143
- method_map[key](value)
144
- else:
145
- print(f"Warning: '{key}' is not a recognized property.")
146
-
147
- def set_fft(self, enable: bool = False):
148
- """
149
- Set the FFT of the image.
150
-
151
- Args:
152
- enable(bool): Whether to perform FFT on the monitor data.
153
- """
154
- self.config.processing.fft = enable
155
-
156
- def set_log(self, enable: bool = False):
157
- """
158
- Set the log of the image.
159
-
160
- Args:
161
- enable(bool): Whether to perform log on the monitor data.
162
- """
163
- self.config.processing.log = enable
164
- if enable and self.color_bar and self.config.color_bar == "full":
165
- self.color_bar.autoHistogramRange()
166
-
167
- def set_rotation(self, deg_90: int = 0):
168
- """
169
- Set the rotation of the image.
170
-
171
- Args:
172
- deg_90(int): The rotation angle of the monitor data before displaying.
173
- """
174
- self.config.processing.rotation = deg_90
175
-
176
- def set_transpose(self, enable: bool = False):
177
- """
178
- Set the transpose of the image.
179
-
180
- Args:
181
- enable(bool): Whether to transpose the image.
182
- """
183
- self.config.processing.transpose = enable
184
-
185
- def set_opacity(self, opacity: float = 1.0):
186
- """
187
- Set the opacity of the image.
188
-
189
- Args:
190
- opacity(float): The opacity of the image.
191
- """
192
- self.setOpacity(opacity)
193
- self.config.opacity = opacity
194
-
195
- def set_autorange(self, autorange: bool = False):
196
- """
197
- Set the autorange of the color bar.
198
-
199
- Args:
200
- autorange(bool): Whether to autorange the color bar.
201
- """
202
- self.config.autorange = autorange
203
- if self.color_bar is not None:
204
- self.color_bar.autoHistogramRange()
205
-
206
- def set_color_map(self, cmap: str = "magma"):
207
- """
208
- Set the color map of the image.
209
-
210
- Args:
211
- cmap(str): The color map of the image.
212
- """
213
- self.setColorMap(cmap)
214
- if self.color_bar is not None:
215
- if self.config.color_bar == "simple":
216
- self.color_bar.setColorMap(cmap)
217
- elif self.config.color_bar == "full":
218
- self.color_bar.gradient.loadPreset(cmap)
219
- self.config.color_map = cmap
220
-
221
- def set_auto_downsample(self, auto: bool = True):
222
- """
223
- Set the auto downsample of the image.
224
-
225
- Args:
226
- auto(bool): Whether to downsample the image.
227
- """
228
- self.setAutoDownsample(auto)
229
- self.config.downsample = auto
230
-
231
- def set_monitor(self, monitor: str):
232
- """
233
- Set the monitor of the image.
234
-
235
- Args:
236
- monitor(str): The name of the monitor.
237
- """
238
- self.config.monitor = monitor
239
-
240
- def set_vrange(self, vmin: float = None, vmax: float = None, vrange: tuple[int, int] = None):
241
- """
242
- Set the range of the color bar.
243
-
244
- Args:
245
- vmin(float): Minimum value of the color bar.
246
- vmax(float): Maximum value of the color bar.
247
- """
248
- if vrange is not None:
249
- vmin, vmax = vrange
250
- self.setLevels([vmin, vmax])
251
- self.config.vrange = (vmin, vmax)
252
- self.config.autorange = False
253
- if self.color_bar is not None:
254
- if self.config.color_bar == "simple":
255
- self.color_bar.setLevels(low=vmin, high=vmax)
256
- elif self.config.color_bar == "full":
257
- self.color_bar.setLevels(min=vmin, max=vmax)
258
- self.color_bar.setHistogramRange(vmin - 0.1 * vmin, vmax + 0.1 * vmax)
259
-
260
- def get_data(self) -> np.ndarray:
261
- """
262
- Get the data of the image.
263
- Returns:
264
- np.ndarray: The data of the image.
265
- """
266
- return self.image
267
-
268
- def _add_color_bar(
269
- self, color_bar_style: str = "simple", vrange: Optional[tuple[int, int]] = None
270
- ):
271
- """
272
- Add color bar to the layout.
273
-
274
- Args:
275
- style(Literal["simple,full"]): The style of the color bar.
276
- vrange(tuple[int,int]): The range of the color bar.
277
- """
278
- if color_bar_style == "simple":
279
- self.color_bar = pg.ColorBarItem(colorMap=self.config.color_map)
280
- if vrange is not None:
281
- self.color_bar.setLevels(low=vrange[0], high=vrange[1])
282
- self.color_bar.setImageItem(self)
283
- self.parent_image.addItem(self.color_bar) # , row=0, col=1)
284
- self.config.color_bar = "simple"
285
- elif color_bar_style == "full":
286
- # Setting histogram
287
- self.color_bar = pg.HistogramLUTItem()
288
- self.color_bar.setImageItem(self)
289
- self.color_bar.gradient.loadPreset(self.config.color_map)
290
- if vrange is not None:
291
- self.color_bar.setLevels(min=vrange[0], max=vrange[1])
292
- self.color_bar.setHistogramRange(
293
- vrange[0] - 0.1 * vrange[0], vrange[1] + 0.1 * vrange[1]
294
- )
295
-
296
- # Adding histogram to the layout
297
- self.parent_image.addItem(self.color_bar) # , row=0, col=1)
298
-
299
- # save settings
300
- self.config.color_bar = "full"
301
- else:
302
- raise ValueError("style should be 'simple' or 'full'")
303
-
304
-
305
26
  class BECImageShow(BECPlotBase):
306
27
  USER_ACCESS = [
307
28
  "rpc_id",
@@ -837,134 +558,3 @@ class BECImageShow(BECPlotBase):
837
558
  image.cleanup()
838
559
 
839
560
  super().cleanup()
840
-
841
-
842
- class ImageProcessor:
843
- """
844
- Class for processing the image data.
845
- """
846
-
847
- def __init__(self, config: ProcessingConfig = None):
848
- if config is None:
849
- config = ProcessingConfig()
850
- self.config = config
851
-
852
- def set_config(self, config: ProcessingConfig):
853
- """
854
- Set the configuration of the processor.
855
-
856
- Args:
857
- config(ProcessingConfig): The configuration of the processor.
858
- """
859
- self.config = config
860
-
861
- def FFT(self, data: np.ndarray) -> np.ndarray:
862
- """
863
- Perform FFT on the data.
864
-
865
- Args:
866
- data(np.ndarray): The data to be processed.
867
-
868
- Returns:
869
- np.ndarray: The processed data.
870
- """
871
- return np.abs(np.fft.fftshift(np.fft.fft2(data)))
872
-
873
- def rotation(self, data: np.ndarray, rotate_90: int) -> np.ndarray:
874
- """
875
- Rotate the data by 90 degrees n times.
876
-
877
- Args:
878
- data(np.ndarray): The data to be processed.
879
- rotate_90(int): The number of 90 degree rotations.
880
-
881
- Returns:
882
- np.ndarray: The processed data.
883
- """
884
- return np.rot90(data, k=rotate_90, axes=(0, 1))
885
-
886
- def transpose(self, data: np.ndarray) -> np.ndarray:
887
- """
888
- Transpose the data.
889
-
890
- Args:
891
- data(np.ndarray): The data to be processed.
892
-
893
- Returns:
894
- np.ndarray: The processed data.
895
- """
896
- return np.transpose(data)
897
-
898
- def log(self, data: np.ndarray) -> np.ndarray:
899
- """
900
- Perform log on the data.
901
-
902
- Args:
903
- data(np.ndarray): The data to be processed.
904
-
905
- Returns:
906
- np.ndarray: The processed data.
907
- """
908
- # TODO this is not final solution -> data should stay as int16
909
- data = data.astype(np.float32)
910
- offset = 1e-6
911
- data_offset = data + offset
912
- return np.log10(data_offset)
913
-
914
- # def center_of_mass(self, data: np.ndarray) -> tuple: # TODO check functionality
915
- # return np.unravel_index(np.argmax(data), data.shape)
916
-
917
- def process_image(self, data: np.ndarray) -> np.ndarray:
918
- """
919
- Process the data according to the configuration.
920
-
921
- Args:
922
- data(np.ndarray): The data to be processed.
923
-
924
- Returns:
925
- np.ndarray: The processed data.
926
- """
927
- if self.config.fft:
928
- data = self.FFT(data)
929
- if self.config.rotation is not None:
930
- data = self.rotation(data, self.config.rotation)
931
- if self.config.transpose:
932
- data = self.transpose(data)
933
- if self.config.log:
934
- data = self.log(data)
935
- return data
936
-
937
-
938
- class ProcessorWorker(QObject):
939
- """
940
- Worker for processing the image data.
941
- """
942
-
943
- processed = pyqtSignal(str, np.ndarray)
944
- stopRequested = pyqtSignal()
945
- finished = pyqtSignal()
946
-
947
- def __init__(self, processor):
948
- super().__init__()
949
- self.processor = processor
950
- self._isRunning = False
951
- self.stopRequested.connect(self.stop)
952
-
953
- @pyqtSlot(str, np.ndarray)
954
- def process_image(self, device: str, image: np.ndarray):
955
- """
956
- Process the image data.
957
-
958
- Args:
959
- device(str): The name of the device.
960
- image(np.ndarray): The image data.
961
- """
962
- self._isRunning = True
963
- processed_image = self.processor.process_image(image)
964
- self._isRunning = False
965
- if not self._isRunning:
966
- self.processed.emit(device, processed_image)
967
- self.finished.emit()
968
-
969
- def stop(self):
970
- self._isRunning = False