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.
Files changed (44) hide show
  1. .gitlab-ci.yml +113 -8
  2. CHANGELOG.md +34 -28
  3. PKG-INFO +3 -1
  4. bec_widgets/examples/jupyter_console/jupyter_console_window.py +28 -38
  5. bec_widgets/examples/motor_movement/motor_control_compilations.py +1 -7
  6. bec_widgets/utils/__init__.py +1 -0
  7. bec_widgets/utils/crosshair.py +13 -9
  8. bec_widgets/utils/ui_loader.py +58 -0
  9. bec_widgets/widgets/motor_control/motor_table/motor_table.py +44 -43
  10. bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +25 -23
  11. bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +51 -48
  12. bec_widgets/widgets/spiral_progress_bar/ring.py +5 -5
  13. {bec_widgets-0.55.0.dist-info → bec_widgets-0.56.1.dist-info}/METADATA +3 -1
  14. {bec_widgets-0.55.0.dist-info → bec_widgets-0.56.1.dist-info}/RECORD +22 -43
  15. docs/user/apps.md +1 -26
  16. pyproject.toml +2 -1
  17. tests/end-2-end/test_bec_dock_rpc_e2e.py +1 -1
  18. tests/unit_tests/test_client_utils.py +2 -2
  19. tests/unit_tests/test_crosshair.py +5 -5
  20. tests/unit_tests/test_motor_control.py +49 -45
  21. bec_widgets/examples/eiger_plot/__init__.py +0 -0
  22. bec_widgets/examples/eiger_plot/eiger_plot.py +0 -307
  23. bec_widgets/examples/eiger_plot/eiger_plot.ui +0 -207
  24. bec_widgets/examples/mca_readout/__init__.py +0 -0
  25. bec_widgets/examples/mca_readout/mca_plot.py +0 -159
  26. bec_widgets/examples/mca_readout/mca_sim.py +0 -28
  27. bec_widgets/examples/modular_app/___init__.py +0 -0
  28. bec_widgets/examples/modular_app/modular.ui +0 -92
  29. bec_widgets/examples/modular_app/modular_app.py +0 -197
  30. bec_widgets/examples/motor_movement/config_example.yaml +0 -17
  31. bec_widgets/examples/motor_movement/csax_bec_config.yaml +0 -10
  32. bec_widgets/examples/motor_movement/csaxs_config.yaml +0 -17
  33. bec_widgets/examples/motor_movement/motor_example.py +0 -1344
  34. bec_widgets/examples/stream_plot/__init__.py +0 -0
  35. bec_widgets/examples/stream_plot/line_plot.ui +0 -155
  36. bec_widgets/examples/stream_plot/stream_plot.py +0 -337
  37. docs/user/apps/modular_app.md +0 -6
  38. docs/user/apps/motor_app.md +0 -34
  39. docs/user/apps/motor_app_10fps.gif +0 -0
  40. docs/user/apps/plot_app.md +0 -6
  41. tests/unit_tests/test_eiger_plot.py +0 -115
  42. tests/unit_tests/test_stream_plot.py +0 -158
  43. {bec_widgets-0.55.0.dist-info → bec_widgets-0.56.1.dist-info}/WHEEL +0 -0
  44. {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)