bec-widgets 0.83.1__py3-none-any.whl → 0.84.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 +34 -36
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +61 -8
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +105 -57
- bec_widgets/utils/bec_dispatcher.py +5 -2
- bec_widgets/widgets/figure/figure.py +21 -114
- bec_widgets/widgets/figure/plots/waveform/waveform.py +651 -94
- bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +9 -2
- {bec_widgets-0.83.1.dist-info → bec_widgets-0.84.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.83.1.dist-info → bec_widgets-0.84.0.dist-info}/RECORD +17 -18
- pyproject.toml +1 -1
- tests/unit_tests/client_mocks.py +13 -4
- tests/unit_tests/test_device_input_widgets.py +2 -0
- tests/unit_tests/test_waveform1d.py +202 -23
- bec_widgets/examples/jupyter_console/jupyter_console_window.ui +0 -54
- {bec_widgets-0.83.1.dist-info → bec_widgets-0.84.0.dist-info}/WHEEL +0 -0
- {bec_widgets-0.83.1.dist-info → bec_widgets-0.84.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.83.1.dist-info → bec_widgets-0.84.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import datetime
|
3
4
|
import time
|
4
5
|
from collections import defaultdict
|
5
6
|
from typing import Any, Literal, Optional
|
@@ -7,8 +8,8 @@ from typing import Any, Literal, Optional
|
|
7
8
|
import numpy as np
|
8
9
|
import pyqtgraph as pg
|
9
10
|
from bec_lib import messages
|
11
|
+
from bec_lib.device import ReadoutPriority
|
10
12
|
from bec_lib.endpoints import MessageEndpoints
|
11
|
-
from bec_lib.scan_data import ScanData
|
12
13
|
from pydantic import Field, ValidationError
|
13
14
|
from qtpy.QtCore import Signal as pyqtSignal
|
14
15
|
from qtpy.QtCore import Slot as pyqtSlot
|
@@ -27,18 +28,26 @@ from bec_widgets.widgets.figure.plots.waveform.waveform_curve import (
|
|
27
28
|
class Waveform1DConfig(SubplotConfig):
|
28
29
|
color_palette: Literal["plasma", "viridis", "inferno", "magma"] = Field(
|
29
30
|
"plasma", description="The color palette of the figure widget."
|
30
|
-
)
|
31
|
+
)
|
31
32
|
curves: dict[str, CurveConfig] = Field(
|
32
33
|
{}, description="The list of curves to be added to the 1D waveform widget."
|
33
34
|
)
|
34
35
|
|
35
36
|
|
36
37
|
class BECWaveform(BECPlotBase):
|
38
|
+
READOUT_PRIORITY_HANDLER = {
|
39
|
+
ReadoutPriority.ON_REQUEST: "on_request",
|
40
|
+
ReadoutPriority.BASELINE: "baseline",
|
41
|
+
ReadoutPriority.MONITORED: "monitored",
|
42
|
+
ReadoutPriority.ASYNC: "async",
|
43
|
+
ReadoutPriority.CONTINUOUS: "continuous",
|
44
|
+
}
|
37
45
|
USER_ACCESS = [
|
38
46
|
"_rpc_id",
|
39
47
|
"_config_dict",
|
40
48
|
"plot",
|
41
49
|
"add_dap",
|
50
|
+
"set_x",
|
42
51
|
"get_dap_params",
|
43
52
|
"remove_curve",
|
44
53
|
"scan_history",
|
@@ -56,10 +65,13 @@ class BECWaveform(BECPlotBase):
|
|
56
65
|
"set_grid",
|
57
66
|
"lock_aspect_ratio",
|
58
67
|
"remove",
|
68
|
+
"clear_all",
|
59
69
|
"set_legend_label_size",
|
60
70
|
]
|
61
71
|
scan_signal_update = pyqtSignal()
|
72
|
+
async_signal_update = pyqtSignal()
|
62
73
|
dap_params_update = pyqtSignal(dict)
|
74
|
+
autorange_signal = pyqtSignal()
|
63
75
|
|
64
76
|
def __init__(
|
65
77
|
self,
|
@@ -78,20 +90,31 @@ class BECWaveform(BECPlotBase):
|
|
78
90
|
self._curves_data = defaultdict(dict)
|
79
91
|
self.old_scan_id = None
|
80
92
|
self.scan_id = None
|
93
|
+
self.scan_item = None
|
94
|
+
self._x_axis_mode = {
|
95
|
+
"name": None,
|
96
|
+
"entry": None,
|
97
|
+
"readout_priority": None,
|
98
|
+
"label_suffix": "",
|
99
|
+
}
|
81
100
|
|
82
101
|
# Scan segment update proxy
|
83
102
|
self.proxy_update_plot = pg.SignalProxy(
|
84
|
-
self.scan_signal_update, rateLimit=25, slot=self.
|
103
|
+
self.scan_signal_update, rateLimit=25, slot=self._update_scan_curves
|
85
104
|
)
|
86
|
-
|
87
105
|
self.proxy_update_dap = pg.SignalProxy(
|
88
106
|
self.scan_signal_update, rateLimit=25, slot=self.refresh_dap
|
89
107
|
)
|
108
|
+
self.async_signal_update.connect(self.replot_async_curve)
|
109
|
+
self.autorange_signal.connect(self.auto_range)
|
110
|
+
|
90
111
|
# Get bec shortcuts dev, scans, queue, scan_storage, dap
|
91
112
|
self.get_bec_shortcuts()
|
92
113
|
|
93
114
|
# Connect dispatcher signals
|
94
115
|
self.bec_dispatcher.connect_slot(self.on_scan_segment, MessageEndpoints.scan_segment())
|
116
|
+
# TODO disabled -> scan_status is SET_AND_PUBLISH -> do not work in combination with autoupdate from CLI
|
117
|
+
# self.bec_dispatcher.connect_slot(self.on_scan_status, MessageEndpoints.scan_status())
|
95
118
|
|
96
119
|
self.entry_validator = EntryValidator(self.dev)
|
97
120
|
|
@@ -139,6 +162,41 @@ class BECWaveform(BECPlotBase):
|
|
139
162
|
for curve in self.curves:
|
140
163
|
curve.config.parent_id = new_gui_id
|
141
164
|
|
165
|
+
###################################
|
166
|
+
# Waveform Properties
|
167
|
+
###################################
|
168
|
+
|
169
|
+
@property
|
170
|
+
def curves(self) -> list[BECCurve]:
|
171
|
+
"""
|
172
|
+
Get the curves of the plot widget as a list
|
173
|
+
Returns:
|
174
|
+
list: List of curves.
|
175
|
+
"""
|
176
|
+
return self._curves
|
177
|
+
|
178
|
+
@curves.setter
|
179
|
+
def curves(self, value: list[BECCurve]):
|
180
|
+
self._curves = value
|
181
|
+
|
182
|
+
@property
|
183
|
+
def x_axis_mode(self) -> dict:
|
184
|
+
"""
|
185
|
+
Get the x axis mode of the plot widget.
|
186
|
+
|
187
|
+
Returns:
|
188
|
+
dict: The x axis mode.
|
189
|
+
"""
|
190
|
+
return self._x_axis_mode
|
191
|
+
|
192
|
+
@x_axis_mode.setter
|
193
|
+
def x_axis_mode(self, value: dict):
|
194
|
+
self._x_axis_mode = value
|
195
|
+
|
196
|
+
###################################
|
197
|
+
# Adding and Removing Curves
|
198
|
+
###################################
|
199
|
+
|
142
200
|
def add_curve_by_config(self, curve_config: CurveConfig | dict) -> BECCurve:
|
143
201
|
"""
|
144
202
|
Add a curve to the plot widget by its configuration.
|
@@ -173,19 +231,6 @@ class BECWaveform(BECPlotBase):
|
|
173
231
|
else:
|
174
232
|
return curves[curve_id].config
|
175
233
|
|
176
|
-
@property
|
177
|
-
def curves(self) -> list[BECCurve]:
|
178
|
-
"""
|
179
|
-
Get the curves of the plot widget as a list
|
180
|
-
Returns:
|
181
|
-
list: List of curves.
|
182
|
-
"""
|
183
|
-
return self._curves
|
184
|
-
|
185
|
-
@curves.setter
|
186
|
-
def curves(self, value: list[BECCurve]):
|
187
|
-
self._curves = value
|
188
|
-
|
189
234
|
def get_curve(self, identifier) -> BECCurve:
|
190
235
|
"""
|
191
236
|
Get the curve by its index or ID.
|
@@ -208,8 +253,9 @@ class BECWaveform(BECPlotBase):
|
|
208
253
|
|
209
254
|
def plot(
|
210
255
|
self,
|
211
|
-
|
256
|
+
arg1: list | np.ndarray | str | None = None,
|
212
257
|
y: list | np.ndarray | None = None,
|
258
|
+
x: list | np.ndarray | None = None,
|
213
259
|
x_name: str | None = None,
|
214
260
|
y_name: str | None = None,
|
215
261
|
z_name: str | None = None,
|
@@ -225,10 +271,16 @@ class BECWaveform(BECPlotBase):
|
|
225
271
|
) -> BECCurve:
|
226
272
|
"""
|
227
273
|
Plot a curve to the plot widget.
|
274
|
+
|
228
275
|
Args:
|
229
|
-
|
276
|
+
arg1(list | np.ndarray | str | None): First argument which can be x data, y data, or y_name.
|
230
277
|
y(list | np.ndarray): Custom y data to plot.
|
231
|
-
|
278
|
+
x(list | np.ndarray): Custom y data to plot.
|
279
|
+
x_name(str): Name of the x signal.
|
280
|
+
- "best_effort": Use the best effort signal.
|
281
|
+
- "timestamp": Use the timestamp signal.
|
282
|
+
- "index": Use the index signal.
|
283
|
+
- Custom signal name of device from BEC.
|
232
284
|
y_name(str): The name of the device for the y-axis.
|
233
285
|
z_name(str): The name of the device for the z-axis.
|
234
286
|
x_entry(str): The name of the entry for the x-axis.
|
@@ -238,30 +290,136 @@ class BECWaveform(BECPlotBase):
|
|
238
290
|
color_map_z(str): The color map to use for the z-axis.
|
239
291
|
label(str): The label of the curve.
|
240
292
|
validate(bool): If True, validate the device names and entries.
|
241
|
-
dap(str): The dap model to use for the curve. If not specified, none will be added.
|
293
|
+
dap(str): The dap model to use for the curve, only available for sync devices. If not specified, none will be added.
|
242
294
|
|
243
295
|
Returns:
|
244
296
|
BECCurve: The curve object.
|
245
297
|
"""
|
246
|
-
|
247
298
|
if x is not None and y is not None:
|
248
299
|
return self.add_curve_custom(x=x, y=y, label=label, color=color, **kwargs)
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
label=label,
|
262
|
-
|
263
|
-
|
300
|
+
|
301
|
+
if isinstance(arg1, str):
|
302
|
+
y_name = arg1
|
303
|
+
elif isinstance(arg1, list):
|
304
|
+
if isinstance(y, list):
|
305
|
+
return self.add_curve_custom(x=arg1, y=y, label=label, color=color, **kwargs)
|
306
|
+
if y is None:
|
307
|
+
x = np.arange(len(arg1))
|
308
|
+
return self.add_curve_custom(x=x, y=arg1, label=label, color=color, **kwargs)
|
309
|
+
elif isinstance(arg1, np.ndarray) and y is None:
|
310
|
+
if arg1.ndim == 1:
|
311
|
+
x = np.arange(arg1.size)
|
312
|
+
return self.add_curve_custom(x=x, y=arg1, label=label, color=color, **kwargs)
|
313
|
+
if arg1.ndim == 2:
|
314
|
+
x = arg1[:, 0]
|
315
|
+
y = arg1[:, 1]
|
316
|
+
return self.add_curve_custom(x=x, y=y, label=label, color=color, **kwargs)
|
317
|
+
if y_name is None:
|
318
|
+
raise ValueError("y_name must be provided.")
|
319
|
+
if dap:
|
320
|
+
self.add_dap(x_name=x_name, y_name=y_name, dap=dap)
|
321
|
+
curve = self.add_curve_bec(
|
322
|
+
x_name=x_name,
|
323
|
+
y_name=y_name,
|
324
|
+
z_name=z_name,
|
325
|
+
x_entry=x_entry,
|
326
|
+
y_entry=y_entry,
|
327
|
+
z_entry=z_entry,
|
328
|
+
color=color,
|
329
|
+
color_map_z=color_map_z,
|
330
|
+
label=label,
|
331
|
+
validate_bec=validate,
|
332
|
+
**kwargs,
|
333
|
+
)
|
334
|
+
self.scan_signal_update.emit()
|
335
|
+
self.async_signal_update.emit()
|
336
|
+
|
337
|
+
return curve
|
338
|
+
|
339
|
+
def set_x(self, x_name: str, x_entry: str | None = None):
|
340
|
+
"""
|
341
|
+
Change the x axis of the plot widget.
|
342
|
+
|
343
|
+
Args:
|
344
|
+
x_name(str): Name of the x signal.
|
345
|
+
- "best_effort": Use the best effort signal.
|
346
|
+
- "timestamp": Use the timestamp signal.
|
347
|
+
- "index": Use the index signal.
|
348
|
+
- Custom signal name of device from BEC.
|
349
|
+
x_entry(str): Entry of the x signal.
|
350
|
+
"""
|
351
|
+
curve_configs = self.config.curves
|
352
|
+
curve_ids = list(curve_configs.keys())
|
353
|
+
curve_configs = list(curve_configs.values())
|
354
|
+
self.set_auto_range(True, "xy")
|
355
|
+
|
356
|
+
x_entry, _, _ = self._validate_signal_entries(
|
357
|
+
x_name, None, None, x_entry, None, None, validate_bec=True
|
358
|
+
)
|
359
|
+
|
360
|
+
readout_priority_x = None
|
361
|
+
if x_name not in ["best_effort", "timestamp", "index"]:
|
362
|
+
readout_priority_x = self._get_device_readout_priority(x_name)
|
363
|
+
|
364
|
+
self.x_axis_mode = {
|
365
|
+
"name": x_name,
|
366
|
+
"entry": x_entry,
|
367
|
+
readout_priority_x: readout_priority_x,
|
368
|
+
}
|
369
|
+
|
370
|
+
if len(self.curves) > 0:
|
371
|
+
# validate all curves
|
372
|
+
for curve in self.curves:
|
373
|
+
self._validate_x_axis_behaviour(curve.config.signals.y.name, x_name, x_entry, False)
|
374
|
+
self._switch_x_axis_item(
|
375
|
+
f"{x_name}-{x_entry}"
|
376
|
+
if x_name not in ["best_effort", "timestamp", "index"]
|
377
|
+
else x_name
|
264
378
|
)
|
379
|
+
for curve_id, curve_config in zip(curve_ids, curve_configs):
|
380
|
+
if curve_config.signals.x:
|
381
|
+
curve_config.signals.x.name = x_name
|
382
|
+
curve_config.signals.x.entry = x_entry
|
383
|
+
self.remove_curve(curve_id)
|
384
|
+
self.add_curve_by_config(curve_config)
|
385
|
+
|
386
|
+
self.async_signal_update.emit()
|
387
|
+
self.scan_signal_update.emit()
|
388
|
+
# self.autorange_timer.start(200)
|
389
|
+
|
390
|
+
@pyqtSlot()
|
391
|
+
def auto_range(self):
|
392
|
+
self.plot_item.autoRange()
|
393
|
+
|
394
|
+
def set_auto_range(self, enabled: bool, axis: str = "xy"):
|
395
|
+
"""
|
396
|
+
Set the auto range of the plot widget.
|
397
|
+
|
398
|
+
Args:
|
399
|
+
enabled(bool): If True, enable the auto range.
|
400
|
+
axis(str, optional): The axis to enable the auto range.
|
401
|
+
- "xy": Enable auto range for both x and y axis.
|
402
|
+
- "x": Enable auto range for x axis.
|
403
|
+
- "y": Enable auto range for y axis.
|
404
|
+
"""
|
405
|
+
self.plot_item.enableAutoRange(axis, enabled)
|
406
|
+
|
407
|
+
@pyqtSlot()
|
408
|
+
def auto_range(self):
|
409
|
+
self.plot_item.autoRange()
|
410
|
+
|
411
|
+
def set_auto_range(self, enabled: bool, axis: str = "xy"):
|
412
|
+
"""
|
413
|
+
Set the auto range of the plot widget.
|
414
|
+
|
415
|
+
Args:
|
416
|
+
enabled(bool): If True, enable the auto range.
|
417
|
+
axis(str, optional): The axis to enable the auto range.
|
418
|
+
- "xy": Enable auto range for both x and y axis.
|
419
|
+
- "x": Enable auto range for x axis.
|
420
|
+
- "y": Enable auto range for y axis.
|
421
|
+
"""
|
422
|
+
self.plot_item.enableAutoRange(axis, enabled)
|
265
423
|
|
266
424
|
def add_curve_custom(
|
267
425
|
self,
|
@@ -317,20 +475,20 @@ class BECWaveform(BECPlotBase):
|
|
317
475
|
)
|
318
476
|
return curve
|
319
477
|
|
320
|
-
def
|
478
|
+
def add_curve_bec(
|
321
479
|
self,
|
322
|
-
x_name: str,
|
323
|
-
y_name: str,
|
324
|
-
z_name:
|
325
|
-
x_entry:
|
326
|
-
y_entry:
|
327
|
-
z_entry:
|
328
|
-
color:
|
329
|
-
color_map_z:
|
330
|
-
label:
|
480
|
+
x_name: str | None = None,
|
481
|
+
y_name: str | None = None,
|
482
|
+
z_name: str | None = None,
|
483
|
+
x_entry: str | None = None,
|
484
|
+
y_entry: str | None = None,
|
485
|
+
z_entry: str | None = None,
|
486
|
+
color: str | None = None,
|
487
|
+
color_map_z: str | None = "plasma",
|
488
|
+
label: str | None = None,
|
331
489
|
validate_bec: bool = True,
|
332
|
-
|
333
|
-
|
490
|
+
dap: str | None = None,
|
491
|
+
source: str | None = None,
|
334
492
|
**kwargs,
|
335
493
|
) -> BECCurve:
|
336
494
|
"""
|
@@ -346,28 +504,50 @@ class BECWaveform(BECPlotBase):
|
|
346
504
|
color(str, optional): Color of the curve. Defaults to None.
|
347
505
|
color_map_z(str): The color map to use for the z-axis.
|
348
506
|
label(str, optional): Label of the curve. Defaults to None.
|
507
|
+
validate_bec(bool, optional): If True, validate the signal with BEC. Defaults to True.
|
508
|
+
dap(str, optional): The dap model to use for the curve. Defaults to None.
|
349
509
|
**kwargs: Additional keyword arguments for the curve configuration.
|
350
510
|
|
351
511
|
Returns:
|
352
512
|
BECCurve: The curve object.
|
353
513
|
"""
|
354
|
-
# Check
|
355
|
-
|
514
|
+
# 1. Check - y_name must be provided
|
515
|
+
if y_name is None:
|
516
|
+
raise ValueError("y_name must be provided.")
|
517
|
+
|
518
|
+
# 2. Check - check if there is already a x axis signal
|
519
|
+
if x_name is None:
|
520
|
+
mode = self.x_axis_mode["name"]
|
521
|
+
x_name = mode if mode is not None else "best_effort"
|
522
|
+
self.x_axis_mode["name"] = x_name
|
356
523
|
|
357
|
-
# Get entry if not provided and validate
|
524
|
+
# 3. Check - Get entry if not provided and validate
|
358
525
|
x_entry, y_entry, z_entry = self._validate_signal_entries(
|
359
526
|
x_name, y_name, z_name, x_entry, y_entry, z_entry, validate_bec
|
360
527
|
)
|
361
528
|
|
529
|
+
# 4. Check - get source of the device
|
530
|
+
if source is None:
|
531
|
+
if validate_bec is True:
|
532
|
+
source = self._validate_device_source_compatibity(y_name)
|
533
|
+
else:
|
534
|
+
source = "scan_segment"
|
535
|
+
|
362
536
|
if z_name is not None and z_entry is not None:
|
363
537
|
label = label or f"{z_name}-{z_entry}"
|
364
538
|
else:
|
365
539
|
label = label or f"{y_name}-{y_entry}"
|
366
540
|
|
541
|
+
# 5. Check - Check if curve already exists
|
367
542
|
curve_exits = self._check_curve_id(label, self._curves_data)
|
368
543
|
if curve_exits:
|
369
544
|
raise ValueError(f"Curve with ID '{label}' already exists in widget '{self.gui_id}'.")
|
370
545
|
|
546
|
+
# Validate or define x axis behaviour and compatibility with y_name readoutPriority
|
547
|
+
if validate_bec is True:
|
548
|
+
self._validate_x_axis_behaviour(y_name, x_name, x_entry)
|
549
|
+
|
550
|
+
# Create color if not specified
|
371
551
|
color = (
|
372
552
|
color
|
373
553
|
or Colors.golden_angle_color(
|
@@ -382,27 +562,29 @@ class BECWaveform(BECPlotBase):
|
|
382
562
|
label=label,
|
383
563
|
color=color,
|
384
564
|
color_map_z=color_map_z,
|
385
|
-
source=
|
565
|
+
source=source,
|
386
566
|
signals=Signal(
|
387
|
-
source=
|
388
|
-
x=SignalData(name=x_name, entry=x_entry),
|
567
|
+
source=source,
|
568
|
+
x=SignalData(name=x_name, entry=x_entry) if x_name else None,
|
389
569
|
y=SignalData(name=y_name, entry=y_entry),
|
390
570
|
z=SignalData(name=z_name, entry=z_entry) if z_name else None,
|
391
571
|
dap=dap,
|
392
572
|
),
|
393
573
|
**kwargs,
|
394
574
|
)
|
395
|
-
|
575
|
+
|
576
|
+
curve = self._add_curve_object(name=label, source=source, config=curve_config)
|
396
577
|
return curve
|
397
578
|
|
398
579
|
def add_dap(
|
399
580
|
self,
|
400
|
-
x_name: str,
|
401
|
-
y_name: str,
|
581
|
+
x_name: str | None = None,
|
582
|
+
y_name: str | None = None,
|
402
583
|
x_entry: Optional[str] = None,
|
403
584
|
y_entry: Optional[str] = None,
|
404
585
|
color: Optional[str] = None,
|
405
586
|
dap: str = "GaussianModel",
|
587
|
+
validate_bec: bool = True,
|
406
588
|
**kwargs,
|
407
589
|
) -> BECCurve:
|
408
590
|
"""
|
@@ -417,16 +599,31 @@ class BECWaveform(BECPlotBase):
|
|
417
599
|
color_map_z(str): The color map to use for the z-axis.
|
418
600
|
label(str, optional): Label of the curve. Defaults to None.
|
419
601
|
dap(str): The dap model to use for the curve.
|
602
|
+
validate_bec(bool, optional): If True, validate the signal with BEC. Defaults to True.
|
420
603
|
**kwargs: Additional keyword arguments for the curve configuration.
|
421
604
|
|
422
605
|
Returns:
|
423
606
|
BECCurve: The curve object.
|
424
607
|
"""
|
425
|
-
|
426
|
-
x_name
|
427
|
-
|
608
|
+
if x_name is None:
|
609
|
+
x_name = self.x_axis_mode["name"]
|
610
|
+
x_entry = self.x_axis_mode["entry"]
|
611
|
+
if x_name == "timestamp" or x_name == "index":
|
612
|
+
raise ValueError(
|
613
|
+
f"Cannot use x axis '{x_name}' for DAP curve. Please provide a custom x axis signal or switch to 'best_effort' signal mode."
|
614
|
+
)
|
615
|
+
|
616
|
+
if self.x_axis_mode["readout_priority"] == "async":
|
617
|
+
raise ValueError(
|
618
|
+
f"Async signals cannot be fitted at the moment. Please switch to 'monitored' or 'baseline' signals."
|
619
|
+
)
|
620
|
+
|
621
|
+
if validate_bec is True:
|
622
|
+
x_entry, y_entry, _ = self._validate_signal_entries(
|
623
|
+
x_name, y_name, None, x_entry, y_entry, None
|
624
|
+
)
|
428
625
|
label = f"{y_name}-{y_entry}-{dap}"
|
429
|
-
curve = self.
|
626
|
+
curve = self.add_curve_bec(
|
430
627
|
x_name=x_name,
|
431
628
|
y_name=y_name,
|
432
629
|
x_entry=x_entry,
|
@@ -444,6 +641,7 @@ class BECWaveform(BECPlotBase):
|
|
444
641
|
self.refresh_dap()
|
445
642
|
return curve
|
446
643
|
|
644
|
+
@pyqtSlot()
|
447
645
|
def get_dap_params(self) -> dict:
|
448
646
|
"""
|
449
647
|
Get the DAP parameters of all DAP curves.
|
@@ -484,10 +682,118 @@ class BECWaveform(BECPlotBase):
|
|
484
682
|
self.set_legend_label_size()
|
485
683
|
return curve
|
486
684
|
|
685
|
+
def _validate_device_source_compatibity(self, name: str):
|
686
|
+
readout_priority_y = self._get_device_readout_priority(name)
|
687
|
+
if readout_priority_y == "monitored" or readout_priority_y == "baseline":
|
688
|
+
source = "scan_segment"
|
689
|
+
elif readout_priority_y == "async":
|
690
|
+
source = "async"
|
691
|
+
else:
|
692
|
+
raise ValueError(
|
693
|
+
f"Readout priority '{readout_priority_y}' of device '{name}' is not supported for y signal."
|
694
|
+
)
|
695
|
+
return source
|
696
|
+
|
697
|
+
def _validate_x_axis_behaviour(
|
698
|
+
self, y_name: str, x_name: str | None = None, x_entry: str | None = None, auto_switch=True
|
699
|
+
) -> None:
|
700
|
+
"""
|
701
|
+
Validate the x axis behaviour and consistency for the plot item.
|
702
|
+
|
703
|
+
Args:
|
704
|
+
source(str): Source of updating device. Can be either "scan_segment" or "async".
|
705
|
+
x_name(str): Name of the x signal.
|
706
|
+
- "best_effort": Use the best effort signal.
|
707
|
+
- "timestamp": Use the timestamp signal.
|
708
|
+
- "index": Use the index signal.
|
709
|
+
- Custom signal name of device from BEC.
|
710
|
+
x_entry(str): Entry of the x signal.
|
711
|
+
"""
|
712
|
+
|
713
|
+
readout_priority_y = self._get_device_readout_priority(y_name)
|
714
|
+
|
715
|
+
# Check if the x axis behaviour is already set
|
716
|
+
if self._x_axis_mode["name"] is not None:
|
717
|
+
# Case 1: The same x axis signal is used, check if source is compatible with the device
|
718
|
+
if x_name != self._x_axis_mode["name"] and x_entry != self._x_axis_mode["entry"]:
|
719
|
+
# A different x axis signal is used, raise an exception
|
720
|
+
raise ValueError(
|
721
|
+
f"All curves must have the same x axis.\n"
|
722
|
+
f" Current valid x axis: '{self._x_axis_mode['name']}'\n"
|
723
|
+
f" Attempted to add curve with x axis: '{x_name}'\n"
|
724
|
+
f"If you want to change the x-axis of the curve, please remove previous curves or change the x axis of the plot widget with '.set_x({x_name})'."
|
725
|
+
)
|
726
|
+
|
727
|
+
# If x_axis_mode["name"] is None, determine the mode based on x_name
|
728
|
+
# With async the best effort is always "index"
|
729
|
+
# Setting mode to either "best_effort", "timestamp", "index", or a custom one
|
730
|
+
if x_name is None and readout_priority_y == "async":
|
731
|
+
x_name = "index"
|
732
|
+
x_entry = "index"
|
733
|
+
if x_name in ["best_effort", "timestamp", "index"]:
|
734
|
+
self._x_axis_mode["name"] = x_name
|
735
|
+
self._x_axis_mode["entry"] = x_entry
|
736
|
+
else:
|
737
|
+
self._x_axis_mode["name"] = x_name
|
738
|
+
self._x_axis_mode["entry"] = x_entry
|
739
|
+
if readout_priority_y == "async":
|
740
|
+
raise ValueError(
|
741
|
+
f"Async devices '{y_name}' cannot be used with custom x signal '{x_name}-{x_entry}'.\n"
|
742
|
+
f"Please use mode 'best_effort', 'timestamp', or 'index' signal for x axis."
|
743
|
+
f"You can change the x axis mode with '.set_x(mode)'"
|
744
|
+
)
|
745
|
+
|
746
|
+
if auto_switch is True:
|
747
|
+
# Switch the x axis mode accordingly
|
748
|
+
self._switch_x_axis_item(
|
749
|
+
f"{x_name}-{x_entry}"
|
750
|
+
if x_name not in ["best_effort", "timestamp", "index"]
|
751
|
+
else x_name
|
752
|
+
)
|
753
|
+
|
754
|
+
def _get_device_readout_priority(self, name: str):
|
755
|
+
"""
|
756
|
+
Get the type of device from the entry_validator.
|
757
|
+
|
758
|
+
Args:
|
759
|
+
name(str): Name of the device.
|
760
|
+
entry(str): Entry of the device.
|
761
|
+
|
762
|
+
Returns:
|
763
|
+
str: Type of the device.
|
764
|
+
"""
|
765
|
+
return self.READOUT_PRIORITY_HANDLER[self.dev[name].readout_priority]
|
766
|
+
|
767
|
+
def _switch_x_axis_item(self, mode: str):
|
768
|
+
"""
|
769
|
+
Switch the x-axis mode between timestamp, index, the best effort and custom signal.
|
770
|
+
|
771
|
+
Args:
|
772
|
+
mode(str): Mode of the x-axis.
|
773
|
+
- "timestamp": Use the timestamp signal.
|
774
|
+
- "index": Use the index signal.
|
775
|
+
- "best_effort": Use the best effort signal.
|
776
|
+
- Custom signal name of device from BEC.
|
777
|
+
"""
|
778
|
+
current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
|
779
|
+
date_axis = pg.graphicsItems.DateAxisItem.DateAxisItem(orientation="bottom")
|
780
|
+
default_axis = pg.AxisItem(orientation="bottom")
|
781
|
+
self._x_axis_mode["label_suffix"] = f" [{mode}]"
|
782
|
+
|
783
|
+
if mode == "timestamp":
|
784
|
+
self.plot_item.setAxisItems({"bottom": date_axis})
|
785
|
+
self.plot_item.setLabel("bottom", f"{current_label}{self._x_axis_mode['label_suffix']}")
|
786
|
+
elif mode == "index":
|
787
|
+
self.plot_item.setAxisItems({"bottom": default_axis})
|
788
|
+
self.plot_item.setLabel("bottom", f"{current_label}{self._x_axis_mode['label_suffix']}")
|
789
|
+
else:
|
790
|
+
self.plot_item.setAxisItems({"bottom": default_axis})
|
791
|
+
self.plot_item.setLabel("bottom", f"{current_label}{self._x_axis_mode['label_suffix']}")
|
792
|
+
|
487
793
|
def _validate_signal_entries(
|
488
794
|
self,
|
489
|
-
x_name: str,
|
490
|
-
y_name: str,
|
795
|
+
x_name: str | None,
|
796
|
+
y_name: str | None,
|
491
797
|
z_name: str | None,
|
492
798
|
x_entry: str | None,
|
493
799
|
y_entry: str | None,
|
@@ -510,8 +816,16 @@ class BECWaveform(BECPlotBase):
|
|
510
816
|
tuple[str,str,str|None]: Validated x, y, z entries.
|
511
817
|
"""
|
512
818
|
if validate_bec:
|
513
|
-
|
514
|
-
|
819
|
+
if x_name is None:
|
820
|
+
x_name = "best_effort"
|
821
|
+
x_entry = "best_effort"
|
822
|
+
if x_name:
|
823
|
+
if x_name == "index" or x_name == "timestamp" or x_name == "best_effort":
|
824
|
+
x_entry = x_name
|
825
|
+
else:
|
826
|
+
x_entry = self.entry_validator.validate_signal(x_name, x_entry)
|
827
|
+
if y_name:
|
828
|
+
y_entry = self.entry_validator.validate_signal(y_name, y_entry)
|
515
829
|
if z_name:
|
516
830
|
z_entry = self.entry_validator.validate_signal(z_name, z_entry)
|
517
831
|
else:
|
@@ -593,30 +907,57 @@ class BECWaveform(BECPlotBase):
|
|
593
907
|
else:
|
594
908
|
raise IndexError(f"Curve order {N} out of range.")
|
595
909
|
|
596
|
-
@pyqtSlot(dict
|
597
|
-
def
|
910
|
+
@pyqtSlot(dict)
|
911
|
+
def on_scan_status(self, msg):
|
598
912
|
"""
|
599
|
-
Handle
|
913
|
+
Handle the scan status message.
|
600
914
|
|
601
915
|
Args:
|
602
|
-
msg
|
603
|
-
metadata (dict): Metadata of the scan.
|
916
|
+
msg(dict): Message received with scan status.
|
604
917
|
"""
|
918
|
+
|
605
919
|
current_scan_id = msg.get("scan_id", None)
|
606
920
|
if current_scan_id is None:
|
607
921
|
return
|
608
922
|
|
609
923
|
if current_scan_id != self.scan_id:
|
924
|
+
self.set_auto_range(True, "xy")
|
610
925
|
self.old_scan_id = self.scan_id
|
611
926
|
self.scan_id = current_scan_id
|
612
|
-
self.
|
613
|
-
|
614
|
-
|
615
|
-
|
927
|
+
self.scan_item = self.queue.scan_storage.find_scan_by_ID(self.scan_id)
|
928
|
+
if self._curves_data["DAP"]:
|
929
|
+
self.setup_dap(self.old_scan_id, self.scan_id)
|
930
|
+
if self._curves_data["async"]:
|
931
|
+
for curve_id, curve in self._curves_data["async"].items():
|
932
|
+
self.setup_async(curve.config.signals.y.name)
|
616
933
|
|
934
|
+
@pyqtSlot(dict, dict)
|
935
|
+
def on_scan_segment(self, msg: dict, metadata: dict):
|
936
|
+
"""
|
937
|
+
Handle new scan segments and saves data to a dictionary. Linked through bec_dispatcher.
|
938
|
+
Used only for triggering scan segment update from the BECClient scan storage.
|
939
|
+
|
940
|
+
Args:
|
941
|
+
msg (dict): Message received with scan data.
|
942
|
+
metadata (dict): Metadata of the scan.
|
943
|
+
"""
|
944
|
+
self.on_scan_status(msg)
|
617
945
|
self.scan_signal_update.emit()
|
946
|
+
# self.autorange_timer.start(100)
|
947
|
+
|
948
|
+
def set_x_label(self, label: str, size: int = None):
|
949
|
+
"""
|
950
|
+
Set the label of the x-axis.
|
951
|
+
|
952
|
+
Args:
|
953
|
+
label(str): Label of the x-axis.
|
954
|
+
size(int): Font size of the label.
|
955
|
+
"""
|
956
|
+
super().set_x_label(label, size)
|
957
|
+
current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
|
958
|
+
self.plot_item.setLabel("bottom", f"{current_label}{self._x_axis_mode['label_suffix']}")
|
618
959
|
|
619
|
-
def setup_dap(self, old_scan_id, new_scan_id):
|
960
|
+
def setup_dap(self, old_scan_id: str | None, new_scan_id: str | None):
|
620
961
|
"""
|
621
962
|
Setup DAP for the new scan.
|
622
963
|
|
@@ -626,21 +967,61 @@ class BECWaveform(BECPlotBase):
|
|
626
967
|
|
627
968
|
"""
|
628
969
|
self.bec_dispatcher.disconnect_slot(
|
629
|
-
self.update_dap, MessageEndpoints.dap_response(old_scan_id)
|
970
|
+
self.update_dap, MessageEndpoints.dap_response(f"{old_scan_id}-{self.gui_id}")
|
630
971
|
)
|
631
972
|
if len(self._curves_data["DAP"]) > 0:
|
632
973
|
self.bec_dispatcher.connect_slot(
|
633
|
-
self.update_dap, MessageEndpoints.dap_response(new_scan_id)
|
974
|
+
self.update_dap, MessageEndpoints.dap_response(f"{new_scan_id}-{self.gui_id}")
|
975
|
+
)
|
976
|
+
|
977
|
+
@pyqtSlot(str)
|
978
|
+
def setup_async(self, device: str):
|
979
|
+
self.bec_dispatcher.disconnect_slot(
|
980
|
+
self.on_async_readback, MessageEndpoints.device_async_readback(self.old_scan_id, device)
|
981
|
+
)
|
982
|
+
try:
|
983
|
+
self._curves_data["async"][f"{device}-{device}"].clear_data()
|
984
|
+
except KeyError:
|
985
|
+
pass
|
986
|
+
if len(self._curves_data["async"]) > 0:
|
987
|
+
self.bec_dispatcher.connect_slot(
|
988
|
+
self.on_async_readback,
|
989
|
+
MessageEndpoints.device_async_readback(self.scan_id, device),
|
990
|
+
from_start=True,
|
634
991
|
)
|
635
992
|
|
993
|
+
@pyqtSlot()
|
636
994
|
def refresh_dap(self):
|
637
995
|
"""
|
638
996
|
Refresh the DAP curves with the latest data from the DAP model MessageEndpoints.dap_response().
|
639
997
|
"""
|
640
998
|
for curve_id, curve in self._curves_data["DAP"].items():
|
641
|
-
|
999
|
+
if len(self._curves_data["async"]) > 0:
|
1000
|
+
curve.remove()
|
1001
|
+
raise ValueError(
|
1002
|
+
f"Cannot refresh DAP curve '{curve_id}' while async curves are present. Removing {curve_id} from display."
|
1003
|
+
)
|
1004
|
+
if self._x_axis_mode["name"] == "best_effort":
|
1005
|
+
try:
|
1006
|
+
x_name = self.scan_item.status_message.info["scan_report_devices"][0]
|
1007
|
+
x_entry = self.entry_validator.validate_signal(x_name, None)
|
1008
|
+
except AttributeError:
|
1009
|
+
return
|
1010
|
+
elif curve.config.signals.x is not None:
|
1011
|
+
x_name = curve.config.signals.x.name
|
1012
|
+
x_entry = curve.config.signals.x.entry
|
1013
|
+
if (
|
1014
|
+
x_name == "timestamp" or x_name == "index"
|
1015
|
+
): # timestamp and index not supported by DAP
|
1016
|
+
return
|
1017
|
+
try: # to prevent DAP update if the x axis is not the same as the current scan
|
1018
|
+
current_x_names = self.scan_item.status_message.info["scan_report_devices"]
|
1019
|
+
if x_name not in current_x_names:
|
1020
|
+
return
|
1021
|
+
except AttributeError:
|
1022
|
+
return
|
1023
|
+
|
642
1024
|
y_name = curve.config.signals.y.name
|
643
|
-
x_entry = curve.config.signals.x.entry
|
644
1025
|
y_entry = curve.config.signals.y.entry
|
645
1026
|
model_name = curve.config.signals.dap
|
646
1027
|
model = getattr(self.dap, model_name)
|
@@ -654,7 +1035,7 @@ class BECWaveform(BECPlotBase):
|
|
654
1035
|
"class_args": model._plugin_info["class_args"],
|
655
1036
|
"class_kwargs": model._plugin_info["class_kwargs"],
|
656
1037
|
},
|
657
|
-
metadata={"RID": self.scan_id},
|
1038
|
+
metadata={"RID": f"{self.scan_id}-{self.gui_id}"},
|
658
1039
|
)
|
659
1040
|
self.client.connector.set_and_publish(MessageEndpoints.dap_request(), msg)
|
660
1041
|
|
@@ -676,32 +1057,96 @@ class BECWaveform(BECPlotBase):
|
|
676
1057
|
self.dap_params_update.emit(curve.dap_params)
|
677
1058
|
break
|
678
1059
|
|
679
|
-
|
680
|
-
|
681
|
-
data = self.scan_segment_data.data
|
682
|
-
self._update_scan_curves(data)
|
683
|
-
|
684
|
-
def _update_scan_curves(self, data: ScanData):
|
1060
|
+
@pyqtSlot(dict, dict)
|
1061
|
+
def on_async_readback(self, msg, metadata):
|
685
1062
|
"""
|
686
|
-
|
1063
|
+
Get async data readback.
|
687
1064
|
|
688
1065
|
Args:
|
689
|
-
|
1066
|
+
msg(dict): Message with the async data.
|
1067
|
+
metadata(dict): Metadata of the message.
|
1068
|
+
"""
|
1069
|
+
instruction = metadata.get("async_update")
|
1070
|
+
for curve_id, curve in self._curves_data["async"].items():
|
1071
|
+
y_name = curve.config.signals.y.name
|
1072
|
+
y_entry = curve.config.signals.y.entry
|
1073
|
+
x_name = self._x_axis_mode["name"]
|
1074
|
+
for device, async_data in msg["signals"].items():
|
1075
|
+
if device == y_entry:
|
1076
|
+
data_plot = async_data["value"]
|
1077
|
+
if instruction == "extend":
|
1078
|
+
x_data, y_data = curve.get_data()
|
1079
|
+
if y_data is not None:
|
1080
|
+
new_data = np.hstack((y_data, data_plot))
|
1081
|
+
else:
|
1082
|
+
new_data = data_plot
|
1083
|
+
if x_name == "timestamp":
|
1084
|
+
if x_data is not None:
|
1085
|
+
x_data = np.hstack((x_data, async_data["timestamp"]))
|
1086
|
+
else:
|
1087
|
+
x_data = async_data["timestamp"]
|
1088
|
+
curve.setData(x_data, new_data)
|
1089
|
+
else:
|
1090
|
+
curve.setData(new_data)
|
1091
|
+
elif instruction == "replace":
|
1092
|
+
if x_name == "timestamp":
|
1093
|
+
x_data = async_data["timestamp"]
|
1094
|
+
curve.setData(x_data, data_plot)
|
1095
|
+
else:
|
1096
|
+
curve.setData(data_plot)
|
1097
|
+
|
1098
|
+
@pyqtSlot()
|
1099
|
+
def replot_async_curve(self):
|
1100
|
+
try:
|
1101
|
+
data = self.scan_item.async_data
|
1102
|
+
except AttributeError:
|
1103
|
+
return
|
1104
|
+
for curve_id, curve in self._curves_data["async"].items():
|
1105
|
+
y_name = curve.config.signals.y.name
|
1106
|
+
y_entry = curve.config.signals.y.entry
|
1107
|
+
x_name = None
|
1108
|
+
|
1109
|
+
if curve.config.signals.x:
|
1110
|
+
x_name = curve.config.signals.x.name
|
1111
|
+
|
1112
|
+
if x_name == "timestamp":
|
1113
|
+
data_x = data[y_name][y_entry]["timestamp"]
|
1114
|
+
else:
|
1115
|
+
data_x = None
|
1116
|
+
data_y = data[y_name][y_entry]["value"]
|
1117
|
+
|
1118
|
+
if data_x is None:
|
1119
|
+
curve.setData(data_y)
|
1120
|
+
else:
|
1121
|
+
curve.setData(data_x, data_y)
|
1122
|
+
|
1123
|
+
@pyqtSlot()
|
1124
|
+
def _update_scan_curves(self):
|
1125
|
+
"""
|
1126
|
+
Update the scan curves with the data from the scan segment.
|
690
1127
|
"""
|
1128
|
+
try:
|
1129
|
+
data = self.scan_item.data
|
1130
|
+
except AttributeError:
|
1131
|
+
return
|
1132
|
+
|
691
1133
|
data_x = None
|
692
1134
|
data_y = None
|
693
1135
|
data_z = None
|
1136
|
+
|
694
1137
|
for curve_id, curve in self._curves_data["scan_segment"].items():
|
695
|
-
|
696
|
-
x_entry = curve.config.signals.x.entry
|
1138
|
+
|
697
1139
|
y_name = curve.config.signals.y.name
|
698
1140
|
y_entry = curve.config.signals.y.entry
|
699
1141
|
if curve.config.signals.z:
|
700
1142
|
z_name = curve.config.signals.z.name
|
701
1143
|
z_entry = curve.config.signals.z.entry
|
702
1144
|
|
1145
|
+
data_x = self._get_x_data(curve, y_name, y_entry)
|
1146
|
+
if len(data) == 0: # case if the data is empty because motor is not scanned
|
1147
|
+
return
|
1148
|
+
|
703
1149
|
try:
|
704
|
-
data_x = data[x_name][x_entry].val
|
705
1150
|
data_y = data[y_name][y_entry].val
|
706
1151
|
if curve.config.signals.z:
|
707
1152
|
data_z = data[z_name][z_entry].val
|
@@ -714,9 +1159,107 @@ class BECWaveform(BECPlotBase):
|
|
714
1159
|
curve.setData(x=data_x, y=data_y, symbolBrush=color_z)
|
715
1160
|
except:
|
716
1161
|
return
|
1162
|
+
if data_x is None:
|
1163
|
+
curve.setData(data_y)
|
717
1164
|
else:
|
718
1165
|
curve.setData(data_x, data_y)
|
719
1166
|
|
1167
|
+
def _get_x_data(self, curve: BECCurve, y_name: str, y_entry: str) -> list | np.ndarray | None:
|
1168
|
+
"""
|
1169
|
+
Get the x data for the curve with the decision logic based on the curve configuration:
|
1170
|
+
- If x is called 'timestamp', use the timestamp data from the scan item.
|
1171
|
+
- If x is called 'index', use the rolling index.
|
1172
|
+
- If x is a custom signal, use the data from the scan item.
|
1173
|
+
- If x is not specified, use the first device from the scan report.
|
1174
|
+
|
1175
|
+
Args:
|
1176
|
+
curve(BECCurve): The curve object.
|
1177
|
+
|
1178
|
+
Returns:
|
1179
|
+
list|np.ndarray|None: X data for the curve.
|
1180
|
+
"""
|
1181
|
+
x_data = None
|
1182
|
+
if self._x_axis_mode["name"] == "timestamp":
|
1183
|
+
timestamps = self.scan_item.data[y_name][y_entry].timestamps
|
1184
|
+
|
1185
|
+
x_data = timestamps
|
1186
|
+
print(x_data)
|
1187
|
+
return x_data
|
1188
|
+
if self._x_axis_mode["name"] == "index":
|
1189
|
+
x_data = None
|
1190
|
+
return x_data
|
1191
|
+
|
1192
|
+
if self._x_axis_mode["name"] is None or self._x_axis_mode["name"] == "best_effort":
|
1193
|
+
if len(self._curves_data["async"]) > 0:
|
1194
|
+
x_data = None
|
1195
|
+
self._x_axis_mode["label_suffix"] = f" [auto: index]"
|
1196
|
+
current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
|
1197
|
+
self.plot_item.setLabel(
|
1198
|
+
"bottom", f"{current_label}{self._x_axis_mode['label_suffix']}"
|
1199
|
+
)
|
1200
|
+
return x_data
|
1201
|
+
else:
|
1202
|
+
x_name = self.scan_item.status_message.info["scan_report_devices"][0]
|
1203
|
+
x_entry = self.entry_validator.validate_signal(x_name, None)
|
1204
|
+
x_data = self.scan_item.data[x_name][x_entry].val
|
1205
|
+
self._x_axis_mode["label_suffix"] = f" [auto: {x_name}-{x_entry}]"
|
1206
|
+
current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
|
1207
|
+
self.plot_item.setLabel(
|
1208
|
+
"bottom", f"{current_label}{self._x_axis_mode['label_suffix']}"
|
1209
|
+
)
|
1210
|
+
|
1211
|
+
else:
|
1212
|
+
x_name = curve.config.signals.x.name
|
1213
|
+
x_entry = curve.config.signals.x.entry
|
1214
|
+
try:
|
1215
|
+
x_data = self.scan_item.data[x_name][x_entry].val
|
1216
|
+
except TypeError:
|
1217
|
+
x_data = []
|
1218
|
+
return x_data
|
1219
|
+
|
1220
|
+
# def _get_x_data(self, curve: BECCurve, y_name: str, y_entry: str) -> list | np.ndarray | None:
|
1221
|
+
# """
|
1222
|
+
# Get the x data for the curve with the decision logic based on the curve configuration:
|
1223
|
+
# - If x is called 'timestamp', use the timestamp data from the scan item.
|
1224
|
+
# - If x is called 'index', use the rolling index.
|
1225
|
+
# - If x is a custom signal, use the data from the scan item.
|
1226
|
+
# - If x is not specified, use the first device from the scan report.
|
1227
|
+
#
|
1228
|
+
# Args:
|
1229
|
+
# curve(BECCurve): The curve object.
|
1230
|
+
#
|
1231
|
+
# Returns:
|
1232
|
+
# list|np.ndarray|None: X data for the curve.
|
1233
|
+
# """
|
1234
|
+
# x_data = None
|
1235
|
+
# if curve.config.signals.x is not None:
|
1236
|
+
# if curve.config.signals.x.name == "timestamp":
|
1237
|
+
# timestamps = self.scan_item.data[y_name][y_entry].timestamps
|
1238
|
+
# x_data = self.convert_timestamps(timestamps)
|
1239
|
+
# elif curve.config.signals.x.name == "index":
|
1240
|
+
# x_data = None
|
1241
|
+
# else:
|
1242
|
+
# x_name = curve.config.signals.x.name
|
1243
|
+
# x_entry = curve.config.signals.x.entry
|
1244
|
+
# try:
|
1245
|
+
# x_data = self.scan_item.data[x_name][x_entry].val
|
1246
|
+
# except TypeError:
|
1247
|
+
# x_data = []
|
1248
|
+
# else:
|
1249
|
+
# if len(self._curves_data["async"]) > 0:
|
1250
|
+
# x_data = None
|
1251
|
+
# else:
|
1252
|
+
# x_name = self.scan_item.status_message.info["scan_report_devices"][0]
|
1253
|
+
# x_entry = self.entry_validator.validate_signal(x_name, None)
|
1254
|
+
# x_data = self.scan_item.data[x_name][x_entry].val
|
1255
|
+
# self._x_axis_mode["label_suffix"] = f" [auto: {x_name}-{x_entry}]"
|
1256
|
+
# current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
|
1257
|
+
# self.plot_item.setLabel(
|
1258
|
+
# "bottom", f"{current_label}{self._x_axis_mode['label_suffix']}"
|
1259
|
+
# )
|
1260
|
+
#
|
1261
|
+
# return x_data
|
1262
|
+
|
720
1263
|
def _make_z_gradient(self, data_z: list | np.ndarray, colormap: str) -> list | None:
|
721
1264
|
"""
|
722
1265
|
Make a gradient color for the z values.
|
@@ -765,8 +1308,9 @@ class BECWaveform(BECPlotBase):
|
|
765
1308
|
self.scan_id = scan_id
|
766
1309
|
|
767
1310
|
self.setup_dap(self.old_scan_id, self.scan_id)
|
768
|
-
|
769
|
-
self.
|
1311
|
+
self.scan_item = self.queue.scan_storage.find_scan_by_ID(self.scan_id)
|
1312
|
+
self.scan_signal_update.emit()
|
1313
|
+
self.async_signal_update.emit()
|
770
1314
|
|
771
1315
|
def get_all_data(self, output: Literal["dict", "pandas"] = "dict") -> dict | pd.DataFrame:
|
772
1316
|
"""
|
@@ -808,12 +1352,25 @@ class BECWaveform(BECPlotBase):
|
|
808
1352
|
return combined_data
|
809
1353
|
return data
|
810
1354
|
|
1355
|
+
def clear_all(self):
|
1356
|
+
curves_data = self._curves_data
|
1357
|
+
sources = list(curves_data.keys())
|
1358
|
+
for source in sources:
|
1359
|
+
curve_ids_to_remove = list(curves_data[source].keys())
|
1360
|
+
for curve_id in curve_ids_to_remove:
|
1361
|
+
self.remove_curve(curve_id)
|
1362
|
+
|
811
1363
|
def cleanup(self):
|
812
1364
|
"""Cleanup the widget connection from BECDispatcher."""
|
813
1365
|
self.bec_dispatcher.disconnect_slot(self.on_scan_segment, MessageEndpoints.scan_segment())
|
814
1366
|
self.bec_dispatcher.disconnect_slot(
|
815
1367
|
self.update_dap, MessageEndpoints.dap_response(self.scan_id)
|
816
1368
|
)
|
1369
|
+
for curve_id, curve in self._curves_data["async"].items():
|
1370
|
+
self.bec_dispatcher.disconnect_slot(
|
1371
|
+
self.on_async_readback,
|
1372
|
+
MessageEndpoints.device_async_readback(self.scan_id, curve_id),
|
1373
|
+
)
|
817
1374
|
for curve in self.curves:
|
818
1375
|
curve.cleanup()
|
819
1376
|
super().cleanup()
|