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
@@ -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 BaseModel, Field, ValidationError
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 BECConnector, Colors, ConnectionConfig, EntryValidator
18
- from bec_widgets.widgets.plots.plot_base import BECPlotBase, SubplotConfig
19
-
20
-
21
- class SignalData(BaseModel):
22
- """The data configuration of a signal in the 1D waveform widget for x and y axis."""
23
-
24
- name: str
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",