bec-widgets 0.54.0__py3-none-any.whl → 0.56.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. .gitlab-ci.yml +113 -8
  2. CHANGELOG.md +32 -21
  3. PKG-INFO +3 -1
  4. bec_widgets/cli/client.py +252 -0
  5. bec_widgets/cli/generate_cli.py +4 -1
  6. bec_widgets/cli/rpc_wigdet_handler.py +2 -1
  7. bec_widgets/examples/jupyter_console/jupyter_console_window.py +29 -37
  8. bec_widgets/examples/motor_movement/motor_control_compilations.py +1 -7
  9. bec_widgets/utils/__init__.py +1 -0
  10. bec_widgets/utils/crosshair.py +13 -9
  11. bec_widgets/utils/ui_loader.py +58 -0
  12. bec_widgets/widgets/__init__.py +1 -0
  13. bec_widgets/widgets/motor_control/motor_table/motor_table.py +44 -43
  14. bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +25 -23
  15. bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +51 -48
  16. bec_widgets/widgets/spiral_progress_bar/__init__.py +1 -0
  17. bec_widgets/widgets/spiral_progress_bar/ring.py +184 -0
  18. bec_widgets/widgets/spiral_progress_bar/spiral_progress_bar.py +594 -0
  19. {bec_widgets-0.54.0.dist-info → bec_widgets-0.56.0.dist-info}/METADATA +3 -1
  20. {bec_widgets-0.54.0.dist-info → bec_widgets-0.56.0.dist-info}/RECORD +29 -46
  21. docs/user/apps.md +1 -26
  22. pyproject.toml +2 -1
  23. tests/end-2-end/test_bec_dock_rpc_e2e.py +81 -0
  24. tests/unit_tests/test_client_utils.py +2 -2
  25. tests/unit_tests/test_crosshair.py +5 -5
  26. tests/unit_tests/test_motor_control.py +49 -45
  27. tests/unit_tests/test_spiral_progress_bar.py +338 -0
  28. bec_widgets/examples/eiger_plot/__init__.py +0 -0
  29. bec_widgets/examples/eiger_plot/eiger_plot.py +0 -307
  30. bec_widgets/examples/eiger_plot/eiger_plot.ui +0 -207
  31. bec_widgets/examples/mca_readout/__init__.py +0 -0
  32. bec_widgets/examples/mca_readout/mca_plot.py +0 -159
  33. bec_widgets/examples/mca_readout/mca_sim.py +0 -28
  34. bec_widgets/examples/modular_app/___init__.py +0 -0
  35. bec_widgets/examples/modular_app/modular.ui +0 -92
  36. bec_widgets/examples/modular_app/modular_app.py +0 -197
  37. bec_widgets/examples/motor_movement/config_example.yaml +0 -17
  38. bec_widgets/examples/motor_movement/csax_bec_config.yaml +0 -10
  39. bec_widgets/examples/motor_movement/csaxs_config.yaml +0 -17
  40. bec_widgets/examples/motor_movement/motor_example.py +0 -1344
  41. bec_widgets/examples/stream_plot/__init__.py +0 -0
  42. bec_widgets/examples/stream_plot/line_plot.ui +0 -155
  43. bec_widgets/examples/stream_plot/stream_plot.py +0 -337
  44. docs/user/apps/modular_app.md +0 -6
  45. docs/user/apps/motor_app.md +0 -34
  46. docs/user/apps/motor_app_10fps.gif +0 -0
  47. docs/user/apps/plot_app.md +0 -6
  48. tests/unit_tests/test_eiger_plot.py +0 -115
  49. tests/unit_tests/test_stream_plot.py +0 -158
  50. {bec_widgets-0.54.0.dist-info → bec_widgets-0.56.0.dist-info}/WHEEL +0 -0
  51. {bec_widgets-0.54.0.dist-info → bec_widgets-0.56.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,338 @@
1
+ # pylint: disable=missing-function-docstring, missing-module-docstring, unused-import
2
+
3
+ import pytest
4
+ from bec_lib.endpoints import MessageEndpoints
5
+ from pydantic import ValidationError
6
+
7
+ from bec_widgets.utils import Colors
8
+ from bec_widgets.widgets import SpiralProgressBar
9
+ from bec_widgets.widgets.spiral_progress_bar.ring import RingConfig, RingConnections
10
+ from bec_widgets.widgets.spiral_progress_bar.spiral_progress_bar import SpiralProgressBarConfig
11
+
12
+ from .client_mocks import mocked_client
13
+
14
+
15
+ @pytest.fixture
16
+ def spiral_progress_bar(qtbot, mocked_client):
17
+ widget = SpiralProgressBar(client=mocked_client)
18
+ qtbot.addWidget(widget)
19
+ qtbot.waitExposed(widget)
20
+ yield widget
21
+ widget.close()
22
+
23
+
24
+ def test_bar_init(spiral_progress_bar):
25
+ assert spiral_progress_bar is not None
26
+ assert spiral_progress_bar.client is not None
27
+ assert isinstance(spiral_progress_bar, SpiralProgressBar)
28
+ assert spiral_progress_bar.config.widget_class == "SpiralProgressBar"
29
+ assert spiral_progress_bar.config.gui_id is not None
30
+ assert spiral_progress_bar.gui_id == spiral_progress_bar.config.gui_id
31
+
32
+
33
+ def test_config_validation_num_of_bars():
34
+ config = SpiralProgressBarConfig(num_bars=100, min_num_bars=1, max_num_bars=10)
35
+
36
+ assert config.num_bars == 10
37
+
38
+
39
+ def test_config_validation_num_of_ring_error():
40
+ ring_config_0 = RingConfig(index=0)
41
+ ring_config_1 = RingConfig(index=1)
42
+
43
+ with pytest.raises(ValidationError) as excinfo:
44
+ SpiralProgressBarConfig(rings=[ring_config_0, ring_config_1], num_bars=1)
45
+ errors = excinfo.value.errors()
46
+ assert len(errors) == 1
47
+ assert errors[0]["type"] == "different number of configs"
48
+ assert "Length of rings configuration (2) does not match the number of bars (1)." in str(
49
+ excinfo.value
50
+ )
51
+
52
+
53
+ def test_config_validation_ring_indices_wrong_order():
54
+ ring_config_0 = RingConfig(index=2)
55
+ ring_config_1 = RingConfig(index=5)
56
+
57
+ with pytest.raises(ValidationError) as excinfo:
58
+ SpiralProgressBarConfig(rings=[ring_config_0, ring_config_1], num_bars=2)
59
+ errors = excinfo.value.errors()
60
+ assert len(errors) == 1
61
+ assert errors[0]["type"] == "wrong indices"
62
+ assert (
63
+ "Indices of ring configurations must be unique and in order from 0 to num_bars 2."
64
+ in str(excinfo.value)
65
+ )
66
+
67
+
68
+ def test_config_validation_ring_same_indices():
69
+ ring_config_0 = RingConfig(index=0)
70
+ ring_config_1 = RingConfig(index=0)
71
+
72
+ with pytest.raises(ValidationError) as excinfo:
73
+ SpiralProgressBarConfig(rings=[ring_config_0, ring_config_1], num_bars=2)
74
+ errors = excinfo.value.errors()
75
+ assert len(errors) == 1
76
+ assert errors[0]["type"] == "wrong indices"
77
+ assert (
78
+ "Indices of ring configurations must be unique and in order from 0 to num_bars 2."
79
+ in str(excinfo.value)
80
+ )
81
+
82
+
83
+ def test_config_validation_invalid_colormap():
84
+ with pytest.raises(ValueError) as excinfo:
85
+ SpiralProgressBarConfig(color_map="crazy_colors")
86
+ errors = excinfo.value.errors()
87
+ assert len(errors) == 1
88
+ assert errors[0]["type"] == "unsupported colormap"
89
+ assert "Colormap 'crazy_colors' not found in the current installation of pyqtgraph" in str(
90
+ excinfo.value
91
+ )
92
+
93
+
94
+ def test_ring_connection_endpoint_validation():
95
+ with pytest.raises(ValueError) as excinfo:
96
+ RingConnections(slot="on_scan_progress", endpoint="non_existing")
97
+ errors = excinfo.value.errors()
98
+ assert len(errors) == 1
99
+ assert errors[0]["type"] == "unsupported endpoint"
100
+ assert (
101
+ "For slot 'on_scan_progress', endpoint must be MessageEndpoint.scan_progress or 'scans/scan_progress'."
102
+ in str(excinfo.value)
103
+ )
104
+
105
+ with pytest.raises(ValueError) as excinfo:
106
+ RingConnections(slot="on_device_readback", endpoint="non_existing")
107
+ errors = excinfo.value.errors()
108
+ assert len(errors) == 1
109
+ assert errors[0]["type"] == "unsupported endpoint"
110
+ assert (
111
+ "For slot 'on_device_readback', endpoint must be MessageEndpoint.device_readback(device) or 'internal/devices/readback/{device}'."
112
+ in str(excinfo.value)
113
+ )
114
+
115
+
116
+ def test_bar_add_number_of_bars(spiral_progress_bar):
117
+ assert spiral_progress_bar.config.num_bars == 1
118
+
119
+ spiral_progress_bar.set_number_of_bars(5)
120
+ assert spiral_progress_bar.config.num_bars == 5
121
+
122
+ spiral_progress_bar.set_number_of_bars(2)
123
+ assert spiral_progress_bar.config.num_bars == 2
124
+
125
+
126
+ def test_add_remove_bars_individually(spiral_progress_bar):
127
+ spiral_progress_bar.add_ring()
128
+ spiral_progress_bar.add_ring()
129
+
130
+ assert spiral_progress_bar.config.num_bars == 3
131
+ assert len(spiral_progress_bar.config.rings) == 3
132
+
133
+ spiral_progress_bar.remove_ring(1)
134
+ assert spiral_progress_bar.config.num_bars == 2
135
+ assert len(spiral_progress_bar.config.rings) == 2
136
+ assert spiral_progress_bar.rings[0].config.index == 0
137
+ assert spiral_progress_bar.rings[1].config.index == 1
138
+
139
+
140
+ def test_bar_set_value(spiral_progress_bar):
141
+ spiral_progress_bar.set_number_of_bars(5)
142
+
143
+ assert spiral_progress_bar.config.num_bars == 5
144
+ assert len(spiral_progress_bar.config.rings) == 5
145
+ assert len(spiral_progress_bar.rings) == 5
146
+
147
+ spiral_progress_bar.set_value([10, 20, 30, 40, 50])
148
+ ring_values = [ring.value for ring in spiral_progress_bar.rings]
149
+ assert ring_values == [10, 20, 30, 40, 50]
150
+
151
+ # update just one bar
152
+ spiral_progress_bar.set_value(90, 1)
153
+ ring_values = [ring.value for ring in spiral_progress_bar.rings]
154
+ assert ring_values == [10, 90, 30, 40, 50]
155
+
156
+
157
+ def test_bar_set_precision(spiral_progress_bar):
158
+ spiral_progress_bar.set_number_of_bars(3)
159
+
160
+ assert spiral_progress_bar.config.num_bars == 3
161
+ assert len(spiral_progress_bar.config.rings) == 3
162
+ assert len(spiral_progress_bar.rings) == 3
163
+
164
+ spiral_progress_bar.set_precision(2)
165
+ ring_precision = [ring.config.precision for ring in spiral_progress_bar.rings]
166
+ assert ring_precision == [2, 2, 2]
167
+
168
+ spiral_progress_bar.set_value([10.1234, 20.1234, 30.1234])
169
+ ring_values = [ring.value for ring in spiral_progress_bar.rings]
170
+ assert ring_values == [10.12, 20.12, 30.12]
171
+
172
+ spiral_progress_bar.set_precision(4, 1)
173
+ ring_precision = [ring.config.precision for ring in spiral_progress_bar.rings]
174
+ assert ring_precision == [2, 4, 2]
175
+
176
+ spiral_progress_bar.set_value([10.1234, 20.1234, 30.1234])
177
+ ring_values = [ring.value for ring in spiral_progress_bar.rings]
178
+ assert ring_values == [10.12, 20.1234, 30.12]
179
+
180
+
181
+ def test_set_min_max_value(spiral_progress_bar):
182
+ spiral_progress_bar.set_number_of_bars(2)
183
+
184
+ spiral_progress_bar.set_min_max_values(0, 10)
185
+ ring_min_values = [ring.config.min_value for ring in spiral_progress_bar.rings]
186
+ ring_max_values = [ring.config.max_value for ring in spiral_progress_bar.rings]
187
+
188
+ assert ring_min_values == [0, 0]
189
+ assert ring_max_values == [10, 10]
190
+
191
+ spiral_progress_bar.set_value([5, 15])
192
+ ring_values = [ring.value for ring in spiral_progress_bar.rings]
193
+ assert ring_values == [5, 10]
194
+
195
+
196
+ def test_setup_colors_from_colormap(spiral_progress_bar):
197
+ spiral_progress_bar.set_number_of_bars(5)
198
+ spiral_progress_bar.set_colors_from_map("viridis", "RGB")
199
+
200
+ expected_colors = Colors.golden_angle_color("viridis", 5, "RGB")
201
+ converted_colors = [ring.color.getRgb() for ring in spiral_progress_bar.rings]
202
+ ring_config_colors = [ring.config.color for ring in spiral_progress_bar.rings]
203
+
204
+ assert expected_colors == converted_colors
205
+ assert ring_config_colors == expected_colors
206
+
207
+
208
+ def get_colors_from_rings(rings):
209
+ converted_colors = [ring.color.getRgb() for ring in rings]
210
+ ring_config_colors = [ring.config.color for ring in rings]
211
+ return converted_colors, ring_config_colors
212
+
213
+
214
+ def test_set_colors_from_colormap_and_change_num_of_bars(spiral_progress_bar):
215
+ spiral_progress_bar.set_number_of_bars(2)
216
+ spiral_progress_bar.set_colors_from_map("viridis", "RGB")
217
+
218
+ expected_colors = Colors.golden_angle_color("viridis", 2, "RGB")
219
+ converted_colors, ring_config_colors = get_colors_from_rings(spiral_progress_bar.rings)
220
+
221
+ assert expected_colors == converted_colors
222
+ assert ring_config_colors == expected_colors
223
+
224
+ # increase the number of bars to 6
225
+ spiral_progress_bar.set_number_of_bars(6)
226
+ expected_colors = Colors.golden_angle_color("viridis", 6, "RGB")
227
+ converted_colors, ring_config_colors = get_colors_from_rings(spiral_progress_bar.rings)
228
+
229
+ assert expected_colors == converted_colors
230
+ assert ring_config_colors == expected_colors
231
+
232
+ # decrease the number of bars to 3
233
+ spiral_progress_bar.set_number_of_bars(3)
234
+ expected_colors = Colors.golden_angle_color("viridis", 3, "RGB")
235
+ converted_colors, ring_config_colors = get_colors_from_rings(spiral_progress_bar.rings)
236
+
237
+ assert expected_colors == converted_colors
238
+ assert ring_config_colors == expected_colors
239
+
240
+
241
+ def test_set_colors_directly(spiral_progress_bar):
242
+ spiral_progress_bar.set_number_of_bars(3)
243
+
244
+ # setting as a list of rgb tuples
245
+ colors = [(255, 0, 0, 255), (0, 255, 0, 255), (0, 0, 255, 255)]
246
+ spiral_progress_bar.set_colors_directly(colors)
247
+ converted_colors = get_colors_from_rings(spiral_progress_bar.rings)[0]
248
+
249
+ assert colors == converted_colors
250
+
251
+ spiral_progress_bar.set_colors_directly((255, 0, 0, 255), 1)
252
+ converted_colors = get_colors_from_rings(spiral_progress_bar.rings)[0]
253
+
254
+ assert converted_colors == [(255, 0, 0, 255), (255, 0, 0, 255), (0, 0, 255, 255)]
255
+
256
+
257
+ def test_set_line_width(spiral_progress_bar):
258
+ spiral_progress_bar.set_number_of_bars(3)
259
+
260
+ spiral_progress_bar.set_line_widths(5)
261
+ line_widths = [ring.config.line_width for ring in spiral_progress_bar.rings]
262
+
263
+ assert line_widths == [5, 5, 5]
264
+
265
+ spiral_progress_bar.set_line_widths([10, 20, 30])
266
+ line_widths = [ring.config.line_width for ring in spiral_progress_bar.rings]
267
+
268
+ assert line_widths == [10, 20, 30]
269
+
270
+ spiral_progress_bar.set_line_widths(15, 1)
271
+ line_widths = [ring.config.line_width for ring in spiral_progress_bar.rings]
272
+
273
+ assert line_widths == [10, 15, 30]
274
+
275
+
276
+ def test_set_gap(spiral_progress_bar):
277
+ spiral_progress_bar.set_number_of_bars(3)
278
+ spiral_progress_bar.set_gap(20)
279
+
280
+ assert spiral_progress_bar.config.gap == 20
281
+
282
+
283
+ def test_auto_update(spiral_progress_bar):
284
+ spiral_progress_bar.enable_auto_updates(True)
285
+
286
+ scan_queue_status_scan_progress = {
287
+ "queue": {
288
+ "primary": {
289
+ "info": [{"active_request_block": {"report_instructions": [{"scan_progress": 10}]}}]
290
+ }
291
+ }
292
+ }
293
+ meta = {}
294
+
295
+ spiral_progress_bar.on_scan_queue_status(scan_queue_status_scan_progress, meta)
296
+
297
+ assert spiral_progress_bar._auto_updates is True
298
+ assert len(spiral_progress_bar._rings) == 1
299
+ assert spiral_progress_bar._rings[0].config.connections == RingConnections(
300
+ slot="on_scan_progress", endpoint=MessageEndpoints.scan_progress()
301
+ )
302
+
303
+ scan_queue_status_device_readback = {
304
+ "queue": {
305
+ "primary": {
306
+ "info": [
307
+ {
308
+ "active_request_block": {
309
+ "report_instructions": [
310
+ {
311
+ "readback": {
312
+ "devices": ["samx", "samy"],
313
+ "start": [1, 2],
314
+ "end": [10, 20],
315
+ }
316
+ }
317
+ ]
318
+ }
319
+ }
320
+ ]
321
+ }
322
+ }
323
+ }
324
+ spiral_progress_bar.on_scan_queue_status(scan_queue_status_device_readback, meta)
325
+
326
+ assert spiral_progress_bar._auto_updates is True
327
+ assert len(spiral_progress_bar._rings) == 2
328
+ assert spiral_progress_bar._rings[0].config.connections == RingConnections(
329
+ slot="on_device_readback", endpoint=MessageEndpoints.device_readback("samx")
330
+ )
331
+ assert spiral_progress_bar._rings[1].config.connections == RingConnections(
332
+ slot="on_device_readback", endpoint=MessageEndpoints.device_readback("samy")
333
+ )
334
+
335
+ assert spiral_progress_bar._rings[0].config.min_value == 1
336
+ assert spiral_progress_bar._rings[0].config.max_value == 10
337
+ assert spiral_progress_bar._rings[1].config.min_value == 2
338
+ assert spiral_progress_bar._rings[1].config.max_value == 20
File without changes
@@ -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())