bec-widgets 0.46.6__py3-none-any.whl → 0.47.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.
@@ -2,6 +2,7 @@ from .bec_connector import BECConnector, ConnectionConfig
2
2
  from .bec_dispatcher import BECDispatcher
3
3
  from .bec_table import BECTable
4
4
  from .colors import Colors
5
+ from .container_utils import WidgetContainerUtils
5
6
  from .crosshair import Crosshair
6
7
  from .entry_validator import EntryValidator
7
8
  from .rpc_decorator import register_rpc_methods, rpc_public
@@ -0,0 +1,45 @@
1
+ import itertools
2
+ from typing import Type
3
+
4
+ from qtpy.QtWidgets import QWidget
5
+
6
+
7
+ class WidgetContainerUtils:
8
+
9
+ @staticmethod
10
+ def generate_unique_widget_id(container: dict, prefix: str = "widget") -> str:
11
+ """
12
+ Generate a unique widget ID.
13
+ Args:
14
+ container(dict): The container of widgets.
15
+ prefix(str): The prefix of the widget ID.
16
+
17
+ Returns:
18
+ widget_id(str): The unique widget ID.
19
+ """
20
+ existing_ids = set(container.keys())
21
+ for i in itertools.count(1):
22
+ widget_id = f"{prefix}_{i}"
23
+ if widget_id not in existing_ids:
24
+ return widget_id
25
+
26
+ @staticmethod
27
+ def find_first_widget_by_class(
28
+ container: dict, widget_class: Type[QWidget], can_fail: bool = True
29
+ ) -> QWidget | None:
30
+ """
31
+ Find the first widget of a given class in the figure.
32
+ Args:
33
+ container(dict): The container of widgets.
34
+ widget_class(Type): The class of the widget to find.
35
+ can_fail(bool): If True, the method will return None if no widget is found. If False, it will raise an error.
36
+ Returns:
37
+ widget: The widget of the given class.
38
+ """
39
+ for widget_id, widget in container.items():
40
+ if isinstance(widget, widget_class):
41
+ return widget
42
+ if can_fail:
43
+ return None
44
+ else:
45
+ raise ValueError(f"No widget of class {widget_class} found.")
@@ -3,6 +3,15 @@ class EntryValidator:
3
3
  self.devices = devices
4
4
 
5
5
  def validate_signal(self, name: str, entry: str = None) -> str:
6
+ """
7
+ Validate a signal entry for a given device. If the entry is not provided, the first signal entry will be used from the device hints.
8
+ Args:
9
+ name(str): Device name
10
+ entry(str): Signal entry
11
+
12
+ Returns:
13
+ str: Signal entry
14
+ """
6
15
  if name not in self.devices:
7
16
  raise ValueError(f"Device '{name}' not found in current BEC session")
8
17
 
@@ -15,3 +24,17 @@ class EntryValidator:
15
24
  raise ValueError(f"Entry '{entry}' not found in device '{name}' signals")
16
25
 
17
26
  return entry
27
+
28
+ def validate_monitor(self, monitor: str) -> str:
29
+ """
30
+ Validate a monitor entry for a given device.
31
+ Args:
32
+ monitor(str): Monitor entry
33
+
34
+ Returns:
35
+ str: Monitor entry
36
+ """
37
+ if monitor not in self.devices:
38
+ raise ValueError(f"Device '{monitor}' not found in current BEC session")
39
+
40
+ return monitor
@@ -0,0 +1,37 @@
1
+ import threading
2
+
3
+
4
+ class ThreadTracker:
5
+ def __init__(self, exclude_names=None):
6
+ self.exclude_names = exclude_names if exclude_names else []
7
+ self.initial_threads = self._capture_threads()
8
+
9
+ def _capture_threads(self):
10
+ return set(
11
+ th
12
+ for th in threading.enumerate()
13
+ if not any(ex_name in th.name for ex_name in self.exclude_names)
14
+ and th is not threading.main_thread()
15
+ )
16
+
17
+ def _thread_info(self, threads):
18
+ return ", \n".join(f"{th.name}(ID: {th.ident})" for th in threads)
19
+
20
+ def check_unfinished_threads(self):
21
+ current_threads = self._capture_threads()
22
+ additional_threads = current_threads - self.initial_threads
23
+ closed_threads = self.initial_threads - current_threads
24
+ if additional_threads:
25
+ raise Exception(
26
+ f"###### Initial threads ######:\n {self._thread_info(self.initial_threads)}\n"
27
+ f"###### Current threads ######:\n {self._thread_info(current_threads)}\n"
28
+ f"###### Closed threads ######:\n {self._thread_info(closed_threads)}\n"
29
+ f"###### Unfinished threads ######:\n {self._thread_info(additional_threads)}"
30
+ )
31
+ else:
32
+ print(
33
+ "All threads properly closed.\n"
34
+ f"###### Initial threads ######:\n {self._thread_info(self.initial_threads)}\n"
35
+ f"###### Current threads ######:\n {self._thread_info(current_threads)}\n"
36
+ f"###### Closed threads ######:\n {self._thread_info(closed_threads)}"
37
+ )
@@ -14,7 +14,7 @@ from pyqtgraph.Qt import uic
14
14
  from qtpy.QtCore import Signal as pyqtSignal
15
15
  from qtpy.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
16
16
 
17
- from bec_widgets.utils import BECConnector, BECDispatcher, ConnectionConfig
17
+ from bec_widgets.utils import BECConnector, BECDispatcher, ConnectionConfig, WidgetContainerUtils
18
18
  from bec_widgets.widgets.plots import (
19
19
  BECImageShow,
20
20
  BECMotorMap,
@@ -188,7 +188,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
188
188
  config(dict): Additional configuration for the widget.
189
189
  **axis_kwargs(dict): Additional axis properties to set on the widget after creation.
190
190
  """
191
- widget_id = self._generate_unique_widget_id()
191
+ widget_id = WidgetContainerUtils.generate_unique_widget_id(self._widgets)
192
192
  waveform = self.add_widget(
193
193
  widget_type="Waveform1D",
194
194
  widget_id=widget_id,
@@ -278,7 +278,9 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
278
278
  Returns:
279
279
  BECWaveform: The waveform plot widget.
280
280
  """
281
- waveform = self._find_first_widget_by_class(BECWaveform, can_fail=True)
281
+ waveform = WidgetContainerUtils.find_first_widget_by_class(
282
+ self._widgets, BECWaveform, can_fail=True
283
+ )
282
284
  if waveform is not None:
283
285
  if axis_kwargs:
284
286
  waveform.set(**axis_kwargs)
@@ -355,7 +357,9 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
355
357
  Returns:
356
358
  BECImageShow: The image widget.
357
359
  """
358
- image = self._find_first_widget_by_class(BECImageShow, can_fail=True)
360
+ image = WidgetContainerUtils.find_first_widget_by_class(
361
+ self._widgets, BECImageShow, can_fail=True
362
+ )
359
363
  if image is not None:
360
364
  if axis_kwargs:
361
365
  image.set(**axis_kwargs)
@@ -410,7 +414,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
410
414
  BECImageShow: The image widget.
411
415
  """
412
416
 
413
- widget_id = self._generate_unique_widget_id()
417
+ widget_id = WidgetContainerUtils.generate_unique_widget_id(self._widgets)
414
418
  if config is None:
415
419
  config = ImageConfig(
416
420
  widget_class="BECImageShow",
@@ -457,7 +461,9 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
457
461
  Returns:
458
462
  BECMotorMap: The motor map widget.
459
463
  """
460
- motor_map = self._find_first_widget_by_class(BECMotorMap, can_fail=True)
464
+ motor_map = WidgetContainerUtils.find_first_widget_by_class(
465
+ self._widgets, BECMotorMap, can_fail=True
466
+ )
461
467
  if motor_map is not None:
462
468
  if axis_kwargs:
463
469
  motor_map.set(**axis_kwargs)
@@ -491,7 +497,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
491
497
  Returns:
492
498
  BECMotorMap: The motor map widget.
493
499
  """
494
- widget_id = self._generate_unique_widget_id()
500
+ widget_id = WidgetContainerUtils.generate_unique_widget_id(self._widgets)
495
501
  if config is None:
496
502
  config = MotorMapConfig(
497
503
  widget_class="BECMotorMap",
@@ -532,7 +538,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
532
538
  **axis_kwargs(dict): Additional axis properties to set on the widget after creation.
533
539
  """
534
540
  if not widget_id:
535
- widget_id = self._generate_unique_widget_id()
541
+ widget_id = WidgetContainerUtils.generate_unique_widget_id(self._widgets)
536
542
  if widget_id in self._widgets:
537
543
  raise ValueError(f"Widget with ID '{widget_id}' already exists.")
538
544
 
@@ -610,25 +616,6 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
610
616
  self.setBackground("k" if theme == "dark" else "w")
611
617
  self.config.theme = theme
612
618
 
613
- def _find_first_widget_by_class(
614
- self, widget_class: Type[BECPlotBase], can_fail: bool = True
615
- ) -> BECPlotBase | None:
616
- """
617
- Find the first widget of a given class in the figure.
618
- Args:
619
- widget_class(Type[BECPlotBase]): The class of the widget to find.
620
- can_fail(bool): If True, the method will return None if no widget is found. If False, it will raise an error.
621
- Returns:
622
- BECPlotBase: The widget of the given class.
623
- """
624
- for widget_id, widget in self._widgets.items():
625
- if isinstance(widget, widget_class):
626
- return widget
627
- if can_fail:
628
- return None
629
- else:
630
- raise ValueError(f"No widget of class {widget_class} found.")
631
-
632
619
  def _remove_by_coordinates(self, row: int, col: int) -> None:
633
620
  """
634
621
  Remove a widget from the figure by its coordinates.
@@ -695,14 +682,6 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
695
682
  row += 1
696
683
  return row, col
697
684
 
698
- def _generate_unique_widget_id(self):
699
- """Generate a unique widget ID."""
700
- existing_ids = set(self._widgets.keys())
701
- for i in itertools.count(1):
702
- widget_id = f"widget_{i}"
703
- if widget_id not in existing_ids:
704
- return widget_id
705
-
706
685
  def _change_grid(self, widget_id: str, row: int, col: int):
707
686
  """
708
687
  Change the grid to reflect the new position of the widget.
@@ -12,7 +12,7 @@ from qtpy.QtCore import Signal as pyqtSignal
12
12
  from qtpy.QtCore import Slot as pyqtSlot
13
13
  from qtpy.QtWidgets import QWidget
14
14
 
15
- from bec_widgets.utils import BECConnector, ConnectionConfig
15
+ from bec_widgets.utils import BECConnector, ConnectionConfig, EntryValidator
16
16
 
17
17
  from .plot_base import BECPlotBase, WidgetConfig
18
18
 
@@ -335,7 +335,9 @@ class BECImageShow(BECPlotBase):
335
335
  super().__init__(
336
336
  parent=parent, parent_figure=parent_figure, config=config, client=client, gui_id=gui_id
337
337
  )
338
-
338
+ # Get bec shortcuts dev, scans, queue, scan_storage, dap
339
+ self.get_bec_shortcuts()
340
+ self.entry_validator = EntryValidator(self.dev)
339
341
  self._images = defaultdict(dict)
340
342
  self.apply_config(self.config)
341
343
  self.processor = ImageProcessor()
@@ -507,6 +509,8 @@ class BECImageShow(BECPlotBase):
507
509
  f"Monitor with ID '{monitor}' already exists in widget '{self.gui_id}'."
508
510
  )
509
511
 
512
+ monitor = self.entry_validator.validate_monitor(monitor)
513
+
510
514
  image_config = ImageItemConfig(
511
515
  widget_class="BECImageItem",
512
516
  parent_id=self.gui_id,
@@ -785,6 +789,22 @@ class BECImageShow(BECPlotBase):
785
789
  return True
786
790
  return False
787
791
 
792
+ def _validate_monitor(self, monitor: str, validate_bec: bool = True):
793
+ """
794
+ Validate the monitor name.
795
+ Args:
796
+ monitor(str): The name of the monitor.
797
+ validate_bec(bool): Whether to validate the monitor name with BEC.
798
+
799
+ Returns:
800
+ bool: True if the monitor name is valid, False otherwise.
801
+ """
802
+ if not monitor or monitor == "":
803
+ return False
804
+ if validate_bec:
805
+ return monitor in self.dev
806
+ return True
807
+
788
808
  def cleanup(self):
789
809
  """
790
810
  Clean up the widget.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bec-widgets
3
- Version: 0.46.6
3
+ Version: 0.47.0
4
4
  Summary: BEC Widgets
5
5
  Home-page: https://gitlab.psi.ch/bec/bec-widgets
6
6
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec-widgets/issues
@@ -25,15 +25,17 @@ bec_widgets/examples/stream_plot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
25
25
  bec_widgets/examples/stream_plot/line_plot.ui,sha256=rgNfhOXu1AcWF0P6wnOlmJKDjS-VIoduVrREvmzJQR8,4626
26
26
  bec_widgets/examples/stream_plot/stream_plot.py,sha256=vHii1p9JxSyGQ_VcCjnk9SHJ41Q6Oi1GGd6swVVHLRM,12177
27
27
  bec_widgets/simulations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- bec_widgets/utils/__init__.py,sha256=-szVMxtb6Aoz5_e6Py652ExbGnmSVjd0w6ULBvumguk,353
28
+ bec_widgets/utils/__init__.py,sha256=xytx86Yosjkta0PU4rHfoeO7FCPcimS15xjMPQUgIXc,403
29
29
  bec_widgets/utils/bec_connector.py,sha256=QHVjgGRGEpojvZ04absLnUNiXEtZu3tvrHsW52MPWOk,4138
30
30
  bec_widgets/utils/bec_dispatcher.py,sha256=sLv9CmJ3GKGDhvCXCDmuKtNRlI4w1oWxuQu_Mq2mDDY,4840
31
31
  bec_widgets/utils/bec_table.py,sha256=Xy5qM343K8EvEpB4g_129b63yo1wdEvEY3wqxB_p_Iw,716
32
32
  bec_widgets/utils/colors.py,sha256=JsLxzkxbw-I8GIuvnIKyiM83n0edhyMG2Fa4Ffm62ww,2392
33
+ bec_widgets/utils/container_utils.py,sha256=rL-ryupQ4-7Y8mKNup5aklXleOfXSd97uwXTa9UR90A,1492
33
34
  bec_widgets/utils/crosshair.py,sha256=5gG4G6jjtp6Bd1Y5EySHP2EkmtR4mYdxLCgVtx9fokE,9406
34
35
  bec_widgets/utils/ctrl_c.py,sha256=NMJlPDZcuqMUGykyhuZY5Ibed4yRI1K_uh16z2MmlXQ,1198
35
- bec_widgets/utils/entry_validator.py,sha256=nPdu87Ry9Vka2QVbOLW4YbLJwEWpRYypNl7hlXaMaHM,614
36
+ bec_widgets/utils/entry_validator.py,sha256=88OpJqaldMjiaEk7F9rRcwPuCOTLCzXmAlSEL-_7iXU,1296
36
37
  bec_widgets/utils/rpc_decorator.py,sha256=pIvtqySQLnuS7l2Ti_UAe4WX7CRivZnsE5ZdKAihxh0,479
38
+ bec_widgets/utils/thread_checker.py,sha256=rDNuA3X6KQyA7JPb67mccTg0z8YkInynLAENQDQpbuE,1607
37
39
  bec_widgets/utils/validator_delegate.py,sha256=Emj1WF6W8Ke1ruBWUfmHdVJpmOSPezuOt4zvQTay_44,442
38
40
  bec_widgets/utils/widget_io.py,sha256=JKl508VnqQSxcaHqKaoBQ1TWSOm3pXhxQGx7iF_pRA0,10875
39
41
  bec_widgets/utils/yaml_dialog.py,sha256=soZI8BOjlqYGfYDga70MEvkxJTsktq4y7B3uog2cSik,1851
@@ -43,7 +45,7 @@ bec_widgets/widgets/__init__.py,sha256=GptryTiWJ4yWZZVBG_03guISJabSOzVpOMRkgW0Ld
43
45
  bec_widgets/widgets/editor/__init__.py,sha256=5mBdFYi_IpygCz81kbLEZUWhd1b6oqiO3nASejuV_ug,30
44
46
  bec_widgets/widgets/editor/editor.py,sha256=pIIYLPqqqhXqT11Xj10cyGEiy-ieNGE4ZujN5lf0e68,15110
45
47
  bec_widgets/widgets/figure/__init__.py,sha256=3hGx_KOV7QHCYAV06aNuUgKq4QIYCjUTad-DrwkUaBM,44
46
- bec_widgets/widgets/figure/figure.py,sha256=jqCGT7Abri74Da_4lEbUyDh4-CltiGLuM-2nu82ibDQ,29335
48
+ bec_widgets/widgets/figure/figure.py,sha256=55Dc3DwdeC4rBDz9KLF6udfQJjnDuLQ-1QJ5oOF4Quw,28559
47
49
  bec_widgets/widgets/monitor/__init__.py,sha256=afXuZcBOxNAuYdCkIQXX5J60R5A3Q_86lNEW2vpFtPI,32
48
50
  bec_widgets/widgets/monitor/config_dialog.py,sha256=Z1a4WRIVlfEGdwC-QG25kba2EHCZWi5J843tBVZlWiI,20275
49
51
  bec_widgets/widgets/monitor/config_dialog.ui,sha256=ISMcF7CLTAMXhfZh2Yv5yezzAjMtb9fxY1pmX4B_jCg,5932
@@ -58,7 +60,7 @@ bec_widgets/widgets/motor_control/motor_control_table.ui,sha256=t6aRKiSmutMfp0Ay
58
60
  bec_widgets/widgets/motor_map/__init__.py,sha256=K3c-3A_LbxK0UJ0_bV3opL-wGLTwBLendsJXsg8GAqE,32
59
61
  bec_widgets/widgets/motor_map/motor_map.py,sha256=vJlLWa0BXv5KMZ7UVT0q3I1I5CXgsDiXoM_ptsAsG3c,21860
60
62
  bec_widgets/widgets/plots/__init__.py,sha256=kGQTORTr-2M9vmVCK-T7AFre4bY5LVVzGxcIzT81-ZU,237
61
- bec_widgets/widgets/plots/image.py,sha256=GvtVVmtgaytuBIlLOzyE54BPQ8H2yRqRYrnDEy6n1rY,32254
63
+ bec_widgets/widgets/plots/image.py,sha256=jgKl9BVu9FGqCHkij4gbV12pYugTwyQVhd-Y_TXEvbE,33005
62
64
  bec_widgets/widgets/plots/motor_map.py,sha256=Uitx080FEyCDCTYHfBt0Ah-98QD4J5nSNZ628lu7EOg,15386
63
65
  bec_widgets/widgets/plots/plot_base.py,sha256=xuNA4S5lwWiYXHT60XklWX09KiwqbrRiDr4J_iDXkes,8447
64
66
  bec_widgets/widgets/plots/waveform.py,sha256=aUaWPg5NL0HoGqEs3Yo5nXEg_qy31C5ZwaOAwIoiqcs,28528
@@ -68,7 +70,7 @@ bec_widgets/widgets/toolbar/__init__.py,sha256=d-TP4_cr_VbpwreMM4ePnfZ5YXsEPQ45i
68
70
  bec_widgets/widgets/toolbar/toolbar.py,sha256=sxz7rbc8XNPS6n2WMObF4-2PqdYfPxVtsOZEGV6mqa0,5124
69
71
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
70
72
  tests/unit_tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
- tests/unit_tests/client_mocks.py,sha256=Hga61hUk6FRz4yp2XLRe_3ZTM42aaDyISrXLsXcMmEY,4103
73
+ tests/unit_tests/client_mocks.py,sha256=LNUgI9Ccv5Ol7_pmybIhoVqZZem1RPIsTDk7ZTARNls,4128
72
74
  tests/unit_tests/conftest.py,sha256=roLbKZ1thm2Bd-5zEtL-eRBB5TTs36sAqXTUdHYYqSw,433
73
75
  tests/unit_tests/test_bec_connector.py,sha256=f2XXGGw3NoZLIUrDuZuEWwF_ttOYmmquCgUrV5XkIOY,1951
74
76
  tests/unit_tests/test_bec_dispatcher.py,sha256=MtNyfC7-Y4na-Fwf1ny9raHBqE45eSnQNWSqqAx79FU,1857
@@ -92,8 +94,8 @@ tests/unit_tests/test_widget_io.py,sha256=FeL3ZYSBQnRt6jxj8VGYw1cmcicRQyHKleahw7
92
94
  tests/unit_tests/test_yaml_dialog.py,sha256=HNrqferkdg02-9ieOhhI2mr2Qvt7GrYgXmQ061YCTbg,5794
93
95
  tests/unit_tests/test_msgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
96
  tests/unit_tests/test_msgs/available_scans_message.py,sha256=m_z97hIrjHXXMa2Ex-UvsPmTxOYXfjxyJaGkIY6StTY,46532
95
- bec_widgets-0.46.6.dist-info/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
96
- bec_widgets-0.46.6.dist-info/METADATA,sha256=b8I7vt8YPo-TpWiWV8Jh1-YABgTVjf-JD-UKyYWyZW8,3714
97
- bec_widgets-0.46.6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
98
- bec_widgets-0.46.6.dist-info/top_level.txt,sha256=EXCwhJYmXmd1DjYYL3hrGsddX-97IwYSiIHrf27FFVk,18
99
- bec_widgets-0.46.6.dist-info/RECORD,,
97
+ bec_widgets-0.47.0.dist-info/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
98
+ bec_widgets-0.47.0.dist-info/METADATA,sha256=fWRxvjg6pFXNEcNvoKCEcl9B3MSBi0eEPh61nI17B7s,3714
99
+ bec_widgets-0.47.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
100
+ bec_widgets-0.47.0.dist-info/top_level.txt,sha256=EXCwhJYmXmd1DjYYL3hrGsddX-97IwYSiIHrf27FFVk,18
101
+ bec_widgets-0.47.0.dist-info/RECORD,,
@@ -91,6 +91,7 @@ DEVICES = [
91
91
  FakeDevice("bpm4i"),
92
92
  FakeDevice("bpm3a"),
93
93
  FakeDevice("bpm3i"),
94
+ FakeDevice("eiger"),
94
95
  ]
95
96
 
96
97