bec-widgets 1.25.0__py3-none-any.whl → 2.0.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.
- .gitlab-ci.yml +11 -6
- CHANGELOG.md +650 -0
- PKG-INFO +3 -3
- bec_widgets/__init__.py +4 -0
- bec_widgets/applications/bw_launch.py +23 -0
- bec_widgets/applications/launch_window.py +430 -0
- bec_widgets/assets/app_icons/auto_update.png +0 -0
- bec_widgets/assets/app_icons/ui_loader_tile.png +0 -0
- bec_widgets/cli/__init__.py +0 -1
- bec_widgets/cli/client.py +1779 -2064
- bec_widgets/cli/client_utils.py +346 -174
- bec_widgets/cli/generate_cli.py +143 -37
- bec_widgets/cli/rpc/rpc_base.py +152 -21
- bec_widgets/cli/rpc/rpc_register.py +113 -6
- bec_widgets/cli/rpc/rpc_widget_handler.py +13 -11
- bec_widgets/cli/server.py +125 -239
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +97 -145
- bec_widgets/examples/plugin_example_pyside/tictactoetaskmenu.py +1 -1
- bec_widgets/utils/bec_connector.py +190 -21
- bec_widgets/utils/bec_designer.py +7 -0
- bec_widgets/utils/bec_dispatcher.py +71 -4
- bec_widgets/utils/bec_plugin_helper.py +89 -0
- bec_widgets/utils/bec_signal_proxy.py +1 -1
- bec_widgets/utils/bec_widget.py +26 -10
- bec_widgets/utils/colors.py +1 -1
- bec_widgets/{qt_utils → utils}/compact_popup.py +2 -0
- bec_widgets/utils/container_utils.py +37 -12
- bec_widgets/utils/crosshair.py +25 -8
- bec_widgets/utils/entry_validator.py +3 -1
- bec_widgets/{qt_utils → utils}/error_popups.py +18 -0
- bec_widgets/{qt_utils → utils}/expandable_frame.py +2 -2
- bec_widgets/utils/forms_from_types/forms.py +182 -0
- bec_widgets/{widgets/editors/scan_metadata/_metadata_widgets.py → utils/forms_from_types/items.py} +41 -30
- bec_widgets/utils/generate_designer_plugin.py +40 -36
- bec_widgets/utils/linear_region_selector.py +2 -0
- bec_widgets/utils/name_utils.py +16 -0
- bec_widgets/{qt_utils → utils}/palette_viewer.py +2 -2
- bec_widgets/utils/plot_indicator_items.py +2 -5
- bec_widgets/utils/plugin_utils.py +47 -1
- bec_widgets/{qt_utils → utils}/round_frame.py +14 -14
- bec_widgets/utils/rpc_server.py +277 -0
- bec_widgets/utils/serialization.py +44 -0
- bec_widgets/{qt_utils → utils}/settings_dialog.py +26 -1
- bec_widgets/{qt_utils → utils}/side_panel.py +17 -10
- bec_widgets/{qt_utils → utils}/toolbar.py +69 -25
- bec_widgets/utils/ui_loader.py +8 -8
- bec_widgets/utils/widget_io.py +166 -25
- bec_widgets/widgets/containers/auto_update/auto_updates.py +364 -0
- bec_widgets/widgets/containers/dock/dock.py +157 -49
- bec_widgets/widgets/containers/dock/dock_area.py +186 -138
- bec_widgets/widgets/containers/layout_manager/layout_manager.py +2 -1
- bec_widgets/widgets/containers/main_window/addons/web_links.py +15 -0
- bec_widgets/widgets/containers/main_window/main_window.py +189 -41
- bec_widgets/widgets/control/buttons/button_abort/button_abort.py +3 -4
- bec_widgets/widgets/control/buttons/button_reset/button_reset.py +3 -4
- bec_widgets/widgets/control/buttons/button_resume/button_resume.py +3 -3
- bec_widgets/widgets/control/buttons/stop_button/stop_button.py +18 -7
- bec_widgets/widgets/control/device_control/position_indicator/position_indicator.py +22 -3
- bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py +37 -18
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.py +28 -4
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.ui +27 -4
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.py +5 -2
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.ui +97 -31
- bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.ui +11 -4
- bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py +2 -3
- bec_widgets/widgets/control/device_input/base_classes/device_input_base.py +29 -4
- bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py +1 -0
- bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py +2 -2
- bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py +2 -2
- bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py +1 -2
- bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.py +1 -2
- bec_widgets/widgets/control/scan_control/scan_control.py +7 -5
- bec_widgets/widgets/control/scan_control/scan_group_box.py +28 -5
- bec_widgets/widgets/dap/dap_combo_box/dap_combo_box.py +1 -2
- bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog.py +3 -4
- bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog_vertical.ui +14 -8
- bec_widgets/widgets/editors/console/console.py +1 -1
- bec_widgets/widgets/editors/{scan_metadata/additional_metadata_table.py → dict_backed_table.py} +29 -6
- bec_widgets/widgets/editors/scan_metadata/__init__.py +0 -7
- bec_widgets/widgets/editors/scan_metadata/_util.py +1 -1
- bec_widgets/widgets/{plots/motor_map/register_bec_motor_map_widget.py → editors/scan_metadata/register_scan_metadata.py} +2 -4
- bec_widgets/widgets/editors/scan_metadata/scan_metadata.py +42 -136
- bec_widgets/widgets/editors/scan_metadata/scan_metadata.pyproject +1 -0
- bec_widgets/widgets/{plots/multi_waveform/bec_multi_waveform_widget_plugin.py → editors/scan_metadata/scan_metadata_plugin.py} +9 -9
- bec_widgets/widgets/editors/text_box/text_box.py +2 -3
- bec_widgets/widgets/editors/website/website.py +2 -2
- bec_widgets/widgets/games/minesweeper.py +3 -2
- bec_widgets/widgets/plots/image/image.py +960 -0
- bec_widgets/widgets/plots/image/image.pyproject +1 -0
- bec_widgets/widgets/plots/image/image_item.py +279 -0
- bec_widgets/widgets/plots/{motor_map/bec_motor_map_widget_plugin.py → image/image_plugin.py} +11 -13
- bec_widgets/widgets/{containers/figure/plots → plots}/image/image_processor.py +31 -64
- bec_widgets/widgets/plots/image/{register_bec_image_widget.py → register_image.py} +2 -2
- bec_widgets/widgets/plots/image/toolbar_bundles/image_selection.py +59 -0
- bec_widgets/widgets/plots/image/toolbar_bundles/processing.py +79 -0
- bec_widgets/widgets/plots/motor_map/motor_map.py +832 -0
- bec_widgets/widgets/plots/motor_map/motor_map.pyproject +1 -0
- bec_widgets/widgets/plots/motor_map/motor_map_plugin.py +54 -0
- bec_widgets/widgets/plots/{multi_waveform/register_bec_multi_waveform_widget.py → motor_map/register_motor_map.py} +2 -4
- bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py +129 -0
- bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.ui +120 -0
- bec_widgets/widgets/plots/motor_map/toolbar_bundles/motor_selection.py +70 -0
- bec_widgets/widgets/plots/multi_waveform/multi_waveform.py +508 -0
- bec_widgets/widgets/plots/multi_waveform/multi_waveform.pyproject +1 -0
- bec_widgets/widgets/plots/multi_waveform/multi_waveform_plugin.py +54 -0
- bec_widgets/widgets/plots/multi_waveform/register_multi_waveform.py +15 -0
- bec_widgets/widgets/plots/multi_waveform/settings/control_panel.py +144 -0
- bec_widgets/widgets/plots/multi_waveform/settings/multi_waveform_controls.ui +164 -0
- bec_widgets/widgets/plots/multi_waveform/toolbar_bundles/monitor_selection.py +65 -0
- bec_widgets/widgets/{plots_next_gen → plots}/plot_base.py +321 -40
- bec_widgets/widgets/plots/{waveform/register_bec_waveform_widget.py → scatter_waveform/register_scatter_waveform.py} +3 -3
- bec_widgets/widgets/plots/scatter_waveform/scatter_curve.py +197 -0
- bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py +553 -0
- bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.pyproject +1 -0
- bec_widgets/widgets/plots/{image/bec_image_widget_plugin.py → scatter_waveform/scatter_waveform_plugin.py} +9 -13
- bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_setting.py +138 -0
- bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_horizontal.ui +195 -0
- bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_vertical.ui +204 -0
- bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings.py +8 -8
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/mouse_interactions.py +4 -18
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/plot_export.py +14 -3
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/roi_bundle.py +6 -1
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/save_state.py +2 -2
- bec_widgets/widgets/{containers/figure/plots/waveform/waveform_curve.py → plots/waveform/curve.py} +119 -49
- bec_widgets/widgets/plots/waveform/register_waveform.py +15 -0
- bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_setting.py +125 -0
- bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py +576 -0
- bec_widgets/widgets/plots/waveform/utils/__init__.py +0 -0
- bec_widgets/widgets/plots/waveform/utils/roi_manager.py +84 -0
- bec_widgets/widgets/plots/waveform/waveform.py +1794 -0
- bec_widgets/widgets/plots/waveform/waveform.pyproject +1 -0
- bec_widgets/widgets/plots/waveform/{bec_waveform_widget_plugin.py → waveform_plugin.py} +9 -13
- bec_widgets/widgets/progress/bec_progressbar/bec_progressbar.py +1 -2
- bec_widgets/widgets/progress/ring_progress_bar/ring.py +11 -10
- bec_widgets/widgets/progress/ring_progress_bar/ring_progress_bar.py +24 -14
- bec_widgets/widgets/services/bec_queue/bec_queue.py +13 -11
- bec_widgets/widgets/services/bec_status_box/bec_status_box.py +3 -4
- bec_widgets/widgets/services/device_browser/device_browser.py +5 -2
- bec_widgets/widgets/services/device_browser/device_item/device_item.py +1 -1
- bec_widgets/widgets/utility/logpanel/logpanel.py +36 -17
- bec_widgets/widgets/utility/spinbox/decimal_spinbox.py +3 -3
- bec_widgets/widgets/utility/spinner/spinner.py +2 -2
- bec_widgets/widgets/utility/visual/color_button/color_button.py +1 -1
- bec_widgets/widgets/utility/visual/colormap_widget/colormap_widget.py +4 -6
- bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py +4 -8
- {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/METADATA +3 -3
- {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/RECORD +169 -154
- pyproject.toml +3 -3
- bec_widgets/applications/alignment/alignment_1d/alignment_1d.py +0 -198
- bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui +0 -615
- bec_widgets/applications/bec_app.py +0 -84
- bec_widgets/cli/auto_updates.py +0 -168
- bec_widgets/widgets/containers/figure/__init__.py +0 -1
- bec_widgets/widgets/containers/figure/figure.py +0 -796
- bec_widgets/widgets/containers/figure/plots/axis_settings.py +0 -91
- bec_widgets/widgets/containers/figure/plots/axis_settings.ui +0 -256
- bec_widgets/widgets/containers/figure/plots/image/image.py +0 -772
- bec_widgets/widgets/containers/figure/plots/image/image_item.py +0 -337
- bec_widgets/widgets/containers/figure/plots/motor_map/motor_map.py +0 -525
- bec_widgets/widgets/containers/figure/plots/multi_waveform/multi_waveform.py +0 -340
- bec_widgets/widgets/containers/figure/plots/plot_base.py +0 -505
- bec_widgets/widgets/containers/figure/plots/waveform/waveform.py +0 -1563
- bec_widgets/widgets/plots/image/bec_image_widget.pyproject +0 -1
- bec_widgets/widgets/plots/image/image_widget.py +0 -515
- bec_widgets/widgets/plots/motor_map/bec_motor_map_widget.pyproject +0 -1
- bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.py +0 -56
- bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.ui +0 -108
- bec_widgets/widgets/plots/motor_map/motor_map_widget.py +0 -234
- bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget.pyproject +0 -1
- bec_widgets/widgets/plots/multi_waveform/multi_waveform_controls.ui +0 -99
- bec_widgets/widgets/plots/multi_waveform/multi_waveform_widget.py +0 -536
- bec_widgets/widgets/plots/waveform/bec_waveform_widget.pyproject +0 -1
- bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.py +0 -336
- bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.ui +0 -372
- bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/dap_summary_dialog.py +0 -25
- bec_widgets/widgets/plots/waveform/waveform_widget.py +0 -751
- /bec_widgets/{qt_utils → utils}/collapsible_panel_manager.py +0 -0
- /bec_widgets/{applications/alignment → utils/forms_from_types}/__init__.py +0 -0
- /bec_widgets/{qt_utils → utils}/redis_message_waiter.py +0 -0
- /bec_widgets/{applications/alignment/alignment_1d → widgets/containers/auto_update}/__init__.py +0 -0
- /bec_widgets/{qt_utils → widgets/containers/main_window/addons}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots → plots/image/toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/image → plots/motor_map/settings}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/motor_map → plots/motor_map/toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/multi_waveform → plots/multi_waveform/settings}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/waveform → plots/multi_waveform/toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/plots/{motor_map/motor_map_dialog → scatter_waveform}/__init__.py +0 -0
- /bec_widgets/widgets/plots/{waveform/waveform_popups → scatter_waveform/settings}/__init__.py +0 -0
- /bec_widgets/widgets/plots/{waveform/waveform_popups/curve_dialog → setting_menus}/__init__.py +0 -0
- /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_horizontal.ui +0 -0
- /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_vertical.ui +0 -0
- /bec_widgets/widgets/plots/{waveform/waveform_popups/dap_summary_dialog → toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/{plots_next_gen/setting_menus → plots/waveform/settings}/__init__.py +0 -0
- /bec_widgets/widgets/{plots_next_gen/toolbar_bundles → plots/waveform/settings/curve_settings}/__init__.py +0 -0
- {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/WHEEL +0 -0
- {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,1563 +0,0 @@
|
|
1
|
-
# pylint: disable=too_many_lines
|
2
|
-
from __future__ import annotations
|
3
|
-
|
4
|
-
from collections import defaultdict
|
5
|
-
from typing import Any, Literal, Optional
|
6
|
-
|
7
|
-
import numpy as np
|
8
|
-
import pyqtgraph as pg
|
9
|
-
from bec_lib import messages
|
10
|
-
from bec_lib.device import ReadoutPriority
|
11
|
-
from bec_lib.endpoints import MessageEndpoints
|
12
|
-
from bec_lib.logger import bec_logger
|
13
|
-
from pydantic import Field, ValidationError, field_validator
|
14
|
-
from pyqtgraph.exporters import MatplotlibExporter
|
15
|
-
from qtpy.QtCore import Signal as pyqtSignal
|
16
|
-
from qtpy.QtWidgets import QWidget
|
17
|
-
|
18
|
-
from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
|
19
|
-
from bec_widgets.utils import Colors, EntryValidator
|
20
|
-
from bec_widgets.utils.colors import get_accent_colors
|
21
|
-
from bec_widgets.utils.linear_region_selector import LinearRegionWrapper
|
22
|
-
from bec_widgets.widgets.containers.figure.plots.plot_base import BECPlotBase, SubplotConfig
|
23
|
-
from bec_widgets.widgets.containers.figure.plots.waveform.waveform_curve import (
|
24
|
-
BECCurve,
|
25
|
-
CurveConfig,
|
26
|
-
Signal,
|
27
|
-
SignalData,
|
28
|
-
)
|
29
|
-
|
30
|
-
logger = bec_logger.logger
|
31
|
-
|
32
|
-
|
33
|
-
class Waveform1DConfig(SubplotConfig):
|
34
|
-
color_palette: Optional[str] = Field(
|
35
|
-
"magma", description="The color palette of the figure widget.", validate_default=True
|
36
|
-
)
|
37
|
-
curves: dict[str, CurveConfig] = Field(
|
38
|
-
{}, description="The list of curves to be added to the 1D waveform widget."
|
39
|
-
)
|
40
|
-
|
41
|
-
model_config: dict = {"validate_assignment": True}
|
42
|
-
_validate_color_map_z = field_validator("color_palette")(Colors.validate_color_map)
|
43
|
-
|
44
|
-
|
45
|
-
class BECWaveform(BECPlotBase):
|
46
|
-
READOUT_PRIORITY_HANDLER = {
|
47
|
-
ReadoutPriority.ON_REQUEST: "on_request",
|
48
|
-
ReadoutPriority.BASELINE: "baseline",
|
49
|
-
ReadoutPriority.MONITORED: "monitored",
|
50
|
-
ReadoutPriority.ASYNC: "async",
|
51
|
-
ReadoutPriority.CONTINUOUS: "continuous",
|
52
|
-
}
|
53
|
-
USER_ACCESS = [
|
54
|
-
"_rpc_id",
|
55
|
-
"_config_dict",
|
56
|
-
"plot",
|
57
|
-
"add_dap",
|
58
|
-
"get_dap_params",
|
59
|
-
"set_x",
|
60
|
-
"remove_curve",
|
61
|
-
"scan_history",
|
62
|
-
"curves",
|
63
|
-
"get_curve",
|
64
|
-
"get_all_data",
|
65
|
-
"set",
|
66
|
-
"set_title",
|
67
|
-
"set_x_label",
|
68
|
-
"set_y_label",
|
69
|
-
"set_x_scale",
|
70
|
-
"set_y_scale",
|
71
|
-
"set_x_lim",
|
72
|
-
"set_y_lim",
|
73
|
-
"set_grid",
|
74
|
-
"set_colormap",
|
75
|
-
"enable_scatter",
|
76
|
-
"enable_fps_monitor",
|
77
|
-
"lock_aspect_ratio",
|
78
|
-
"export",
|
79
|
-
"remove",
|
80
|
-
"clear_all",
|
81
|
-
"set_legend_label_size",
|
82
|
-
"toggle_roi",
|
83
|
-
"select_roi",
|
84
|
-
]
|
85
|
-
scan_signal_update = pyqtSignal()
|
86
|
-
async_signal_update = pyqtSignal()
|
87
|
-
dap_params_update = pyqtSignal(dict, dict)
|
88
|
-
dap_summary_update = pyqtSignal(dict, dict)
|
89
|
-
autorange_signal = pyqtSignal()
|
90
|
-
new_scan = pyqtSignal()
|
91
|
-
roi_changed = pyqtSignal(tuple)
|
92
|
-
roi_active = pyqtSignal(bool)
|
93
|
-
request_dap_refresh = pyqtSignal()
|
94
|
-
|
95
|
-
def __init__(
|
96
|
-
self,
|
97
|
-
parent: Optional[QWidget] = None,
|
98
|
-
parent_figure=None,
|
99
|
-
config: Optional[Waveform1DConfig] = None,
|
100
|
-
client=None,
|
101
|
-
gui_id: Optional[str] = None,
|
102
|
-
**kwargs,
|
103
|
-
):
|
104
|
-
if config is None:
|
105
|
-
config = Waveform1DConfig(widget_class=self.__class__.__name__)
|
106
|
-
super().__init__(
|
107
|
-
parent=parent,
|
108
|
-
parent_figure=parent_figure,
|
109
|
-
config=config,
|
110
|
-
client=client,
|
111
|
-
gui_id=gui_id,
|
112
|
-
**kwargs,
|
113
|
-
)
|
114
|
-
|
115
|
-
self._curves_data = defaultdict(dict)
|
116
|
-
self.old_scan_id = None
|
117
|
-
self.scan_id = None
|
118
|
-
self.scan_item = None
|
119
|
-
self._roi_region = None
|
120
|
-
self.roi_select = None
|
121
|
-
self._accent_colors = get_accent_colors()
|
122
|
-
self._x_axis_mode = {
|
123
|
-
"name": None,
|
124
|
-
"entry": None,
|
125
|
-
"readout_priority": None,
|
126
|
-
"label_suffix": "",
|
127
|
-
}
|
128
|
-
|
129
|
-
self._slice_index = None
|
130
|
-
|
131
|
-
# Scan segment update proxy
|
132
|
-
self.proxy_update_plot = pg.SignalProxy(
|
133
|
-
self.scan_signal_update, rateLimit=25, slot=self._update_scan_curves
|
134
|
-
)
|
135
|
-
self.proxy_update_dap = pg.SignalProxy(
|
136
|
-
self.scan_signal_update, rateLimit=25, slot=self.refresh_dap
|
137
|
-
)
|
138
|
-
self.async_signal_update.connect(self.replot_async_curve)
|
139
|
-
self.autorange_signal.connect(self.auto_range)
|
140
|
-
|
141
|
-
# Get bec shortcuts dev, scans, queue, scan_storage, dap
|
142
|
-
self.get_bec_shortcuts()
|
143
|
-
|
144
|
-
# Connect dispatcher signals
|
145
|
-
self.bec_dispatcher.connect_slot(self.on_scan_segment, MessageEndpoints.scan_segment())
|
146
|
-
# TODO disabled -> scan_status is SET_AND_PUBLISH -> do not work in combination with autoupdate from CLI
|
147
|
-
# self.bec_dispatcher.connect_slot(self.on_scan_status, MessageEndpoints.scan_status())
|
148
|
-
|
149
|
-
self.entry_validator = EntryValidator(self.dev)
|
150
|
-
|
151
|
-
self.add_legend()
|
152
|
-
self.apply_config(self.config)
|
153
|
-
|
154
|
-
@Slot(bool)
|
155
|
-
def toggle_roi(self, toggled: bool) -> None:
|
156
|
-
"""Toggle the linear region selector on the plot.
|
157
|
-
|
158
|
-
Args:
|
159
|
-
toggled(bool): If True, enable the linear region selector.
|
160
|
-
"""
|
161
|
-
if toggled:
|
162
|
-
return self._hook_roi()
|
163
|
-
return self._unhook_roi()
|
164
|
-
|
165
|
-
@Slot(tuple)
|
166
|
-
def select_roi(self, region: tuple[float, float]):
|
167
|
-
"""Set the fit region of the plot widget. At the moment only a single region is supported.
|
168
|
-
To remove the roi region again, use toggle_roi_region
|
169
|
-
|
170
|
-
Args:
|
171
|
-
region(tuple[float, float]): The fit region.
|
172
|
-
"""
|
173
|
-
if self.roi_region == (None, None):
|
174
|
-
self.toggle_roi(True)
|
175
|
-
try:
|
176
|
-
self.roi_select.linear_region_selector.setRegion(region)
|
177
|
-
except Exception as e:
|
178
|
-
logger.error(f"Error setting region {tuple}; Exception raised: {e}")
|
179
|
-
raise ValueError(f"Error setting region {tuple}; Exception raised: {e}") from e
|
180
|
-
|
181
|
-
def _hook_roi(self):
|
182
|
-
"""Hook the linear region selector to the plot."""
|
183
|
-
color = self._accent_colors.default
|
184
|
-
color.setAlpha(int(0.2 * 255))
|
185
|
-
hover_color = self._accent_colors.default
|
186
|
-
hover_color.setAlpha(int(0.35 * 255))
|
187
|
-
if self.roi_select is None:
|
188
|
-
self.roi_select = LinearRegionWrapper(
|
189
|
-
self.plot_item, color=color, hover_color=hover_color, parent=self
|
190
|
-
)
|
191
|
-
self.roi_select.add_region_selector()
|
192
|
-
self.roi_select.region_changed.connect(self.roi_changed)
|
193
|
-
self.roi_select.region_changed.connect(self.set_roi_region)
|
194
|
-
self.request_dap_refresh.connect(self.refresh_dap)
|
195
|
-
self._emit_roi_region()
|
196
|
-
self.roi_active.emit(True)
|
197
|
-
|
198
|
-
def _unhook_roi(self):
|
199
|
-
"""Unhook the linear region selector from the plot."""
|
200
|
-
if self.roi_select is not None:
|
201
|
-
self.roi_select.region_changed.disconnect(self.roi_changed)
|
202
|
-
self.roi_select.region_changed.disconnect(self.set_roi_region)
|
203
|
-
self.request_dap_refresh.disconnect(self.refresh_dap)
|
204
|
-
self.roi_active.emit(False)
|
205
|
-
self.roi_region = None
|
206
|
-
self.refresh_dap()
|
207
|
-
self.roi_select.cleanup()
|
208
|
-
self.roi_select.deleteLater()
|
209
|
-
self.roi_select = None
|
210
|
-
|
211
|
-
def apply_config(self, config: dict | SubplotConfig, replot_last_scan: bool = False):
|
212
|
-
"""
|
213
|
-
Apply the configuration to the 1D waveform widget.
|
214
|
-
|
215
|
-
Args:
|
216
|
-
config(dict|SubplotConfig): Configuration settings.
|
217
|
-
replot_last_scan(bool, optional): If True, replot the last scan. Defaults to False.
|
218
|
-
"""
|
219
|
-
if isinstance(config, dict):
|
220
|
-
try:
|
221
|
-
config = Waveform1DConfig(**config)
|
222
|
-
except ValidationError as e:
|
223
|
-
logger.error(f"Validation error when applying config to BECWaveform1D: {e}")
|
224
|
-
return
|
225
|
-
|
226
|
-
self.config = config
|
227
|
-
self.plot_item.clear() # TODO not sure if on the plot or layout level
|
228
|
-
|
229
|
-
self.apply_axis_config()
|
230
|
-
# Reset curves
|
231
|
-
self._curves_data = defaultdict(dict)
|
232
|
-
self._curves = self.plot_item.curves
|
233
|
-
for curve_config in self.config.curves.values():
|
234
|
-
self.add_curve_by_config(curve_config)
|
235
|
-
if replot_last_scan:
|
236
|
-
self.scan_history(scan_index=-1)
|
237
|
-
|
238
|
-
def change_gui_id(self, new_gui_id: str):
|
239
|
-
"""
|
240
|
-
Change the GUI ID of the waveform widget and update the parent_id in all associated curves.
|
241
|
-
|
242
|
-
Args:
|
243
|
-
new_gui_id (str): The new GUI ID to be set for the waveform widget.
|
244
|
-
"""
|
245
|
-
# Update the gui_id in the waveform widget itself
|
246
|
-
self.gui_id = new_gui_id
|
247
|
-
self.config.gui_id = new_gui_id
|
248
|
-
|
249
|
-
for curve in self.curves:
|
250
|
-
curve.config.parent_id = new_gui_id
|
251
|
-
|
252
|
-
###################################
|
253
|
-
# Fit Range Properties
|
254
|
-
###################################
|
255
|
-
|
256
|
-
@property
|
257
|
-
def roi_region(self) -> tuple[float, float] | None:
|
258
|
-
"""
|
259
|
-
Get the fit region of the plot widget.
|
260
|
-
|
261
|
-
Returns:
|
262
|
-
tuple: The fit region.
|
263
|
-
"""
|
264
|
-
if self._roi_region is not None:
|
265
|
-
return self._roi_region
|
266
|
-
return None, None
|
267
|
-
|
268
|
-
@roi_region.setter
|
269
|
-
def roi_region(self, value: tuple[float, float] | None):
|
270
|
-
"""Set the fit region of the plot widget.
|
271
|
-
|
272
|
-
Args:
|
273
|
-
value(tuple[float, float]|None): The fit region.
|
274
|
-
"""
|
275
|
-
self._roi_region = value
|
276
|
-
if value is not None:
|
277
|
-
self.request_dap_refresh.emit()
|
278
|
-
|
279
|
-
@Slot(tuple)
|
280
|
-
def set_roi_region(self, region: tuple[float, float]):
|
281
|
-
"""
|
282
|
-
Set the fit region of the plot widget.
|
283
|
-
|
284
|
-
Args:
|
285
|
-
region(tuple[float, float]): The fit region.
|
286
|
-
"""
|
287
|
-
self.roi_region = region
|
288
|
-
|
289
|
-
def _emit_roi_region(self):
|
290
|
-
"""Emit the current ROI from selector the plot widget."""
|
291
|
-
if self.roi_select is not None:
|
292
|
-
self.set_roi_region(self.roi_select.linear_region_selector.getRegion())
|
293
|
-
|
294
|
-
###################################
|
295
|
-
# Waveform Properties
|
296
|
-
###################################
|
297
|
-
|
298
|
-
@property
|
299
|
-
def curves(self) -> list[BECCurve]:
|
300
|
-
"""
|
301
|
-
Get the curves of the plot widget as a list
|
302
|
-
Returns:
|
303
|
-
list: List of curves.
|
304
|
-
"""
|
305
|
-
return self._curves
|
306
|
-
|
307
|
-
@curves.setter
|
308
|
-
def curves(self, value: list[BECCurve]):
|
309
|
-
self._curves = value
|
310
|
-
|
311
|
-
@property
|
312
|
-
def x_axis_mode(self) -> dict:
|
313
|
-
"""
|
314
|
-
Get the x axis mode of the plot widget.
|
315
|
-
|
316
|
-
Returns:
|
317
|
-
dict: The x axis mode.
|
318
|
-
"""
|
319
|
-
return self._x_axis_mode
|
320
|
-
|
321
|
-
@x_axis_mode.setter
|
322
|
-
def x_axis_mode(self, value: dict):
|
323
|
-
self._x_axis_mode = value
|
324
|
-
|
325
|
-
###################################
|
326
|
-
# Adding and Removing Curves
|
327
|
-
###################################
|
328
|
-
|
329
|
-
def add_curve_by_config(self, curve_config: CurveConfig | dict) -> BECCurve:
|
330
|
-
"""
|
331
|
-
Add a curve to the plot widget by its configuration.
|
332
|
-
|
333
|
-
Args:
|
334
|
-
curve_config(CurveConfig|dict): Configuration of the curve to be added.
|
335
|
-
|
336
|
-
Returns:
|
337
|
-
BECCurve: The curve object.
|
338
|
-
"""
|
339
|
-
if isinstance(curve_config, dict):
|
340
|
-
curve_config = CurveConfig(**curve_config)
|
341
|
-
curve = self._add_curve_object(
|
342
|
-
name=curve_config.label, source=curve_config.source, config=curve_config
|
343
|
-
)
|
344
|
-
return curve
|
345
|
-
|
346
|
-
def get_curve_config(self, curve_id: str, dict_output: bool = True) -> CurveConfig | dict:
|
347
|
-
"""
|
348
|
-
Get the configuration of a curve by its ID.
|
349
|
-
|
350
|
-
Args:
|
351
|
-
curve_id(str): ID of the curve.
|
352
|
-
|
353
|
-
Returns:
|
354
|
-
CurveConfig|dict: Configuration of the curve.
|
355
|
-
"""
|
356
|
-
for curves in self._curves_data.values():
|
357
|
-
if curve_id in curves:
|
358
|
-
if dict_output:
|
359
|
-
return curves[curve_id].config.model_dump()
|
360
|
-
else:
|
361
|
-
return curves[curve_id].config
|
362
|
-
|
363
|
-
def get_curve(self, identifier) -> BECCurve:
|
364
|
-
"""
|
365
|
-
Get the curve by its index or ID.
|
366
|
-
|
367
|
-
Args:
|
368
|
-
identifier(int|str): Identifier of the curve. Can be either an integer (index) or a string (curve_id).
|
369
|
-
|
370
|
-
Returns:
|
371
|
-
BECCurve: The curve object.
|
372
|
-
"""
|
373
|
-
if isinstance(identifier, int):
|
374
|
-
return self.plot_item.curves[identifier]
|
375
|
-
elif isinstance(identifier, str):
|
376
|
-
for curves in self._curves_data.values():
|
377
|
-
if identifier in curves:
|
378
|
-
return curves[identifier]
|
379
|
-
raise ValueError(f"Curve with ID '{identifier}' not found.")
|
380
|
-
else:
|
381
|
-
raise ValueError("Identifier must be either an integer (index) or a string (curve_id).")
|
382
|
-
|
383
|
-
def enable_scatter(self, enable: bool):
|
384
|
-
"""
|
385
|
-
Enable/Disable scatter plot on all curves.
|
386
|
-
|
387
|
-
Args:
|
388
|
-
enable(bool): If True, enable scatter markers; if False, disable them.
|
389
|
-
"""
|
390
|
-
for curve in self.curves:
|
391
|
-
if isinstance(curve, BECCurve):
|
392
|
-
if enable:
|
393
|
-
curve.set_symbol("o") # You can choose any symbol you like
|
394
|
-
else:
|
395
|
-
curve.set_symbol(None)
|
396
|
-
|
397
|
-
def plot(
|
398
|
-
self,
|
399
|
-
arg1: list | np.ndarray | str | None = None,
|
400
|
-
y: list | np.ndarray | None = None,
|
401
|
-
x: list | np.ndarray | None = None,
|
402
|
-
x_name: str | None = None,
|
403
|
-
y_name: str | None = None,
|
404
|
-
z_name: str | None = None,
|
405
|
-
x_entry: str | None = None,
|
406
|
-
y_entry: str | None = None,
|
407
|
-
z_entry: str | None = None,
|
408
|
-
color: str | None = None,
|
409
|
-
color_map_z: str | None = "magma",
|
410
|
-
label: str | None = None,
|
411
|
-
validate: bool = True,
|
412
|
-
dap: str | None = None, # TODO add dap custom curve wrapper
|
413
|
-
**kwargs,
|
414
|
-
) -> BECCurve:
|
415
|
-
"""
|
416
|
-
Plot a curve to the plot widget.
|
417
|
-
|
418
|
-
Args:
|
419
|
-
arg1(list | np.ndarray | str | None): First argument which can be x data, y data, or y_name.
|
420
|
-
y(list | np.ndarray): Custom y data to plot.
|
421
|
-
x(list | np.ndarray): Custom y data to plot.
|
422
|
-
x_name(str): Name of the x signal.
|
423
|
-
- "best_effort": Use the best effort signal.
|
424
|
-
- "timestamp": Use the timestamp signal.
|
425
|
-
- "index": Use the index signal.
|
426
|
-
- Custom signal name of device from BEC.
|
427
|
-
y_name(str): The name of the device for the y-axis.
|
428
|
-
z_name(str): The name of the device for the z-axis.
|
429
|
-
x_entry(str): The name of the entry for the x-axis.
|
430
|
-
y_entry(str): The name of the entry for the y-axis.
|
431
|
-
z_entry(str): The name of the entry for the z-axis.
|
432
|
-
color(str): The color of the curve.
|
433
|
-
color_map_z(str): The color map to use for the z-axis.
|
434
|
-
label(str): The label of the curve.
|
435
|
-
validate(bool): If True, validate the device names and entries.
|
436
|
-
dap(str): The dap model to use for the curve, only available for sync devices. If not specified, none will be added.
|
437
|
-
|
438
|
-
Returns:
|
439
|
-
BECCurve: The curve object.
|
440
|
-
"""
|
441
|
-
if x is not None and y is not None:
|
442
|
-
return self.add_curve_custom(x=x, y=y, label=label, color=color, **kwargs)
|
443
|
-
|
444
|
-
if isinstance(arg1, str):
|
445
|
-
y_name = arg1
|
446
|
-
elif isinstance(arg1, list):
|
447
|
-
if isinstance(y, list):
|
448
|
-
return self.add_curve_custom(x=arg1, y=y, label=label, color=color, **kwargs)
|
449
|
-
if y is None:
|
450
|
-
x = np.arange(len(arg1))
|
451
|
-
return self.add_curve_custom(x=x, y=arg1, label=label, color=color, **kwargs)
|
452
|
-
elif isinstance(arg1, np.ndarray) and y is None:
|
453
|
-
if arg1.ndim == 1:
|
454
|
-
x = np.arange(arg1.size)
|
455
|
-
return self.add_curve_custom(x=x, y=arg1, label=label, color=color, **kwargs)
|
456
|
-
if arg1.ndim == 2:
|
457
|
-
x = arg1[:, 0]
|
458
|
-
y = arg1[:, 1]
|
459
|
-
return self.add_curve_custom(x=x, y=y, label=label, color=color, **kwargs)
|
460
|
-
if y_name is None:
|
461
|
-
raise ValueError("y_name must be provided.")
|
462
|
-
if dap:
|
463
|
-
self.add_dap(x_name=x_name, y_name=y_name, dap=dap)
|
464
|
-
curve = self.add_curve_bec(
|
465
|
-
x_name=x_name,
|
466
|
-
y_name=y_name,
|
467
|
-
z_name=z_name,
|
468
|
-
x_entry=x_entry,
|
469
|
-
y_entry=y_entry,
|
470
|
-
z_entry=z_entry,
|
471
|
-
color=color,
|
472
|
-
color_map_z=color_map_z,
|
473
|
-
label=label,
|
474
|
-
validate_bec=validate,
|
475
|
-
**kwargs,
|
476
|
-
)
|
477
|
-
self.scan_signal_update.emit()
|
478
|
-
self.async_signal_update.emit()
|
479
|
-
|
480
|
-
return curve
|
481
|
-
|
482
|
-
def set_x(self, x_name: str, x_entry: str | None = None):
|
483
|
-
"""
|
484
|
-
Change the x axis of the plot widget.
|
485
|
-
|
486
|
-
Args:
|
487
|
-
x_name(str): Name of the x signal.
|
488
|
-
- "best_effort": Use the best effort signal.
|
489
|
-
- "timestamp": Use the timestamp signal.
|
490
|
-
- "index": Use the index signal.
|
491
|
-
- Custom signal name of device from BEC.
|
492
|
-
x_entry(str): Entry of the x signal.
|
493
|
-
"""
|
494
|
-
if not x_name:
|
495
|
-
# this can happen, if executed by a signal from a widget
|
496
|
-
return
|
497
|
-
|
498
|
-
curve_configs = self.config.curves
|
499
|
-
curve_ids = list(curve_configs.keys())
|
500
|
-
curve_configs = list(curve_configs.values())
|
501
|
-
self.set_auto_range(True, "xy")
|
502
|
-
|
503
|
-
x_entry, _, _ = self._validate_signal_entries(
|
504
|
-
x_name, None, None, x_entry, None, None, validate_bec=True
|
505
|
-
)
|
506
|
-
|
507
|
-
readout_priority_x = None
|
508
|
-
if x_name not in ["best_effort", "timestamp", "index"]:
|
509
|
-
readout_priority_x = self._get_device_readout_priority(x_name)
|
510
|
-
|
511
|
-
self.x_axis_mode = {
|
512
|
-
"name": x_name,
|
513
|
-
"entry": x_entry,
|
514
|
-
"readout_priority": readout_priority_x,
|
515
|
-
}
|
516
|
-
|
517
|
-
if len(self.curves) > 0:
|
518
|
-
# validate all curves
|
519
|
-
for curve in self.curves:
|
520
|
-
if not isinstance(curve, BECCurve):
|
521
|
-
continue
|
522
|
-
if curve.config.source == "custom":
|
523
|
-
continue
|
524
|
-
self._validate_x_axis_behaviour(curve.config.signals.y.name, x_name, x_entry, False)
|
525
|
-
self._switch_x_axis_item(
|
526
|
-
f"{x_name}-{x_entry}"
|
527
|
-
if x_name not in ["best_effort", "timestamp", "index"]
|
528
|
-
else x_name
|
529
|
-
)
|
530
|
-
for curve_id, curve_config in zip(curve_ids, curve_configs):
|
531
|
-
if curve_config.signals is None:
|
532
|
-
continue
|
533
|
-
if curve_config.signals.x is None:
|
534
|
-
continue
|
535
|
-
curve_config.signals.x.name = x_name
|
536
|
-
curve_config.signals.x.entry = x_entry
|
537
|
-
self.remove_curve(curve_id)
|
538
|
-
self.add_curve_by_config(curve_config)
|
539
|
-
|
540
|
-
self.async_signal_update.emit()
|
541
|
-
self.scan_signal_update.emit()
|
542
|
-
|
543
|
-
@Slot()
|
544
|
-
def auto_range(self):
|
545
|
-
"""Manually set auto range of the plotitem"""
|
546
|
-
self.plot_item.autoRange()
|
547
|
-
|
548
|
-
def set_auto_range(self, enabled: bool, axis: str = "xy"):
|
549
|
-
"""
|
550
|
-
Set the auto range of the plot widget.
|
551
|
-
|
552
|
-
Args:
|
553
|
-
enabled(bool): If True, enable the auto range.
|
554
|
-
axis(str, optional): The axis to enable the auto range.
|
555
|
-
- "xy": Enable auto range for both x and y axis.
|
556
|
-
- "x": Enable auto range for x axis.
|
557
|
-
- "y": Enable auto range for y axis.
|
558
|
-
"""
|
559
|
-
self.plot_item.enableAutoRange(axis, enabled)
|
560
|
-
|
561
|
-
def add_curve_custom(
|
562
|
-
self,
|
563
|
-
x: list | np.ndarray,
|
564
|
-
y: list | np.ndarray,
|
565
|
-
label: str = None,
|
566
|
-
color: str = None,
|
567
|
-
curve_source: str = "custom",
|
568
|
-
**kwargs,
|
569
|
-
) -> BECCurve:
|
570
|
-
"""
|
571
|
-
Add a custom data curve to the plot widget.
|
572
|
-
|
573
|
-
Args:
|
574
|
-
x(list|np.ndarray): X data of the curve.
|
575
|
-
y(list|np.ndarray): Y data of the curve.
|
576
|
-
label(str, optional): Label of the curve. Defaults to None.
|
577
|
-
color(str, optional): Color of the curve. Defaults to None.
|
578
|
-
curve_source(str, optional): Tag for source of the curve. Defaults to "custom".
|
579
|
-
**kwargs: Additional keyword arguments for the curve configuration.
|
580
|
-
|
581
|
-
Returns:
|
582
|
-
BECCurve: The curve object.
|
583
|
-
"""
|
584
|
-
curve_id = label or f"Curve {len(self.plot_item.curves) + 1}"
|
585
|
-
|
586
|
-
curve_exits = self._check_curve_id(curve_id, self._curves_data)
|
587
|
-
if curve_exits:
|
588
|
-
raise ValueError(
|
589
|
-
f"Curve with ID '{curve_id}' already exists in widget '{self.gui_id}'."
|
590
|
-
)
|
591
|
-
|
592
|
-
color = (
|
593
|
-
color
|
594
|
-
or Colors.golden_angle_color(
|
595
|
-
colormap=self.config.color_palette,
|
596
|
-
num=max(10, len(self.plot_item.curves) + 1),
|
597
|
-
format="HEX",
|
598
|
-
)[len(self.plot_item.curves)]
|
599
|
-
)
|
600
|
-
|
601
|
-
# Create curve by config
|
602
|
-
curve_config = CurveConfig(
|
603
|
-
widget_class="BECCurve",
|
604
|
-
parent_id=self.gui_id,
|
605
|
-
label=curve_id,
|
606
|
-
color=color,
|
607
|
-
source=curve_source,
|
608
|
-
**kwargs,
|
609
|
-
)
|
610
|
-
|
611
|
-
curve = self._add_curve_object(
|
612
|
-
name=curve_id, source=curve_source, config=curve_config, data=(x, y)
|
613
|
-
)
|
614
|
-
return curve
|
615
|
-
|
616
|
-
def add_curve_bec(
|
617
|
-
self,
|
618
|
-
x_name: str | None = None,
|
619
|
-
y_name: str | None = None,
|
620
|
-
z_name: str | None = None,
|
621
|
-
x_entry: str | None = None,
|
622
|
-
y_entry: str | None = None,
|
623
|
-
z_entry: str | None = None,
|
624
|
-
color: str | None = None,
|
625
|
-
color_map_z: str | None = "magma",
|
626
|
-
label: str | None = None,
|
627
|
-
validate_bec: bool = True,
|
628
|
-
dap: str | None = None,
|
629
|
-
source: str | None = None,
|
630
|
-
**kwargs,
|
631
|
-
) -> BECCurve:
|
632
|
-
"""
|
633
|
-
Add a curve to the plot widget from the scan segment. #TODO adapt docs to DAP
|
634
|
-
|
635
|
-
Args:
|
636
|
-
x_name(str): Name of the x signal.
|
637
|
-
x_entry(str): Entry of the x signal.
|
638
|
-
y_name(str): Name of the y signal.
|
639
|
-
y_entry(str): Entry of the y signal.
|
640
|
-
z_name(str): Name of the z signal.
|
641
|
-
z_entry(str): Entry of the z signal.
|
642
|
-
color(str, optional): Color of the curve. Defaults to None.
|
643
|
-
color_map_z(str): The color map to use for the z-axis.
|
644
|
-
label(str, optional): Label of the curve. Defaults to None.
|
645
|
-
validate_bec(bool, optional): If True, validate the signal with BEC. Defaults to True.
|
646
|
-
dap(str, optional): The dap model to use for the curve. Defaults to None.
|
647
|
-
**kwargs: Additional keyword arguments for the curve configuration.
|
648
|
-
|
649
|
-
Returns:
|
650
|
-
BECCurve: The curve object.
|
651
|
-
"""
|
652
|
-
# 1. Check - y_name must be provided
|
653
|
-
if y_name is None:
|
654
|
-
raise ValueError("y_name must be provided.")
|
655
|
-
|
656
|
-
# 2. Check - check if there is already a x axis signal
|
657
|
-
if x_name is None:
|
658
|
-
mode = self.x_axis_mode["name"]
|
659
|
-
x_name = mode if mode is not None else "best_effort"
|
660
|
-
self.x_axis_mode["name"] = x_name
|
661
|
-
|
662
|
-
if not x_name or not y_name:
|
663
|
-
# can happen if executed from a signal from a widget ;
|
664
|
-
# the code above has to be executed to set some other
|
665
|
-
# variables, but it cannot continue if both names are
|
666
|
-
# not set properly -> exit here
|
667
|
-
return
|
668
|
-
|
669
|
-
# 3. Check - Get entry if not provided and validate
|
670
|
-
x_entry, y_entry, z_entry = self._validate_signal_entries(
|
671
|
-
x_name, y_name, z_name, x_entry, y_entry, z_entry, validate_bec
|
672
|
-
)
|
673
|
-
|
674
|
-
# 4. Check - get source of the device
|
675
|
-
if source is None:
|
676
|
-
if validate_bec is True:
|
677
|
-
source = self._validate_device_source_compatibity(y_name)
|
678
|
-
else:
|
679
|
-
source = "scan_segment"
|
680
|
-
|
681
|
-
if z_name is not None and z_entry is not None:
|
682
|
-
label = label or f"{z_name}-{z_entry}"
|
683
|
-
else:
|
684
|
-
label = label or f"{y_name}-{y_entry}"
|
685
|
-
|
686
|
-
# 5. Check - Check if curve already exists
|
687
|
-
curve_exits = self._check_curve_id(label, self._curves_data)
|
688
|
-
if curve_exits:
|
689
|
-
raise ValueError(f"Curve with ID '{label}' already exists in widget '{self.gui_id}'.")
|
690
|
-
|
691
|
-
# Validate or define x axis behaviour and compatibility with y_name readoutPriority
|
692
|
-
if validate_bec is True:
|
693
|
-
self._validate_x_axis_behaviour(y_name, x_name, x_entry)
|
694
|
-
|
695
|
-
# Create color if not specified
|
696
|
-
color = (
|
697
|
-
color
|
698
|
-
or Colors.golden_angle_color(
|
699
|
-
colormap=self.config.color_palette,
|
700
|
-
num=max(10, len(self.plot_item.curves) + 1),
|
701
|
-
format="HEX",
|
702
|
-
)[len(self.plot_item.curves)]
|
703
|
-
)
|
704
|
-
logger.info(f"Color: {color}")
|
705
|
-
|
706
|
-
# Create curve by config
|
707
|
-
curve_config = CurveConfig(
|
708
|
-
widget_class="BECCurve",
|
709
|
-
parent_id=self.gui_id,
|
710
|
-
label=label,
|
711
|
-
color=color,
|
712
|
-
color_map_z=color_map_z,
|
713
|
-
source=source,
|
714
|
-
signals=Signal(
|
715
|
-
source=source,
|
716
|
-
x=SignalData(name=x_name, entry=x_entry) if x_name else None,
|
717
|
-
y=SignalData(name=y_name, entry=y_entry),
|
718
|
-
z=SignalData(name=z_name, entry=z_entry) if z_name else None,
|
719
|
-
dap=dap,
|
720
|
-
),
|
721
|
-
**kwargs,
|
722
|
-
)
|
723
|
-
|
724
|
-
curve = self._add_curve_object(name=label, source=source, config=curve_config)
|
725
|
-
return curve
|
726
|
-
|
727
|
-
def add_dap(
|
728
|
-
self,
|
729
|
-
x_name: str | None = None,
|
730
|
-
y_name: str | None = None,
|
731
|
-
x_entry: Optional[str] = None,
|
732
|
-
y_entry: Optional[str] = None,
|
733
|
-
color: Optional[str] = None,
|
734
|
-
dap: str = "GaussianModel",
|
735
|
-
validate_bec: bool = True,
|
736
|
-
**kwargs,
|
737
|
-
) -> BECCurve:
|
738
|
-
"""
|
739
|
-
Add LMFIT dap model curve to the plot widget.
|
740
|
-
|
741
|
-
Args:
|
742
|
-
x_name(str): Name of the x signal.
|
743
|
-
x_entry(str): Entry of the x signal.
|
744
|
-
y_name(str): Name of the y signal.
|
745
|
-
y_entry(str): Entry of the y signal.
|
746
|
-
color(str, optional): Color of the curve. Defaults to None.
|
747
|
-
dap(str): The dap model to use for the curve.
|
748
|
-
validate_bec(bool, optional): If True, validate the signal with BEC. Defaults to True.
|
749
|
-
**kwargs: Additional keyword arguments for the curve configuration.
|
750
|
-
|
751
|
-
Returns:
|
752
|
-
BECCurve: The curve object.
|
753
|
-
"""
|
754
|
-
if x_name is None:
|
755
|
-
x_name = self.x_axis_mode["name"]
|
756
|
-
x_entry = self.x_axis_mode["entry"]
|
757
|
-
if x_name == "timestamp" or x_name == "index":
|
758
|
-
raise ValueError(
|
759
|
-
f"Cannot use x axis '{x_name}' for DAP curve. Please provide a custom x axis signal or switch to 'best_effort' signal mode."
|
760
|
-
)
|
761
|
-
|
762
|
-
if self.x_axis_mode["readout_priority"] == "async":
|
763
|
-
raise ValueError(
|
764
|
-
"Async signals cannot be fitted at the moment. Please switch to 'monitored' or 'baseline' signals."
|
765
|
-
)
|
766
|
-
|
767
|
-
if validate_bec is True:
|
768
|
-
x_entry, y_entry, _ = self._validate_signal_entries(
|
769
|
-
x_name, y_name, None, x_entry, y_entry, None
|
770
|
-
)
|
771
|
-
label = f"{y_name}-{y_entry}-{dap}"
|
772
|
-
curve = self.add_curve_bec(
|
773
|
-
x_name=x_name,
|
774
|
-
y_name=y_name,
|
775
|
-
x_entry=x_entry,
|
776
|
-
y_entry=y_entry,
|
777
|
-
color=color,
|
778
|
-
label=label,
|
779
|
-
source="DAP",
|
780
|
-
dap=dap,
|
781
|
-
symbol="star",
|
782
|
-
**kwargs,
|
783
|
-
)
|
784
|
-
|
785
|
-
self.setup_dap(self.old_scan_id, self.scan_id)
|
786
|
-
self.refresh_dap()
|
787
|
-
return curve
|
788
|
-
|
789
|
-
@Slot()
|
790
|
-
def get_dap_params(self) -> dict:
|
791
|
-
"""
|
792
|
-
Get the DAP parameters of all DAP curves.
|
793
|
-
|
794
|
-
Returns:
|
795
|
-
dict: DAP parameters of all DAP curves.
|
796
|
-
"""
|
797
|
-
params = {}
|
798
|
-
for curve_id, curve in self._curves_data["DAP"].items():
|
799
|
-
params[curve_id] = curve.dap_params
|
800
|
-
return params
|
801
|
-
|
802
|
-
@Slot()
|
803
|
-
def get_dap_summary(self) -> dict:
|
804
|
-
"""
|
805
|
-
Get the DAP summary of all DAP curves.
|
806
|
-
|
807
|
-
Returns:
|
808
|
-
dict: DAP summary of all DAP curves.
|
809
|
-
"""
|
810
|
-
summary = {}
|
811
|
-
for curve_id, curve in self._curves_data["DAP"].items():
|
812
|
-
summary[curve_id] = curve.dap_summary
|
813
|
-
return summary
|
814
|
-
|
815
|
-
def _add_curve_object(
|
816
|
-
self,
|
817
|
-
name: str,
|
818
|
-
source: str,
|
819
|
-
config: CurveConfig,
|
820
|
-
data: tuple[list | np.ndarray, list | np.ndarray] = None,
|
821
|
-
) -> BECCurve:
|
822
|
-
"""
|
823
|
-
Add a curve object to the plot widget.
|
824
|
-
|
825
|
-
Args:
|
826
|
-
name(str): ID of the curve.
|
827
|
-
source(str): Source of the curve.
|
828
|
-
config(CurveConfig): Configuration of the curve.
|
829
|
-
data(tuple[list|np.ndarray,list|np.ndarray], optional): Data (x,y) to be plotted. Defaults to None.
|
830
|
-
|
831
|
-
Returns:
|
832
|
-
BECCurve: The curve object.
|
833
|
-
"""
|
834
|
-
curve = BECCurve(config=config, name=name, parent_item=self)
|
835
|
-
self._curves_data[source][name] = curve
|
836
|
-
self.plot_item.addItem(curve)
|
837
|
-
self.config.curves[name] = curve.config
|
838
|
-
if data is not None:
|
839
|
-
curve.setData(data[0], data[1])
|
840
|
-
self.set_legend_label_size()
|
841
|
-
return curve
|
842
|
-
|
843
|
-
def _validate_device_source_compatibity(self, name: str):
|
844
|
-
readout_priority_y = self._get_device_readout_priority(name)
|
845
|
-
if readout_priority_y == "monitored" or readout_priority_y == "baseline":
|
846
|
-
source = "scan_segment"
|
847
|
-
elif readout_priority_y == "async":
|
848
|
-
source = "async"
|
849
|
-
else:
|
850
|
-
raise ValueError(
|
851
|
-
f"Readout priority '{readout_priority_y}' of device '{name}' is not supported for y signal."
|
852
|
-
)
|
853
|
-
return source
|
854
|
-
|
855
|
-
def _validate_x_axis_behaviour(
|
856
|
-
self, y_name: str, x_name: str | None = None, x_entry: str | None = None, auto_switch=True
|
857
|
-
) -> None:
|
858
|
-
"""
|
859
|
-
Validate the x axis behaviour and consistency for the plot item.
|
860
|
-
|
861
|
-
Args:
|
862
|
-
source(str): Source of updating device. Can be either "scan_segment" or "async".
|
863
|
-
x_name(str): Name of the x signal.
|
864
|
-
- "best_effort": Use the best effort signal.
|
865
|
-
- "timestamp": Use the timestamp signal.
|
866
|
-
- "index": Use the index signal.
|
867
|
-
- Custom signal name of device from BEC.
|
868
|
-
x_entry(str): Entry of the x signal.
|
869
|
-
"""
|
870
|
-
|
871
|
-
readout_priority_y = self._get_device_readout_priority(y_name)
|
872
|
-
|
873
|
-
# Check if the x axis behaviour is already set
|
874
|
-
if self._x_axis_mode["name"] is not None:
|
875
|
-
# Case 1: The same x axis signal is used, check if source is compatible with the device
|
876
|
-
if x_name != self._x_axis_mode["name"] and x_entry != self._x_axis_mode["entry"]:
|
877
|
-
# A different x axis signal is used, raise an exception
|
878
|
-
raise ValueError(
|
879
|
-
f"All curves must have the same x axis.\n"
|
880
|
-
f" Current valid x axis: '{self._x_axis_mode['name']}'\n"
|
881
|
-
f" Attempted to add curve with x axis: '{x_name}'\n"
|
882
|
-
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})'."
|
883
|
-
)
|
884
|
-
|
885
|
-
# If x_axis_mode["name"] is None, determine the mode based on x_name
|
886
|
-
# With async the best effort is always "index"
|
887
|
-
# Setting mode to either "best_effort", "timestamp", "index", or a custom one
|
888
|
-
if x_name is None and readout_priority_y == "async":
|
889
|
-
x_name = "index"
|
890
|
-
x_entry = "index"
|
891
|
-
if x_name in ["best_effort", "timestamp", "index"]:
|
892
|
-
self._x_axis_mode["name"] = x_name
|
893
|
-
self._x_axis_mode["entry"] = x_entry
|
894
|
-
else:
|
895
|
-
self._x_axis_mode["name"] = x_name
|
896
|
-
self._x_axis_mode["entry"] = x_entry
|
897
|
-
if readout_priority_y == "async":
|
898
|
-
raise ValueError(
|
899
|
-
f"Async devices '{y_name}' cannot be used with custom x signal '{x_name}-{x_entry}'.\n"
|
900
|
-
f"Please use mode 'best_effort', 'timestamp', or 'index' signal for x axis."
|
901
|
-
f"You can change the x axis mode with '.set_x(mode)'"
|
902
|
-
)
|
903
|
-
|
904
|
-
if auto_switch is True:
|
905
|
-
# Switch the x axis mode accordingly
|
906
|
-
self._switch_x_axis_item(
|
907
|
-
f"{x_name}-{x_entry}"
|
908
|
-
if x_name not in ["best_effort", "timestamp", "index"]
|
909
|
-
else x_name
|
910
|
-
)
|
911
|
-
|
912
|
-
def _get_device_readout_priority(self, name: str):
|
913
|
-
"""
|
914
|
-
Get the type of device from the entry_validator.
|
915
|
-
|
916
|
-
Args:
|
917
|
-
name(str): Name of the device.
|
918
|
-
entry(str): Entry of the device.
|
919
|
-
|
920
|
-
Returns:
|
921
|
-
str: Type of the device.
|
922
|
-
"""
|
923
|
-
return self.READOUT_PRIORITY_HANDLER[self.dev[name].readout_priority]
|
924
|
-
|
925
|
-
def _switch_x_axis_item(self, mode: str):
|
926
|
-
"""
|
927
|
-
Switch the x-axis mode between timestamp, index, the best effort and custom signal.
|
928
|
-
|
929
|
-
Args:
|
930
|
-
mode(str): Mode of the x-axis.
|
931
|
-
- "timestamp": Use the timestamp signal.
|
932
|
-
- "index": Use the index signal.
|
933
|
-
- "best_effort": Use the best effort signal.
|
934
|
-
- Custom signal name of device from BEC.
|
935
|
-
"""
|
936
|
-
current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
|
937
|
-
date_axis = pg.graphicsItems.DateAxisItem.DateAxisItem(orientation="bottom")
|
938
|
-
default_axis = pg.AxisItem(orientation="bottom")
|
939
|
-
self._x_axis_mode["label_suffix"] = f" [{mode}]"
|
940
|
-
|
941
|
-
if mode == "timestamp":
|
942
|
-
self.plot_item.setAxisItems({"bottom": date_axis})
|
943
|
-
self.plot_item.setLabel("bottom", f"{current_label}{self._x_axis_mode['label_suffix']}")
|
944
|
-
elif mode == "index":
|
945
|
-
self.plot_item.setAxisItems({"bottom": default_axis})
|
946
|
-
self.plot_item.setLabel("bottom", f"{current_label}{self._x_axis_mode['label_suffix']}")
|
947
|
-
else:
|
948
|
-
self.plot_item.setAxisItems({"bottom": default_axis})
|
949
|
-
self.plot_item.setLabel("bottom", f"{current_label}{self._x_axis_mode['label_suffix']}")
|
950
|
-
|
951
|
-
def _validate_signal_entries(
|
952
|
-
self,
|
953
|
-
x_name: str | None,
|
954
|
-
y_name: str | None,
|
955
|
-
z_name: str | None,
|
956
|
-
x_entry: str | None,
|
957
|
-
y_entry: str | None,
|
958
|
-
z_entry: str | None,
|
959
|
-
validate_bec: bool = True,
|
960
|
-
) -> tuple[str, str, str | None]:
|
961
|
-
"""
|
962
|
-
Validate the signal name and entry.
|
963
|
-
|
964
|
-
Args:
|
965
|
-
x_name(str): Name of the x signal.
|
966
|
-
y_name(str): Name of the y signal.
|
967
|
-
z_name(str): Name of the z signal.
|
968
|
-
x_entry(str|None): Entry of the x signal.
|
969
|
-
y_entry(str|None): Entry of the y signal.
|
970
|
-
z_entry(str|None): Entry of the z signal.
|
971
|
-
validate_bec(bool, optional): If True, validate the signal with BEC. Defaults to True.
|
972
|
-
|
973
|
-
Returns:
|
974
|
-
tuple[str,str,str|None]: Validated x, y, z entries.
|
975
|
-
"""
|
976
|
-
if validate_bec:
|
977
|
-
if x_name is None:
|
978
|
-
x_name = "best_effort"
|
979
|
-
x_entry = "best_effort"
|
980
|
-
if x_name:
|
981
|
-
if x_name == "index" or x_name == "timestamp" or x_name == "best_effort":
|
982
|
-
x_entry = x_name
|
983
|
-
else:
|
984
|
-
x_entry = self.entry_validator.validate_signal(x_name, x_entry)
|
985
|
-
if y_name:
|
986
|
-
y_entry = self.entry_validator.validate_signal(y_name, y_entry)
|
987
|
-
if z_name:
|
988
|
-
z_entry = self.entry_validator.validate_signal(z_name, z_entry)
|
989
|
-
else:
|
990
|
-
x_entry = x_name if x_entry is None else x_entry
|
991
|
-
y_entry = y_name if y_entry is None else y_entry
|
992
|
-
z_entry = z_name if z_entry is None else z_entry
|
993
|
-
return x_entry, y_entry, z_entry
|
994
|
-
|
995
|
-
def _check_curve_id(self, val: Any, dict_to_check: dict) -> bool:
|
996
|
-
"""
|
997
|
-
Check if val is in the values of the dict_to_check or in the values of the nested dictionaries.
|
998
|
-
|
999
|
-
Args:
|
1000
|
-
val(Any): Value to check.
|
1001
|
-
dict_to_check(dict): Dictionary to check.
|
1002
|
-
|
1003
|
-
Returns:
|
1004
|
-
bool: True if val is in the values of the dict_to_check or in the values of the nested dictionaries, False otherwise.
|
1005
|
-
"""
|
1006
|
-
if val in dict_to_check.keys():
|
1007
|
-
return True
|
1008
|
-
for key in dict_to_check:
|
1009
|
-
if isinstance(dict_to_check[key], dict):
|
1010
|
-
if self._check_curve_id(val, dict_to_check[key]):
|
1011
|
-
return True
|
1012
|
-
return False
|
1013
|
-
|
1014
|
-
def remove_curve(self, *identifiers):
|
1015
|
-
"""
|
1016
|
-
Remove a curve from the plot widget.
|
1017
|
-
|
1018
|
-
Args:
|
1019
|
-
*identifiers: Identifier of the curve to be removed. Can be either an integer (index) or a string (curve_id).
|
1020
|
-
"""
|
1021
|
-
for identifier in identifiers:
|
1022
|
-
if isinstance(identifier, int):
|
1023
|
-
self._remove_curve_by_order(identifier)
|
1024
|
-
elif isinstance(identifier, str):
|
1025
|
-
self._remove_curve_by_id(identifier)
|
1026
|
-
else:
|
1027
|
-
raise ValueError(
|
1028
|
-
"Each identifier must be either an integer (index) or a string (curve_id)."
|
1029
|
-
)
|
1030
|
-
|
1031
|
-
def _remove_curve_by_id(self, curve_id):
|
1032
|
-
"""
|
1033
|
-
Remove a curve by its ID from the plot widget.
|
1034
|
-
|
1035
|
-
Args:
|
1036
|
-
curve_id(str): ID of the curve to be removed.
|
1037
|
-
"""
|
1038
|
-
for curves in self._curves_data.values():
|
1039
|
-
if curve_id in curves:
|
1040
|
-
curve = curves.pop(curve_id)
|
1041
|
-
self.plot_item.removeItem(curve)
|
1042
|
-
del self.config.curves[curve_id]
|
1043
|
-
if curve in self.plot_item.curves:
|
1044
|
-
self.plot_item.curves.remove(curve)
|
1045
|
-
return
|
1046
|
-
raise KeyError(f"Curve with ID '{curve_id}' not found.")
|
1047
|
-
|
1048
|
-
def _remove_curve_by_order(self, N):
|
1049
|
-
"""
|
1050
|
-
Remove a curve by its order from the plot widget.
|
1051
|
-
|
1052
|
-
Args:
|
1053
|
-
N(int): Order of the curve to be removed.
|
1054
|
-
"""
|
1055
|
-
if N < len(self.plot_item.curves):
|
1056
|
-
curve = self.plot_item.curves[N]
|
1057
|
-
curve_id = curve.name() # Assuming curve's name is used as its ID
|
1058
|
-
self.plot_item.removeItem(curve)
|
1059
|
-
del self.config.curves[curve_id]
|
1060
|
-
# Remove from self.curve_data
|
1061
|
-
for curves in self._curves_data.values():
|
1062
|
-
if curve_id in curves:
|
1063
|
-
del curves[curve_id]
|
1064
|
-
break
|
1065
|
-
else:
|
1066
|
-
raise IndexError(f"Curve order {N} out of range.")
|
1067
|
-
|
1068
|
-
@Slot(dict)
|
1069
|
-
def on_scan_status(self, msg):
|
1070
|
-
"""
|
1071
|
-
Handle the scan status message.
|
1072
|
-
|
1073
|
-
Args:
|
1074
|
-
msg(dict): Message received with scan status.
|
1075
|
-
"""
|
1076
|
-
|
1077
|
-
current_scan_id = msg.get("scan_id", None)
|
1078
|
-
if current_scan_id is None:
|
1079
|
-
return
|
1080
|
-
|
1081
|
-
if current_scan_id != self.scan_id:
|
1082
|
-
self.reset()
|
1083
|
-
self.new_scan.emit()
|
1084
|
-
self.set_auto_range(True, "xy")
|
1085
|
-
self.old_scan_id = self.scan_id
|
1086
|
-
self.scan_id = current_scan_id
|
1087
|
-
self.scan_item = self.queue.scan_storage.find_scan_by_ID(self.scan_id)
|
1088
|
-
if self._curves_data["DAP"]:
|
1089
|
-
self.setup_dap(self.old_scan_id, self.scan_id)
|
1090
|
-
if self._curves_data["async"]:
|
1091
|
-
for curve in self._curves_data["async"].values():
|
1092
|
-
self.setup_async(
|
1093
|
-
name=curve.config.signals.y.name, entry=curve.config.signals.y.entry
|
1094
|
-
)
|
1095
|
-
|
1096
|
-
@Slot(dict, dict)
|
1097
|
-
def on_scan_segment(self, msg: dict, metadata: dict):
|
1098
|
-
"""
|
1099
|
-
Handle new scan segments and saves data to a dictionary. Linked through bec_dispatcher.
|
1100
|
-
Used only for triggering scan segment update from the BECClient scan storage.
|
1101
|
-
|
1102
|
-
Args:
|
1103
|
-
msg (dict): Message received with scan data.
|
1104
|
-
metadata (dict): Metadata of the scan.
|
1105
|
-
"""
|
1106
|
-
self.on_scan_status(msg)
|
1107
|
-
self.scan_signal_update.emit()
|
1108
|
-
# self.autorange_timer.start(100)
|
1109
|
-
|
1110
|
-
def set_x_label(self, label: str, size: int = None):
|
1111
|
-
"""
|
1112
|
-
Set the label of the x-axis.
|
1113
|
-
|
1114
|
-
Args:
|
1115
|
-
label(str): Label of the x-axis.
|
1116
|
-
size(int): Font size of the label.
|
1117
|
-
"""
|
1118
|
-
super().set_x_label(label, size)
|
1119
|
-
current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
|
1120
|
-
self.plot_item.setLabel("bottom", f"{current_label}{self._x_axis_mode['label_suffix']}")
|
1121
|
-
|
1122
|
-
def set_colormap(self, colormap: str | None = None):
|
1123
|
-
"""
|
1124
|
-
Set the colormap of the plot widget.
|
1125
|
-
|
1126
|
-
Args:
|
1127
|
-
colormap(str, optional): Scale the colors of curves to colormap. If None, use the default color palette.
|
1128
|
-
"""
|
1129
|
-
if colormap is not None:
|
1130
|
-
self.config.color_palette = colormap
|
1131
|
-
|
1132
|
-
colors = Colors.golden_angle_color(
|
1133
|
-
colormap=self.config.color_palette, num=len(self.plot_item.curves) + 1, format="HEX"
|
1134
|
-
)
|
1135
|
-
for curve, color in zip(self.curves, colors):
|
1136
|
-
curve.set_color(color)
|
1137
|
-
|
1138
|
-
def setup_dap(self, old_scan_id: str | None, new_scan_id: str | None):
|
1139
|
-
"""
|
1140
|
-
Setup DAP for the new scan.
|
1141
|
-
|
1142
|
-
Args:
|
1143
|
-
old_scan_id(str): old_scan_id, used to disconnect the previous dispatcher connection.
|
1144
|
-
new_scan_id(str): new_scan_id, used to connect the new dispatcher connection.
|
1145
|
-
|
1146
|
-
"""
|
1147
|
-
self.bec_dispatcher.disconnect_slot(
|
1148
|
-
self.update_dap, MessageEndpoints.dap_response(f"{old_scan_id}-{self.gui_id}")
|
1149
|
-
)
|
1150
|
-
if len(self._curves_data["DAP"]) > 0:
|
1151
|
-
self.bec_dispatcher.connect_slot(
|
1152
|
-
self.update_dap, MessageEndpoints.dap_response(f"{new_scan_id}-{self.gui_id}")
|
1153
|
-
)
|
1154
|
-
|
1155
|
-
@Slot(str)
|
1156
|
-
def setup_async(self, name: str, entry: str):
|
1157
|
-
self.bec_dispatcher.disconnect_slot(
|
1158
|
-
self.on_async_readback, MessageEndpoints.device_async_readback(self.old_scan_id, name)
|
1159
|
-
)
|
1160
|
-
try:
|
1161
|
-
self._curves_data["async"][f"{name}-{entry}"].clear_data()
|
1162
|
-
except KeyError:
|
1163
|
-
pass
|
1164
|
-
if len(self._curves_data["async"]) > 0:
|
1165
|
-
self.bec_dispatcher.connect_slot(
|
1166
|
-
self.on_async_readback,
|
1167
|
-
MessageEndpoints.device_async_readback(self.scan_id, name),
|
1168
|
-
from_start=True,
|
1169
|
-
)
|
1170
|
-
|
1171
|
-
@Slot()
|
1172
|
-
def refresh_dap(self, _=None):
|
1173
|
-
"""
|
1174
|
-
Refresh the DAP curves with the latest data from the DAP model MessageEndpoints.dap_response().
|
1175
|
-
"""
|
1176
|
-
for curve_id, curve in self._curves_data["DAP"].items():
|
1177
|
-
if len(self._curves_data["async"]) > 0:
|
1178
|
-
curve.remove()
|
1179
|
-
raise ValueError(
|
1180
|
-
f"Cannot refresh DAP curve '{curve_id}' while async curves are present. Removing {curve_id} from display."
|
1181
|
-
)
|
1182
|
-
if self._x_axis_mode["name"] == "best_effort":
|
1183
|
-
try:
|
1184
|
-
x_name = self.scan_item.status_message.info["scan_report_devices"][0]
|
1185
|
-
x_entry = self.entry_validator.validate_signal(x_name, None)
|
1186
|
-
except AttributeError:
|
1187
|
-
return
|
1188
|
-
elif curve.config.signals.x is not None:
|
1189
|
-
x_name = curve.config.signals.x.name
|
1190
|
-
x_entry = curve.config.signals.x.entry
|
1191
|
-
if (
|
1192
|
-
x_name == "timestamp" or x_name == "index"
|
1193
|
-
): # timestamp and index not supported by DAP
|
1194
|
-
return
|
1195
|
-
try: # to prevent DAP update if the x axis is not the same as the current scan
|
1196
|
-
current_x_names = self.scan_item.status_message.info["scan_report_devices"]
|
1197
|
-
if x_name not in current_x_names:
|
1198
|
-
return
|
1199
|
-
except AttributeError:
|
1200
|
-
return
|
1201
|
-
|
1202
|
-
y_name = curve.config.signals.y.name
|
1203
|
-
y_entry = curve.config.signals.y.entry
|
1204
|
-
model_name = curve.config.signals.dap
|
1205
|
-
model = getattr(self.dap, model_name)
|
1206
|
-
x_min, x_max = self.roi_region
|
1207
|
-
|
1208
|
-
msg = messages.DAPRequestMessage(
|
1209
|
-
dap_cls="LmfitService1D",
|
1210
|
-
dap_type="on_demand",
|
1211
|
-
config={
|
1212
|
-
"args": [self.scan_id, x_name, x_entry, y_name, y_entry],
|
1213
|
-
"kwargs": {"x_min": x_min, "x_max": x_max},
|
1214
|
-
"class_args": model._plugin_info["class_args"],
|
1215
|
-
"class_kwargs": model._plugin_info["class_kwargs"],
|
1216
|
-
},
|
1217
|
-
metadata={"RID": f"{self.scan_id}-{self.gui_id}"},
|
1218
|
-
)
|
1219
|
-
self.client.connector.set_and_publish(MessageEndpoints.dap_request(), msg)
|
1220
|
-
|
1221
|
-
@Slot(dict, dict)
|
1222
|
-
def update_dap(self, msg, metadata):
|
1223
|
-
"""Callback for DAP response message."""
|
1224
|
-
|
1225
|
-
# pylint: disable=unused-variable
|
1226
|
-
scan_id, x_name, x_entry, y_name, y_entry = msg["dap_request"].content["config"]["args"]
|
1227
|
-
model = msg["dap_request"].content["config"]["class_kwargs"]["model"]
|
1228
|
-
|
1229
|
-
curve_id_request = f"{y_name}-{y_entry}-{model}"
|
1230
|
-
|
1231
|
-
for curve_id, curve in self._curves_data["DAP"].items():
|
1232
|
-
if curve_id == curve_id_request:
|
1233
|
-
if msg["data"] is not None:
|
1234
|
-
x = msg["data"][0]["x"]
|
1235
|
-
y = msg["data"][0]["y"]
|
1236
|
-
curve.setData(x, y)
|
1237
|
-
curve.dap_params = msg["data"][1]["fit_parameters"]
|
1238
|
-
curve.dap_summary = msg["data"][1]["fit_summary"]
|
1239
|
-
metadata.update({"curve_id": curve_id_request})
|
1240
|
-
self.dap_params_update.emit(curve.dap_params, metadata)
|
1241
|
-
self.dap_summary_update.emit(curve.dap_summary, metadata)
|
1242
|
-
break
|
1243
|
-
|
1244
|
-
@Slot(dict, dict)
|
1245
|
-
def on_async_readback(self, msg, metadata):
|
1246
|
-
"""
|
1247
|
-
Get async data readback.
|
1248
|
-
|
1249
|
-
Args:
|
1250
|
-
msg(dict): Message with the async data.
|
1251
|
-
metadata(dict): Metadata of the message.
|
1252
|
-
"""
|
1253
|
-
y_data = None
|
1254
|
-
x_data = None
|
1255
|
-
instruction = metadata.get("async_update", {}).get("type")
|
1256
|
-
max_shape = metadata.get("async_update", {}).get("max_shape", [])
|
1257
|
-
all_async_curves = self._curves_data["async"].values()
|
1258
|
-
# for curve in self._curves_data["async"].values():
|
1259
|
-
for curve in all_async_curves:
|
1260
|
-
y_entry = curve.config.signals.y.entry
|
1261
|
-
x_name = self._x_axis_mode["name"]
|
1262
|
-
for device, async_data in msg["signals"].items():
|
1263
|
-
if device == y_entry:
|
1264
|
-
data_plot = async_data["value"]
|
1265
|
-
if instruction == "add":
|
1266
|
-
if len(max_shape) > 1:
|
1267
|
-
if len(data_plot.shape) > 1:
|
1268
|
-
data_plot = data_plot[-1, :]
|
1269
|
-
else:
|
1270
|
-
x_data, y_data = curve.get_data()
|
1271
|
-
if y_data is not None:
|
1272
|
-
new_data = np.hstack((y_data, data_plot))
|
1273
|
-
else:
|
1274
|
-
new_data = data_plot
|
1275
|
-
if x_name == "timestamp":
|
1276
|
-
if x_data is not None:
|
1277
|
-
x_data = np.hstack((x_data, async_data["timestamp"]))
|
1278
|
-
else:
|
1279
|
-
x_data = async_data["timestamp"]
|
1280
|
-
curve.setData(x_data, new_data)
|
1281
|
-
else:
|
1282
|
-
curve.setData(new_data)
|
1283
|
-
elif instruction == "add_slice":
|
1284
|
-
current_slice_id = metadata.get("async_update", {}).get("index")
|
1285
|
-
data_plot = async_data["value"]
|
1286
|
-
if current_slice_id != self._slice_index:
|
1287
|
-
self._slice_index = current_slice_id
|
1288
|
-
new_data = data_plot
|
1289
|
-
else:
|
1290
|
-
x_data, y_data = curve.get_data()
|
1291
|
-
new_data = np.hstack((y_data, data_plot))
|
1292
|
-
|
1293
|
-
curve.setData(new_data)
|
1294
|
-
|
1295
|
-
elif instruction == "replace":
|
1296
|
-
if x_name == "timestamp":
|
1297
|
-
x_data = async_data["timestamp"]
|
1298
|
-
curve.setData(x_data, data_plot)
|
1299
|
-
else:
|
1300
|
-
curve.setData(data_plot)
|
1301
|
-
|
1302
|
-
@Slot()
|
1303
|
-
def replot_async_curve(self):
|
1304
|
-
try:
|
1305
|
-
data = self.scan_item.async_data
|
1306
|
-
except AttributeError:
|
1307
|
-
return
|
1308
|
-
for curve_id, curve in self._curves_data["async"].items():
|
1309
|
-
y_name = curve.config.signals.y.name
|
1310
|
-
y_entry = curve.config.signals.y.entry
|
1311
|
-
x_name = None
|
1312
|
-
|
1313
|
-
if curve.config.signals.x:
|
1314
|
-
x_name = curve.config.signals.x.name
|
1315
|
-
|
1316
|
-
if x_name == "timestamp":
|
1317
|
-
data_x = data[y_name][y_entry]["timestamp"]
|
1318
|
-
else:
|
1319
|
-
data_x = None
|
1320
|
-
data_y = data[y_name][y_entry]["value"]
|
1321
|
-
|
1322
|
-
if data_x is None:
|
1323
|
-
curve.setData(data_y)
|
1324
|
-
else:
|
1325
|
-
curve.setData(data_x, data_y)
|
1326
|
-
|
1327
|
-
@Slot()
|
1328
|
-
def _update_scan_curves(self, _=None):
|
1329
|
-
"""
|
1330
|
-
Update the scan curves with the data from the scan segment.
|
1331
|
-
"""
|
1332
|
-
try:
|
1333
|
-
data = (
|
1334
|
-
self.scan_item.live_data
|
1335
|
-
if hasattr(self.scan_item, "live_data") # backward compatibility
|
1336
|
-
else self.scan_item.data
|
1337
|
-
)
|
1338
|
-
except AttributeError:
|
1339
|
-
return
|
1340
|
-
|
1341
|
-
data_x = None
|
1342
|
-
data_y = None
|
1343
|
-
data_z = None
|
1344
|
-
|
1345
|
-
for curve_id, curve in self._curves_data["scan_segment"].items():
|
1346
|
-
|
1347
|
-
y_name = curve.config.signals.y.name
|
1348
|
-
y_entry = curve.config.signals.y.entry
|
1349
|
-
if curve.config.signals.z:
|
1350
|
-
z_name = curve.config.signals.z.name
|
1351
|
-
z_entry = curve.config.signals.z.entry
|
1352
|
-
|
1353
|
-
data_x = self._get_x_data(curve, y_name, y_entry)
|
1354
|
-
if len(data) == 0: # case if the data is empty because motor is not scanned
|
1355
|
-
return
|
1356
|
-
|
1357
|
-
try:
|
1358
|
-
data_y = data[y_name][y_entry].val
|
1359
|
-
if curve.config.signals.z:
|
1360
|
-
data_z = data[z_name][z_entry].val
|
1361
|
-
color_z = self._make_z_gradient(data_z, curve.config.color_map_z)
|
1362
|
-
except TypeError:
|
1363
|
-
continue
|
1364
|
-
|
1365
|
-
if data_z is not None and color_z is not None:
|
1366
|
-
try:
|
1367
|
-
curve.setData(x=data_x, y=data_y, symbolBrush=color_z)
|
1368
|
-
except:
|
1369
|
-
return
|
1370
|
-
if data_x is None:
|
1371
|
-
curve.setData(data_y)
|
1372
|
-
else:
|
1373
|
-
curve.setData(data_x, data_y)
|
1374
|
-
|
1375
|
-
def _get_x_data(self, curve: BECCurve, y_name: str, y_entry: str) -> list | np.ndarray | None:
|
1376
|
-
"""
|
1377
|
-
Get the x data for the curve with the decision logic based on the curve configuration:
|
1378
|
-
- If x is called 'timestamp', use the timestamp data from the scan item.
|
1379
|
-
- If x is called 'index', use the rolling index.
|
1380
|
-
- If x is a custom signal, use the data from the scan item.
|
1381
|
-
- If x is not specified, use the first device from the scan report.
|
1382
|
-
|
1383
|
-
Args:
|
1384
|
-
curve(BECCurve): The curve object.
|
1385
|
-
|
1386
|
-
Returns:
|
1387
|
-
list|np.ndarray|None: X data for the curve.
|
1388
|
-
"""
|
1389
|
-
x_data = None
|
1390
|
-
live_data = (
|
1391
|
-
self.scan_item.live_data
|
1392
|
-
if hasattr(self.scan_item, "live_data")
|
1393
|
-
else self.scan_item.data
|
1394
|
-
)
|
1395
|
-
if self._x_axis_mode["name"] == "timestamp":
|
1396
|
-
|
1397
|
-
timestamps = live_data[y_name][y_entry].timestamps
|
1398
|
-
|
1399
|
-
x_data = timestamps
|
1400
|
-
return x_data
|
1401
|
-
if self._x_axis_mode["name"] == "index":
|
1402
|
-
x_data = None
|
1403
|
-
return x_data
|
1404
|
-
|
1405
|
-
if self._x_axis_mode["name"] is None or self._x_axis_mode["name"] == "best_effort":
|
1406
|
-
if len(self._curves_data["async"]) > 0:
|
1407
|
-
x_data = None
|
1408
|
-
self._x_axis_mode["label_suffix"] = " [auto: index]"
|
1409
|
-
current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
|
1410
|
-
self.plot_item.setLabel(
|
1411
|
-
"bottom", f"{current_label}{self._x_axis_mode['label_suffix']}"
|
1412
|
-
)
|
1413
|
-
return x_data
|
1414
|
-
else:
|
1415
|
-
x_name = self.scan_item.status_message.info["scan_report_devices"][0]
|
1416
|
-
x_entry = self.entry_validator.validate_signal(x_name, None)
|
1417
|
-
x_data = live_data[x_name][x_entry].val
|
1418
|
-
self._x_axis_mode["label_suffix"] = f" [auto: {x_name}-{x_entry}]"
|
1419
|
-
current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
|
1420
|
-
self.plot_item.setLabel(
|
1421
|
-
"bottom", f"{current_label}{self._x_axis_mode['label_suffix']}"
|
1422
|
-
)
|
1423
|
-
|
1424
|
-
else:
|
1425
|
-
x_name = curve.config.signals.x.name
|
1426
|
-
x_entry = curve.config.signals.x.entry
|
1427
|
-
try:
|
1428
|
-
x_data = live_data[x_name][x_entry].val
|
1429
|
-
except TypeError:
|
1430
|
-
x_data = []
|
1431
|
-
return x_data
|
1432
|
-
|
1433
|
-
def _make_z_gradient(self, data_z: list | np.ndarray, colormap: str) -> list | None:
|
1434
|
-
"""
|
1435
|
-
Make a gradient color for the z values.
|
1436
|
-
|
1437
|
-
Args:
|
1438
|
-
data_z(list|np.ndarray): Z values.
|
1439
|
-
colormap(str): Colormap for the gradient color.
|
1440
|
-
|
1441
|
-
Returns:
|
1442
|
-
list: List of colors for the z values.
|
1443
|
-
"""
|
1444
|
-
# Normalize z_values for color mapping
|
1445
|
-
z_min, z_max = np.min(data_z), np.max(data_z)
|
1446
|
-
|
1447
|
-
if z_max != z_min: # Ensure that there is a range in the z values
|
1448
|
-
z_values_norm = (data_z - z_min) / (z_max - z_min)
|
1449
|
-
colormap = pg.colormap.get(colormap) # using colormap from global settings
|
1450
|
-
colors = [colormap.map(z, mode="qcolor") for z in z_values_norm]
|
1451
|
-
return colors
|
1452
|
-
else:
|
1453
|
-
return None
|
1454
|
-
|
1455
|
-
def scan_history(self, scan_index: int = None, scan_id: str = None):
|
1456
|
-
"""
|
1457
|
-
Update the scan curves with the data from the scan storage.
|
1458
|
-
Provide only one of scan_id or scan_index.
|
1459
|
-
|
1460
|
-
Args:
|
1461
|
-
scan_id(str, optional): ScanID of the scan to be updated. Defaults to None.
|
1462
|
-
scan_index(int, optional): Index of the scan to be updated. Defaults to None.
|
1463
|
-
"""
|
1464
|
-
if scan_index is not None and scan_id is not None:
|
1465
|
-
raise ValueError("Only one of scan_id or scan_index can be provided.")
|
1466
|
-
|
1467
|
-
# Reset DAP connector
|
1468
|
-
self.bec_dispatcher.disconnect_slot(
|
1469
|
-
self.update_dap, MessageEndpoints.dap_response(self.scan_id)
|
1470
|
-
)
|
1471
|
-
if scan_index is not None:
|
1472
|
-
try:
|
1473
|
-
self.scan_id = self.queue.scan_storage.storage[scan_index].scan_id
|
1474
|
-
except IndexError:
|
1475
|
-
logger.error(f"Scan index {scan_index} out of range.")
|
1476
|
-
return
|
1477
|
-
elif scan_id is not None:
|
1478
|
-
self.scan_id = scan_id
|
1479
|
-
|
1480
|
-
self.setup_dap(self.old_scan_id, self.scan_id)
|
1481
|
-
self.scan_item = self.queue.scan_storage.find_scan_by_ID(self.scan_id)
|
1482
|
-
self.scan_signal_update.emit()
|
1483
|
-
self.async_signal_update.emit()
|
1484
|
-
|
1485
|
-
def get_all_data(self, output: Literal["dict", "pandas"] = "dict") -> dict: # | pd.DataFrame:
|
1486
|
-
"""
|
1487
|
-
Extract all curve data into a dictionary or a pandas DataFrame.
|
1488
|
-
|
1489
|
-
Args:
|
1490
|
-
output (Literal["dict", "pandas"]): Format of the output data.
|
1491
|
-
|
1492
|
-
Returns:
|
1493
|
-
dict | pd.DataFrame: Data of all curves in the specified format.
|
1494
|
-
"""
|
1495
|
-
data = {}
|
1496
|
-
try:
|
1497
|
-
import pandas as pd
|
1498
|
-
except ImportError:
|
1499
|
-
pd = None
|
1500
|
-
if output == "pandas":
|
1501
|
-
logger.warning(
|
1502
|
-
"Pandas is not installed. "
|
1503
|
-
"Please install pandas using 'pip install pandas'."
|
1504
|
-
"Output will be dictionary instead."
|
1505
|
-
)
|
1506
|
-
output = "dict"
|
1507
|
-
|
1508
|
-
for curve in self.plot_item.curves:
|
1509
|
-
x_data, y_data = curve.get_data()
|
1510
|
-
if x_data is not None or y_data is not None:
|
1511
|
-
if output == "dict":
|
1512
|
-
data[curve.name()] = {"x": x_data.tolist(), "y": y_data.tolist()}
|
1513
|
-
elif output == "pandas" and pd is not None:
|
1514
|
-
data[curve.name()] = pd.DataFrame({"x": x_data, "y": y_data})
|
1515
|
-
|
1516
|
-
if output == "pandas" and pd is not None:
|
1517
|
-
combined_data = pd.concat(
|
1518
|
-
[data[curve.name()] for curve in self.plot_item.curves],
|
1519
|
-
axis=1,
|
1520
|
-
keys=[curve.name() for curve in self.plot_item.curves],
|
1521
|
-
)
|
1522
|
-
return combined_data
|
1523
|
-
return data
|
1524
|
-
|
1525
|
-
def export_to_matplotlib(self):
|
1526
|
-
"""
|
1527
|
-
Export current waveform to matplotlib gui. Available only if matplotlib is installed in the enviroment.
|
1528
|
-
|
1529
|
-
"""
|
1530
|
-
MatplotlibExporter(self.plot_item).export()
|
1531
|
-
|
1532
|
-
def clear_source(self, source: Literal["DAP", "async", "scan_segment", "custom"]):
|
1533
|
-
"""Clear speicific source from self._curves_data.
|
1534
|
-
|
1535
|
-
Args:
|
1536
|
-
source (Literal["DAP", "async", "scan_segment", "custom"]): Source to be cleared.
|
1537
|
-
"""
|
1538
|
-
curves_data = self._curves_data
|
1539
|
-
curve_ids_to_remove = list(curves_data[source].keys())
|
1540
|
-
for curve_id in curve_ids_to_remove:
|
1541
|
-
self.remove_curve(curve_id)
|
1542
|
-
|
1543
|
-
def reset(self):
|
1544
|
-
self._slice_index = None
|
1545
|
-
super().reset()
|
1546
|
-
|
1547
|
-
def clear_all(self):
|
1548
|
-
sources = list(self._curves_data.keys())
|
1549
|
-
for source in sources:
|
1550
|
-
self.clear_source(source)
|
1551
|
-
|
1552
|
-
def cleanup(self):
|
1553
|
-
"""Cleanup the widget connection from BECDispatcher."""
|
1554
|
-
self.bec_dispatcher.disconnect_slot(self.on_scan_segment, MessageEndpoints.scan_segment())
|
1555
|
-
self.bec_dispatcher.disconnect_slot(
|
1556
|
-
self.update_dap, MessageEndpoints.dap_response(self.scan_id)
|
1557
|
-
)
|
1558
|
-
for curve_id in self._curves_data["async"]:
|
1559
|
-
self.bec_dispatcher.disconnect_slot(
|
1560
|
-
self.on_async_readback,
|
1561
|
-
MessageEndpoints.device_async_readback(self.scan_id, curve_id),
|
1562
|
-
)
|
1563
|
-
self.curves.clear()
|