bec-widgets 0.83.0__py3-none-any.whl → 0.84.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 (30) hide show
  1. CHANGELOG.md +50 -54
  2. PKG-INFO +2 -2
  3. bec_widgets/cli/client.py +61 -8
  4. bec_widgets/examples/jupyter_console/jupyter_console_window.py +107 -59
  5. bec_widgets/qt_utils/toolbar.py +3 -1
  6. bec_widgets/utils/bec_dispatcher.py +5 -2
  7. bec_widgets/utils/colors.py +27 -0
  8. bec_widgets/widgets/bec_status_box/bec_status_box.py +2 -2
  9. bec_widgets/widgets/device_box/device_box.py +2 -2
  10. bec_widgets/widgets/figure/figure.py +23 -117
  11. bec_widgets/widgets/figure/plots/axis_settings.py +2 -2
  12. bec_widgets/widgets/figure/plots/waveform/waveform.py +651 -94
  13. bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +9 -2
  14. bec_widgets/widgets/scan_control/scan_control.py +2 -2
  15. bec_widgets/widgets/spinner/spinner.py +4 -3
  16. {bec_widgets-0.83.0.dist-info → bec_widgets-0.84.0.dist-info}/METADATA +2 -2
  17. {bec_widgets-0.83.0.dist-info → bec_widgets-0.84.0.dist-info}/RECORD +29 -30
  18. pyproject.toml +2 -2
  19. tests/references/SpinnerWidget/SpinnerWidget_darwin.png +0 -0
  20. tests/references/SpinnerWidget/SpinnerWidget_linux.png +0 -0
  21. tests/references/SpinnerWidget/SpinnerWidget_started_darwin.png +0 -0
  22. tests/references/SpinnerWidget/SpinnerWidget_started_linux.png +0 -0
  23. tests/unit_tests/client_mocks.py +13 -4
  24. tests/unit_tests/test_device_input_widgets.py +2 -0
  25. tests/unit_tests/test_spinner.py +2 -2
  26. tests/unit_tests/test_waveform1d.py +202 -23
  27. bec_widgets/examples/jupyter_console/jupyter_console_window.ui +0 -54
  28. {bec_widgets-0.83.0.dist-info → bec_widgets-0.84.0.dist-info}/WHEEL +0 -0
  29. {bec_widgets-0.83.0.dist-info → bec_widgets-0.84.0.dist-info}/entry_points.txt +0 -0
  30. {bec_widgets-0.83.0.dist-info → bec_widgets-0.84.0.dist-info}/licenses/LICENSE +0 -0
@@ -14,36 +14,36 @@ def test_adding_curve_to_waveform(bec_figure):
14
14
  w1 = bec_figure.plot()
15
15
 
16
16
  # adding curve which is in bec - only names
17
- c1 = w1.add_curve_scan(x_name="samx", y_name="bpm4i")
17
+ c1 = w1.add_curve_bec(x_name="samx", y_name="bpm4i")
18
18
  assert c1.config.label == "bpm4i-bpm4i"
19
19
 
20
20
  # adding curve which is in bec - names and entry
21
- c2 = w1.add_curve_scan(x_name="samx", x_entry="samx", y_name="bpm3a", y_entry="bpm3a")
21
+ c2 = w1.add_curve_bec(x_name="samx", x_entry="samx", y_name="bpm3a", y_entry="bpm3a")
22
22
  assert c2.config.label == "bpm3a-bpm3a"
23
23
 
24
24
  # adding curve which is not in bec
25
25
  with pytest.raises(ValueError) as excinfo:
26
- w1.add_curve_scan(x_name="non_existent_device", y_name="non_existent_device")
26
+ w1.add_curve_bec(x_name="non_existent_device", y_name="non_existent_device")
27
27
  assert "Device 'non_existent_device' not found in current BEC session" in str(excinfo.value)
28
28
 
29
29
  # adding wrong entry for samx
30
30
  with pytest.raises(ValueError) as excinfo:
31
- w1.add_curve_scan(
31
+ w1.add_curve_bec(
32
32
  x_name="samx", x_entry="non_existent_entry", y_name="bpm3a", y_entry="bpm3a"
33
33
  )
34
34
  assert "Entry 'non_existent_entry' not found in device 'samx' signals" in str(excinfo.value)
35
35
 
36
36
  # adding wrong device with validation switched off
37
- c3 = w1.add_curve_scan(x_name="samx", y_name="non_existent_device", validate_bec=False)
37
+ c3 = w1.add_curve_bec(x_name="samx", y_name="non_existent_device", validate_bec=False)
38
38
  assert c3.config.label == "non_existent_device-non_existent_device"
39
39
 
40
40
 
41
41
  def test_adding_curve_with_same_id(bec_figure):
42
42
  w1 = bec_figure.plot()
43
- c1 = w1.add_curve_scan(x_name="samx", y_name="bpm4i", gui_id="test_curve")
43
+ c1 = w1.add_curve_bec(x_name="samx", y_name="bpm4i", gui_id="test_curve")
44
44
 
45
45
  with pytest.raises(ValueError) as excinfo:
46
- w1.add_curve_scan(x_name="samx", y_name="bpm4i", gui_id="test_curve")
46
+ w1.add_curve_bec(x_name="samx", y_name="bpm4i", gui_id="test_curve")
47
47
  assert "Curve with ID 'test_curve' already exists." in str(excinfo.value)
48
48
 
49
49
 
@@ -134,7 +134,7 @@ def test_create_waveform1D_by_config(bec_figure):
134
134
 
135
135
  def test_change_gui_id(bec_figure):
136
136
  w1 = bec_figure.plot()
137
- c1 = w1.add_curve_scan(x_name="samx", y_name="bpm4i")
137
+ c1 = w1.add_curve_bec(x_name="samx", y_name="bpm4i")
138
138
  w1.change_gui_id("new_id")
139
139
 
140
140
  assert w1.config.gui_id == "new_id"
@@ -143,7 +143,7 @@ def test_change_gui_id(bec_figure):
143
143
 
144
144
  def test_getting_curve(bec_figure):
145
145
  w1 = bec_figure.plot()
146
- c1 = w1.add_curve_scan(x_name="samx", y_name="bpm4i", gui_id="test_curve")
146
+ c1 = w1.add_curve_bec(x_name="samx", y_name="bpm4i", gui_id="test_curve")
147
147
  c1_expected_config = CurveConfig(
148
148
  widget_class="BECCurve",
149
149
  gui_id="test_curve",
@@ -166,6 +166,8 @@ def test_getting_curve(bec_figure):
166
166
  assert w1.curves[0].config == c1_expected_config
167
167
  assert w1._curves_data["scan_segment"]["bpm4i-bpm4i"].config == c1_expected_config
168
168
  assert w1.get_curve(0).config == c1_expected_config
169
+ assert w1.get_curve_config("bpm4i-bpm4i", dict_output=True) == c1_expected_config.model_dump()
170
+ assert w1.get_curve_config("bpm4i-bpm4i", dict_output=False) == c1_expected_config
169
171
  assert w1.get_curve("bpm4i-bpm4i").config == c1_expected_config
170
172
  assert c1.get_config(False) == c1_expected_config
171
173
  assert c1.get_config() == c1_expected_config.model_dump()
@@ -173,7 +175,7 @@ def test_getting_curve(bec_figure):
173
175
 
174
176
  def test_getting_curve_errors(bec_figure):
175
177
  w1 = bec_figure.plot()
176
- c1 = w1.add_curve_scan(x_name="samx", y_name="bpm4i", gui_id="test_curve")
178
+ c1 = w1.add_curve_bec(x_name="samx", y_name="bpm4i", gui_id="test_curve")
177
179
 
178
180
  with pytest.raises(ValueError) as excinfo:
179
181
  w1.get_curve("non_existent_curve")
@@ -191,7 +193,7 @@ def test_getting_curve_errors(bec_figure):
191
193
  def test_add_curve(bec_figure):
192
194
  w1 = bec_figure.plot()
193
195
 
194
- c1 = w1.add_curve_scan(x_name="samx", y_name="bpm4i")
196
+ c1 = w1.add_curve_bec(x_name="samx", y_name="bpm4i")
195
197
 
196
198
  assert len(w1.curves) == 1
197
199
  assert w1._curves_data["scan_segment"] == {"bpm4i-bpm4i": c1}
@@ -202,7 +204,7 @@ def test_add_curve(bec_figure):
202
204
  def test_change_legend_font_size(bec_figure):
203
205
  plot = bec_figure.plot()
204
206
 
205
- w1 = plot.add_curve_scan(x_name="samx", y_name="bpm4i")
207
+ w1 = plot.add_curve_bec(x_name="samx", y_name="bpm4i")
206
208
  my_func = plot.plot_item.legend
207
209
  with mock.patch.object(my_func, "setScale") as mock_set_scale:
208
210
  plot.set_legend_label_size(18)
@@ -214,8 +216,8 @@ def test_change_legend_font_size(bec_figure):
214
216
  def test_remove_curve(bec_figure):
215
217
  w1 = bec_figure.plot()
216
218
 
217
- w1.add_curve_scan(x_name="samx", y_name="bpm4i")
218
- w1.add_curve_scan(x_name="samx", y_name="bpm3a")
219
+ w1.add_curve_bec(x_name="samx", y_name="bpm4i")
220
+ w1.add_curve_bec(x_name="samx", y_name="bpm3a")
219
221
  w1.remove_curve(0)
220
222
  w1.remove_curve("bpm3a-bpm3a")
221
223
 
@@ -232,7 +234,7 @@ def test_remove_curve(bec_figure):
232
234
  def test_change_curve_appearance_methods(bec_figure, qtbot):
233
235
  w1 = bec_figure.plot()
234
236
 
235
- c1 = w1.add_curve_scan(x_name="samx", y_name="bpm4i")
237
+ c1 = w1.add_curve_bec(x_name="samx", y_name="bpm4i")
236
238
 
237
239
  c1.set_color("#0000ff")
238
240
  c1.set_symbol("x")
@@ -261,7 +263,7 @@ def test_change_curve_appearance_methods(bec_figure, qtbot):
261
263
  def test_change_curve_appearance_args(bec_figure):
262
264
  w1 = bec_figure.plot()
263
265
 
264
- c1 = w1.add_curve_scan(x_name="samx", y_name="bpm4i")
266
+ c1 = w1.add_curve_bec(x_name="samx", y_name="bpm4i")
265
267
 
266
268
  c1.set(
267
269
  color="#0000ff",
@@ -414,7 +416,7 @@ def test_curve_add_by_config(bec_figure):
414
416
  def test_scan_update(bec_figure, qtbot):
415
417
  w1 = bec_figure.plot()
416
418
 
417
- c1 = w1.add_curve_scan(x_name="samx", y_name="bpm4i")
419
+ c1 = w1.add_curve_bec(x_name="samx", y_name="bpm4i")
418
420
 
419
421
  msg_waveform = {
420
422
  "data": {
@@ -448,7 +450,7 @@ def test_scan_update(bec_figure, qtbot):
448
450
  def test_scan_history_with_val_access(bec_figure, qtbot):
449
451
  w1 = bec_figure.plot()
450
452
 
451
- c1 = w1.add_curve_scan(x_name="samx", y_name="bpm4i")
453
+ w1.plot(x_name="samx", y_name="bpm4i")
452
454
 
453
455
  mock_scan_data = {
454
456
  "samx": {"samx": mock.MagicMock(val=np.array([1, 2, 3]))}, # Use mock.MagicMock for .val
@@ -464,7 +466,7 @@ def test_scan_history_with_val_access(bec_figure, qtbot):
464
466
 
465
467
  qtbot.wait(500)
466
468
 
467
- x_data, y_data = c1.get_data()
469
+ x_data, y_data = w1.curves[0].get_data()
468
470
 
469
471
  assert np.array_equal(x_data, [1, 2, 3])
470
472
  assert np.array_equal(y_data, [4, 5, 6])
@@ -473,7 +475,7 @@ def test_scan_history_with_val_access(bec_figure, qtbot):
473
475
  def test_scatter_2d_update(bec_figure, qtbot):
474
476
  w1 = bec_figure.plot()
475
477
 
476
- c1 = w1.add_curve_scan(x_name="samx", y_name="samx", z_name="bpm4i")
478
+ c1 = w1.add_curve_bec(x_name="samx", y_name="samx", z_name="bpm4i")
477
479
 
478
480
  msg = {
479
481
  "data": {
@@ -485,8 +487,8 @@ def test_scatter_2d_update(bec_figure, qtbot):
485
487
  }
486
488
  msg_metadata = {"scan_name": "line_scan"}
487
489
 
488
- mock_scan_data = mock.MagicMock()
489
- mock_scan_data.data = {
490
+ mock_scan_item = mock.MagicMock()
491
+ mock_scan_item.data = {
490
492
  device_name: {
491
493
  entry: mock.MagicMock(val=msg["data"][device_name][entry]["value"])
492
494
  for entry in msg["data"][device_name]
@@ -494,7 +496,7 @@ def test_scatter_2d_update(bec_figure, qtbot):
494
496
  for device_name in msg["data"]
495
497
  }
496
498
 
497
- w1.queue.scan_storage.find_scan_by_ID.return_value = mock_scan_data
499
+ w1.queue.scan_storage.find_scan_by_ID.return_value = mock_scan_item
498
500
 
499
501
  w1.on_scan_segment(msg, msg_metadata)
500
502
  qtbot.wait(500)
@@ -508,3 +510,180 @@ def test_scatter_2d_update(bec_figure, qtbot):
508
510
 
509
511
  assert np.array_equal(data, expected_x_y_data)
510
512
  assert colors == expected_z_colors
513
+
514
+
515
+ def test_waveform_single_arg_inputs(bec_figure, qtbot):
516
+ w1 = bec_figure.plot()
517
+
518
+ w1.plot("bpm4i")
519
+ w1.plot([1, 2, 3], label="just_y")
520
+ w1.plot([3, 4, 5], [7, 8, 9], label="x_y")
521
+ w1.plot(x=[1, 2, 3], y=[4, 5, 6], label="x_y_kwargs")
522
+ data_array_1D = np.random.rand(10)
523
+ data_array_2D = np.random.rand(10, 2)
524
+ w1.plot(data_array_1D, label="np_ndarray 1D")
525
+ w1.plot(data_array_2D, label="np_ndarray 2D")
526
+
527
+ qtbot.wait(200)
528
+
529
+ assert w1._curves_data["scan_segment"]["bpm4i-bpm4i"].config.label == "bpm4i-bpm4i"
530
+ assert w1._curves_data["custom"]["just_y"].config.label == "just_y"
531
+ assert w1._curves_data["custom"]["x_y"].config.label == "x_y"
532
+ assert w1._curves_data["custom"]["x_y_kwargs"].config.label == "x_y_kwargs"
533
+
534
+ assert np.array_equal(w1._curves_data["custom"]["just_y"].get_data(), ([0, 1, 2], [1, 2, 3]))
535
+ assert np.array_equal(w1._curves_data["custom"]["just_y"].get_data(), ([0, 1, 2], [1, 2, 3]))
536
+ assert np.array_equal(w1._curves_data["custom"]["x_y"].get_data(), ([3, 4, 5], [7, 8, 9]))
537
+ assert np.array_equal(
538
+ w1._curves_data["custom"]["x_y_kwargs"].get_data(), ([1, 2, 3], [4, 5, 6])
539
+ )
540
+ assert np.array_equal(
541
+ w1._curves_data["custom"]["np_ndarray 1D"].get_data(),
542
+ (np.arange(data_array_1D.size), data_array_1D.T),
543
+ )
544
+ assert np.array_equal(w1._curves_data["custom"]["np_ndarray 2D"].get_data(), data_array_2D.T)
545
+
546
+
547
+ def test_waveform_set_x_sync(bec_figure, qtbot):
548
+ w1 = bec_figure.plot()
549
+ custom_label = "custom_label"
550
+ w1.plot("bpm4i")
551
+ w1.set_x_label(custom_label)
552
+
553
+ scan_item_mock = mock.MagicMock()
554
+ mock_data = {
555
+ "samx": {"samx": mock.MagicMock(val=np.array([1, 2, 3]))},
556
+ "samy": {"samy": mock.MagicMock(val=np.array([4, 5, 6]))},
557
+ "bpm4i": {
558
+ "bpm4i": mock.MagicMock(
559
+ val=np.array([7, 8, 9]),
560
+ timestamps=np.array([1720520189.959115, 1720520189.986618, 1720520190.0157812]),
561
+ )
562
+ },
563
+ }
564
+
565
+ scan_item_mock.data = mock_data
566
+ scan_item_mock.status_message.info = {"scan_report_devices": ["samx"]}
567
+
568
+ w1.queue.scan_storage.find_scan_by_ID.return_value = scan_item_mock
569
+
570
+ w1.on_scan_segment({"scan_id": 1}, {})
571
+ qtbot.wait(200)
572
+
573
+ # Best effort - samx
574
+ x_data, y_data = w1.curves[0].get_data()
575
+ assert np.array_equal(x_data, [1, 2, 3])
576
+ assert np.array_equal(y_data, [7, 8, 9])
577
+ assert w1.plot_item.getAxis("bottom").labelText == custom_label + " [auto: samx-samx]"
578
+
579
+ # Change to samy
580
+ w1.set_x("samy")
581
+ qtbot.wait(200)
582
+ x_data, y_data = w1.curves[0].get_data()
583
+ assert np.array_equal(x_data, [4, 5, 6])
584
+ assert np.array_equal(y_data, [7, 8, 9])
585
+ assert w1.plot_item.getAxis("bottom").labelText == custom_label + " [samy-samy]"
586
+
587
+ # change to index
588
+ w1.set_x("index")
589
+ qtbot.wait(200)
590
+ x_data, y_data = w1.curves[0].get_data()
591
+ assert np.array_equal(x_data, [0, 1, 2])
592
+ assert np.array_equal(y_data, [7, 8, 9])
593
+ assert w1.plot_item.getAxis("bottom").labelText == custom_label + " [index]"
594
+
595
+ # change to timestamp
596
+ w1.set_x("timestamp")
597
+ qtbot.wait(200)
598
+ x_data, y_data = w1.curves[0].get_data()
599
+ assert np.allclose(x_data, np.array([1.72052019e09, 1.72052019e09, 1.72052019e09]))
600
+ assert np.array_equal(y_data, [7, 8, 9])
601
+ assert w1.plot_item.getAxis("bottom").labelText == custom_label + " [timestamp]"
602
+
603
+
604
+ def test_waveform_async_data_update(bec_figure, qtbot):
605
+ w1 = bec_figure.plot("async_device")
606
+ custom_label = "custom_label"
607
+ w1.set_x_label(custom_label)
608
+
609
+ # scan_item_mock = mock.MagicMock()
610
+ # mock_data = {
611
+ # "async_device": {
612
+ # "async_device": mock.MagicMock(
613
+ # val=np.array([7, 8, 9]),
614
+ # timestamps=np.array([1720520189.959115, 1720520189.986618, 1720520190.0157812]),
615
+ # )
616
+ # }
617
+ # }
618
+ #
619
+ # scan_item_mock.async_data = mock_data
620
+ # w1.queue.scan_storage.find_scan_by_ID.return_value = scan_item_mock
621
+
622
+ msg_1 = {"signals": {"async_device": {"value": [7, 8, 9]}}}
623
+ w1.on_async_readback(msg_1, {"async_update": "extend"})
624
+
625
+ qtbot.wait(200)
626
+ x_data, y_data = w1.curves[0].get_data()
627
+ assert np.array_equal(x_data, [0, 1, 2])
628
+ assert np.array_equal(y_data, [7, 8, 9])
629
+ assert w1.plot_item.getAxis("bottom").labelText == custom_label + " [best_effort]"
630
+
631
+ msg_2 = {"signals": {"async_device": {"value": [10, 11, 12]}}}
632
+ w1.on_async_readback(msg_2, {"async_update": "extend"})
633
+
634
+ qtbot.wait(200)
635
+ x_data, y_data = w1.curves[0].get_data()
636
+ assert np.array_equal(x_data, [0, 1, 2, 3, 4, 5])
637
+ assert np.array_equal(y_data, [7, 8, 9, 10, 11, 12])
638
+ assert w1.plot_item.getAxis("bottom").labelText == custom_label + " [best_effort]"
639
+
640
+ msg_3 = {"signals": {"async_device": {"value": [20, 21, 22]}}}
641
+ w1.on_async_readback(msg_3, {"async_update": "replace"})
642
+
643
+ qtbot.wait(200)
644
+ x_data, y_data = w1.curves[0].get_data()
645
+ assert np.array_equal(x_data, [0, 1, 2])
646
+ assert np.array_equal(y_data, [20, 21, 22])
647
+ assert w1.plot_item.getAxis("bottom").labelText == custom_label + " [best_effort]"
648
+
649
+
650
+ def test_waveform_set_x_async(bec_figure, qtbot):
651
+ w1 = bec_figure.plot("async_device")
652
+ custom_label = "custom_label"
653
+ w1.set_x_label(custom_label)
654
+
655
+ scan_item_mock = mock.MagicMock()
656
+ mock_data = {
657
+ "async_device": {
658
+ "async_device": {
659
+ "value": np.array([7, 8, 9]),
660
+ "timestamp": np.array([1720520189.959115, 1720520189.986618, 1720520190.0157812]),
661
+ }
662
+ }
663
+ }
664
+
665
+ scan_item_mock.async_data = mock_data
666
+ w1.queue.scan_storage.find_scan_by_ID.return_value = scan_item_mock
667
+
668
+ w1.on_scan_status({"scan_id": 1})
669
+ w1.replot_async_curve()
670
+
671
+ qtbot.wait(200)
672
+ x_data, y_data = w1.curves[0].get_data()
673
+ assert np.array_equal(x_data, [0, 1, 2])
674
+ assert np.array_equal(y_data, [7, 8, 9])
675
+ assert w1.plot_item.getAxis("bottom").labelText == custom_label + " [best_effort]"
676
+
677
+ w1.set_x("timestamp")
678
+ qtbot.wait(200)
679
+ x_data, y_data = w1.curves[0].get_data()
680
+ assert np.allclose(x_data, np.array([1.72052019e09, 1.72052019e09, 1.72052019e09]))
681
+ assert np.array_equal(y_data, [7, 8, 9])
682
+ assert w1.plot_item.getAxis("bottom").labelText == custom_label + " [timestamp]"
683
+
684
+ w1.set_x("index")
685
+ qtbot.wait(200)
686
+ x_data, y_data = w1.curves[0].get_data()
687
+ assert np.array_equal(x_data, [0, 1, 2])
688
+ assert np.array_equal(y_data, [7, 8, 9])
689
+ assert w1.plot_item.getAxis("bottom").labelText == custom_label + " [index]"
@@ -1,54 +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>2104</width>
10
- <height>966</height>
11
- </rect>
12
- </property>
13
- <property name="windowTitle">
14
- <string>Plotting Console</string>
15
- </property>
16
- <layout class="QVBoxLayout" name="verticalLayout_4">
17
- <item>
18
- <widget class="QSplitter" name="splitter">
19
- <property name="orientation">
20
- <enum>Qt::Horizontal</enum>
21
- </property>
22
- <widget class="QTabWidget" name="tabWidget">
23
- <property name="currentIndex">
24
- <number>0</number>
25
- </property>
26
- <widget class="QWidget" name="tab_1">
27
- <attribute name="title">
28
- <string>BECDock</string>
29
- </attribute>
30
- <layout class="QVBoxLayout" name="verticalLayout">
31
- <item>
32
- <widget class="QWidget" name="dock_placeholder" native="true"/>
33
- </item>
34
- </layout>
35
- </widget>
36
- <widget class="QWidget" name="tab_2">
37
- <attribute name="title">
38
- <string>BECFigure</string>
39
- </attribute>
40
- <layout class="QVBoxLayout" name="verticalLayout_3">
41
- <item>
42
- <widget class="QWidget" name="glw" native="true"/>
43
- </item>
44
- </layout>
45
- </widget>
46
- </widget>
47
- <widget class="QWidget" name="widget_console" native="true"/>
48
- </widget>
49
- </item>
50
- </layout>
51
- </widget>
52
- <resources/>
53
- <connections/>
54
- </ui>