bec-widgets 0.96.0__py3-none-any.whl → 0.96.2__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 +2 -2
- CHANGELOG.md +24 -28
- PKG-INFO +1 -1
- bec_widgets/qt_utils/toolbar.py +29 -1
- bec_widgets/utils/crosshair.py +115 -74
- bec_widgets/widgets/figure/plots/plot_base.py +56 -1
- bec_widgets/widgets/figure/plots/waveform/waveform.py +13 -20
- bec_widgets/widgets/waveform/waveform_widget.py +32 -3
- {bec_widgets-0.96.0.dist-info → bec_widgets-0.96.2.dist-info}/METADATA +1 -1
- {bec_widgets-0.96.0.dist-info → bec_widgets-0.96.2.dist-info}/RECORD +15 -15
- pyproject.toml +1 -1
- tests/unit_tests/test_crosshair.py +40 -47
- {bec_widgets-0.96.0.dist-info → bec_widgets-0.96.2.dist-info}/WHEEL +0 -0
- {bec_widgets-0.96.0.dist-info → bec_widgets-0.96.2.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.96.0.dist-info → bec_widgets-0.96.2.dist-info}/licenses/LICENSE +0 -0
.gitlab-ci.yml
CHANGED
@@ -140,7 +140,7 @@ tests:
|
|
140
140
|
- *install-os-packages
|
141
141
|
- *install-repos
|
142
142
|
- pip install -e .[dev,pyqt6]
|
143
|
-
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests/unit_tests
|
143
|
+
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --maxfail=2 --random-order --full-trace ./tests/unit_tests
|
144
144
|
- coverage report
|
145
145
|
- coverage xml
|
146
146
|
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
|
@@ -177,7 +177,7 @@ test-matrix:
|
|
177
177
|
- *install-os-packages
|
178
178
|
- *install-repos
|
179
179
|
- pip install -e .[dev,$QT_PCKG]
|
180
|
-
- pytest -v --junitxml=report.xml --random-order ./tests/unit_tests
|
180
|
+
- pytest -v --maxfail=2 --junitxml=report.xml --random-order ./tests/unit_tests
|
181
181
|
allow_failure: true
|
182
182
|
|
183
183
|
end-2-end-conda:
|
CHANGELOG.md
CHANGED
@@ -1,5 +1,29 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## v0.96.2 (2024-08-22)
|
4
|
+
|
5
|
+
### Fix
|
6
|
+
|
7
|
+
* fix(waveform): validation of custom curves removed ([`af28574`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/af28574bd58457a05f1269f121db01ad627b5769))
|
8
|
+
|
9
|
+
* fix(waveform): skip validation for curves that are not BECCurve instances ([`617db36`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/617db36ed4932c8a0633724079b695bc67d5c77b))
|
10
|
+
|
11
|
+
## v0.96.1 (2024-08-22)
|
12
|
+
|
13
|
+
### Ci
|
14
|
+
|
15
|
+
* ci: fail pytest after 2 failed tests ([`f0203d9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f0203d9bf60c4975ba5ab93a057d9091762454d5))
|
16
|
+
|
17
|
+
### Fix
|
18
|
+
|
19
|
+
* fix(crosshair): update markers if necessary ([`4473805`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/44738057a36f5de2bbb55affdd309f92286d4a0f))
|
20
|
+
|
21
|
+
* fix(waveform_widget): fixed icon appearance ([`f98a9f9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f98a9f9771b93226d47830aa52f45739624f51b4))
|
22
|
+
|
23
|
+
* fix: bubble-up signals ([`2fe72c9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2fe72c9ccb71bcb196a1b78197b73acf9aa3f506))
|
24
|
+
|
25
|
+
* fix(crosshair): fixed crosshair for image and waveforms ([`37835cb`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/37835cbf76ca3ba1081f514ee7793244ac500e7f))
|
26
|
+
|
3
27
|
## v0.96.0 (2024-08-22)
|
4
28
|
|
5
29
|
### Documentation
|
@@ -133,31 +157,3 @@ Terminating client connections has to be done at the application level ([`198c1d
|
|
133
157
|
### Fix
|
134
158
|
|
135
159
|
* fix(positioner_box): icons fixed ([`281633d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/281633deff15b6879dac3a4f0770fa6949aaecdc))
|
136
|
-
|
137
|
-
### Refactor
|
138
|
-
|
139
|
-
* refactor: add button for positioner selection ([`0d190c5`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0d190c5c5996e59fec4bdd44d2003e10e200b009))
|
140
|
-
|
141
|
-
### Test
|
142
|
-
|
143
|
-
* test(dap): wait for fit ([`6269009`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6269009e5451f830cdee58a514c7858483488a8d))
|
144
|
-
|
145
|
-
* test(auto-update): wait for rendering ([`6d2442d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6d2442d23c683fe92af13df982ce681c07e99cde))
|
146
|
-
|
147
|
-
## v0.93.4 (2024-08-07)
|
148
|
-
|
149
|
-
### Fix
|
150
|
-
|
151
|
-
* fix: rename DeviceBox to PositionerBox, fix test for validation ([`37aa371`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/37aa371e7c4c62d70abf37abc125db0c088790fe))
|
152
|
-
|
153
|
-
* fix: add validation for bec_lib.device.Positioner; closes #268 ([`eb54e9f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/eb54e9f788e97af23db8fe0c78f8facb8688bb99))
|
154
|
-
|
155
|
-
## v0.93.3 (2024-08-07)
|
156
|
-
|
157
|
-
### Fix
|
158
|
-
|
159
|
-
* fix(dock): properly shut down docks and temp areas ([`99ee545`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/99ee545e41c6078654958b668b5b329f85553d16))
|
160
|
-
|
161
|
-
### Test
|
162
|
-
|
163
|
-
* test: removed quit from teardown ([`cf94599`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/cf94599c2544d6831c8afbe7b340082077557ed1))
|
PKG-INFO
CHANGED
bec_widgets/qt_utils/toolbar.py
CHANGED
@@ -3,8 +3,9 @@ import os
|
|
3
3
|
from abc import ABC, abstractmethod
|
4
4
|
from collections import defaultdict
|
5
5
|
|
6
|
+
from bec_qthemes import material_icon
|
6
7
|
from qtpy.QtCore import QSize
|
7
|
-
from qtpy.QtGui import QAction, QIcon
|
8
|
+
from qtpy.QtGui import QAction, QGuiApplication, QIcon
|
8
9
|
from qtpy.QtWidgets import QHBoxLayout, QLabel, QMenu, QToolBar, QToolButton, QWidget
|
9
10
|
|
10
11
|
import bec_widgets
|
@@ -70,6 +71,33 @@ class IconAction(ToolBarAction):
|
|
70
71
|
toolbar.addAction(self.action)
|
71
72
|
|
72
73
|
|
74
|
+
class MaterialIconAction:
|
75
|
+
"""
|
76
|
+
Abstract base class for toolbar actions.
|
77
|
+
|
78
|
+
Args:
|
79
|
+
icon_path (str, optional): The name of the icon file from `assets/toolbar_icons`. Defaults to None.
|
80
|
+
tooltip (bool, optional): The tooltip for the action. Defaults to None.
|
81
|
+
checkable (bool, optional): Whether the action is checkable. Defaults to False.
|
82
|
+
"""
|
83
|
+
|
84
|
+
def __init__(self, icon_name: str = None, tooltip: str = None, checkable: bool = False):
|
85
|
+
self.icon_name = icon_name
|
86
|
+
self.tooltip = tooltip
|
87
|
+
self.checkable = checkable
|
88
|
+
self.action = None
|
89
|
+
|
90
|
+
def add_to_toolbar(self, toolbar: QToolBar, target: QWidget):
|
91
|
+
palette = QGuiApplication.palette()
|
92
|
+
color = "#FFFFFF" # FIXME: This should be a theme color but the toolbar doesn't respect the theme atm
|
93
|
+
# one fixed, change it to palette.toolTipBase().color()
|
94
|
+
|
95
|
+
icon = material_icon(self.icon_name, size=(20, 20), color=color)
|
96
|
+
self.action = QAction(QIcon(icon), self.tooltip, target)
|
97
|
+
self.action.setCheckable(self.checkable)
|
98
|
+
toolbar.addAction(self.action)
|
99
|
+
|
100
|
+
|
73
101
|
class DeviceSelectionAction(ToolBarAction):
|
74
102
|
"""
|
75
103
|
Action for selecting a device in a combobox.
|
bec_widgets/utils/crosshair.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
from collections import defaultdict
|
2
|
+
|
1
3
|
import numpy as np
|
2
4
|
import pyqtgraph as pg
|
3
5
|
|
4
6
|
# from qtpy.QtCore import QObject, pyqtSignal
|
5
|
-
from qtpy.QtCore import QObject
|
7
|
+
from qtpy.QtCore import QObject, Qt
|
6
8
|
from qtpy.QtCore import Signal as pyqtSignal
|
7
9
|
|
8
10
|
|
@@ -26,10 +28,13 @@ class Crosshair(QObject):
|
|
26
28
|
super().__init__(parent)
|
27
29
|
self.is_log_y = None
|
28
30
|
self.is_log_x = None
|
31
|
+
self.is_derivative = None
|
29
32
|
self.plot_item = plot_item
|
30
33
|
self.precision = precision
|
31
34
|
self.v_line = pg.InfiniteLine(angle=90, movable=False)
|
35
|
+
self.v_line.skip_auto_range = True
|
32
36
|
self.h_line = pg.InfiniteLine(angle=0, movable=False)
|
37
|
+
self.h_line.skip_auto_range = True
|
33
38
|
self.plot_item.addItem(self.v_line, ignoreBounds=True)
|
34
39
|
self.plot_item.addItem(self.h_line, ignoreBounds=True)
|
35
40
|
self.proxy = pg.SignalProxy(
|
@@ -37,74 +42,75 @@ class Crosshair(QObject):
|
|
37
42
|
)
|
38
43
|
self.plot_item.scene().sigMouseClicked.connect(self.mouse_clicked)
|
39
44
|
|
45
|
+
self.plot_item.ctrl.derivativeCheck.checkStateChanged.connect(self.check_derivatives)
|
46
|
+
self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
|
47
|
+
self.plot_item.ctrl.logYCheck.checkStateChanged.connect(self.check_log)
|
48
|
+
|
40
49
|
# Initialize markers
|
41
|
-
self.marker_moved_1d =
|
42
|
-
self.marker_clicked_1d =
|
50
|
+
self.marker_moved_1d = {}
|
51
|
+
self.marker_clicked_1d = {}
|
43
52
|
self.marker_2d = None
|
44
53
|
self.update_markers()
|
45
54
|
|
46
55
|
def update_markers(self):
|
47
56
|
"""Update the markers for the crosshair, creating new ones if necessary."""
|
48
57
|
|
49
|
-
# Clear existing markers
|
50
|
-
for marker in self.marker_moved_1d + self.marker_clicked_1d:
|
51
|
-
self.plot_item.removeItem(marker)
|
52
|
-
if self.marker_2d:
|
53
|
-
self.plot_item.removeItem(self.marker_2d)
|
54
|
-
|
55
58
|
# Create new markers
|
56
|
-
self.marker_moved_1d = []
|
57
|
-
self.marker_clicked_1d = []
|
58
|
-
self.marker_2d = None
|
59
59
|
for item in self.plot_item.items:
|
60
60
|
if isinstance(item, pg.PlotDataItem): # 1D plot
|
61
|
+
if item.name() in self.marker_moved_1d:
|
62
|
+
continue
|
61
63
|
pen = item.opts["pen"]
|
62
64
|
color = pen.color() if hasattr(pen, "color") else pg.mkColor(pen)
|
63
65
|
marker_moved = pg.ScatterPlotItem(
|
64
66
|
size=10, pen=pg.mkPen(color), brush=pg.mkBrush(None)
|
65
67
|
)
|
66
|
-
|
67
|
-
|
68
|
-
)
|
69
|
-
self.marker_moved_1d.append(marker_moved)
|
68
|
+
marker_moved.skip_auto_range = True
|
69
|
+
self.marker_moved_1d[item.name()] = marker_moved
|
70
70
|
self.plot_item.addItem(marker_moved)
|
71
|
+
|
71
72
|
# Create glowing effect markers for clicked events
|
72
|
-
marker_clicked_list = []
|
73
73
|
for size, alpha in [(18, 64), (14, 128), (10, 255)]:
|
74
74
|
marker_clicked = pg.ScatterPlotItem(
|
75
75
|
size=size,
|
76
76
|
pen=pg.mkPen(None),
|
77
77
|
brush=pg.mkBrush(color.red(), color.green(), color.blue(), alpha),
|
78
78
|
)
|
79
|
-
|
79
|
+
marker_clicked.skip_auto_range = True
|
80
|
+
self.marker_clicked_1d[item.name()] = marker_clicked
|
80
81
|
self.plot_item.addItem(marker_clicked)
|
81
82
|
|
82
|
-
self.marker_clicked_1d.append(marker_clicked_list)
|
83
83
|
elif isinstance(item, pg.ImageItem): # 2D plot
|
84
|
+
if self.marker_2d is not None:
|
85
|
+
continue
|
84
86
|
self.marker_2d = pg.ROI(
|
85
87
|
[0, 0], size=[1, 1], pen=pg.mkPen("r", width=2), movable=False
|
86
88
|
)
|
87
89
|
self.plot_item.addItem(self.marker_2d)
|
88
90
|
|
89
|
-
def snap_to_data(self, x, y) -> tuple:
|
91
|
+
def snap_to_data(self, x, y) -> tuple[defaultdict[list], defaultdict[list]]:
|
90
92
|
"""
|
91
93
|
Finds the nearest data points to the given x and y coordinates.
|
92
94
|
|
93
95
|
Args:
|
94
|
-
x: The x-coordinate
|
95
|
-
y: The y-coordinate
|
96
|
+
x: The x-coordinate of the mouse cursor
|
97
|
+
y: The y-coordinate of the mouse cursor
|
96
98
|
|
97
99
|
Returns:
|
98
|
-
tuple:
|
100
|
+
tuple: x and y values snapped to the nearest data
|
99
101
|
"""
|
100
|
-
|
101
|
-
|
102
|
+
y_values = defaultdict(list)
|
103
|
+
x_values = defaultdict(list)
|
102
104
|
image_2d = None
|
103
105
|
|
104
106
|
# Iterate through items in the plot
|
105
107
|
for item in self.plot_item.items:
|
106
108
|
if isinstance(item, pg.PlotDataItem): # 1D plot
|
107
|
-
|
109
|
+
name = item.name()
|
110
|
+
plot_data = item._getDisplayDataset()
|
111
|
+
if plot_data is None:
|
112
|
+
continue
|
113
|
+
x_data, y_data = plot_data.x, plot_data.y
|
108
114
|
if x_data is not None and y_data is not None:
|
109
115
|
if self.is_log_x:
|
110
116
|
min_x_data = np.min(x_data[x_data > 0])
|
@@ -112,25 +118,25 @@ class Crosshair(QObject):
|
|
112
118
|
min_x_data = np.min(x_data)
|
113
119
|
max_x_data = np.max(x_data)
|
114
120
|
if x < min_x_data or x > max_x_data:
|
115
|
-
|
121
|
+
y_values[name] = None
|
122
|
+
x_values[name] = None
|
123
|
+
continue
|
116
124
|
closest_x, closest_y = self.closest_x_y_value(x, x_data, y_data)
|
117
|
-
|
118
|
-
|
125
|
+
y_values[name] = closest_y
|
126
|
+
x_values[name] = closest_x
|
119
127
|
elif isinstance(item, pg.ImageItem): # 2D plot
|
128
|
+
name = item.config.monitor
|
120
129
|
image_2d = item.image
|
130
|
+
# clip the x and y values to the image dimensions to avoid out of bounds errors
|
131
|
+
y_values[name] = int(np.clip(y, 0, image_2d.shape[1] - 1))
|
132
|
+
x_values[name] = int(np.clip(x, 0, image_2d.shape[0] - 1))
|
121
133
|
|
122
|
-
|
123
|
-
|
124
|
-
|
134
|
+
if x_values and y_values:
|
135
|
+
if all(v is None for v in x_values.values()) or all(
|
136
|
+
v is None for v in y_values.values()
|
137
|
+
):
|
125
138
|
return None, None
|
126
|
-
|
127
|
-
return closest_x, y_values_1d
|
128
|
-
|
129
|
-
# Handle 2D plot
|
130
|
-
if image_2d is not None:
|
131
|
-
x_idx = int(np.clip(x, 0, image_2d.shape[0] - 1))
|
132
|
-
y_idx = int(np.clip(y, 0, image_2d.shape[1] - 1))
|
133
|
-
return x_idx, y_idx
|
139
|
+
return x_values, y_values
|
134
140
|
|
135
141
|
return None, None
|
136
142
|
|
@@ -156,8 +162,8 @@ class Crosshair(QObject):
|
|
156
162
|
Args:
|
157
163
|
event: The mouse moved event
|
158
164
|
"""
|
159
|
-
self.check_log()
|
160
165
|
pos = event[0]
|
166
|
+
self.update_markers()
|
161
167
|
if self.plot_item.vb.sceneBoundingRect().contains(pos):
|
162
168
|
mouse_point = self.plot_item.vb.mapSceneToView(pos)
|
163
169
|
self.v_line.setPos(mouse_point.x())
|
@@ -168,27 +174,34 @@ class Crosshair(QObject):
|
|
168
174
|
x = 10**x
|
169
175
|
if self.is_log_y:
|
170
176
|
y = 10**y
|
171
|
-
|
177
|
+
x_snap_values, y_snap_values = self.snap_to_data(x, y)
|
178
|
+
if x_snap_values is None or y_snap_values is None:
|
179
|
+
return
|
180
|
+
if all(v is None for v in x_snap_values.values()) or all(
|
181
|
+
v is None for v in y_snap_values.values()
|
182
|
+
):
|
183
|
+
# not sure how we got here, but just to be safe...
|
184
|
+
return
|
172
185
|
|
173
186
|
for item in self.plot_item.items:
|
174
187
|
if isinstance(item, pg.PlotDataItem):
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
)
|
188
|
+
name = item.name()
|
189
|
+
x, y = x_snap_values[name], y_snap_values[name]
|
190
|
+
if x is None or y is None:
|
191
|
+
continue
|
192
|
+
self.marker_moved_1d[name].setData([x], [y])
|
193
|
+
coordinate_to_emit = (name, round(x, self.precision), round(y, self.precision))
|
181
194
|
self.coordinatesChanged1D.emit(coordinate_to_emit)
|
182
|
-
for i, y_val in enumerate(y_values):
|
183
|
-
self.marker_moved_1d[i].setData(
|
184
|
-
[x if not self.is_log_x else np.log10(x)],
|
185
|
-
[y_val if not self.is_log_y else np.log10(y_val)],
|
186
|
-
)
|
187
195
|
elif isinstance(item, pg.ImageItem):
|
188
|
-
|
189
|
-
|
190
|
-
|
196
|
+
name = item.config.monitor
|
197
|
+
x, y = x_snap_values[name], y_snap_values[name]
|
198
|
+
if x is None or y is None:
|
199
|
+
continue
|
200
|
+
self.marker_2d.setPos([x, y])
|
201
|
+
coordinate_to_emit = (name, x, y)
|
191
202
|
self.coordinatesChanged2D.emit(coordinate_to_emit)
|
203
|
+
else:
|
204
|
+
continue
|
192
205
|
|
193
206
|
def mouse_clicked(self, event):
|
194
207
|
"""Handles the mouse clicked event, updating the crosshair position and emitting signals.
|
@@ -196,7 +209,11 @@ class Crosshair(QObject):
|
|
196
209
|
Args:
|
197
210
|
event: The mouse clicked event
|
198
211
|
"""
|
199
|
-
|
212
|
+
|
213
|
+
# we only accept left mouse clicks
|
214
|
+
if event.button() != Qt.MouseButton.LeftButton:
|
215
|
+
return
|
216
|
+
self.update_markers()
|
200
217
|
if self.plot_item.vb.sceneBoundingRect().contains(event._scenePos):
|
201
218
|
mouse_point = self.plot_item.vb.mapSceneToView(event._scenePos)
|
202
219
|
x, y = mouse_point.x(), mouse_point.y()
|
@@ -205,31 +222,55 @@ class Crosshair(QObject):
|
|
205
222
|
x = 10**x
|
206
223
|
if self.is_log_y:
|
207
224
|
y = 10**y
|
208
|
-
|
225
|
+
x_snap_values, y_snap_values = self.snap_to_data(x, y)
|
226
|
+
|
227
|
+
if x_snap_values is None or y_snap_values is None:
|
228
|
+
return
|
229
|
+
if all(v is None for v in x_snap_values.values()) or all(
|
230
|
+
v is None for v in y_snap_values.values()
|
231
|
+
):
|
232
|
+
# not sure how we got here, but just to be safe...
|
233
|
+
return
|
209
234
|
|
210
235
|
for item in self.plot_item.items:
|
211
236
|
if isinstance(item, pg.PlotDataItem):
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
)
|
237
|
+
name = item.name()
|
238
|
+
x, y = x_snap_values[name], y_snap_values[name]
|
239
|
+
if x is None or y is None:
|
240
|
+
continue
|
241
|
+
self.marker_clicked_1d[name].setData([x], [y])
|
242
|
+
coordinate_to_emit = (name, round(x, self.precision), round(y, self.precision))
|
218
243
|
self.coordinatesClicked1D.emit(coordinate_to_emit)
|
219
|
-
for i, y_val in enumerate(y_values):
|
220
|
-
for marker in self.marker_clicked_1d[i]:
|
221
|
-
marker.setData(
|
222
|
-
[x if not self.is_log_x else np.log10(x)],
|
223
|
-
[y_val if not self.is_log_y else np.log10(y_val)],
|
224
|
-
)
|
225
244
|
elif isinstance(item, pg.ImageItem):
|
226
|
-
|
227
|
-
|
228
|
-
|
245
|
+
name = item.config.monitor
|
246
|
+
x, y = x_snap_values[name], y_snap_values[name]
|
247
|
+
if x is None or y is None:
|
248
|
+
continue
|
249
|
+
self.marker_2d.setPos([x, y])
|
250
|
+
coordinate_to_emit = (name, x, y)
|
229
251
|
self.coordinatesClicked2D.emit(coordinate_to_emit)
|
230
|
-
|
252
|
+
else:
|
253
|
+
continue
|
254
|
+
|
255
|
+
def clear_markers(self):
|
256
|
+
"""Clears the markers from the plot."""
|
257
|
+
for marker in self.marker_moved_1d.values():
|
258
|
+
marker.clear()
|
259
|
+
for marker in self.marker_clicked_1d.values():
|
260
|
+
marker.clear()
|
231
261
|
|
232
262
|
def check_log(self):
|
233
263
|
"""Checks if the x or y axis is in log scale and updates the internal state accordingly."""
|
234
264
|
self.is_log_x = self.plot_item.ctrl.logXCheck.isChecked()
|
235
265
|
self.is_log_y = self.plot_item.ctrl.logYCheck.isChecked()
|
266
|
+
self.clear_markers()
|
267
|
+
|
268
|
+
def check_derivatives(self):
|
269
|
+
"""Checks if the derivatives are enabled and updates the internal state accordingly."""
|
270
|
+
self.is_derivative = self.plot_item.ctrl.derivativeCheck.isChecked()
|
271
|
+
self.clear_markers()
|
272
|
+
|
273
|
+
def cleanup(self):
|
274
|
+
self.v_line.deleteLater()
|
275
|
+
self.h_line.deleteLater()
|
276
|
+
self.clear_markers()
|
@@ -4,9 +4,11 @@ from typing import Literal, Optional
|
|
4
4
|
|
5
5
|
import pyqtgraph as pg
|
6
6
|
from pydantic import BaseModel, Field
|
7
|
+
from qtpy.QtCore import Signal, Slot
|
7
8
|
from qtpy.QtWidgets import QWidget
|
8
9
|
|
9
10
|
from bec_widgets.utils import BECConnector, ConnectionConfig
|
11
|
+
from bec_widgets.utils.crosshair import Crosshair
|
10
12
|
|
11
13
|
|
12
14
|
class AxisConfig(BaseModel):
|
@@ -41,7 +43,21 @@ class SubplotConfig(ConnectionConfig):
|
|
41
43
|
)
|
42
44
|
|
43
45
|
|
46
|
+
class BECViewBox(pg.ViewBox):
|
47
|
+
|
48
|
+
def itemBoundsChanged(self, item):
|
49
|
+
self._itemBoundsCache.pop(item, None)
|
50
|
+
if (self.state["autoRange"][0] is not False) or (self.state["autoRange"][1] is not False):
|
51
|
+
# check if the call is coming from a mouse-move event
|
52
|
+
if hasattr(item, "skip_auto_range") and item.skip_auto_range:
|
53
|
+
return
|
54
|
+
self._autoRangeNeedsUpdate = True
|
55
|
+
self.update()
|
56
|
+
|
57
|
+
|
44
58
|
class BECPlotBase(BECConnector, pg.GraphicsLayout):
|
59
|
+
crosshair_coordinates_changed = Signal(tuple)
|
60
|
+
crosshair_coordinates_clicked = Signal(tuple)
|
45
61
|
USER_ACCESS = [
|
46
62
|
"_config_dict",
|
47
63
|
"set",
|
@@ -73,9 +89,13 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
|
|
73
89
|
pg.GraphicsLayout.__init__(self, parent)
|
74
90
|
|
75
91
|
self.figure = parent_figure
|
76
|
-
|
92
|
+
|
93
|
+
# self.plot_item = self.addPlot(row=0, col=0)
|
94
|
+
self.plot_item = pg.PlotItem(viewBox=BECViewBox(parent=self, enableMenu=True), parent=self)
|
95
|
+
self.addItem(self.plot_item, row=0, col=0)
|
77
96
|
|
78
97
|
self.add_legend()
|
98
|
+
self.crosshair = None
|
79
99
|
|
80
100
|
def set(self, **kwargs) -> None:
|
81
101
|
"""
|
@@ -304,6 +324,40 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
|
|
304
324
|
"""
|
305
325
|
self.plot_item.enableAutoRange(axis, enabled)
|
306
326
|
|
327
|
+
def hook_crosshair(self) -> None:
|
328
|
+
"""Hook the crosshair to all plots."""
|
329
|
+
if self.crosshair is None:
|
330
|
+
self.crosshair = Crosshair(self.plot_item, precision=3)
|
331
|
+
self.crosshair.coordinatesChanged1D.connect(self.crosshair_coordinates_changed)
|
332
|
+
self.crosshair.coordinatesClicked1D.connect(self.crosshair_coordinates_clicked)
|
333
|
+
self.crosshair.coordinatesChanged2D.connect(self.crosshair_coordinates_changed)
|
334
|
+
self.crosshair.coordinatesClicked2D.connect(self.crosshair_coordinates_clicked)
|
335
|
+
|
336
|
+
def unhook_crosshair(self) -> None:
|
337
|
+
"""Unhook the crosshair from all plots."""
|
338
|
+
if self.crosshair is not None:
|
339
|
+
self.crosshair.coordinatesChanged1D.disconnect(self.crosshair_coordinates_changed)
|
340
|
+
self.crosshair.coordinatesClicked1D.disconnect(self.crosshair_coordinates_clicked)
|
341
|
+
self.crosshair.coordinatesChanged2D.disconnect(self.crosshair_coordinates_changed)
|
342
|
+
self.crosshair.coordinatesClicked2D.disconnect(self.crosshair_coordinates_clicked)
|
343
|
+
self.crosshair.cleanup()
|
344
|
+
self.crosshair.deleteLater()
|
345
|
+
self.crosshair = None
|
346
|
+
|
347
|
+
def toggle_crosshair(self) -> None:
|
348
|
+
"""Toggle the crosshair on all plots."""
|
349
|
+
if self.crosshair is None:
|
350
|
+
return self.hook_crosshair()
|
351
|
+
|
352
|
+
self.unhook_crosshair()
|
353
|
+
|
354
|
+
@Slot()
|
355
|
+
def reset(self) -> None:
|
356
|
+
"""Reset the plot widget."""
|
357
|
+
if self.crosshair is not None:
|
358
|
+
self.crosshair.clear_markers()
|
359
|
+
self.crosshair.update_markers()
|
360
|
+
|
307
361
|
def export(self):
|
308
362
|
"""Show the Export Dialog of the plot widget."""
|
309
363
|
scene = self.plot_item.scene()
|
@@ -317,6 +371,7 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
|
|
317
371
|
|
318
372
|
def cleanup_pyqtgraph(self):
|
319
373
|
"""Cleanup pyqtgraph items."""
|
374
|
+
self.unhook_crosshair()
|
320
375
|
item = self.plot_item
|
321
376
|
item.vb.menu.close()
|
322
377
|
item.vb.menu.deleteLater()
|
@@ -77,6 +77,7 @@ class BECWaveform(BECPlotBase):
|
|
77
77
|
dap_params_update = pyqtSignal(dict)
|
78
78
|
dap_summary_update = pyqtSignal(dict)
|
79
79
|
autorange_signal = pyqtSignal()
|
80
|
+
new_scan = pyqtSignal()
|
80
81
|
|
81
82
|
def __init__(
|
82
83
|
self,
|
@@ -375,6 +376,10 @@ class BECWaveform(BECPlotBase):
|
|
375
376
|
if len(self.curves) > 0:
|
376
377
|
# validate all curves
|
377
378
|
for curve in self.curves:
|
379
|
+
if not isinstance(curve, BECCurve):
|
380
|
+
continue
|
381
|
+
if curve.config.source == "custom":
|
382
|
+
continue
|
378
383
|
self._validate_x_axis_behaviour(curve.config.signals.y.name, x_name, x_entry, False)
|
379
384
|
self._switch_x_axis_item(
|
380
385
|
f"{x_name}-{x_entry}"
|
@@ -382,9 +387,12 @@ class BECWaveform(BECPlotBase):
|
|
382
387
|
else x_name
|
383
388
|
)
|
384
389
|
for curve_id, curve_config in zip(curve_ids, curve_configs):
|
385
|
-
if curve_config.signals
|
386
|
-
|
387
|
-
|
390
|
+
if curve_config.signals is None:
|
391
|
+
continue
|
392
|
+
if curve_config.signals.x is None:
|
393
|
+
continue
|
394
|
+
curve_config.signals.x.name = x_name
|
395
|
+
curve_config.signals.x.entry = x_entry
|
388
396
|
self.remove_curve(curve_id)
|
389
397
|
self.add_curve_by_config(curve_config)
|
390
398
|
|
@@ -408,23 +416,6 @@ class BECWaveform(BECPlotBase):
|
|
408
416
|
"""
|
409
417
|
self.plot_item.enableAutoRange(axis, enabled)
|
410
418
|
|
411
|
-
@Slot()
|
412
|
-
def auto_range(self):
|
413
|
-
self.plot_item.autoRange()
|
414
|
-
|
415
|
-
def set_auto_range(self, enabled: bool, axis: str = "xy"):
|
416
|
-
"""
|
417
|
-
Set the auto range of the plot widget.
|
418
|
-
|
419
|
-
Args:
|
420
|
-
enabled(bool): If True, enable the auto range.
|
421
|
-
axis(str, optional): The axis to enable the auto range.
|
422
|
-
- "xy": Enable auto range for both x and y axis.
|
423
|
-
- "x": Enable auto range for x axis.
|
424
|
-
- "y": Enable auto range for y axis.
|
425
|
-
"""
|
426
|
-
self.plot_item.enableAutoRange(axis, enabled)
|
427
|
-
|
428
419
|
def add_curve_custom(
|
429
420
|
self,
|
430
421
|
x: list | np.ndarray,
|
@@ -935,6 +926,8 @@ class BECWaveform(BECPlotBase):
|
|
935
926
|
return
|
936
927
|
|
937
928
|
if current_scan_id != self.scan_id:
|
929
|
+
self.reset()
|
930
|
+
self.new_scan.emit()
|
938
931
|
self.set_auto_range(True, "xy")
|
939
932
|
self.old_scan_id = self.scan_id
|
940
933
|
self.scan_id = current_scan_id
|
@@ -5,11 +5,17 @@ from typing import Literal
|
|
5
5
|
|
6
6
|
import numpy as np
|
7
7
|
import pyqtgraph as pg
|
8
|
+
from qtpy.QtCore import Signal
|
8
9
|
from qtpy.QtWidgets import QVBoxLayout, QWidget
|
9
10
|
|
10
11
|
from bec_widgets.qt_utils.error_popups import SafeSlot, WarningPopupUtility
|
11
12
|
from bec_widgets.qt_utils.settings_dialog import SettingsDialog
|
12
|
-
from bec_widgets.qt_utils.toolbar import
|
13
|
+
from bec_widgets.qt_utils.toolbar import (
|
14
|
+
IconAction,
|
15
|
+
MaterialIconAction,
|
16
|
+
ModularToolBar,
|
17
|
+
SeparatorAction,
|
18
|
+
)
|
13
19
|
from bec_widgets.utils.bec_widget import BECWidget
|
14
20
|
from bec_widgets.widgets.figure import BECFigure
|
15
21
|
from bec_widgets.widgets.figure.plots.axis_settings import AxisSettings
|
@@ -51,6 +57,14 @@ class BECWaveformWidget(BECWidget, QWidget):
|
|
51
57
|
"export",
|
52
58
|
"export_to_matplotlib",
|
53
59
|
]
|
60
|
+
scan_signal_update = Signal()
|
61
|
+
async_signal_update = Signal()
|
62
|
+
dap_params_update = Signal(dict)
|
63
|
+
dap_summary_update = Signal(dict)
|
64
|
+
autorange_signal = Signal()
|
65
|
+
new_scan = Signal()
|
66
|
+
crosshair_coordinates_changed = Signal(tuple)
|
67
|
+
crosshair_coordinates_clicked = Signal(tuple)
|
54
68
|
|
55
69
|
def __init__(
|
56
70
|
self,
|
@@ -93,8 +107,11 @@ class BECWaveformWidget(BECWidget, QWidget):
|
|
93
107
|
"fit_params": IconAction(
|
94
108
|
icon_path="fitting_parameters.svg", tooltip="Open Fitting Parameters"
|
95
109
|
),
|
96
|
-
"axis_settings":
|
97
|
-
|
110
|
+
"axis_settings": MaterialIconAction(
|
111
|
+
icon_name="settings", tooltip="Open Configuration Dialog"
|
112
|
+
),
|
113
|
+
"crosshair": MaterialIconAction(
|
114
|
+
icon_name="point_scan", tooltip="Show Crosshair", checkable=True
|
98
115
|
),
|
99
116
|
},
|
100
117
|
target_widget=self,
|
@@ -110,8 +127,19 @@ class BECWaveformWidget(BECWidget, QWidget):
|
|
110
127
|
|
111
128
|
self.config = config
|
112
129
|
|
130
|
+
self.hook_waveform_signals()
|
113
131
|
self._hook_actions()
|
114
132
|
|
133
|
+
def hook_waveform_signals(self):
|
134
|
+
self.waveform.scan_signal_update.connect(self.scan_signal_update)
|
135
|
+
self.waveform.async_signal_update.connect(self.async_signal_update)
|
136
|
+
self.waveform.dap_params_update.connect(self.dap_params_update)
|
137
|
+
self.waveform.dap_summary_update.connect(self.dap_summary_update)
|
138
|
+
self.waveform.autorange_signal.connect(self.autorange_signal)
|
139
|
+
self.waveform.new_scan.connect(self.new_scan)
|
140
|
+
self.waveform.crosshair_coordinates_changed.connect(self.crosshair_coordinates_changed)
|
141
|
+
self.waveform.crosshair_coordinates_clicked.connect(self.crosshair_coordinates_clicked)
|
142
|
+
|
115
143
|
def _hook_actions(self):
|
116
144
|
self.toolbar.widgets["save"].action.triggered.connect(self.export)
|
117
145
|
self.toolbar.widgets["matplotlib"].action.triggered.connect(self.export_to_matplotlib)
|
@@ -123,6 +151,7 @@ class BECWaveformWidget(BECWidget, QWidget):
|
|
123
151
|
self.toolbar.widgets["curves"].action.triggered.connect(self.show_curve_settings)
|
124
152
|
self.toolbar.widgets["fit_params"].action.triggered.connect(self.show_fit_summary_dialog)
|
125
153
|
self.toolbar.widgets["axis_settings"].action.triggered.connect(self.show_axis_settings)
|
154
|
+
self.toolbar.widgets["crosshair"].action.triggered.connect(self.waveform.toggle_crosshair)
|
126
155
|
# self.toolbar.widgets["import"].action.triggered.connect(
|
127
156
|
# lambda: self.load_config(path=None, gui=True)
|
128
157
|
# )
|
@@ -1,12 +1,12 @@
|
|
1
1
|
.gitignore,sha256=cMQ1MLmnoR88aMCCJwUyfoTnufzl4-ckmHtlFUqHcT4,3253
|
2
|
-
.gitlab-ci.yml,sha256=
|
2
|
+
.gitlab-ci.yml,sha256=9dZ_EvimZrDzG8MtII0qtBYMM_Nc6xqh2iFRds0rlvE,8370
|
3
3
|
.pylintrc,sha256=eeY8YwSI74oFfq6IYIbCqnx3Vk8ZncKaatv96n_Y8Rs,18544
|
4
4
|
.readthedocs.yaml,sha256=aSOc277LqXcsTI6lgvm_JY80lMlr69GbPKgivua2cS0,603
|
5
|
-
CHANGELOG.md,sha256=
|
5
|
+
CHANGELOG.md,sha256=JevFb8k7ihFuKfooyCqcyptPCHkn6vBmWWLI6YOyNHg,6717
|
6
6
|
LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
7
|
-
PKG-INFO,sha256=
|
7
|
+
PKG-INFO,sha256=xRvEZ02kp6ROhkDYvw-jCEJA0QqyjeTRUHYJnbgxgXY,1325
|
8
8
|
README.md,sha256=Od69x-RS85Hph0-WwWACwal4yUd67XkEn4APEfHhHFw,2649
|
9
|
-
pyproject.toml,sha256=
|
9
|
+
pyproject.toml,sha256=S3RcibQIzFA7_Ydzi3rai8TL0k5643CILs3-IMEJ_8s,2416
|
10
10
|
.git_hooks/pre-commit,sha256=n3RofIZHJl8zfJJIUomcMyYGFi_rwq4CC19z0snz3FI,286
|
11
11
|
.gitlab/issue_templates/bug_report_template.md,sha256=gAuyEwl7XlnebBrkiJ9AqffSNOywmr8vygUFWKTuQeI,386
|
12
12
|
.gitlab/issue_templates/documentation_update_template.md,sha256=FHLdb3TS_D9aL4CYZCjyXSulbaW5mrN2CmwTaeLPbNw,860
|
@@ -85,7 +85,7 @@ bec_widgets/qt_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
85
85
|
bec_widgets/qt_utils/error_popups.py,sha256=y9gKKWaafp468ioHr96nBhf02ZpEgjDc-BAVOTWh-e8,7680
|
86
86
|
bec_widgets/qt_utils/redis_message_waiter.py,sha256=fvL_QgC0cTDv_FPJdRyp5AKjf401EJU4z3r38p47ydY,1745
|
87
87
|
bec_widgets/qt_utils/settings_dialog.py,sha256=NhtzTer_xzlB2lLLrGklkI1QYLJEWQpJoZbCz4o5daI,3645
|
88
|
-
bec_widgets/qt_utils/toolbar.py,sha256=
|
88
|
+
bec_widgets/qt_utils/toolbar.py,sha256=A5vXNlF7kxvdcPCmRvsy9P3f6M5NomlT2PgtUmnt94U,7669
|
89
89
|
bec_widgets/utils/__init__.py,sha256=1930ji1Jj6dVuY81Wd2kYBhHYNV-2R0bN_L4o9zBj1U,533
|
90
90
|
bec_widgets/utils/bec_connector.py,sha256=SivHKXVyNVqeu3kCXYEPpbleTVw8g1cW0FKq1QrQgco,9987
|
91
91
|
bec_widgets/utils/bec_designer.py,sha256=ak3G8FdojUPjVBBwdPXw7tN5P2Uxr-SSoQt394jXeAA,4308
|
@@ -94,7 +94,7 @@ bec_widgets/utils/bec_table.py,sha256=nA2b8ukSeUfquFMAxGrUVOqdrzMoDYD6O_4EYbOG2z
|
|
94
94
|
bec_widgets/utils/bec_widget.py,sha256=Bo2v1aP7rgSAQajW8GBJbI3iovTn_hGCsmeFMo7bT10,707
|
95
95
|
bec_widgets/utils/colors.py,sha256=hNIi99EpMv3t3hTJIL2jBe5nzh5f2fuCyEKXEPWSQSc,10501
|
96
96
|
bec_widgets/utils/container_utils.py,sha256=m3VUyAYmSWkEwApP9tBvKxPYVtc2kHw4toxIpMryJy4,1495
|
97
|
-
bec_widgets/utils/crosshair.py,sha256=
|
97
|
+
bec_widgets/utils/crosshair.py,sha256=ywj4Pr2Xx8tFsD5qrHIocanKlNqDd51ElbEfxenuYM0,11363
|
98
98
|
bec_widgets/utils/entry_validator.py,sha256=3skJIsUwTYicT76AMHm_M78RiWtUgyD2zb-Rxo2HdHQ,1313
|
99
99
|
bec_widgets/utils/generate_designer_plugin.py,sha256=eidqauS8YLgoxkPntPL0oSG_lYqI2D7fSyOZvOtCU_U,5891
|
100
100
|
bec_widgets/utils/layout_manager.py,sha256=H0nKsIMaPxRkof1MEXlSmW6w1dFxA6astaGzf4stI84,4727
|
@@ -162,7 +162,7 @@ bec_widgets/widgets/figure/figure.py,sha256=yujtlDj5NutRJ0gfHMkCVXvBNj5r2z3pb3gw
|
|
162
162
|
bec_widgets/widgets/figure/plots/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
163
163
|
bec_widgets/widgets/figure/plots/axis_settings.py,sha256=QxRpQwgfBr1H0HTjfOpiXi_-n8I0BaZhS8LRXNeVfFg,3544
|
164
164
|
bec_widgets/widgets/figure/plots/axis_settings.ui,sha256=a2qIuK9lyi9HCyrSvPr6wxzmm1FymaWcpmyOhMIiFt8,11013
|
165
|
-
bec_widgets/widgets/figure/plots/plot_base.py,sha256=
|
165
|
+
bec_widgets/widgets/figure/plots/plot_base.py,sha256=30KsfR6SlbeuV_t96kILcPU17vf_qMHajlvFwfBvN2Y,13593
|
166
166
|
bec_widgets/widgets/figure/plots/image/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
167
167
|
bec_widgets/widgets/figure/plots/image/image.py,sha256=y2MqgJv6Njv-huDN_exn0Fq1rAh5vs_assKCKHgQH6I,24941
|
168
168
|
bec_widgets/widgets/figure/plots/image/image_item.py,sha256=RljjbkqJEr2cKDlqj1j5GQ1h89jpqOV-OpFz1TbED8I,10937
|
@@ -170,7 +170,7 @@ bec_widgets/widgets/figure/plots/image/image_processor.py,sha256=GeTtWjbldy6VejM
|
|
170
170
|
bec_widgets/widgets/figure/plots/motor_map/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
171
171
|
bec_widgets/widgets/figure/plots/motor_map/motor_map.py,sha256=wgARzsm98Y8SHPPwVp1LzNlXCxKEi6a8by8yYzIWsbY,18319
|
172
172
|
bec_widgets/widgets/figure/plots/waveform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
173
|
-
bec_widgets/widgets/figure/plots/waveform/waveform.py,sha256
|
173
|
+
bec_widgets/widgets/figure/plots/waveform/waveform.py,sha256=uW9GQHWiVd3q63-yXjQ2uewDiiQ3t226swYTPQhaGoY,51579
|
174
174
|
bec_widgets/widgets/figure/plots/waveform/waveform_curve.py,sha256=ZwRxSfPHbMWEvgUC-mL2orpZvtxR-DcrYAFikkdWEzk,8654
|
175
175
|
bec_widgets/widgets/image/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
176
176
|
bec_widgets/widgets/image/bec_image_widget.pyproject,sha256=PHisdBo5_5UCApd27GkizzqgfdjsDx2bFZa_p9LiSW8,30
|
@@ -244,7 +244,7 @@ bec_widgets/widgets/waveform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
244
244
|
bec_widgets/widgets/waveform/bec_waveform_widget.pyproject,sha256=GLD8GN9dXx9wNbtnevrxqqcwk7vKV-Uv8QYSycdaoaI,33
|
245
245
|
bec_widgets/widgets/waveform/bec_waveform_widget_plugin.py,sha256=f8zgTQGp6hbfxOAnMqLvaI3N9KwxEMtUKkZQZBAL_rw,1514
|
246
246
|
bec_widgets/widgets/waveform/register_bec_waveform_widget.py,sha256=qZHVZH_lP2hvzkG1Ra0EyrXlMeLkRCy0aceH-bfJ1cs,490
|
247
|
-
bec_widgets/widgets/waveform/waveform_widget.py,sha256=
|
247
|
+
bec_widgets/widgets/waveform/waveform_widget.py,sha256=nzRDgo_qfmmp_aSGbXhwy2U7yq5cR0pHdjM0YDCvTKE,20409
|
248
248
|
bec_widgets/widgets/waveform/waveform_popups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
249
249
|
bec_widgets/widgets/waveform/waveform_popups/curve_dialog/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
250
250
|
bec_widgets/widgets/waveform/waveform_popups/curve_dialog/curve_dialog.py,sha256=S1j44i1xxJtmCcNtbOsxF8XdklMPsG9t4-1DZ2YfOPw,13128
|
@@ -366,7 +366,7 @@ tests/unit_tests/test_bec_status_box.py,sha256=gZdjyy9DNuUP9UwleTLj2Dp5HUImiqnkH
|
|
366
366
|
tests/unit_tests/test_client_utils.py,sha256=CBdWIVJ_UiyFzTJnX3XJm4PGw2uXhFvRCP_Y9ifckbw,2630
|
367
367
|
tests/unit_tests/test_color_map_selector.py,sha256=dTsizpT7TUpX2AEWIc0v09KPOryhWepFXFI9duQ3NF8,1497
|
368
368
|
tests/unit_tests/test_color_validation.py,sha256=xbFbtFDia36XLgaNrX2IwvAX3IDC_Odpj5BGoJSgiIE,2389
|
369
|
-
tests/unit_tests/test_crosshair.py,sha256=
|
369
|
+
tests/unit_tests/test_crosshair.py,sha256=POB9EFCO9gCCAEHgddiyQV28C4rMxIosHX0aR-zr8gg,4595
|
370
370
|
tests/unit_tests/test_device_browser.py,sha256=LOvvUFepNcoQptX7d6oUoomfg5nyjjdCMJ2iHviF9aQ,2948
|
371
371
|
tests/unit_tests/test_device_input_base.py,sha256=LY-3adMb2xM9pBiP6V2bAJF_65csCe2Xfaq5ArVEE1E,2859
|
372
372
|
tests/unit_tests/test_device_input_widgets.py,sha256=Y3mc_EaeQAPxpj6DijvxLLyMPSxnaNN86KhIL4ASO3E,5821
|
@@ -399,8 +399,8 @@ tests/unit_tests/test_configs/config_device_no_entry.yaml,sha256=hdvue9KLc_kfNzG
|
|
399
399
|
tests/unit_tests/test_configs/config_scan.yaml,sha256=vo484BbWOjA_e-h6bTjSV9k7QaQHrlAvx-z8wtY-P4E,1915
|
400
400
|
tests/unit_tests/test_msgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
401
401
|
tests/unit_tests/test_msgs/available_scans_message.py,sha256=m_z97hIrjHXXMa2Ex-UvsPmTxOYXfjxyJaGkIY6StTY,46532
|
402
|
-
bec_widgets-0.96.
|
403
|
-
bec_widgets-0.96.
|
404
|
-
bec_widgets-0.96.
|
405
|
-
bec_widgets-0.96.
|
406
|
-
bec_widgets-0.96.
|
402
|
+
bec_widgets-0.96.2.dist-info/METADATA,sha256=xRvEZ02kp6ROhkDYvw-jCEJA0QqyjeTRUHYJnbgxgXY,1325
|
403
|
+
bec_widgets-0.96.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
404
|
+
bec_widgets-0.96.2.dist-info/entry_points.txt,sha256=3otEkCdDB9LZJuBLzG4pFLK5Di0CVybN_12IsZrQ-58,166
|
405
|
+
bec_widgets-0.96.2.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
406
|
+
bec_widgets-0.96.2.dist-info/RECORD,,
|
pyproject.toml
CHANGED
@@ -1,19 +1,40 @@
|
|
1
1
|
# pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
|
2
2
|
import numpy as np
|
3
|
-
import
|
3
|
+
import pytest
|
4
4
|
from qtpy.QtCore import QPointF
|
5
5
|
|
6
|
-
from bec_widgets.
|
6
|
+
from bec_widgets.widgets.image.image_widget import BECImageWidget
|
7
|
+
from bec_widgets.widgets.waveform.waveform_widget import BECWaveformWidget
|
7
8
|
|
9
|
+
from .client_mocks import mocked_client
|
8
10
|
|
9
|
-
|
10
|
-
# Create a PlotWidget and add a PlotItem
|
11
|
-
plot_widget = pg.PlotWidget(title="1D PlotWidget with multiple curves")
|
12
|
-
plot_item = plot_widget.getPlotItem()
|
13
|
-
plot_item.plot([1, 2, 3], [4, 5, 6])
|
11
|
+
# pylint: disable = redefined-outer-name
|
14
12
|
|
15
|
-
|
16
|
-
|
13
|
+
|
14
|
+
@pytest.fixture
|
15
|
+
def plot_widget_with_crosshair(qtbot, mocked_client):
|
16
|
+
widget = BECWaveformWidget(client=mocked_client())
|
17
|
+
widget.plot(x=[1, 2, 3], y=[4, 5, 6])
|
18
|
+
widget.waveform.hook_crosshair()
|
19
|
+
qtbot.addWidget(widget)
|
20
|
+
qtbot.waitExposed(widget)
|
21
|
+
|
22
|
+
yield widget.waveform.crosshair, widget.waveform.plot_item
|
23
|
+
|
24
|
+
|
25
|
+
@pytest.fixture
|
26
|
+
def image_widget_with_crosshair(qtbot, mocked_client):
|
27
|
+
widget = BECImageWidget(client=mocked_client())
|
28
|
+
widget._image.add_custom_image(name="test", data=np.random.random((100, 200)))
|
29
|
+
widget._image.hook_crosshair()
|
30
|
+
qtbot.addWidget(widget)
|
31
|
+
qtbot.waitExposed(widget)
|
32
|
+
|
33
|
+
yield widget._image.crosshair, widget._image.plot_item
|
34
|
+
|
35
|
+
|
36
|
+
def test_mouse_moved_lines(plot_widget_with_crosshair):
|
37
|
+
crosshair, plot_item = plot_widget_with_crosshair
|
17
38
|
|
18
39
|
# Connect the signals to slots that will store the emitted values
|
19
40
|
emitted_values_1D = []
|
@@ -32,14 +53,8 @@ def test_mouse_moved_lines(qtbot):
|
|
32
53
|
assert crosshair.h_line.pos().y() == 5
|
33
54
|
|
34
55
|
|
35
|
-
def test_mouse_moved_signals(
|
36
|
-
|
37
|
-
plot_widget = pg.PlotWidget(title="1D PlotWidget with multiple curves")
|
38
|
-
plot_item = plot_widget.getPlotItem()
|
39
|
-
plot_item.plot([1, 2, 3], [4, 5, 6])
|
40
|
-
|
41
|
-
# Create a Crosshair instance
|
42
|
-
crosshair = Crosshair(plot_item=plot_item, precision=2)
|
56
|
+
def test_mouse_moved_signals(plot_widget_with_crosshair):
|
57
|
+
crosshair, plot_item = plot_widget_with_crosshair
|
43
58
|
|
44
59
|
# Create a slot that will store the emitted values as tuples
|
45
60
|
emitted_values_1D = []
|
@@ -59,17 +74,11 @@ def test_mouse_moved_signals(qtbot):
|
|
59
74
|
crosshair.mouse_moved(event_mock)
|
60
75
|
|
61
76
|
# Assert the expected behavior
|
62
|
-
assert emitted_values_1D == [(2,
|
63
|
-
|
77
|
+
assert emitted_values_1D == [("Curve 1", 2, 5)]
|
64
78
|
|
65
|
-
def test_mouse_moved_signals_outside(qtbot):
|
66
|
-
# Create a PlotWidget and add a PlotItem
|
67
|
-
plot_widget = pg.PlotWidget(title="1D PlotWidget with multiple curves")
|
68
|
-
plot_item = plot_widget.getPlotItem()
|
69
|
-
plot_item.plot([1, 2, 3], [4, 5, 6])
|
70
79
|
|
71
|
-
|
72
|
-
crosshair
|
80
|
+
def test_mouse_moved_signals_outside(plot_widget_with_crosshair):
|
81
|
+
crosshair, plot_item = plot_widget_with_crosshair
|
73
82
|
|
74
83
|
# Create a slot that will store the emitted values as tuples
|
75
84
|
emitted_values_1D = []
|
@@ -92,17 +101,9 @@ def test_mouse_moved_signals_outside(qtbot):
|
|
92
101
|
assert emitted_values_1D == []
|
93
102
|
|
94
103
|
|
95
|
-
def test_mouse_moved_signals_2D(
|
96
|
-
|
104
|
+
def test_mouse_moved_signals_2D(image_widget_with_crosshair):
|
105
|
+
crosshair, plot_item = image_widget_with_crosshair
|
97
106
|
|
98
|
-
# Create a PlotWidget and add a PlotItem
|
99
|
-
plot_widget = pg.PlotWidget(title="2D plot with crosshair and ROI square")
|
100
|
-
data_2D = np.random.random((100, 200))
|
101
|
-
plot_item = plot_widget.getPlotItem()
|
102
|
-
image_item = pg.ImageItem(data_2D)
|
103
|
-
plot_item.addItem(image_item)
|
104
|
-
# Create a Crosshair instance
|
105
|
-
crosshair = Crosshair(plot_item=plot_item)
|
106
107
|
# Create a slot that will store the emitted values as tuples
|
107
108
|
emitted_values_2D = []
|
108
109
|
|
@@ -118,20 +119,12 @@ def test_mouse_moved_signals_2D(qtbot):
|
|
118
119
|
# Call the mouse_moved method
|
119
120
|
crosshair.mouse_moved(event_mock)
|
120
121
|
# Assert the expected behavior
|
121
|
-
assert emitted_values_2D == [(22.0, 55.0)]
|
122
|
+
assert emitted_values_2D == [("test", 22.0, 55.0)]
|
122
123
|
|
123
124
|
|
124
|
-
def test_mouse_moved_signals_2D_outside(
|
125
|
-
|
125
|
+
def test_mouse_moved_signals_2D_outside(image_widget_with_crosshair):
|
126
|
+
crosshair, plot_item = image_widget_with_crosshair
|
126
127
|
|
127
|
-
# Create a PlotWidget and add a PlotItem
|
128
|
-
plot_widget = pg.PlotWidget(title="2D plot with crosshair and ROI square")
|
129
|
-
data_2D = np.random.random((100, 200))
|
130
|
-
plot_item = plot_widget.getPlotItem()
|
131
|
-
image_item = pg.ImageItem(data_2D)
|
132
|
-
plot_item.addItem(image_item)
|
133
|
-
# Create a Crosshair instance
|
134
|
-
crosshair = Crosshair(plot_item=plot_item, precision=2)
|
135
128
|
# Create a slot that will store the emitted values as tuples
|
136
129
|
emitted_values_2D = []
|
137
130
|
|
File without changes
|
File without changes
|
File without changes
|