bec-widgets 0.53.3__py3-none-any.whl → 0.55.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.
- CHANGELOG.md +24 -26
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +265 -13
- bec_widgets/cli/client_utils.py +0 -3
- bec_widgets/cli/generate_cli.py +10 -5
- bec_widgets/cli/rpc_wigdet_handler.py +2 -1
- bec_widgets/cli/server.py +5 -7
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +11 -5
- bec_widgets/examples/motor_movement/motor_control_compilations.py +17 -16
- bec_widgets/widgets/__init__.py +1 -10
- bec_widgets/widgets/figure/figure.py +40 -23
- bec_widgets/widgets/figure/plots/__init__.py +0 -0
- bec_widgets/widgets/figure/plots/image/__init__.py +0 -0
- bec_widgets/widgets/{plots → figure/plots/image}/image.py +6 -416
- bec_widgets/widgets/figure/plots/image/image_item.py +277 -0
- bec_widgets/widgets/figure/plots/image/image_processor.py +152 -0
- bec_widgets/widgets/figure/plots/motor_map/__init__.py +0 -0
- bec_widgets/widgets/{plots → figure/plots/motor_map}/motor_map.py +2 -2
- bec_widgets/widgets/figure/plots/waveform/__init__.py +0 -0
- bec_widgets/widgets/{plots → figure/plots/waveform}/waveform.py +9 -222
- bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +227 -0
- bec_widgets/widgets/motor_control/__init__.py +0 -7
- bec_widgets/widgets/motor_control/motor_control.py +2 -948
- bec_widgets/widgets/motor_control/motor_table/__init__.py +0 -0
- bec_widgets/widgets/motor_control/motor_table/motor_table.py +483 -0
- bec_widgets/widgets/motor_control/movement_absolute/__init__.py +0 -0
- bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +157 -0
- bec_widgets/widgets/motor_control/movement_relative/__init__.py +0 -0
- bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +227 -0
- bec_widgets/widgets/motor_control/selection/__init__.py +0 -0
- bec_widgets/widgets/motor_control/selection/selection.py +110 -0
- bec_widgets/widgets/spiral_progress_bar/__init__.py +1 -0
- bec_widgets/widgets/spiral_progress_bar/ring.py +184 -0
- bec_widgets/widgets/spiral_progress_bar/spiral_progress_bar.py +594 -0
- {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/RECORD +56 -53
- docs/requirements.txt +1 -0
- pyproject.toml +1 -1
- tests/end-2-end/test_bec_dock_rpc_e2e.py +82 -1
- tests/end-2-end/test_bec_figure_rpc_e2e.py +4 -4
- tests/end-2-end/test_rpc_register_e2e.py +1 -1
- tests/unit_tests/test_bec_dock.py +1 -1
- tests/unit_tests/test_bec_figure.py +6 -4
- tests/unit_tests/test_bec_motor_map.py +2 -3
- tests/unit_tests/test_motor_control.py +6 -5
- tests/unit_tests/test_spiral_progress_bar.py +338 -0
- tests/unit_tests/test_waveform1d.py +13 -1
- bec_widgets/validation/__init__.py +0 -2
- bec_widgets/validation/monitor_config_validator.py +0 -258
- bec_widgets/widgets/monitor/__init__.py +0 -1
- bec_widgets/widgets/monitor/config_dialog.py +0 -574
- bec_widgets/widgets/monitor/config_dialog.ui +0 -210
- bec_widgets/widgets/monitor/example_configs/config_device.yaml +0 -60
- bec_widgets/widgets/monitor/example_configs/config_scans.yaml +0 -92
- bec_widgets/widgets/monitor/monitor.py +0 -845
- bec_widgets/widgets/monitor/tab_template.ui +0 -180
- bec_widgets/widgets/motor_map/__init__.py +0 -1
- bec_widgets/widgets/motor_map/motor_map.py +0 -594
- bec_widgets/widgets/plots/__init__.py +0 -4
- tests/unit_tests/test_bec_monitor.py +0 -220
- tests/unit_tests/test_config_dialog.py +0 -178
- tests/unit_tests/test_motor_map.py +0 -171
- tests/unit_tests/test_validator_errors.py +0 -110
- /bec_widgets/{cli → assets}/bec_widgets_icon.png +0 -0
- /bec_widgets/{examples/jupyter_console → assets}/terminal_icon.png +0 -0
- /bec_widgets/widgets/{plots → figure/plots}/plot_base.py +0 -0
- /bec_widgets/widgets/motor_control/{motor_control_table.ui → motor_table/motor_table.ui} +0 -0
- /bec_widgets/widgets/motor_control/{motor_control_absolute.ui → movement_absolute/movement_absolute.ui} +0 -0
- /bec_widgets/widgets/motor_control/{motor_control_relative.ui → movement_relative/movement_relative.ui} +0 -0
- /bec_widgets/widgets/motor_control/{motor_control_selection.ui → selection/selection.ui} +0 -0
- {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,277 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING, Literal, Optional
|
4
|
+
|
5
|
+
import numpy as np
|
6
|
+
import pyqtgraph as pg
|
7
|
+
from pydantic import Field
|
8
|
+
|
9
|
+
from bec_widgets.utils import BECConnector, ConnectionConfig
|
10
|
+
from bec_widgets.widgets.figure.plots.image.image_processor import ProcessingConfig
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from bec_widgets.widgets.figure.plots.image.image import BECImageShow
|
14
|
+
|
15
|
+
|
16
|
+
class ImageItemConfig(ConnectionConfig):
|
17
|
+
parent_id: Optional[str] = Field(None, description="The parent plot of the image.")
|
18
|
+
monitor: Optional[str] = Field(None, description="The name of the monitor.")
|
19
|
+
source: Optional[str] = Field(None, description="The source of the curve.")
|
20
|
+
color_map: Optional[str] = Field("magma", description="The color map of the image.")
|
21
|
+
downsample: Optional[bool] = Field(True, description="Whether to downsample the image.")
|
22
|
+
opacity: Optional[float] = Field(1.0, description="The opacity of the image.")
|
23
|
+
vrange: Optional[tuple[int, int]] = Field(
|
24
|
+
None, description="The range of the color bar. If None, the range is automatically set."
|
25
|
+
)
|
26
|
+
color_bar: Optional[Literal["simple", "full"]] = Field(
|
27
|
+
"simple", description="The type of the color bar."
|
28
|
+
)
|
29
|
+
autorange: Optional[bool] = Field(True, description="Whether to autorange the color bar.")
|
30
|
+
processing: ProcessingConfig = Field(
|
31
|
+
default_factory=ProcessingConfig, description="The post processing of the image."
|
32
|
+
)
|
33
|
+
|
34
|
+
|
35
|
+
class BECImageItem(BECConnector, pg.ImageItem):
|
36
|
+
USER_ACCESS = [
|
37
|
+
"rpc_id",
|
38
|
+
"config_dict",
|
39
|
+
"set",
|
40
|
+
"set_fft",
|
41
|
+
"set_log",
|
42
|
+
"set_rotation",
|
43
|
+
"set_transpose",
|
44
|
+
"set_opacity",
|
45
|
+
"set_autorange",
|
46
|
+
"set_color_map",
|
47
|
+
"set_auto_downsample",
|
48
|
+
"set_monitor",
|
49
|
+
"set_vrange",
|
50
|
+
"get_data",
|
51
|
+
]
|
52
|
+
|
53
|
+
def __init__(
|
54
|
+
self,
|
55
|
+
config: Optional[ImageItemConfig] = None,
|
56
|
+
gui_id: Optional[str] = None,
|
57
|
+
parent_image: Optional[BECImageShow] = None,
|
58
|
+
**kwargs,
|
59
|
+
):
|
60
|
+
if config is None:
|
61
|
+
config = ImageItemConfig(widget_class=self.__class__.__name__)
|
62
|
+
self.config = config
|
63
|
+
else:
|
64
|
+
self.config = config
|
65
|
+
super().__init__(config=config, gui_id=gui_id)
|
66
|
+
pg.ImageItem.__init__(self)
|
67
|
+
|
68
|
+
self.parent_image = parent_image
|
69
|
+
self.colorbar_bar = None
|
70
|
+
|
71
|
+
self._add_color_bar(
|
72
|
+
self.config.color_bar, self.config.vrange
|
73
|
+
) # TODO can also support None to not have any colorbar
|
74
|
+
self.apply_config()
|
75
|
+
if kwargs:
|
76
|
+
self.set(**kwargs)
|
77
|
+
|
78
|
+
def apply_config(self):
|
79
|
+
"""
|
80
|
+
Apply current configuration.
|
81
|
+
"""
|
82
|
+
self.set_color_map(self.config.color_map)
|
83
|
+
self.set_auto_downsample(self.config.downsample)
|
84
|
+
if self.config.vrange is not None:
|
85
|
+
self.set_vrange(vrange=self.config.vrange)
|
86
|
+
|
87
|
+
def set(self, **kwargs):
|
88
|
+
"""
|
89
|
+
Set the properties of the image.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
**kwargs: Keyword arguments for the properties to be set.
|
93
|
+
|
94
|
+
Possible properties:
|
95
|
+
- downsample
|
96
|
+
- color_map
|
97
|
+
- monitor
|
98
|
+
- opacity
|
99
|
+
- vrange
|
100
|
+
- fft
|
101
|
+
- log
|
102
|
+
- rot
|
103
|
+
- transpose
|
104
|
+
"""
|
105
|
+
method_map = {
|
106
|
+
"downsample": self.set_auto_downsample,
|
107
|
+
"color_map": self.set_color_map,
|
108
|
+
"monitor": self.set_monitor,
|
109
|
+
"opacity": self.set_opacity,
|
110
|
+
"vrange": self.set_vrange,
|
111
|
+
"fft": self.set_fft,
|
112
|
+
"log": self.set_log,
|
113
|
+
"rot": self.set_rotation,
|
114
|
+
"transpose": self.set_transpose,
|
115
|
+
}
|
116
|
+
for key, value in kwargs.items():
|
117
|
+
if key in method_map:
|
118
|
+
method_map[key](value)
|
119
|
+
else:
|
120
|
+
print(f"Warning: '{key}' is not a recognized property.")
|
121
|
+
|
122
|
+
def set_fft(self, enable: bool = False):
|
123
|
+
"""
|
124
|
+
Set the FFT of the image.
|
125
|
+
|
126
|
+
Args:
|
127
|
+
enable(bool): Whether to perform FFT on the monitor data.
|
128
|
+
"""
|
129
|
+
self.config.processing.fft = enable
|
130
|
+
|
131
|
+
def set_log(self, enable: bool = False):
|
132
|
+
"""
|
133
|
+
Set the log of the image.
|
134
|
+
|
135
|
+
Args:
|
136
|
+
enable(bool): Whether to perform log on the monitor data.
|
137
|
+
"""
|
138
|
+
self.config.processing.log = enable
|
139
|
+
if enable and self.color_bar and self.config.color_bar == "full":
|
140
|
+
self.color_bar.autoHistogramRange()
|
141
|
+
|
142
|
+
def set_rotation(self, deg_90: int = 0):
|
143
|
+
"""
|
144
|
+
Set the rotation of the image.
|
145
|
+
|
146
|
+
Args:
|
147
|
+
deg_90(int): The rotation angle of the monitor data before displaying.
|
148
|
+
"""
|
149
|
+
self.config.processing.rotation = deg_90
|
150
|
+
|
151
|
+
def set_transpose(self, enable: bool = False):
|
152
|
+
"""
|
153
|
+
Set the transpose of the image.
|
154
|
+
|
155
|
+
Args:
|
156
|
+
enable(bool): Whether to transpose the image.
|
157
|
+
"""
|
158
|
+
self.config.processing.transpose = enable
|
159
|
+
|
160
|
+
def set_opacity(self, opacity: float = 1.0):
|
161
|
+
"""
|
162
|
+
Set the opacity of the image.
|
163
|
+
|
164
|
+
Args:
|
165
|
+
opacity(float): The opacity of the image.
|
166
|
+
"""
|
167
|
+
self.setOpacity(opacity)
|
168
|
+
self.config.opacity = opacity
|
169
|
+
|
170
|
+
def set_autorange(self, autorange: bool = False):
|
171
|
+
"""
|
172
|
+
Set the autorange of the color bar.
|
173
|
+
|
174
|
+
Args:
|
175
|
+
autorange(bool): Whether to autorange the color bar.
|
176
|
+
"""
|
177
|
+
self.config.autorange = autorange
|
178
|
+
if self.color_bar is not None:
|
179
|
+
self.color_bar.autoHistogramRange()
|
180
|
+
|
181
|
+
def set_color_map(self, cmap: str = "magma"):
|
182
|
+
"""
|
183
|
+
Set the color map of the image.
|
184
|
+
|
185
|
+
Args:
|
186
|
+
cmap(str): The color map of the image.
|
187
|
+
"""
|
188
|
+
self.setColorMap(cmap)
|
189
|
+
if self.color_bar is not None:
|
190
|
+
if self.config.color_bar == "simple":
|
191
|
+
self.color_bar.setColorMap(cmap)
|
192
|
+
elif self.config.color_bar == "full":
|
193
|
+
self.color_bar.gradient.loadPreset(cmap)
|
194
|
+
self.config.color_map = cmap
|
195
|
+
|
196
|
+
def set_auto_downsample(self, auto: bool = True):
|
197
|
+
"""
|
198
|
+
Set the auto downsample of the image.
|
199
|
+
|
200
|
+
Args:
|
201
|
+
auto(bool): Whether to downsample the image.
|
202
|
+
"""
|
203
|
+
self.setAutoDownsample(auto)
|
204
|
+
self.config.downsample = auto
|
205
|
+
|
206
|
+
def set_monitor(self, monitor: str):
|
207
|
+
"""
|
208
|
+
Set the monitor of the image.
|
209
|
+
|
210
|
+
Args:
|
211
|
+
monitor(str): The name of the monitor.
|
212
|
+
"""
|
213
|
+
self.config.monitor = monitor
|
214
|
+
|
215
|
+
def set_vrange(self, vmin: float = None, vmax: float = None, vrange: tuple[int, int] = None):
|
216
|
+
"""
|
217
|
+
Set the range of the color bar.
|
218
|
+
|
219
|
+
Args:
|
220
|
+
vmin(float): Minimum value of the color bar.
|
221
|
+
vmax(float): Maximum value of the color bar.
|
222
|
+
"""
|
223
|
+
if vrange is not None:
|
224
|
+
vmin, vmax = vrange
|
225
|
+
self.setLevels([vmin, vmax])
|
226
|
+
self.config.vrange = (vmin, vmax)
|
227
|
+
self.config.autorange = False
|
228
|
+
if self.color_bar is not None:
|
229
|
+
if self.config.color_bar == "simple":
|
230
|
+
self.color_bar.setLevels(low=vmin, high=vmax)
|
231
|
+
elif self.config.color_bar == "full":
|
232
|
+
self.color_bar.setLevels(min=vmin, max=vmax)
|
233
|
+
self.color_bar.setHistogramRange(vmin - 0.1 * vmin, vmax + 0.1 * vmax)
|
234
|
+
|
235
|
+
def get_data(self) -> np.ndarray:
|
236
|
+
"""
|
237
|
+
Get the data of the image.
|
238
|
+
Returns:
|
239
|
+
np.ndarray: The data of the image.
|
240
|
+
"""
|
241
|
+
return self.image
|
242
|
+
|
243
|
+
def _add_color_bar(
|
244
|
+
self, color_bar_style: str = "simple", vrange: Optional[tuple[int, int]] = None
|
245
|
+
):
|
246
|
+
"""
|
247
|
+
Add color bar to the layout.
|
248
|
+
|
249
|
+
Args:
|
250
|
+
style(Literal["simple,full"]): The style of the color bar.
|
251
|
+
vrange(tuple[int,int]): The range of the color bar.
|
252
|
+
"""
|
253
|
+
if color_bar_style == "simple":
|
254
|
+
self.color_bar = pg.ColorBarItem(colorMap=self.config.color_map)
|
255
|
+
if vrange is not None:
|
256
|
+
self.color_bar.setLevels(low=vrange[0], high=vrange[1])
|
257
|
+
self.color_bar.setImageItem(self)
|
258
|
+
self.parent_image.addItem(self.color_bar) # , row=0, col=1)
|
259
|
+
self.config.color_bar = "simple"
|
260
|
+
elif color_bar_style == "full":
|
261
|
+
# Setting histogram
|
262
|
+
self.color_bar = pg.HistogramLUTItem()
|
263
|
+
self.color_bar.setImageItem(self)
|
264
|
+
self.color_bar.gradient.loadPreset(self.config.color_map)
|
265
|
+
if vrange is not None:
|
266
|
+
self.color_bar.setLevels(min=vrange[0], max=vrange[1])
|
267
|
+
self.color_bar.setHistogramRange(
|
268
|
+
vrange[0] - 0.1 * vrange[0], vrange[1] + 0.1 * vrange[1]
|
269
|
+
)
|
270
|
+
|
271
|
+
# Adding histogram to the layout
|
272
|
+
self.parent_image.addItem(self.color_bar) # , row=0, col=1)
|
273
|
+
|
274
|
+
# save settings
|
275
|
+
self.config.color_bar = "full"
|
276
|
+
else:
|
277
|
+
raise ValueError("style should be 'simple' or 'full'")
|
@@ -0,0 +1,152 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
import numpy as np
|
6
|
+
from pydantic import BaseModel, Field
|
7
|
+
from qtpy.QtCore import QObject, Signal, Slot
|
8
|
+
|
9
|
+
|
10
|
+
class ProcessingConfig(BaseModel):
|
11
|
+
fft: Optional[bool] = Field(False, description="Whether to perform FFT on the monitor data.")
|
12
|
+
log: Optional[bool] = Field(False, description="Whether to perform log on the monitor data.")
|
13
|
+
center_of_mass: Optional[bool] = Field(
|
14
|
+
False, description="Whether to calculate the center of mass of the monitor data."
|
15
|
+
)
|
16
|
+
transpose: Optional[bool] = Field(
|
17
|
+
False, description="Whether to transpose the monitor data before displaying."
|
18
|
+
)
|
19
|
+
rotation: Optional[int] = Field(
|
20
|
+
None, description="The rotation angle of the monitor data before displaying."
|
21
|
+
)
|
22
|
+
|
23
|
+
|
24
|
+
class ImageProcessor:
|
25
|
+
"""
|
26
|
+
Class for processing the image data.
|
27
|
+
"""
|
28
|
+
|
29
|
+
def __init__(self, config: ProcessingConfig = None):
|
30
|
+
if config is None:
|
31
|
+
config = ProcessingConfig()
|
32
|
+
self.config = config
|
33
|
+
|
34
|
+
def set_config(self, config: ProcessingConfig):
|
35
|
+
"""
|
36
|
+
Set the configuration of the processor.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
config(ProcessingConfig): The configuration of the processor.
|
40
|
+
"""
|
41
|
+
self.config = config
|
42
|
+
|
43
|
+
def FFT(self, data: np.ndarray) -> np.ndarray:
|
44
|
+
"""
|
45
|
+
Perform FFT on the data.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
data(np.ndarray): The data to be processed.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
np.ndarray: The processed data.
|
52
|
+
"""
|
53
|
+
return np.abs(np.fft.fftshift(np.fft.fft2(data)))
|
54
|
+
|
55
|
+
def rotation(self, data: np.ndarray, rotate_90: int) -> np.ndarray:
|
56
|
+
"""
|
57
|
+
Rotate the data by 90 degrees n times.
|
58
|
+
|
59
|
+
Args:
|
60
|
+
data(np.ndarray): The data to be processed.
|
61
|
+
rotate_90(int): The number of 90 degree rotations.
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
np.ndarray: The processed data.
|
65
|
+
"""
|
66
|
+
return np.rot90(data, k=rotate_90, axes=(0, 1))
|
67
|
+
|
68
|
+
def transpose(self, data: np.ndarray) -> np.ndarray:
|
69
|
+
"""
|
70
|
+
Transpose the data.
|
71
|
+
|
72
|
+
Args:
|
73
|
+
data(np.ndarray): The data to be processed.
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
np.ndarray: The processed data.
|
77
|
+
"""
|
78
|
+
return np.transpose(data)
|
79
|
+
|
80
|
+
def log(self, data: np.ndarray) -> np.ndarray:
|
81
|
+
"""
|
82
|
+
Perform log on the data.
|
83
|
+
|
84
|
+
Args:
|
85
|
+
data(np.ndarray): The data to be processed.
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
np.ndarray: The processed data.
|
89
|
+
"""
|
90
|
+
# TODO this is not final solution -> data should stay as int16
|
91
|
+
data = data.astype(np.float32)
|
92
|
+
offset = 1e-6
|
93
|
+
data_offset = data + offset
|
94
|
+
return np.log10(data_offset)
|
95
|
+
|
96
|
+
# def center_of_mass(self, data: np.ndarray) -> tuple: # TODO check functionality
|
97
|
+
# return np.unravel_index(np.argmax(data), data.shape)
|
98
|
+
|
99
|
+
def process_image(self, data: np.ndarray) -> np.ndarray:
|
100
|
+
"""
|
101
|
+
Process the data according to the configuration.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
data(np.ndarray): The data to be processed.
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
np.ndarray: The processed data.
|
108
|
+
"""
|
109
|
+
if self.config.fft:
|
110
|
+
data = self.FFT(data)
|
111
|
+
if self.config.rotation is not None:
|
112
|
+
data = self.rotation(data, self.config.rotation)
|
113
|
+
if self.config.transpose:
|
114
|
+
data = self.transpose(data)
|
115
|
+
if self.config.log:
|
116
|
+
data = self.log(data)
|
117
|
+
return data
|
118
|
+
|
119
|
+
|
120
|
+
class ProcessorWorker(QObject):
|
121
|
+
"""
|
122
|
+
Worker for processing the image data.
|
123
|
+
"""
|
124
|
+
|
125
|
+
processed = Signal(str, np.ndarray)
|
126
|
+
stopRequested = Signal()
|
127
|
+
finished = Signal()
|
128
|
+
|
129
|
+
def __init__(self, processor):
|
130
|
+
super().__init__()
|
131
|
+
self.processor = processor
|
132
|
+
self._isRunning = False
|
133
|
+
self.stopRequested.connect(self.stop)
|
134
|
+
|
135
|
+
@Slot(str, np.ndarray)
|
136
|
+
def process_image(self, device: str, image: np.ndarray):
|
137
|
+
"""
|
138
|
+
Process the image data.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
device(str): The name of the device.
|
142
|
+
image(np.ndarray): The image data.
|
143
|
+
"""
|
144
|
+
self._isRunning = True
|
145
|
+
processed_image = self.processor.process_image(image)
|
146
|
+
self._isRunning = False
|
147
|
+
if not self._isRunning:
|
148
|
+
self.processed.emit(device, processed_image)
|
149
|
+
self.finished.emit()
|
150
|
+
|
151
|
+
def stop(self):
|
152
|
+
self._isRunning = False
|
File without changes
|
@@ -13,8 +13,8 @@ from qtpy.QtCore import Slot as pyqtSlot
|
|
13
13
|
from qtpy.QtWidgets import QWidget
|
14
14
|
|
15
15
|
from bec_widgets.utils import EntryValidator
|
16
|
-
from bec_widgets.widgets.plots.plot_base import BECPlotBase, SubplotConfig
|
17
|
-
from bec_widgets.widgets.plots.waveform import Signal, SignalData
|
16
|
+
from bec_widgets.widgets.figure.plots.plot_base import BECPlotBase, SubplotConfig
|
17
|
+
from bec_widgets.widgets.figure.plots.waveform.waveform import Signal, SignalData
|
18
18
|
|
19
19
|
|
20
20
|
class MotorMapConfig(SubplotConfig):
|
File without changes
|
@@ -7,50 +7,19 @@ import numpy as np
|
|
7
7
|
import pyqtgraph as pg
|
8
8
|
from bec_lib.endpoints import MessageEndpoints
|
9
9
|
from bec_lib.scan_data import ScanData
|
10
|
-
from pydantic import
|
11
|
-
from pyqtgraph import mkBrush
|
12
|
-
from qtpy import QtCore
|
10
|
+
from pydantic import Field, ValidationError
|
13
11
|
from qtpy.QtCore import Signal as pyqtSignal
|
14
12
|
from qtpy.QtCore import Slot as pyqtSlot
|
15
13
|
from qtpy.QtWidgets import QWidget
|
16
14
|
|
17
|
-
from bec_widgets.utils import
|
18
|
-
from bec_widgets.widgets.plots.plot_base import BECPlotBase, SubplotConfig
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
entry: str
|
26
|
-
unit: Optional[str] = None # todo implement later
|
27
|
-
modifier: Optional[str] = None # todo implement later
|
28
|
-
limits: Optional[list[float]] = None # todo implement later
|
29
|
-
|
30
|
-
|
31
|
-
class Signal(BaseModel):
|
32
|
-
"""The configuration of a signal in the 1D waveform widget."""
|
33
|
-
|
34
|
-
source: str
|
35
|
-
x: SignalData # TODO maybe add metadata for config gui later
|
36
|
-
y: SignalData
|
37
|
-
z: Optional[SignalData] = None
|
38
|
-
|
39
|
-
|
40
|
-
class CurveConfig(ConnectionConfig):
|
41
|
-
parent_id: Optional[str] = Field(None, description="The parent plot of the curve.")
|
42
|
-
label: Optional[str] = Field(None, description="The label of the curve.")
|
43
|
-
color: Optional[Any] = Field(None, description="The color of the curve.")
|
44
|
-
symbol: Optional[str] = Field("o", description="The symbol of the curve.")
|
45
|
-
symbol_color: Optional[str] = Field(None, description="The color of the symbol of the curve.")
|
46
|
-
symbol_size: Optional[int] = Field(5, description="The size of the symbol of the curve.")
|
47
|
-
pen_width: Optional[int] = Field(2, description="The width of the pen of the curve.")
|
48
|
-
pen_style: Optional[Literal["solid", "dash", "dot", "dashdot"]] = Field(
|
49
|
-
"solid", description="The style of the pen of the curve."
|
50
|
-
)
|
51
|
-
source: Optional[str] = Field(None, description="The source of the curve.")
|
52
|
-
signals: Optional[Signal] = Field(None, description="The signal of the curve.")
|
53
|
-
colormap: Optional[str] = Field("plasma", description="The colormap of the curves z gradient.")
|
15
|
+
from bec_widgets.utils import Colors, EntryValidator
|
16
|
+
from bec_widgets.widgets.figure.plots.plot_base import BECPlotBase, SubplotConfig
|
17
|
+
from bec_widgets.widgets.figure.plots.waveform.waveform_curve import (
|
18
|
+
BECCurve,
|
19
|
+
CurveConfig,
|
20
|
+
Signal,
|
21
|
+
SignalData,
|
22
|
+
)
|
54
23
|
|
55
24
|
|
56
25
|
class Waveform1DConfig(SubplotConfig):
|
@@ -62,188 +31,6 @@ class Waveform1DConfig(SubplotConfig):
|
|
62
31
|
)
|
63
32
|
|
64
33
|
|
65
|
-
class BECCurve(BECConnector, pg.PlotDataItem):
|
66
|
-
USER_ACCESS = [
|
67
|
-
"remove",
|
68
|
-
"rpc_id",
|
69
|
-
"config_dict",
|
70
|
-
"set",
|
71
|
-
"set_data",
|
72
|
-
"set_color",
|
73
|
-
"set_colormap",
|
74
|
-
"set_symbol",
|
75
|
-
"set_symbol_color",
|
76
|
-
"set_symbol_size",
|
77
|
-
"set_pen_width",
|
78
|
-
"set_pen_style",
|
79
|
-
"get_data",
|
80
|
-
]
|
81
|
-
|
82
|
-
def __init__(
|
83
|
-
self,
|
84
|
-
name: Optional[str] = None,
|
85
|
-
config: Optional[CurveConfig] = None,
|
86
|
-
gui_id: Optional[str] = None,
|
87
|
-
parent_item: Optional[pg.PlotItem] = None,
|
88
|
-
**kwargs,
|
89
|
-
):
|
90
|
-
if config is None:
|
91
|
-
config = CurveConfig(label=name, widget_class=self.__class__.__name__)
|
92
|
-
self.config = config
|
93
|
-
else:
|
94
|
-
self.config = config
|
95
|
-
# config.widget_class = self.__class__.__name__
|
96
|
-
super().__init__(config=config, gui_id=gui_id)
|
97
|
-
pg.PlotDataItem.__init__(self, name=name)
|
98
|
-
|
99
|
-
self.parent_item = parent_item
|
100
|
-
self.apply_config()
|
101
|
-
if kwargs:
|
102
|
-
self.set(**kwargs)
|
103
|
-
|
104
|
-
def apply_config(self):
|
105
|
-
pen_style_map = {
|
106
|
-
"solid": QtCore.Qt.SolidLine,
|
107
|
-
"dash": QtCore.Qt.DashLine,
|
108
|
-
"dot": QtCore.Qt.DotLine,
|
109
|
-
"dashdot": QtCore.Qt.DashDotLine,
|
110
|
-
}
|
111
|
-
pen_style = pen_style_map.get(self.config.pen_style, QtCore.Qt.SolidLine)
|
112
|
-
|
113
|
-
pen = pg.mkPen(color=self.config.color, width=self.config.pen_width, style=pen_style)
|
114
|
-
self.setPen(pen)
|
115
|
-
|
116
|
-
if self.config.symbol:
|
117
|
-
symbol_color = self.config.symbol_color or self.config.color
|
118
|
-
brush = mkBrush(color=symbol_color)
|
119
|
-
self.setSymbolBrush(brush)
|
120
|
-
self.setSymbolSize(self.config.symbol_size)
|
121
|
-
self.setSymbol(self.config.symbol)
|
122
|
-
|
123
|
-
def set_data(self, x, y):
|
124
|
-
if self.config.source == "custom":
|
125
|
-
self.setData(x, y)
|
126
|
-
else:
|
127
|
-
raise ValueError(f"Source {self.config.source} do not allow custom data setting.")
|
128
|
-
|
129
|
-
def set(self, **kwargs):
|
130
|
-
"""
|
131
|
-
Set the properties of the curve.
|
132
|
-
|
133
|
-
Args:
|
134
|
-
**kwargs: Keyword arguments for the properties to be set.
|
135
|
-
|
136
|
-
Possible properties:
|
137
|
-
- color: str
|
138
|
-
- symbol: str
|
139
|
-
- symbol_color: str
|
140
|
-
- symbol_size: int
|
141
|
-
- pen_width: int
|
142
|
-
- pen_style: Literal["solid", "dash", "dot", "dashdot"]
|
143
|
-
"""
|
144
|
-
|
145
|
-
# Mapping of keywords to setter methods
|
146
|
-
method_map = {
|
147
|
-
"color": self.set_color,
|
148
|
-
"colormap": self.set_colormap,
|
149
|
-
"symbol": self.set_symbol,
|
150
|
-
"symbol_color": self.set_symbol_color,
|
151
|
-
"symbol_size": self.set_symbol_size,
|
152
|
-
"pen_width": self.set_pen_width,
|
153
|
-
"pen_style": self.set_pen_style,
|
154
|
-
}
|
155
|
-
for key, value in kwargs.items():
|
156
|
-
if key in method_map:
|
157
|
-
method_map[key](value)
|
158
|
-
else:
|
159
|
-
print(f"Warning: '{key}' is not a recognized property.")
|
160
|
-
|
161
|
-
def set_color(self, color: str, symbol_color: Optional[str] = None):
|
162
|
-
"""
|
163
|
-
Change the color of the curve.
|
164
|
-
|
165
|
-
Args:
|
166
|
-
color(str): Color of the curve.
|
167
|
-
symbol_color(str, optional): Color of the symbol. Defaults to None.
|
168
|
-
"""
|
169
|
-
self.config.color = color
|
170
|
-
self.config.symbol_color = symbol_color or color
|
171
|
-
self.apply_config()
|
172
|
-
|
173
|
-
def set_symbol(self, symbol: str):
|
174
|
-
"""
|
175
|
-
Change the symbol of the curve.
|
176
|
-
|
177
|
-
Args:
|
178
|
-
symbol(str): Symbol of the curve.
|
179
|
-
"""
|
180
|
-
self.config.symbol = symbol
|
181
|
-
self.apply_config()
|
182
|
-
|
183
|
-
def set_symbol_color(self, symbol_color: str):
|
184
|
-
"""
|
185
|
-
Change the symbol color of the curve.
|
186
|
-
|
187
|
-
Args:
|
188
|
-
symbol_color(str): Color of the symbol.
|
189
|
-
"""
|
190
|
-
self.config.symbol_color = symbol_color
|
191
|
-
self.apply_config()
|
192
|
-
|
193
|
-
def set_symbol_size(self, symbol_size: int):
|
194
|
-
"""
|
195
|
-
Change the symbol size of the curve.
|
196
|
-
|
197
|
-
Args:
|
198
|
-
symbol_size(int): Size of the symbol.
|
199
|
-
"""
|
200
|
-
self.config.symbol_size = symbol_size
|
201
|
-
self.apply_config()
|
202
|
-
|
203
|
-
def set_pen_width(self, pen_width: int):
|
204
|
-
"""
|
205
|
-
Change the pen width of the curve.
|
206
|
-
|
207
|
-
Args:
|
208
|
-
pen_width(int): Width of the pen.
|
209
|
-
"""
|
210
|
-
self.config.pen_width = pen_width
|
211
|
-
self.apply_config()
|
212
|
-
|
213
|
-
def set_pen_style(self, pen_style: Literal["solid", "dash", "dot", "dashdot"]):
|
214
|
-
"""
|
215
|
-
Change the pen style of the curve.
|
216
|
-
|
217
|
-
Args:
|
218
|
-
pen_style(Literal["solid", "dash", "dot", "dashdot"]): Style of the pen.
|
219
|
-
"""
|
220
|
-
self.config.pen_style = pen_style
|
221
|
-
self.apply_config()
|
222
|
-
|
223
|
-
def set_colormap(self, colormap: str):
|
224
|
-
"""
|
225
|
-
Set the colormap for the scatter plot z gradient.
|
226
|
-
|
227
|
-
Args:
|
228
|
-
colormap(str): Colormap for the scatter plot.
|
229
|
-
"""
|
230
|
-
self.config.colormap = colormap
|
231
|
-
|
232
|
-
def get_data(self) -> tuple[np.ndarray, np.ndarray]:
|
233
|
-
"""
|
234
|
-
Get the data of the curve.
|
235
|
-
Returns:
|
236
|
-
tuple[np.ndarray,np.ndarray]: X and Y data of the curve.
|
237
|
-
"""
|
238
|
-
x_data, y_data = self.getData()
|
239
|
-
return x_data, y_data
|
240
|
-
|
241
|
-
def remove(self):
|
242
|
-
"""Remove the curve from the plot."""
|
243
|
-
self.parent_item.removeItem(self)
|
244
|
-
self.cleanup()
|
245
|
-
|
246
|
-
|
247
34
|
class BECWaveform(BECPlotBase):
|
248
35
|
USER_ACCESS = [
|
249
36
|
"rpc_id",
|