bec-widgets 0.55.0__py3-none-any.whl → 0.56.1__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 +113 -8
- CHANGELOG.md +34 -28
- PKG-INFO +3 -1
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +28 -38
- bec_widgets/examples/motor_movement/motor_control_compilations.py +1 -7
- bec_widgets/utils/__init__.py +1 -0
- bec_widgets/utils/crosshair.py +13 -9
- bec_widgets/utils/ui_loader.py +58 -0
- bec_widgets/widgets/motor_control/motor_table/motor_table.py +44 -43
- bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +25 -23
- bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +51 -48
- bec_widgets/widgets/spiral_progress_bar/ring.py +5 -5
- {bec_widgets-0.55.0.dist-info → bec_widgets-0.56.1.dist-info}/METADATA +3 -1
- {bec_widgets-0.55.0.dist-info → bec_widgets-0.56.1.dist-info}/RECORD +22 -43
- docs/user/apps.md +1 -26
- pyproject.toml +2 -1
- tests/end-2-end/test_bec_dock_rpc_e2e.py +1 -1
- tests/unit_tests/test_client_utils.py +2 -2
- tests/unit_tests/test_crosshair.py +5 -5
- tests/unit_tests/test_motor_control.py +49 -45
- bec_widgets/examples/eiger_plot/__init__.py +0 -0
- bec_widgets/examples/eiger_plot/eiger_plot.py +0 -307
- bec_widgets/examples/eiger_plot/eiger_plot.ui +0 -207
- bec_widgets/examples/mca_readout/__init__.py +0 -0
- bec_widgets/examples/mca_readout/mca_plot.py +0 -159
- bec_widgets/examples/mca_readout/mca_sim.py +0 -28
- bec_widgets/examples/modular_app/___init__.py +0 -0
- bec_widgets/examples/modular_app/modular.ui +0 -92
- bec_widgets/examples/modular_app/modular_app.py +0 -197
- bec_widgets/examples/motor_movement/config_example.yaml +0 -17
- bec_widgets/examples/motor_movement/csax_bec_config.yaml +0 -10
- bec_widgets/examples/motor_movement/csaxs_config.yaml +0 -17
- bec_widgets/examples/motor_movement/motor_example.py +0 -1344
- bec_widgets/examples/stream_plot/__init__.py +0 -0
- bec_widgets/examples/stream_plot/line_plot.ui +0 -155
- bec_widgets/examples/stream_plot/stream_plot.py +0 -337
- docs/user/apps/modular_app.md +0 -6
- docs/user/apps/motor_app.md +0 -34
- docs/user/apps/motor_app_10fps.gif +0 -0
- docs/user/apps/plot_app.md +0 -6
- tests/unit_tests/test_eiger_plot.py +0 -115
- tests/unit_tests/test_stream_plot.py +0 -158
- {bec_widgets-0.55.0.dist-info → bec_widgets-0.56.1.dist-info}/WHEEL +0 -0
- {bec_widgets-0.55.0.dist-info → bec_widgets-0.56.1.dist-info}/licenses/LICENSE +0 -0
File without changes
|
@@ -1,155 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<ui version="4.0">
|
3
|
-
<class>Form</class>
|
4
|
-
<widget class="QWidget" name="Form">
|
5
|
-
<property name="geometry">
|
6
|
-
<rect>
|
7
|
-
<x>0</x>
|
8
|
-
<y>0</y>
|
9
|
-
<width>845</width>
|
10
|
-
<height>635</height>
|
11
|
-
</rect>
|
12
|
-
</property>
|
13
|
-
<property name="windowTitle">
|
14
|
-
<string>Line Plot</string>
|
15
|
-
</property>
|
16
|
-
<layout class="QGridLayout" name="gridLayout">
|
17
|
-
<item row="0" column="0">
|
18
|
-
<widget class="QSplitter" name="splitter">
|
19
|
-
<property name="orientation">
|
20
|
-
<enum>Qt::Horizontal</enum>
|
21
|
-
</property>
|
22
|
-
<widget class="QSplitter" name="splitter_plot">
|
23
|
-
<property name="sizePolicy">
|
24
|
-
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
25
|
-
<horstretch>1</horstretch>
|
26
|
-
<verstretch>1</verstretch>
|
27
|
-
</sizepolicy>
|
28
|
-
</property>
|
29
|
-
<property name="orientation">
|
30
|
-
<enum>Qt::Vertical</enum>
|
31
|
-
</property>
|
32
|
-
<widget class="GraphicsLayoutWidget" name="glw_plot"/>
|
33
|
-
<widget class="GraphicsLayoutWidget" name="glw_image"/>
|
34
|
-
</widget>
|
35
|
-
<widget class="QWidget" name="">
|
36
|
-
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,1,1,15">
|
37
|
-
<item>
|
38
|
-
<widget class="QPushButton" name="pushButton_generate">
|
39
|
-
<property name="text">
|
40
|
-
<string>Generate 1D and 2D data without stream</string>
|
41
|
-
</property>
|
42
|
-
</widget>
|
43
|
-
</item>
|
44
|
-
<item>
|
45
|
-
<widget class="QGroupBox" name="groupBox">
|
46
|
-
<property name="sizePolicy">
|
47
|
-
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
48
|
-
<horstretch>0</horstretch>
|
49
|
-
<verstretch>0</verstretch>
|
50
|
-
</sizepolicy>
|
51
|
-
</property>
|
52
|
-
<property name="title">
|
53
|
-
<string>1st angle of azimutal segment (deg)</string>
|
54
|
-
</property>
|
55
|
-
<layout class="QHBoxLayout" name="horizontalLayout">
|
56
|
-
<item>
|
57
|
-
<widget class="QDoubleSpinBox" name="doubleSpinBox">
|
58
|
-
<property name="sizePolicy">
|
59
|
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
60
|
-
<horstretch>0</horstretch>
|
61
|
-
<verstretch>0</verstretch>
|
62
|
-
</sizepolicy>
|
63
|
-
</property>
|
64
|
-
<property name="maximum">
|
65
|
-
<double>360.000000000000000</double>
|
66
|
-
</property>
|
67
|
-
<property name="singleStep">
|
68
|
-
<double>0.250000000000000</double>
|
69
|
-
</property>
|
70
|
-
</widget>
|
71
|
-
</item>
|
72
|
-
<item>
|
73
|
-
<widget class="QComboBox" name="comboBox">
|
74
|
-
<property name="sizePolicy">
|
75
|
-
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
76
|
-
<horstretch>0</horstretch>
|
77
|
-
<verstretch>0</verstretch>
|
78
|
-
</sizepolicy>
|
79
|
-
</property>
|
80
|
-
<item>
|
81
|
-
<property name="text">
|
82
|
-
<string>f1amp</string>
|
83
|
-
</property>
|
84
|
-
</item>
|
85
|
-
<item>
|
86
|
-
<property name="text">
|
87
|
-
<string>f2amp</string>
|
88
|
-
</property>
|
89
|
-
</item>
|
90
|
-
<item>
|
91
|
-
<property name="text">
|
92
|
-
<string>f2 phase</string>
|
93
|
-
</property>
|
94
|
-
</item>
|
95
|
-
</widget>
|
96
|
-
</item>
|
97
|
-
</layout>
|
98
|
-
</widget>
|
99
|
-
</item>
|
100
|
-
<item>
|
101
|
-
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
102
|
-
<item>
|
103
|
-
<widget class="QLabel" name="label">
|
104
|
-
<property name="text">
|
105
|
-
<string>Precision</string>
|
106
|
-
</property>
|
107
|
-
</widget>
|
108
|
-
</item>
|
109
|
-
<item>
|
110
|
-
<widget class="QSpinBox" name="spinBox_precision">
|
111
|
-
<property name="value">
|
112
|
-
<number>4</number>
|
113
|
-
</property>
|
114
|
-
</widget>
|
115
|
-
</item>
|
116
|
-
</layout>
|
117
|
-
</item>
|
118
|
-
<item>
|
119
|
-
<widget class="QTableWidget" name="cursor_table">
|
120
|
-
<property name="textElideMode">
|
121
|
-
<enum>Qt::ElideMiddle</enum>
|
122
|
-
</property>
|
123
|
-
<column>
|
124
|
-
<property name="text">
|
125
|
-
<string>Display</string>
|
126
|
-
</property>
|
127
|
-
</column>
|
128
|
-
<column>
|
129
|
-
<property name="text">
|
130
|
-
<string>X</string>
|
131
|
-
</property>
|
132
|
-
</column>
|
133
|
-
<column>
|
134
|
-
<property name="text">
|
135
|
-
<string>Y</string>
|
136
|
-
</property>
|
137
|
-
</column>
|
138
|
-
</widget>
|
139
|
-
</item>
|
140
|
-
</layout>
|
141
|
-
</widget>
|
142
|
-
</widget>
|
143
|
-
</item>
|
144
|
-
</layout>
|
145
|
-
</widget>
|
146
|
-
<customwidgets>
|
147
|
-
<customwidget>
|
148
|
-
<class>GraphicsLayoutWidget</class>
|
149
|
-
<extends>QGraphicsView</extends>
|
150
|
-
<header>pyqtgraph.h</header>
|
151
|
-
</customwidget>
|
152
|
-
</customwidgets>
|
153
|
-
<resources/>
|
154
|
-
<connections/>
|
155
|
-
</ui>
|
@@ -1,337 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import threading
|
3
|
-
import time
|
4
|
-
|
5
|
-
import numpy as np
|
6
|
-
import pyqtgraph
|
7
|
-
import pyqtgraph as pg
|
8
|
-
from bec_lib import messages
|
9
|
-
from bec_lib.endpoints import MessageEndpoints
|
10
|
-
from bec_lib.redis_connector import RedisConnector
|
11
|
-
from pyqtgraph import mkBrush, mkPen
|
12
|
-
from pyqtgraph.Qt import QtCore, QtWidgets, uic
|
13
|
-
from pyqtgraph.Qt.QtCore import pyqtSignal
|
14
|
-
from qtpy.QtCore import Slot as pyqtSlot
|
15
|
-
from qtpy.QtWidgets import QTableWidgetItem
|
16
|
-
|
17
|
-
from bec_widgets.utils import Colors, Crosshair
|
18
|
-
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
19
|
-
|
20
|
-
|
21
|
-
class StreamPlot(QtWidgets.QWidget):
|
22
|
-
update_signal = pyqtSignal()
|
23
|
-
roi_signal = pyqtSignal(tuple)
|
24
|
-
|
25
|
-
def __init__(self, name="", y_value_list=["gauss_bpm"], client=None, parent=None) -> None:
|
26
|
-
"""
|
27
|
-
Basic plot widget for displaying scan data.
|
28
|
-
|
29
|
-
Args:
|
30
|
-
name (str, optional): Name of the plot. Defaults to "".
|
31
|
-
y_value_list (list, optional): List of signals to be plotted. Defaults to ["gauss_bpm"].
|
32
|
-
"""
|
33
|
-
|
34
|
-
# Client and device manager from BEC
|
35
|
-
self.client = BECDispatcher().client if client is None else client
|
36
|
-
|
37
|
-
super(StreamPlot, self).__init__()
|
38
|
-
# Set style for pyqtgraph plots
|
39
|
-
pg.setConfigOption("background", "w")
|
40
|
-
pg.setConfigOption("foreground", "k")
|
41
|
-
current_path = os.path.dirname(__file__)
|
42
|
-
uic.loadUi(os.path.join(current_path, "line_plot.ui"), self)
|
43
|
-
|
44
|
-
self._idle_time = 100
|
45
|
-
self.connector = RedisConnector(["localhost:6379"])
|
46
|
-
|
47
|
-
self.y_value_list = y_value_list
|
48
|
-
self.previous_y_value_list = None
|
49
|
-
self.plotter_data_x = []
|
50
|
-
self.plotter_data_y = []
|
51
|
-
|
52
|
-
self.plotter_scan_id = None
|
53
|
-
|
54
|
-
self._current_proj = None
|
55
|
-
self._current_metadata_ep = "px_stream/projection_{}/metadata"
|
56
|
-
|
57
|
-
self.proxy_update = pg.SignalProxy(self.update_signal, rateLimit=25, slot=self.update)
|
58
|
-
|
59
|
-
self._data_retriever_thread_exit_event = threading.Event()
|
60
|
-
self.data_retriever = threading.Thread(
|
61
|
-
target=self.on_projection, args=(self._data_retriever_thread_exit_event,), daemon=True
|
62
|
-
)
|
63
|
-
self.data_retriever.start()
|
64
|
-
|
65
|
-
##########################
|
66
|
-
# UI
|
67
|
-
##########################
|
68
|
-
self.init_ui()
|
69
|
-
self.init_curves()
|
70
|
-
self.hook_crosshair()
|
71
|
-
|
72
|
-
def close(self):
|
73
|
-
super().close()
|
74
|
-
self._data_retriever_thread_exit_event.set()
|
75
|
-
self.data_retriever.join()
|
76
|
-
|
77
|
-
def init_ui(self):
|
78
|
-
"""Setup all ui elements"""
|
79
|
-
##########################
|
80
|
-
# 1D Plot
|
81
|
-
##########################
|
82
|
-
|
83
|
-
# LabelItem for ROI
|
84
|
-
self.label_plot = pg.LabelItem(justify="center")
|
85
|
-
self.glw_plot.addItem(self.label_plot)
|
86
|
-
self.label_plot.setText("ROI region")
|
87
|
-
|
88
|
-
# ROI selector - so far from [-1,1] #TODO update to scale with xrange
|
89
|
-
self.roi_selector = pg.LinearRegionItem([-1, 1])
|
90
|
-
|
91
|
-
self.glw_plot.nextRow() # TODO update of cursor
|
92
|
-
self.label_plot_moved = pg.LabelItem(justify="center")
|
93
|
-
self.glw_plot.addItem(self.label_plot_moved)
|
94
|
-
self.label_plot_moved.setText("Actual coordinates (X, Y)")
|
95
|
-
|
96
|
-
# Label for coordinates clicked
|
97
|
-
self.glw_plot.nextRow()
|
98
|
-
self.label_plot_clicked = pg.LabelItem(justify="center")
|
99
|
-
self.glw_plot.addItem(self.label_plot_clicked)
|
100
|
-
self.label_plot_clicked.setText("Clicked coordinates (X, Y)")
|
101
|
-
|
102
|
-
# 1D PlotItem
|
103
|
-
self.glw_plot.nextRow()
|
104
|
-
self.plot = pg.PlotItem()
|
105
|
-
self.plot.setLogMode(True, True)
|
106
|
-
self.glw_plot.addItem(self.plot)
|
107
|
-
self.plot.addLegend()
|
108
|
-
|
109
|
-
##########################
|
110
|
-
# 2D Plot
|
111
|
-
##########################
|
112
|
-
|
113
|
-
# Label for coordinates moved
|
114
|
-
self.label_image_moved = pg.LabelItem(justify="center")
|
115
|
-
self.glw_image.addItem(self.label_image_moved)
|
116
|
-
self.label_image_moved.setText("Actual coordinates (X, Y)")
|
117
|
-
|
118
|
-
# Label for coordinates clicked
|
119
|
-
self.glw_image.nextRow()
|
120
|
-
self.label_image_clicked = pg.LabelItem(justify="center")
|
121
|
-
self.glw_image.addItem(self.label_image_clicked)
|
122
|
-
self.label_image_clicked.setText("Clicked coordinates (X, Y)")
|
123
|
-
|
124
|
-
# TODO try to lock aspect ratio with view
|
125
|
-
|
126
|
-
# # Create a window
|
127
|
-
# win = pg.GraphicsLayoutWidget()
|
128
|
-
# win.show()
|
129
|
-
#
|
130
|
-
# # Create a ViewBox
|
131
|
-
# view = win.addViewBox()
|
132
|
-
#
|
133
|
-
# # Lock the aspect ratio
|
134
|
-
# view.setAspectLocked(True)
|
135
|
-
|
136
|
-
# # Create an ImageItem
|
137
|
-
# image_item = pg.ImageItem(np.random.random((100, 100)))
|
138
|
-
#
|
139
|
-
# # Add the ImageItem to the ViewBox
|
140
|
-
# view.addItem(image_item)
|
141
|
-
|
142
|
-
# 2D ImageItem
|
143
|
-
self.glw_image.nextRow()
|
144
|
-
self.plot_image = pg.PlotItem()
|
145
|
-
self.glw_image.addItem(self.plot_image)
|
146
|
-
|
147
|
-
def init_curves(self):
|
148
|
-
# init of 1D plot
|
149
|
-
self.plot.clear()
|
150
|
-
|
151
|
-
self.curves = []
|
152
|
-
self.pens = []
|
153
|
-
self.brushs = []
|
154
|
-
|
155
|
-
self.color_list = Colors.golden_angle_color(colormap="CET-R2", num=len(self.y_value_list))
|
156
|
-
|
157
|
-
for ii, y_value in enumerate(self.y_value_list):
|
158
|
-
pen = mkPen(color=self.color_list[ii], width=2, style=QtCore.Qt.DashLine)
|
159
|
-
brush = mkBrush(color=self.color_list[ii])
|
160
|
-
curve = pg.PlotDataItem(symbolBrush=brush, pen=pen, skipFiniteCheck=True, name=y_value)
|
161
|
-
self.plot.addItem(curve)
|
162
|
-
self.curves.append(curve)
|
163
|
-
self.pens.append(pen)
|
164
|
-
self.brushs.append(brush)
|
165
|
-
|
166
|
-
# check if roi selector is in the plot
|
167
|
-
if self.roi_selector not in self.plot.items:
|
168
|
-
self.plot.addItem(self.roi_selector)
|
169
|
-
|
170
|
-
# init of 2D plot
|
171
|
-
self.plot_image.clear()
|
172
|
-
|
173
|
-
self.img = pg.ImageItem()
|
174
|
-
self.plot_image.addItem(self.img)
|
175
|
-
|
176
|
-
# hooking signals
|
177
|
-
self.hook_crosshair()
|
178
|
-
self.init_table()
|
179
|
-
|
180
|
-
def splitter_sizes(self): ...
|
181
|
-
|
182
|
-
def hook_crosshair(self):
|
183
|
-
self.crosshair_1d = Crosshair(self.plot, precision=4)
|
184
|
-
|
185
|
-
self.crosshair_1d.coordinatesChanged1D.connect(
|
186
|
-
lambda x, y: self.label_plot_moved.setText(f"Moved : ({x}, {y})")
|
187
|
-
)
|
188
|
-
self.crosshair_1d.coordinatesClicked1D.connect(
|
189
|
-
lambda x, y: self.label_plot_clicked.setText(f"Moved : ({x}, {y})")
|
190
|
-
)
|
191
|
-
|
192
|
-
self.crosshair_1d.coordinatesChanged1D.connect(
|
193
|
-
lambda x, y: self.update_table(table_widget=self.cursor_table, x=x, y_values=y)
|
194
|
-
)
|
195
|
-
|
196
|
-
self.crosshair_2D = Crosshair(self.plot_image)
|
197
|
-
|
198
|
-
self.crosshair_2D.coordinatesChanged2D.connect(
|
199
|
-
lambda x, y: self.label_image_moved.setText(f"Moved : ({x}, {y})")
|
200
|
-
)
|
201
|
-
self.crosshair_2D.coordinatesClicked2D.connect(
|
202
|
-
lambda x, y: self.label_image_clicked.setText(f"Moved : ({x}, {y})")
|
203
|
-
)
|
204
|
-
|
205
|
-
# ROI
|
206
|
-
self.roi_selector.sigRegionChangeFinished.connect(self.get_roi_region)
|
207
|
-
|
208
|
-
def get_roi_region(self):
|
209
|
-
"""For testing purpose now, get roi region and print it to self.label as tuple"""
|
210
|
-
region = self.roi_selector.getRegion()
|
211
|
-
self.label_plot.setText(f"x = {(10 ** region[0]):.4f}, y ={(10 ** region[1]):.4f}")
|
212
|
-
return_dict = {
|
213
|
-
"horiz_roi": [
|
214
|
-
np.where(self.plotter_data_x[0] > 10 ** region[0])[0][0],
|
215
|
-
np.where(self.plotter_data_x[0] < 10 ** region[1])[0][-1],
|
216
|
-
]
|
217
|
-
}
|
218
|
-
msg = messages.DeviceMessage(signals=return_dict).dumps()
|
219
|
-
self.connector.set_and_publish("px_stream/gui_event", msg=msg)
|
220
|
-
self.roi_signal.emit(region)
|
221
|
-
|
222
|
-
def init_table(self):
|
223
|
-
# Init number of rows in table according to n of devices
|
224
|
-
self.cursor_table.setRowCount(len(self.y_value_list))
|
225
|
-
# self.table.setHorizontalHeaderLabels(["(X, Y) - Moved", "(X, Y) - Clicked"]) #TODO can be dynamic
|
226
|
-
self.cursor_table.setVerticalHeaderLabels(self.y_value_list)
|
227
|
-
self.cursor_table.resizeColumnsToContents()
|
228
|
-
|
229
|
-
def update_table(self, table_widget, x, y_values):
|
230
|
-
for i, y in enumerate(y_values):
|
231
|
-
table_widget.setItem(i, 1, QTableWidgetItem(str(x)))
|
232
|
-
table_widget.setItem(i, 2, QTableWidgetItem(str(y)))
|
233
|
-
table_widget.resizeColumnsToContents()
|
234
|
-
|
235
|
-
def update(self):
|
236
|
-
"""Update the plot with the new data."""
|
237
|
-
|
238
|
-
# check if QTable was initialised and if list of devices was changed
|
239
|
-
# if self.y_value_list != self.previous_y_value_list:
|
240
|
-
# self.setup_cursor_table()
|
241
|
-
# self.previous_y_value_list = self.y_value_list.copy() if self.y_value_list else None
|
242
|
-
|
243
|
-
self.curves[0].setData(self.plotter_data_x[0], self.plotter_data_y[0])
|
244
|
-
|
245
|
-
@staticmethod
|
246
|
-
def flip_even_rows(arr):
|
247
|
-
arr_copy = np.copy(arr) # Create a writable copy
|
248
|
-
arr_copy[1::2, :] = arr_copy[1::2, ::-1]
|
249
|
-
return arr_copy
|
250
|
-
|
251
|
-
@staticmethod
|
252
|
-
def remove_curve_by_name(plot: pyqtgraph.PlotItem, name: str) -> None:
|
253
|
-
# def remove_curve_by_name(plot: pyqtgraph.PlotItem, checkbox: QtWidgets.QCheckBox, name: str) -> None:
|
254
|
-
"""Removes a curve from the given plot by the specified name.
|
255
|
-
|
256
|
-
Args:
|
257
|
-
plot (pyqtgraph.PlotItem): The plot from which to remove the curve.
|
258
|
-
name (str): The name of the curve to remove.
|
259
|
-
"""
|
260
|
-
# if checkbox.isChecked():
|
261
|
-
for item in plot.items:
|
262
|
-
if isinstance(item, pg.PlotDataItem) and getattr(item, "opts", {}).get("name") == name:
|
263
|
-
plot.removeItem(item)
|
264
|
-
return
|
265
|
-
|
266
|
-
# else:
|
267
|
-
# return
|
268
|
-
|
269
|
-
def on_projection(self, exit_event):
|
270
|
-
while not exit_event.is_set():
|
271
|
-
if self._current_proj is None:
|
272
|
-
time.sleep(0.1)
|
273
|
-
continue
|
274
|
-
endpoint = f"px_stream/projection_{self._current_proj}/data"
|
275
|
-
msgs = self.client.connector.lrange(topic=endpoint, start=-1, end=-1)
|
276
|
-
data = msgs
|
277
|
-
if not data:
|
278
|
-
continue
|
279
|
-
with np.errstate(divide="ignore", invalid="ignore"):
|
280
|
-
self.plotter_data_y = [
|
281
|
-
np.sum(
|
282
|
-
np.sum(data[-1].content["signals"]["data"] * self._current_norm, axis=1)
|
283
|
-
/ np.sum(self._current_norm, axis=0),
|
284
|
-
axis=0,
|
285
|
-
).squeeze()
|
286
|
-
]
|
287
|
-
|
288
|
-
self.update_signal.emit()
|
289
|
-
|
290
|
-
@pyqtSlot(dict, dict)
|
291
|
-
def on_dap_update(self, data: dict, metadata: dict):
|
292
|
-
flipped_data = self.flip_even_rows(data["data"]["z"])
|
293
|
-
|
294
|
-
self.img.setImage(flipped_data)
|
295
|
-
|
296
|
-
@pyqtSlot(dict, dict)
|
297
|
-
def new_proj(self, content: dict, _metadata: dict):
|
298
|
-
proj_nr = content["signals"]["proj_nr"]
|
299
|
-
endpoint = f"px_stream/projection_{proj_nr}/metadata"
|
300
|
-
msg_raw = self.client.connector.get(topic=endpoint)
|
301
|
-
msg = messages.DeviceMessage.loads(msg_raw)
|
302
|
-
self._current_q = msg.content["signals"]["q"]
|
303
|
-
self._current_norm = msg.content["signals"]["norm_sum"]
|
304
|
-
self._current_metadata = msg.content["signals"]["metadata"]
|
305
|
-
|
306
|
-
self.plotter_data_x = [self._current_q]
|
307
|
-
self._current_proj = proj_nr
|
308
|
-
|
309
|
-
|
310
|
-
if __name__ == "__main__":
|
311
|
-
import argparse
|
312
|
-
|
313
|
-
# from bec_widgets import ctrl_c # TODO uncomment when ctrl_c is ready to be compatible with qtpy
|
314
|
-
|
315
|
-
parser = argparse.ArgumentParser()
|
316
|
-
parser.add_argument(
|
317
|
-
"--signals", help="specify recorded signals", nargs="+", default=["gauss_bpm"]
|
318
|
-
)
|
319
|
-
# default = ["gauss_bpm", "bpm4i", "bpm5i", "bpm6i", "xert"],
|
320
|
-
value = parser.parse_args()
|
321
|
-
print(f"Plotting signals for: {', '.join(value.signals)}")
|
322
|
-
|
323
|
-
# Client from dispatcher
|
324
|
-
bec_dispatcher = BECDispatcher()
|
325
|
-
client = bec_dispatcher.client
|
326
|
-
|
327
|
-
app = QtWidgets.QApplication([])
|
328
|
-
# ctrl_c.setup(app) # TODO uncomment when ctrl_c is ready to be compatible with qtpy
|
329
|
-
plot = StreamPlot(y_value_list=value.signals, client=client)
|
330
|
-
|
331
|
-
bec_dispatcher.connect_slot(plot.new_proj, "px_stream/proj_nr")
|
332
|
-
bec_dispatcher.connect_slot(
|
333
|
-
plot.on_dap_update, MessageEndpoints.processed_data("px_dap_worker")
|
334
|
-
)
|
335
|
-
plot.show()
|
336
|
-
# client.callbacks.register("scan_segment", plot, sync=False)
|
337
|
-
app.exec()
|
docs/user/apps/modular_app.md
DELETED
docs/user/apps/motor_app.md
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
(user.apps.motor_app)=
|
2
|
-
# Motor Alignment
|
3
|
-
|
4
|
-
The Motor Alignment Application is a key component of the BEC Widgets suite, designed to facilitate precise alignment of motors.
|
5
|
-
Users can easily launch this app using the script located at `/bec_widgets/example/motor_movement/motor_example.py` script.
|
6
|
-
The application's primary function is to enable users to align motors to specific positions and to visually track the motor's trajectory.
|
7
|
-
|
8
|
-
## Controlling Motors
|
9
|
-
|
10
|
-
In the top middle panel of the application, users will find combobox dropdown menus for selecting the motors they wish to track on the x and y axes of the motor map.
|
11
|
-
These motors are automatically loaded from the current active BEC instance, ensuring seamless integration and ease of use.
|
12
|
-
|
13
|
-
There are two primary methods to control motor movements:
|
14
|
-
|
15
|
-
|
16
|
-
1. **Manual Control with Arrow Keys:** Users can manually drive the motors using arrow keys. Before doing so, they need to select the step size for each motor, allowing for precise and incremental movements.
|
17
|
-
2. **Direct Position Entry:** Alternatively, users can input a desired position in the text input box and then click the Go button. This action will move the motor directly to the specified coordinates.
|
18
|
-
|
19
|
-
As the motors are moved, their trajectory is plotted in real-time, providing users with a visual representation of the motor's path. This feature is particularly useful for understanding the movement patterns and making necessary adjustments.
|
20
|
-
|
21
|
-
|
22
|
-
## Saving and Exporting Data
|
23
|
-
|
24
|
-
Users have the ability to save the current motor position in a table widget. This functionality is beneficial for recalling and returning to specific positions. By clicking the Go button in the table widget, the motors will automatically move back to the saved position.
|
25
|
-
|
26
|
-
Additionally, users can annotate each saved position with notes and comments directly in the table widget. This feature is invaluable for keeping track of specific alignment settings or observations. The contents of the table, including the notes, can be exported to a .csv file. This exported data can be used for initiating scans or for record-keeping purposes.
|
27
|
-
|
28
|
-
The table widget also supports saving and loading functionalities, allowing users to preserve their motor positions and notes across sessions. The saved files are in a user-friendly format for ease of access and use.
|
29
|
-
|
30
|
-
|
31
|
-
## Example of Use
|
32
|
-
|
33
|
-

|
34
|
-
|
Binary file
|
docs/user/apps/plot_app.md
DELETED
@@ -1,115 +0,0 @@
|
|
1
|
-
# pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
|
2
|
-
import json
|
3
|
-
from unittest.mock import MagicMock, patch
|
4
|
-
|
5
|
-
import numpy as np
|
6
|
-
import pyqtgraph as pg
|
7
|
-
import pytest
|
8
|
-
import zmq
|
9
|
-
|
10
|
-
from bec_widgets.examples.eiger_plot.eiger_plot import EigerPlot
|
11
|
-
|
12
|
-
|
13
|
-
# Common fixture for all tests
|
14
|
-
@pytest.fixture
|
15
|
-
def eiger_plot_instance(qtbot):
|
16
|
-
widget = EigerPlot()
|
17
|
-
qtbot.addWidget(widget)
|
18
|
-
qtbot.waitExposed(widget)
|
19
|
-
yield widget
|
20
|
-
widget.close()
|
21
|
-
|
22
|
-
|
23
|
-
@pytest.mark.parametrize(
|
24
|
-
"fft_checked, rotation_index, transpose_checked, log_checked, expected_image",
|
25
|
-
[
|
26
|
-
(False, 0, False, False, np.array([[2, 1], [1, 5]], dtype=float)), # just mask
|
27
|
-
(False, 1, False, False, np.array([[1, 5], [2, 1]], dtype=float)), # 90 deg rotation
|
28
|
-
(False, 2, False, False, np.array([[5, 1], [1, 2]], dtype=float)), # 180 deg rotation
|
29
|
-
(False, 0, True, False, np.array([[2, 1], [1, 5]], dtype=float)), # transposed
|
30
|
-
(False, 0, False, True, np.array([[0.30103, 0.0], [0.0, 0.69897]], dtype=float)), # log
|
31
|
-
(True, 0, False, False, np.array([[5.0, 3.0], [3.0, 9.0]], dtype=float)), # FFT
|
32
|
-
],
|
33
|
-
)
|
34
|
-
def test_on_image_update(
|
35
|
-
qtbot,
|
36
|
-
eiger_plot_instance,
|
37
|
-
fft_checked,
|
38
|
-
rotation_index,
|
39
|
-
transpose_checked,
|
40
|
-
log_checked,
|
41
|
-
expected_image,
|
42
|
-
):
|
43
|
-
# Initialize image and mask
|
44
|
-
eiger_plot_instance.image = np.array([[1, 2], [3, 4]], dtype=float)
|
45
|
-
eiger_plot_instance.mask = np.array([[0, 1], [1, 0]], dtype=float)
|
46
|
-
|
47
|
-
# Mock UI elements
|
48
|
-
eiger_plot_instance.checkBox_FFT = MagicMock()
|
49
|
-
eiger_plot_instance.checkBox_FFT.isChecked.return_value = fft_checked
|
50
|
-
eiger_plot_instance.comboBox_rotation = MagicMock()
|
51
|
-
eiger_plot_instance.comboBox_rotation.currentIndex.return_value = rotation_index
|
52
|
-
eiger_plot_instance.checkBox_transpose = MagicMock()
|
53
|
-
eiger_plot_instance.checkBox_transpose.isChecked.return_value = transpose_checked
|
54
|
-
eiger_plot_instance.checkBox_log = MagicMock()
|
55
|
-
eiger_plot_instance.checkBox_log.isChecked.return_value = log_checked
|
56
|
-
eiger_plot_instance.imageItem = MagicMock()
|
57
|
-
|
58
|
-
# Call the method
|
59
|
-
eiger_plot_instance.on_image_update()
|
60
|
-
|
61
|
-
# Validate the transformations
|
62
|
-
np.testing.assert_array_almost_equal(eiger_plot_instance.image, expected_image, decimal=5)
|
63
|
-
|
64
|
-
# Validate that setImage was called
|
65
|
-
eiger_plot_instance.imageItem.setImage.assert_called_with(
|
66
|
-
eiger_plot_instance.image, autoLevels=False
|
67
|
-
)
|
68
|
-
|
69
|
-
|
70
|
-
def test_init_ui(eiger_plot_instance):
|
71
|
-
assert isinstance(eiger_plot_instance.plot_item, pg.PlotItem)
|
72
|
-
assert isinstance(eiger_plot_instance.imageItem, pg.ImageItem)
|
73
|
-
assert isinstance(eiger_plot_instance.hist, pg.HistogramLUTItem)
|
74
|
-
|
75
|
-
|
76
|
-
def test_start_zmq_consumer(eiger_plot_instance):
|
77
|
-
with patch("threading.Thread") as MockThread:
|
78
|
-
eiger_plot_instance.start_zmq_consumer()
|
79
|
-
MockThread.assert_called_once()
|
80
|
-
MockThread.return_value.start.assert_called_once()
|
81
|
-
|
82
|
-
|
83
|
-
def test_zmq_consumer(eiger_plot_instance, qtbot):
|
84
|
-
fake_meta = json.dumps({"type": "int32", "shape": (2, 2)}).encode("utf-8")
|
85
|
-
fake_data = np.array([[1, 2], [3, 4]], dtype="int32").tobytes()
|
86
|
-
|
87
|
-
with patch("zmq.Context", autospec=True) as MockContext:
|
88
|
-
mock_socket = MagicMock()
|
89
|
-
mock_socket.recv_multipart.side_effect = ((fake_meta, fake_data),)
|
90
|
-
MockContext.return_value.socket.return_value = mock_socket
|
91
|
-
|
92
|
-
# Mocking the update_signal to check if it gets emitted
|
93
|
-
eiger_plot_instance.update_signal = MagicMock()
|
94
|
-
|
95
|
-
with patch("zmq.Poller"):
|
96
|
-
# will do only 1 iteration of the loop in the thread
|
97
|
-
eiger_plot_instance._zmq_consumer_exit_event.set()
|
98
|
-
# Run the method under test
|
99
|
-
consumer_thread = eiger_plot_instance.start_zmq_consumer()
|
100
|
-
consumer_thread.join()
|
101
|
-
|
102
|
-
# Check if zmq methods are called
|
103
|
-
# MockContext.assert_called_once()
|
104
|
-
assert MockContext.call_count == 1
|
105
|
-
mock_socket.connect.assert_called_with("tcp://129.129.95.38:20000")
|
106
|
-
mock_socket.setsockopt_string.assert_called_with(zmq.SUBSCRIBE, "")
|
107
|
-
mock_socket.recv_multipart.assert_called()
|
108
|
-
|
109
|
-
# Check if update_signal was emitted
|
110
|
-
eiger_plot_instance.update_signal.emit.assert_called_once()
|
111
|
-
|
112
|
-
# Validate the image data
|
113
|
-
np.testing.assert_array_equal(
|
114
|
-
eiger_plot_instance.image, np.array([[1, 2], [3, 4]], dtype="int32")
|
115
|
-
)
|