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
@@ -1,307 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
import os
|
3
|
-
import threading
|
4
|
-
|
5
|
-
import h5py
|
6
|
-
import numpy as np
|
7
|
-
import pyqtgraph as pg
|
8
|
-
import zmq
|
9
|
-
from pyqtgraph.Qt import uic
|
10
|
-
from qtpy.QtCore import Signal as pyqtSignal
|
11
|
-
from qtpy.QtCore import Slot as pyqtSlot
|
12
|
-
from qtpy.QtGui import QKeySequence
|
13
|
-
from qtpy.QtWidgets import QDialog, QFileDialog, QFrame, QLabel, QShortcut, QVBoxLayout, QWidget
|
14
|
-
|
15
|
-
# from scipy.stats import multivariate_normal
|
16
|
-
|
17
|
-
|
18
|
-
class EigerPlot(QWidget):
|
19
|
-
update_signal = pyqtSignal()
|
20
|
-
|
21
|
-
def __init__(self, parent=None):
|
22
|
-
super().__init__(parent)
|
23
|
-
# pg.setConfigOptions(background="w", foreground="k", antialias=True)
|
24
|
-
|
25
|
-
current_path = os.path.dirname(__file__)
|
26
|
-
uic.loadUi(os.path.join(current_path, "eiger_plot.ui"), self)
|
27
|
-
|
28
|
-
# Set widow name
|
29
|
-
self.setWindowTitle("Eiger Plot")
|
30
|
-
|
31
|
-
self.hist_lims = None
|
32
|
-
self.mask = None
|
33
|
-
self.image = None
|
34
|
-
|
35
|
-
# UI
|
36
|
-
self.init_ui()
|
37
|
-
self.hook_signals()
|
38
|
-
self.key_bindings()
|
39
|
-
|
40
|
-
# ZMQ Consumer
|
41
|
-
self._zmq_consumer_exit_event = threading.Event()
|
42
|
-
self._zmq_consumer_thread = self.start_zmq_consumer()
|
43
|
-
|
44
|
-
def close(self):
|
45
|
-
super().close()
|
46
|
-
self._zmq_consumer_exit_event.set()
|
47
|
-
self._zmq_consumer_thread.join()
|
48
|
-
|
49
|
-
def init_ui(self):
|
50
|
-
# Create Plot and add ImageItem
|
51
|
-
self.plot_item = pg.PlotItem()
|
52
|
-
self.plot_item.setAspectLocked(True)
|
53
|
-
self.imageItem = pg.ImageItem()
|
54
|
-
self.plot_item.addItem(self.imageItem)
|
55
|
-
|
56
|
-
# Setting up histogram
|
57
|
-
self.hist = pg.HistogramLUTItem()
|
58
|
-
self.hist.setImageItem(self.imageItem)
|
59
|
-
self.hist.gradient.loadPreset("magma")
|
60
|
-
self.update_hist()
|
61
|
-
|
62
|
-
# Adding Items to Graphical Layout
|
63
|
-
self.glw.addItem(self.plot_item)
|
64
|
-
self.glw.addItem(self.hist)
|
65
|
-
|
66
|
-
def hook_signals(self):
|
67
|
-
# Buttons
|
68
|
-
# self.pushButton_test.clicked.connect(self.start_sim_stream)
|
69
|
-
self.pushButton_mask.clicked.connect(self.load_mask_dialog)
|
70
|
-
self.pushButton_delete_mask.clicked.connect(self.delete_mask)
|
71
|
-
self.pushButton_help.clicked.connect(self.show_help_dialog)
|
72
|
-
|
73
|
-
# SpinBoxes
|
74
|
-
self.doubleSpinBox_hist_min.valueChanged.connect(self.update_hist)
|
75
|
-
self.doubleSpinBox_hist_max.valueChanged.connect(self.update_hist)
|
76
|
-
|
77
|
-
# Signal/Slots
|
78
|
-
self.update_signal.connect(self.on_image_update)
|
79
|
-
|
80
|
-
def key_bindings(self):
|
81
|
-
# Key bindings for rotation
|
82
|
-
rotate_plus = QShortcut(QKeySequence("Ctrl+A"), self)
|
83
|
-
rotate_minus = QShortcut(QKeySequence("Ctrl+Z"), self)
|
84
|
-
self.comboBox_rotation.setToolTip("Increase rotation: Ctrl+A\nDecrease rotation: Ctrl+Z")
|
85
|
-
self.checkBox_transpose.setToolTip("Toggle transpose: Ctrl+T")
|
86
|
-
|
87
|
-
max_index = self.comboBox_rotation.count() - 1 # Maximum valid index
|
88
|
-
|
89
|
-
rotate_plus.activated.connect(
|
90
|
-
lambda: self.comboBox_rotation.setCurrentIndex(
|
91
|
-
min(self.comboBox_rotation.currentIndex() + 1, max_index)
|
92
|
-
)
|
93
|
-
)
|
94
|
-
|
95
|
-
rotate_minus.activated.connect(
|
96
|
-
lambda: self.comboBox_rotation.setCurrentIndex(
|
97
|
-
max(self.comboBox_rotation.currentIndex() - 1, 0)
|
98
|
-
)
|
99
|
-
)
|
100
|
-
|
101
|
-
# Key bindings for transpose
|
102
|
-
transpose = QShortcut(QKeySequence("Ctrl+T"), self)
|
103
|
-
transpose.activated.connect(self.checkBox_transpose.toggle)
|
104
|
-
|
105
|
-
FFT = QShortcut(QKeySequence("Ctrl+F"), self)
|
106
|
-
FFT.activated.connect(self.checkBox_FFT.toggle)
|
107
|
-
self.checkBox_FFT.setToolTip("Toggle FFT: Ctrl+F")
|
108
|
-
|
109
|
-
log = QShortcut(QKeySequence("Ctrl+L"), self)
|
110
|
-
log.activated.connect(self.checkBox_log.toggle)
|
111
|
-
self.checkBox_log.setToolTip("Toggle log: Ctrl+L")
|
112
|
-
|
113
|
-
mask = QShortcut(QKeySequence("Ctrl+M"), self)
|
114
|
-
mask.activated.connect(self.pushButton_mask.click)
|
115
|
-
self.pushButton_mask.setToolTip("Load mask: Ctrl+M")
|
116
|
-
|
117
|
-
delete_mask = QShortcut(QKeySequence("Ctrl+D"), self)
|
118
|
-
delete_mask.activated.connect(self.pushButton_delete_mask.click)
|
119
|
-
self.pushButton_delete_mask.setToolTip("Delete mask: Ctrl+D")
|
120
|
-
|
121
|
-
def update_hist(self):
|
122
|
-
self.hist_levels = [
|
123
|
-
self.doubleSpinBox_hist_min.value(),
|
124
|
-
self.doubleSpinBox_hist_max.value(),
|
125
|
-
]
|
126
|
-
self.hist.setLevels(min=self.hist_levels[0], max=self.hist_levels[1])
|
127
|
-
self.hist.setHistogramRange(
|
128
|
-
self.hist_levels[0] - 0.1 * self.hist_levels[0],
|
129
|
-
self.hist_levels[1] + 0.1 * self.hist_levels[1],
|
130
|
-
)
|
131
|
-
|
132
|
-
def load_mask_dialog(self):
|
133
|
-
options = QFileDialog.Options()
|
134
|
-
options |= QFileDialog.ReadOnly
|
135
|
-
file_name, _ = QFileDialog.getOpenFileName(
|
136
|
-
self, "Select Mask File", "", "H5 Files (*.h5);;All Files (*)", options=options
|
137
|
-
)
|
138
|
-
if file_name:
|
139
|
-
self.load_mask(file_name)
|
140
|
-
|
141
|
-
def load_mask(self, path):
|
142
|
-
try:
|
143
|
-
with h5py.File(path, "r") as f:
|
144
|
-
self.mask = f["data"][...]
|
145
|
-
if self.mask is not None:
|
146
|
-
# Set label to mask name without path
|
147
|
-
self.label_mask.setText(os.path.basename(path))
|
148
|
-
except KeyError as e:
|
149
|
-
# Update GUI with the error message
|
150
|
-
print(f"Error: {str(e)}")
|
151
|
-
|
152
|
-
def delete_mask(self):
|
153
|
-
self.mask = None
|
154
|
-
self.label_mask.setText("No Mask")
|
155
|
-
|
156
|
-
@pyqtSlot()
|
157
|
-
def on_image_update(self):
|
158
|
-
# TODO first rotate then transpose
|
159
|
-
if self.mask is not None:
|
160
|
-
# self.image = np.ma.masked_array(self.image, mask=self.mask) #TODO test if np works
|
161
|
-
self.image = self.image * (1 - self.mask) + 1
|
162
|
-
|
163
|
-
if self.checkBox_FFT.isChecked():
|
164
|
-
self.image = np.abs(np.fft.fftshift(np.fft.fft2(self.image)))
|
165
|
-
|
166
|
-
if self.comboBox_rotation.currentIndex() > 0: # rotate
|
167
|
-
self.image = np.rot90(self.image, k=self.comboBox_rotation.currentIndex(), axes=(0, 1))
|
168
|
-
|
169
|
-
if self.checkBox_transpose.isChecked(): # transpose
|
170
|
-
self.image = np.transpose(self.image)
|
171
|
-
|
172
|
-
if self.checkBox_log.isChecked():
|
173
|
-
self.image = np.log10(self.image)
|
174
|
-
|
175
|
-
self.imageItem.setImage(self.image, autoLevels=False)
|
176
|
-
|
177
|
-
###############################
|
178
|
-
# ZMQ Consumer
|
179
|
-
###############################
|
180
|
-
|
181
|
-
def start_zmq_consumer(self):
|
182
|
-
consumer_thread = threading.Thread(
|
183
|
-
target=self.zmq_consumer, args=(self._zmq_consumer_exit_event,), daemon=True
|
184
|
-
)
|
185
|
-
consumer_thread.start()
|
186
|
-
return consumer_thread
|
187
|
-
|
188
|
-
def zmq_consumer(self, exit_event):
|
189
|
-
print("starting consumer")
|
190
|
-
live_stream_url = "tcp://129.129.95.38:20000"
|
191
|
-
receiver = zmq.Context().socket(zmq.SUB)
|
192
|
-
receiver.connect(live_stream_url)
|
193
|
-
receiver.setsockopt_string(zmq.SUBSCRIBE, "")
|
194
|
-
|
195
|
-
poller = zmq.Poller()
|
196
|
-
poller.register(receiver, zmq.POLLIN)
|
197
|
-
|
198
|
-
# code could be a bit simpler here, testing exit_event in
|
199
|
-
# 'while' condition, but like this it is easier for the
|
200
|
-
# 'test_zmq_consumer' test
|
201
|
-
while True:
|
202
|
-
if poller.poll(1000): # 1s timeout
|
203
|
-
raw_meta, raw_data = receiver.recv_multipart(zmq.NOBLOCK)
|
204
|
-
|
205
|
-
meta = json.loads(raw_meta.decode("utf-8"))
|
206
|
-
self.image = np.frombuffer(raw_data, dtype=meta["type"]).reshape(meta["shape"])
|
207
|
-
self.update_signal.emit()
|
208
|
-
if exit_event.is_set():
|
209
|
-
break
|
210
|
-
|
211
|
-
receiver.disconnect(live_stream_url)
|
212
|
-
|
213
|
-
###############################
|
214
|
-
# just simulations from here
|
215
|
-
###############################
|
216
|
-
|
217
|
-
def show_help_dialog(self):
|
218
|
-
dialog = QDialog(self)
|
219
|
-
dialog.setWindowTitle("Help")
|
220
|
-
|
221
|
-
layout = QVBoxLayout()
|
222
|
-
|
223
|
-
# Key bindings section
|
224
|
-
layout.addWidget(QLabel("Keyboard Shortcuts:"))
|
225
|
-
|
226
|
-
key_bindings = [
|
227
|
-
("Ctrl+A", "Increase rotation"),
|
228
|
-
("Ctrl+Z", "Decrease rotation"),
|
229
|
-
("Ctrl+T", "Toggle transpose"),
|
230
|
-
("Ctrl+F", "Toggle FFT"),
|
231
|
-
("Ctrl+L", "Toggle log scale"),
|
232
|
-
("Ctrl+M", "Load mask"),
|
233
|
-
("Ctrl+D", "Delete mask"),
|
234
|
-
]
|
235
|
-
|
236
|
-
for keys, action in key_bindings:
|
237
|
-
layout.addWidget(QLabel(f"{keys} - {action}"))
|
238
|
-
|
239
|
-
# Separator
|
240
|
-
separator = QFrame()
|
241
|
-
separator.setFrameShape(QFrame.HLine)
|
242
|
-
separator.setFrameShadow(QFrame.Sunken)
|
243
|
-
layout.addWidget(separator)
|
244
|
-
|
245
|
-
# Histogram section
|
246
|
-
layout.addWidget(QLabel("Histogram:"))
|
247
|
-
layout.addWidget(
|
248
|
-
QLabel(
|
249
|
-
"Use the Double Spin Boxes to adjust the minimum and maximum values of the histogram."
|
250
|
-
)
|
251
|
-
)
|
252
|
-
|
253
|
-
# Another Separator
|
254
|
-
another_separator = QFrame()
|
255
|
-
another_separator.setFrameShape(QFrame.HLine)
|
256
|
-
another_separator.setFrameShadow(QFrame.Sunken)
|
257
|
-
layout.addWidget(another_separator)
|
258
|
-
|
259
|
-
# Mask section
|
260
|
-
layout.addWidget(QLabel("Mask:"))
|
261
|
-
layout.addWidget(
|
262
|
-
QLabel(
|
263
|
-
"Use 'Load Mask' to load a mask from an H5 file. 'Delete Mask' removes the current mask."
|
264
|
-
)
|
265
|
-
)
|
266
|
-
|
267
|
-
dialog.setLayout(layout)
|
268
|
-
dialog.exec()
|
269
|
-
|
270
|
-
###############################
|
271
|
-
# just simulations from here
|
272
|
-
###############################
|
273
|
-
# def start_sim_stream(self):
|
274
|
-
# sim_stream_thread = threading.Thread(target=self.sim_stream, daemon=True)
|
275
|
-
# sim_stream_thread.start()
|
276
|
-
#
|
277
|
-
# def sim_stream(self):
|
278
|
-
# for i in range(100):
|
279
|
-
# # Generate 100x100 image of random noise
|
280
|
-
# self.image = np.random.rand(100, 100) * 0.2
|
281
|
-
#
|
282
|
-
# # Define Gaussian parameters
|
283
|
-
# x, y = np.mgrid[0:50, 0:50]
|
284
|
-
# pos = np.dstack((x, y))
|
285
|
-
#
|
286
|
-
# # Center at (25, 25) longer along y-axis
|
287
|
-
# rv = multivariate_normal(mean=[25, 25], cov=[[25, 0], [0, 80]])
|
288
|
-
#
|
289
|
-
# # Generate Gaussian in the first quadrant
|
290
|
-
# gaussian_quadrant = rv.pdf(pos) * 40
|
291
|
-
#
|
292
|
-
# # Place Gaussian in the first quadrant
|
293
|
-
# self.image[0:50, 0:50] += gaussian_quadrant * 10
|
294
|
-
#
|
295
|
-
# self.update_signal.emit()
|
296
|
-
# time.sleep(0.1)
|
297
|
-
|
298
|
-
|
299
|
-
if __name__ == "__main__":
|
300
|
-
import sys
|
301
|
-
|
302
|
-
from qtpy.QtWidgets import QApplication
|
303
|
-
|
304
|
-
app = QApplication(sys.argv)
|
305
|
-
plot = EigerPlot()
|
306
|
-
plot.show()
|
307
|
-
sys.exit(app.exec())
|
@@ -1,207 +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>874</width>
|
10
|
-
<height>762</height>
|
11
|
-
</rect>
|
12
|
-
</property>
|
13
|
-
<property name="windowTitle">
|
14
|
-
<string>Form</string>
|
15
|
-
</property>
|
16
|
-
<layout class="QVBoxLayout" name="verticalLayout_2">
|
17
|
-
<item>
|
18
|
-
<layout class="QHBoxLayout" name="horizontalLayout">
|
19
|
-
<item>
|
20
|
-
<widget class="QGroupBox" name="groupBox">
|
21
|
-
<property name="title">
|
22
|
-
<string>Plot Control</string>
|
23
|
-
</property>
|
24
|
-
<layout class="QGridLayout" name="gridLayout">
|
25
|
-
<item row="0" column="0">
|
26
|
-
<widget class="QLabel" name="label">
|
27
|
-
<property name="text">
|
28
|
-
<string>Histogram MIN</string>
|
29
|
-
</property>
|
30
|
-
</widget>
|
31
|
-
</item>
|
32
|
-
<item row="0" column="1">
|
33
|
-
<widget class="QDoubleSpinBox" name="doubleSpinBox_hist_min">
|
34
|
-
<property name="minimum">
|
35
|
-
<double>-100000.000000000000000</double>
|
36
|
-
</property>
|
37
|
-
<property name="maximum">
|
38
|
-
<double>100000.000000000000000</double>
|
39
|
-
</property>
|
40
|
-
</widget>
|
41
|
-
</item>
|
42
|
-
<item row="1" column="0">
|
43
|
-
<widget class="QLabel" name="label_2">
|
44
|
-
<property name="text">
|
45
|
-
<string>Histogram MAX</string>
|
46
|
-
</property>
|
47
|
-
</widget>
|
48
|
-
</item>
|
49
|
-
<item row="1" column="1">
|
50
|
-
<widget class="QDoubleSpinBox" name="doubleSpinBox_hist_max">
|
51
|
-
<property name="minimum">
|
52
|
-
<double>-100000.000000000000000</double>
|
53
|
-
</property>
|
54
|
-
<property name="maximum">
|
55
|
-
<double>100000.000000000000000</double>
|
56
|
-
</property>
|
57
|
-
<property name="value">
|
58
|
-
<double>2.000000000000000</double>
|
59
|
-
</property>
|
60
|
-
</widget>
|
61
|
-
</item>
|
62
|
-
</layout>
|
63
|
-
</widget>
|
64
|
-
</item>
|
65
|
-
<item>
|
66
|
-
<widget class="QGroupBox" name="groupBox_2">
|
67
|
-
<property name="title">
|
68
|
-
<string>Data Control</string>
|
69
|
-
</property>
|
70
|
-
<layout class="QVBoxLayout" name="verticalLayout">
|
71
|
-
<item>
|
72
|
-
<widget class="QCheckBox" name="checkBox_FFT">
|
73
|
-
<property name="enabled">
|
74
|
-
<bool>true</bool>
|
75
|
-
</property>
|
76
|
-
<property name="text">
|
77
|
-
<string>FFT</string>
|
78
|
-
</property>
|
79
|
-
</widget>
|
80
|
-
</item>
|
81
|
-
<item>
|
82
|
-
<widget class="QCheckBox" name="checkBox_log">
|
83
|
-
<property name="text">
|
84
|
-
<string>log</string>
|
85
|
-
</property>
|
86
|
-
</widget>
|
87
|
-
</item>
|
88
|
-
<item>
|
89
|
-
<widget class="QPushButton" name="pushButton_mask">
|
90
|
-
<property name="text">
|
91
|
-
<string>Load Mask</string>
|
92
|
-
</property>
|
93
|
-
</widget>
|
94
|
-
</item>
|
95
|
-
<item>
|
96
|
-
<widget class="QPushButton" name="pushButton_delete_mask">
|
97
|
-
<property name="text">
|
98
|
-
<string>Delete Mask</string>
|
99
|
-
</property>
|
100
|
-
</widget>
|
101
|
-
</item>
|
102
|
-
</layout>
|
103
|
-
</widget>
|
104
|
-
</item>
|
105
|
-
<item>
|
106
|
-
<widget class="QGroupBox" name="groupBox_3">
|
107
|
-
<property name="title">
|
108
|
-
<string>Orientation</string>
|
109
|
-
</property>
|
110
|
-
<layout class="QGridLayout" name="gridLayout_2">
|
111
|
-
<item row="2" column="1">
|
112
|
-
<widget class="QComboBox" name="comboBox_rotation">
|
113
|
-
<item>
|
114
|
-
<property name="text">
|
115
|
-
<string>0</string>
|
116
|
-
</property>
|
117
|
-
</item>
|
118
|
-
<item>
|
119
|
-
<property name="text">
|
120
|
-
<string>90</string>
|
121
|
-
</property>
|
122
|
-
</item>
|
123
|
-
<item>
|
124
|
-
<property name="text">
|
125
|
-
<string>180</string>
|
126
|
-
</property>
|
127
|
-
</item>
|
128
|
-
<item>
|
129
|
-
<property name="text">
|
130
|
-
<string>270</string>
|
131
|
-
</property>
|
132
|
-
</item>
|
133
|
-
</widget>
|
134
|
-
</item>
|
135
|
-
<item row="2" column="0">
|
136
|
-
<widget class="QLabel" name="label_3">
|
137
|
-
<property name="text">
|
138
|
-
<string>Rotation</string>
|
139
|
-
</property>
|
140
|
-
</widget>
|
141
|
-
</item>
|
142
|
-
<item row="0" column="0" colspan="2">
|
143
|
-
<widget class="QCheckBox" name="checkBox_transpose">
|
144
|
-
<property name="text">
|
145
|
-
<string>Transpose</string>
|
146
|
-
</property>
|
147
|
-
</widget>
|
148
|
-
</item>
|
149
|
-
</layout>
|
150
|
-
</widget>
|
151
|
-
</item>
|
152
|
-
<item>
|
153
|
-
<widget class="QGroupBox" name="groupBox_4">
|
154
|
-
<property name="title">
|
155
|
-
<string>Help</string>
|
156
|
-
</property>
|
157
|
-
<layout class="QVBoxLayout" name="verticalLayout_3">
|
158
|
-
<item>
|
159
|
-
<widget class="QLabel" name="label_mask">
|
160
|
-
<property name="text">
|
161
|
-
<string>No Mask</string>
|
162
|
-
</property>
|
163
|
-
<property name="alignment">
|
164
|
-
<set>Qt::AlignCenter</set>
|
165
|
-
</property>
|
166
|
-
</widget>
|
167
|
-
</item>
|
168
|
-
<item>
|
169
|
-
<widget class="QPushButton" name="pushButton_help">
|
170
|
-
<property name="text">
|
171
|
-
<string>Help</string>
|
172
|
-
</property>
|
173
|
-
</widget>
|
174
|
-
</item>
|
175
|
-
</layout>
|
176
|
-
</widget>
|
177
|
-
</item>
|
178
|
-
<item>
|
179
|
-
<spacer name="horizontalSpacer">
|
180
|
-
<property name="orientation">
|
181
|
-
<enum>Qt::Horizontal</enum>
|
182
|
-
</property>
|
183
|
-
<property name="sizeHint" stdset="0">
|
184
|
-
<size>
|
185
|
-
<width>40</width>
|
186
|
-
<height>20</height>
|
187
|
-
</size>
|
188
|
-
</property>
|
189
|
-
</spacer>
|
190
|
-
</item>
|
191
|
-
</layout>
|
192
|
-
</item>
|
193
|
-
<item>
|
194
|
-
<widget class="GraphicsLayoutWidget" name="glw"/>
|
195
|
-
</item>
|
196
|
-
</layout>
|
197
|
-
</widget>
|
198
|
-
<customwidgets>
|
199
|
-
<customwidget>
|
200
|
-
<class>GraphicsLayoutWidget</class>
|
201
|
-
<extends>QGraphicsView</extends>
|
202
|
-
<header>pyqtgraph.h</header>
|
203
|
-
</customwidget>
|
204
|
-
</customwidgets>
|
205
|
-
<resources/>
|
206
|
-
<connections/>
|
207
|
-
</ui>
|
File without changes
|
@@ -1,159 +0,0 @@
|
|
1
|
-
# import simulation_progress as SP
|
2
|
-
import numpy as np
|
3
|
-
import pyqtgraph as pg
|
4
|
-
from bec_lib import messages
|
5
|
-
from bec_lib.endpoints import MessageEndpoints
|
6
|
-
from qtpy.QtCore import Signal as pyqtSignal
|
7
|
-
from qtpy.QtCore import Slot as pyqtSlot
|
8
|
-
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
|
9
|
-
|
10
|
-
|
11
|
-
class StreamApp(QWidget):
|
12
|
-
update_signal = pyqtSignal()
|
13
|
-
new_scan_id = pyqtSignal(str)
|
14
|
-
|
15
|
-
def __init__(self, device, sub_device):
|
16
|
-
super().__init__()
|
17
|
-
pg.setConfigOptions(background="w", foreground="k")
|
18
|
-
self.init_ui()
|
19
|
-
|
20
|
-
self.setWindowTitle("MCA readout")
|
21
|
-
|
22
|
-
self.data = None
|
23
|
-
self.scan_id = None
|
24
|
-
self.stream_consumer = None
|
25
|
-
|
26
|
-
self.device = device
|
27
|
-
self.sub_device = sub_device
|
28
|
-
|
29
|
-
self.start_device_consumer()
|
30
|
-
|
31
|
-
# self.start_device_consumer(self.device) # for simulation
|
32
|
-
|
33
|
-
self.new_scan_id.connect(self.create_new_stream_consumer)
|
34
|
-
self.update_signal.connect(self.plot_new)
|
35
|
-
|
36
|
-
def init_ui(self):
|
37
|
-
# Create layout and add widgets
|
38
|
-
self.layout = QVBoxLayout()
|
39
|
-
self.setLayout(self.layout)
|
40
|
-
|
41
|
-
# Create plot
|
42
|
-
self.glw = pg.GraphicsLayoutWidget()
|
43
|
-
self.layout.addWidget(self.glw)
|
44
|
-
|
45
|
-
# Create Plot and add ImageItem
|
46
|
-
self.plot_item = pg.PlotItem()
|
47
|
-
self.plot_item.setAspectLocked(False)
|
48
|
-
self.imageItem = pg.ImageItem()
|
49
|
-
# self.plot_item1D = pg.PlotItem()
|
50
|
-
# self.plot_item.addItem(self.imageItem)
|
51
|
-
# self.plot_item.addItem(self.plot_item1D)
|
52
|
-
|
53
|
-
# Setting up histogram
|
54
|
-
# self.hist = pg.HistogramLUTItem()
|
55
|
-
# self.hist.setImageItem(self.imageItem)
|
56
|
-
# self.hist.gradient.loadPreset("magma")
|
57
|
-
# self.update_hist()
|
58
|
-
|
59
|
-
# Adding Items to Graphical Layout
|
60
|
-
self.glw.addItem(self.plot_item)
|
61
|
-
# self.glw.addItem(self.hist)
|
62
|
-
|
63
|
-
@pyqtSlot(str)
|
64
|
-
def create_new_stream_consumer(self, scan_id: str):
|
65
|
-
print(f"Creating new stream consumer for scan_id: {scan_id}")
|
66
|
-
|
67
|
-
self.connect_stream_consumer(scan_id, self.device)
|
68
|
-
|
69
|
-
def connect_stream_consumer(self, scan_id, device):
|
70
|
-
if self.stream_consumer is not None:
|
71
|
-
self.stream_consumer.shutdown()
|
72
|
-
|
73
|
-
self.stream_consumer = connector.stream_consumer(
|
74
|
-
topics=MessageEndpoints.device_async_readback(scan_id=scan_id, device=device),
|
75
|
-
cb=self._streamer_cb,
|
76
|
-
parent=self,
|
77
|
-
)
|
78
|
-
|
79
|
-
self.stream_consumer.start()
|
80
|
-
|
81
|
-
def start_device_consumer(self):
|
82
|
-
self.device_consumer = connector.consumer(
|
83
|
-
topics=MessageEndpoints.scan_status(), cb=self._device_cv, parent=self
|
84
|
-
)
|
85
|
-
|
86
|
-
self.device_consumer.start()
|
87
|
-
|
88
|
-
# def start_device_consumer(self, device): #for simulation
|
89
|
-
# self.device_consumer = connector.consumer(
|
90
|
-
# topics=MessageEndpoints.device_status(device), cb=self._device_cv, parent=self
|
91
|
-
# )
|
92
|
-
#
|
93
|
-
# self.device_consumer.start()
|
94
|
-
|
95
|
-
def plot_new(self):
|
96
|
-
print(f"Printing data from plot update: {self.data}")
|
97
|
-
self.plot_item.plot(self.data[0])
|
98
|
-
# self.imageItem.setImage(self.data, autoLevels=False)
|
99
|
-
|
100
|
-
@staticmethod
|
101
|
-
def _streamer_cb(msg, *, parent, **_kwargs) -> None:
|
102
|
-
msgMCS = msg.value
|
103
|
-
print(msgMCS)
|
104
|
-
row = msgMCS.content["signals"][parent.sub_device]
|
105
|
-
metadata = msgMCS.metadata
|
106
|
-
|
107
|
-
# Check if the current number of rows is odd
|
108
|
-
# if parent.data is not None and parent.data.shape[0] % 2 == 1:
|
109
|
-
# row = np.flip(row) # Flip the row
|
110
|
-
print(f"Printing data from callback update: {row}")
|
111
|
-
parent.data = np.array([row])
|
112
|
-
# if parent.data is None:
|
113
|
-
# parent.data = np.array([row])
|
114
|
-
# else:
|
115
|
-
# parent.data = np.vstack((parent.data, row))
|
116
|
-
|
117
|
-
parent.update_signal.emit()
|
118
|
-
|
119
|
-
@staticmethod
|
120
|
-
def _device_cv(msg, *, parent, **_kwargs) -> None:
|
121
|
-
print("Getting ScanID")
|
122
|
-
|
123
|
-
msgDEV = msg.value
|
124
|
-
|
125
|
-
current_scan_id = msgDEV.content["scan_id"]
|
126
|
-
|
127
|
-
if parent.scan_id is None:
|
128
|
-
parent.scan_id = current_scan_id
|
129
|
-
parent.new_scan_id.emit(current_scan_id)
|
130
|
-
print(f"New scan_id: {current_scan_id}")
|
131
|
-
|
132
|
-
if current_scan_id != parent.scan_id:
|
133
|
-
parent.scan_id = current_scan_id
|
134
|
-
# parent.data = None
|
135
|
-
# parent.imageItem.clear()
|
136
|
-
parent.new_scan_id.emit(current_scan_id)
|
137
|
-
|
138
|
-
print(f"New scan_id: {current_scan_id}")
|
139
|
-
|
140
|
-
|
141
|
-
if __name__ == "__main__":
|
142
|
-
import argparse
|
143
|
-
|
144
|
-
from bec_lib.redis_connector import RedisConnector
|
145
|
-
|
146
|
-
parser = argparse.ArgumentParser(description="Stream App.")
|
147
|
-
parser.add_argument("--port", type=str, default="pc15543:6379", help="Port for RedisConnector")
|
148
|
-
parser.add_argument("--device", type=str, default="mcs", help="Device name")
|
149
|
-
parser.add_argument("--sub_device", type=str, default="mca4", help="Sub-device name")
|
150
|
-
|
151
|
-
args = parser.parse_args()
|
152
|
-
|
153
|
-
connector = RedisConnector(args.port)
|
154
|
-
|
155
|
-
app = QApplication([])
|
156
|
-
streamApp = StreamApp(device=args.device, sub_device=args.sub_device)
|
157
|
-
|
158
|
-
streamApp.show()
|
159
|
-
app.exec()
|
@@ -1,28 +0,0 @@
|
|
1
|
-
import time
|
2
|
-
|
3
|
-
from bec_lib import messages
|
4
|
-
from bec_lib.endpoints import MessageEndpoints
|
5
|
-
from bec_lib.redis_connector import RedisConnector
|
6
|
-
|
7
|
-
connector = RedisConnector("localhost:6379")
|
8
|
-
metadata = {}
|
9
|
-
|
10
|
-
scan_id = "ScanID1"
|
11
|
-
|
12
|
-
metadata.update(
|
13
|
-
{"scan_id": scan_id, "async_update": "append"} # this will be different for each scan
|
14
|
-
)
|
15
|
-
for ii in range(20):
|
16
|
-
data = {"mca1": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "mca2": [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]}
|
17
|
-
msg = messages.DeviceMessage(signals=data, metadata=metadata).dumps()
|
18
|
-
|
19
|
-
connector.xadd(
|
20
|
-
topic=MessageEndpoints.device_async_readback(
|
21
|
-
scan_id=scan_id, device="mca"
|
22
|
-
), # scan_id will be different for each scan
|
23
|
-
msg={"data": msg}, # TODO should be msg_dict
|
24
|
-
expire=1800,
|
25
|
-
)
|
26
|
-
|
27
|
-
print(f"Sent {ii}")
|
28
|
-
time.sleep(0.5)
|