bec-widgets 0.57.1__py3-none-any.whl → 0.57.3__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.
@@ -0,0 +1,82 @@
1
+ (user.auto_updates)=
2
+ # Auto updates
3
+ BEC Widgets provides a simple way to update the entire GUI configuration based on events. These events can be of different types, such as a new scan being started or completed, a button being pressed, a device reporting an error or the existence of a specific metadata key. This allows the users to streamline the experience of the GUI and to focus on the data and the analysis, rather than on the GUI itself.
4
+
5
+ The default auto update only takes control over a single `BECFigure` widget, which is automatically added to the GUI instance. The update instance is accessible via the `bec.gui.auto_updates` object. The user can disable / enable the auto updates by setting the `enabled` attribute of the `bec.gui.auto_updates` object, e.g.
6
+
7
+ ```python
8
+ bec.gui.auto_updates.enabled = False
9
+ ```
10
+
11
+ Without further customization, the auto update will automatically update the `BECFigure` widget based on the currently performed scan. The behaviour is determined by the `handler` method of the `AutoUpdate` class:
12
+
13
+ ````{dropdown} Auto Updates Handler
14
+ :icon: code-square
15
+ :animate: fade-in-slide-down
16
+ :open:
17
+ ```{literalinclude} ../../../bec_widgets/cli/auto_updates.py
18
+ :pyobject: AutoUpdates.handler
19
+ ```
20
+ ````
21
+
22
+ As shown, the default handler switches between different scan names and updates the `BECFigure` widget accordingly. If the scan is a line scan, the `simple_line_scan` update method is executed.
23
+
24
+ ````{dropdown} Auto Updates Simple Line Scan
25
+ :icon: code-square
26
+ :animate: fade-in-slide-down
27
+ :open:
28
+ ```{literalinclude} ../../../bec_widgets/cli/auto_updates.py
29
+ :pyobject: AutoUpdates.simple_line_scan
30
+ ```
31
+ ````
32
+
33
+ As it can be seen from the above snippet, the update method gets the default figure by calling the `get_default_figure` method. If the figure is not found, maybe because the user has deleted or closed it, no update is performed. If the figure is found, the scan info is used to extract the first reported device for the x axis and the first device of the monitored devices for the y axis. The y axis can also be set by the user using the `selected_device` attribute:
34
+
35
+ ```python
36
+ bec.gui.auto_updates.selected_device = 'bpm4i'
37
+ ```
38
+
39
+
40
+ ````{dropdown} Auto Updates Code
41
+ :icon: code-square
42
+ :animate: fade-in-slide-down
43
+ ```{literalinclude} ../../../bec_widgets/cli/auto_updates.py
44
+ ```
45
+ ````
46
+
47
+ ## Custom Auto Updates
48
+ The beamline can customize their default behaviour through customized auto update classes. This can be achieved by modifying the class located in the beamline plugin repository: `<beamline_plugin>/bec_widgets/auto_updates.py`. The class should inherit from the `AutoUpdates` class and overwrite the `handler` method.
49
+
50
+ ```python
51
+ from bec_widgets.cli.auto_updates import AutoUpdates, ScanInfo
52
+
53
+
54
+ class PlotUpdate(AutoUpdates):
55
+ create_default_dock = True
56
+ enabled = True
57
+
58
+ # def simple_line_scan(self, info: ScanInfo) -> None:
59
+ # """
60
+ # Simple line scan.
61
+ # """
62
+ # fig = self.get_default_figure()
63
+ # if not fig:
64
+ # return
65
+ # dev_x = info.scan_report_devices[0]
66
+ # dev_y = self.get_selected_device(info.monitored_devices, self.gui.selected_device)
67
+ # if not dev_y:
68
+ # return
69
+ # fig.clear_all()
70
+ # plt = fig.plot(x_name=dev_x, y_name=dev_y)
71
+ # plt.set(title=f"Custom Plot {info.scan_number}", x_label=dev_x, y_label=dev_y)
72
+
73
+ def handler(self, info: ScanInfo) -> None:
74
+ # EXAMPLES:
75
+ # if info.scan_name == "line_scan" and info.scan_report_devices:
76
+ # self.simple_line_scan(info)
77
+ # return
78
+ # if info.scan_name == "grid_scan" and info.scan_report_devices:
79
+ # self.run_grid_scan_update(info)
80
+ # return
81
+ super().handler(info)
82
+ ```
@@ -9,5 +9,6 @@ hidden: true
9
9
  ---
10
10
 
11
11
  installation/
12
- command_line_introduction/
12
+ quick_start/
13
+ auto_updates/
13
14
  ```
@@ -1,34 +1,53 @@
1
1
  (user.command_line_introduction)=
2
- # Command Line Introduction
3
- In order to use BEC Widgets as a plotting tool for BEC, it needs to be [installed](#user.installation) in the same python environment as the BECIpythonClient. If that is the case, it will automatically launch a GUI and assign the control of the GUI to the `gui` object in the client. The GUI backend will also be automatically connect to the BEC server, giving access to all information on the server and allowing the user to visualize the data in real-time.
2
+ # Quick start
3
+ In order to use BEC Widgets as a plotting tool for BEC, it needs to be [installed](#user.installation) in the same Python environment as the BEC IPython client (please refer to the [BEC documentation](https://bec.readthedocs.io/en/latest/user/command_line_interface.html#start-up) for more details). Upon startup, the client will automatically launch a GUI and store it as a `bec.gui` object in the client. The GUI backend will also be automatically connect to the BEC server, giving access to all information on the server and allowing the user to visualize the data in real-time.
4
4
 
5
5
  ## BECDockArea
6
- The `gui` object represents the top level of hierarchy in BEC Widgets. It is a [`BECDockArea`](/api_reference/_autosummary/bec_widgets.cli.client.BECDockArea) based on the [pyqtgraph.dockarea](https://pyqtgraph.readthedocs.io/en/latest/api_reference/dockarea.html). The GUI can be further composed of multiple [`BECDock`](/api_reference/_autosummary/bec_widgets.cli.client.BECDock)s that can be detached and attached to the main area. These docks allow users to freely arrange and customize the widgets they add to the gui, giving them the necessary freedom to design the user interface they desire.
6
+ The `bec.gui` object is your entry point to BEC Widgets. It is a [`BECDockArea`](/api_reference/_autosummary/bec_widgets.cli.client.BECDockArea) instance that can be composed of multiple [`BECDock`](/api_reference/_autosummary/bec_widgets.cli.client.BECDock)s that can be attached / detached to the main area. These docks allow users to freely arrange and customize the widgets they add to the gui, providing a flexible and customizable interface to visualize data.
7
7
 
8
8
  ## Widgets
9
- Widgets are the building blocks of the BEC Widgets framework. They are the visual components that allow users to interact with the data and control the behavior of the application. Each dock can contain multiple widgets, although we recommend using a direct mapping of a single widget to a dock. We currently support two type of widgets:
10
- - [`BECFigure`](/api_reference/_autosummary/bec_widgets.cli.client.BECFigure): A widget that can be used to visualize data from BEC. It is automatically connected to the BEC server and can be used to plot data from BEC.
11
- - [`SpiralProgressBar`](/api_reference/_autosummary/bec_widgets.cli.client.SpiralProgressBar): A custom widget that can be used to show progress in a spiral shape.
9
+ Widgets are the building blocks of the BEC Widgets framework. They are the visual components that allow users to interact with the data and control the behavior of the application. Each dock can contain multiple widgets, albeit we recommend for most use cases a single widget per dock. BEC Widgets provides a set of core widgets (cf. [widgets](#user.widgets)). More widgets can be added by the users, and we invite you to explore the [developer documentation](developer.widgets) to learn how to create custom widgets.
10
+ For the introduction given here, we will focus on the `BECFigure` widget, as it is the most commonly used widget for visualizing data from BEC. The same access pattern can be used for all other widgets.
12
11
 
13
12
  **BECFigure**
14
- The [`BECFigure`](/api_reference/_autosummary/bec_widgets.cli.client.BECFigure) widget is one of the core widgets developed for BEC and designed to be flexible and can be used to visualize different types of data. It can be used to visualize [1D Waveform](user.widgets.waveform_1d), [2D Scatter Plot](user.widgets.scatter_2d), [Motor Position Map](user.widgets.motor_map) and 2D image data. As mentioned before, starting the BECIPythonClient will automatically launch a `gui` with a single dock area and a BECFigure widget attached to it. This widget is accessible via the `fig` object from the client directly. We also provide two methods [`plot()`](/api_reference/_autosummary/bec_widgets.cli.client.BECFigure.rst#bec_widgets.cli.client.BECFigure.plot), [`image()`](/api_reference/_autosummary/bec_widgets.cli.client.BECFigure.rst#bec_widgets.cli.client.BECFigure.image) and [`motor_map()`](/api_reference/_autosummary/bec_widgets.cli.client.BECFigure.rst#bec_widgets.cli.client.BECFigure.motor_map) as shortcuts to add a plot, image or motor map to the BECFigure.
13
+
14
+ The [`BECFigure`](/api_reference/_autosummary/bec_widgets.cli.client.BECFigure) widget is one of the core widgets developed for BEC and can be used to visualize different plot types, such as [1D waveforms](user.widgets.waveform_1d), [2D scatter plots](user.widgets.scatter_2d), [position maps](user.widgets.motor_map) and [2D images](user.widgets.image_2d).
15
+ If BEC Widgets is installed, the default behaviour of BEC is to automatically add a BECFigure Widget to the existing GUI instance. This widget is directly accessible via the `fig` object from the client. Moreover, a best-effort attempt is made to automatically determine the best plot type based on the currently performed scan. This behaviour can be changed or disabled by the user. For more details, please refer to the [auto update](user.auto_updates) section.
16
+
17
+ <!-- We also provide two methods [`plot()`](/api_reference/_autosummary/bec_widgets.cli.client.BECFigure.rst#bec_widgets.cli.client.BECFigure.plot), [`image()`](/api_reference/_autosummary/bec_widgets.cli.client.BECFigure.rst#bec_widgets.cli.client.BECFigure.image) and [`motor_map()`](/api_reference/_autosummary/bec_widgets.cli.client.BECFigure.rst#bec_widgets.cli.client.BECFigure.motor_map) as shortcuts to add a plot, image or motor map to the BECFigure. -->
15
18
 
16
19
  **Waveform Plot**
20
+
17
21
  The [`BECWaveForm`](/api_reference/_autosummary/bec_widgets.cli.client.BECWaveform) is a widget that can be used to visualize 1D waveform data, i.e. to plot data of a monitor against a motor position. The method [`plot()`](/api_reference/_autosummary/bec_widgets.cli.client.BECFigure.rst#bec_widgets.cli.client.BECFigure.plot) of BECFigure adds a BECWaveForm widget to the figure, and returns the plot object.
18
22
 
19
23
  ```python
20
- plt = fig.add_plot('samx', 'bpm4i')
24
+ plt = fig.plot(x_name='samx', y_name='bpm4i')
25
+ ```
26
+ Here, we create a new plot with a subscription to the devices `samx` and `bpm4i` and assign the plot to the object `plt`. We can now use this object to further customize the plot, e.g. changing the title ([`set_title()`](/api_reference/_autosummary/bec_widgets.cli.client.BECWaveform.rst#bec_widgets.cli.client.BECWaveform.set_title)), axis labels ([`set_x_label()`](/api_reference/_autosummary/bec_widgets.cli.client.BECWaveform.rst#bec_widgets.cli.client.BECWaveform.set_x_label)) or limits ([`set_x_lim()`](/api_reference/_autosummary/bec_widgets.cli.client.BECWaveform.rst#bec_widgets.cli.client.BECWaveform.set_x_lim)). We invite you to explore the API of the BECWaveForm in the [documentation](user.widgets.waveform_1d) or directly in the command line.
27
+
28
+ To plot custom data, i.e. data that is not directly available through a scan in BEC, we can use the same method, but provide the data directly to the plot.
29
+
30
+ ```python
31
+ plt = fig.plot([1,2,3,4], [1,4,9,16])
32
+ # or
33
+ plt = fig.plot(x=[1,2,3,4], y=[1,4,9,16])
34
+ # or
35
+ plt = fig.plot(x=np.array([1,2,3,4]), y=np.array([1,4,9,16]))
36
+ # or
37
+ plt = fig.plot(np.random.rand(10,2))
21
38
  ```
22
- Here, we assign the plot to the object `plt`. We can now use this object to further customize the plot, e.g. changing the title ([`set_title()`](/api_reference/_autosummary/bec_widgets.cli.client.BECWaveform.rst#bec_widgets.cli.client.BECWaveform.set_title)), axis labels ([`set_x_label()`](/api_reference/_autosummary/bec_widgets.cli.client.BECWaveform.rst#bec_widgets.cli.client.BECWaveform.set_x_label)) or limits ([`set_x_lim()`](/api_reference/_autosummary/bec_widgets.cli.client.BECWaveform.rst#bec_widgets.cli.client.BECWaveform.set_x_lim)). We invite you to explore the API of the BECWaveForm in the [documentation](user.widgets.waveform_1d) or directly in the command line.
23
39
 
24
40
  **Scatter Plot**
41
+
25
42
  The [`BECWaveForm`](/api_reference/_autosummary/bec_widgets.cli.client.BECWaveForm) widget can also be used to visualize 2D scatter plots. More details on setting up the scatter plot are available in the widget documentation of the [scatter plot](user.widgets.scatter_2d).
26
43
 
27
44
  **Motor Map**
45
+
28
46
  The [`BECMotorMap`](/api_reference/_autosummary/bec_widgets.cli.client.BECMotorMap) widget can be used to visualize the position of motors. It's focused on tracking and visualizing the position of motors, crucial for precise alignment and movement tracking during scans. More details on setting up the motor map are available in the widget documentation of the [motor map](user.widgets.motor_map).
29
47
 
30
48
  **Image Plot**
31
- The [`BECImageItem`](/api_reference/_autosummary/bec_widgets.cli.client.BECImageItem) widget can be used to visualize 2D image data for example a camera. More details on setting up the image plot are available in the widget documentation of the [image plot](user.widgets.image)
49
+
50
+ The [`BECImageItem`](/api_reference/_autosummary/bec_widgets.cli.client.BECImageItem) widget can be used to visualize 2D image data for example a camera. More details on setting up the image plot are available in the widget documentation of the [image plot](user.widgets.image).
32
51
 
33
52
  ### Useful Commands
34
53
  We recommend users to explore the API of the widgets by themselves since we assume that the user interface is supposed to be intuitive and self-explanatory. We appreciate feedback from user in order to constantly improve the experience and allow easy access to the gui, widgets and their functionality. We recommend checking the [API documentation](user.api_reference), but also by using BEC Widgets, exploring the available functions and check their dockstrings.
@@ -43,32 +62,32 @@ gui.panels # returns a dictionary of all docks in the gui
43
62
  gui.add_dock() # adds a new dock to the gui
44
63
 
45
64
  dock = gui.panels['dock_2']
46
- dock.add_widget_bec('BECFigure') # adds a new widget of BECFigure to the dock
65
+ dock.add_widget('BECFigure') # adds a new widget of BECFigure to the dock
47
66
  dock.widget_list # returns a list of all widgets in the dock
48
67
 
49
68
  figure = dock.widget_list[0] # assigns the created BECFigure to figure
50
- plt = figure.add_plot('samx', 'bpm4i') # adds a BECWaveForm plot to the figure
69
+ plt = figure.plot(x_name='samx', y_name='bpm4i') # adds a BECWaveForm plot to the figure
51
70
  plt.curves # returns a list of all curves in the plot
52
71
  ```
53
72
 
54
- We note that commands can also be chained. For example, `gui.add_dock().add_widget_bec('BECFigure')` will add a new dock to the gui and add a new widget of `BECFigure` to the dock.
73
+ We note that commands can also be chained. For example, `gui.add_dock().add_widget('BECFigure')` will add a new dock to the gui and add a new widget of `BECFigure` to the dock.
55
74
 
56
75
  ## Composing a larger GUI
57
76
  The example given above introduces BEC Widgets with its different components, and provides an overview of how to interact with the widgets. Nevertheless, another power aspect of BEC Widgets lies in the ability to compose a larger GUI with multiple docks and widgets. This section aims to provide a tutorial like guide on how to compose a more complex GUI that (A) live-plots a 1D waveform, (B) plots data from a camera, and (C) tracks the positions of two motors.
58
77
  Let's assume BEC was just started and the `gui` object is available in the client. A single dock is already attached together with a BEC Figure. Let's add the 1D waveform to this dock, change the color of the line to white and add the title *1D Waveform* to the plot.
59
78
 
60
79
  ```python
61
- plt = fig.add_plot('samx', 'bpm4i')
80
+ plt = fig.plot(x_name='samx', y_name='bpm4i')
62
81
  plt.curves[0].set_color(color="white")
63
82
  plt.set_title('1D Waveform')
64
83
  ```
65
84
 
66
85
  Next, we add 2 new docks to the gui, one to plot the data of a camera and one to track the positions of two motors.
67
86
  ```ipython
68
- cam_widget= gui.add_dock(name="cam_dock").add_widget_bec('BECFigure').image("eiger")
69
- motor_widget = gui.add_dock(name="mot_dock").add_widget_bec('BECFigure').motor_map("samx", "samy")
87
+ cam_widget= gui.add_dock(name="cam_dock").add_widget('BECFigure').image("eiger")
88
+ motor_widget = gui.add_dock(name="mot_dock").add_widget('BECFigure').motor_map("samx", "samy")
70
89
  ```
71
- Note, we chain commands here which is possible since the `add_dock` and `add_widget_bec` methods return the dock and the widget respectively. We can now further customize the widgets by changing the title, axis labels, etc.
90
+ Note, we chain commands here which is possible since the `add_dock` and `add_widget` methods return the dock and the widget respectively. We can now further customize the widgets by changing the title, axis labels, etc.
72
91
 
73
92
  ```python
74
93
  cam_widget.set_title("Camera Image Eiger")
@@ -78,7 +97,7 @@ As a final step, we can now add also a SpiralProgressBar to a new dock, and perf
78
97
  As you see in the example below, all docks are arranged below each other. This is the default behavior of the `add_dock` method. However, the docks can be freely arranged by drag and drop as desired by the user. We invite you to explore this by yourself following the example in the video, and build your custom GUI with BEC Widgets.
79
98
 
80
99
  ```python
81
- prog_bar = gui.add_dock(name="prog_dock").add_widget_bec('SpiralProgressBar')
100
+ prog_bar = gui.add_dock(name="prog_dock").add_widget('SpiralProgressBar')
82
101
  prog_bar.set_line_widths(15)
83
102
  scans.grid_scan(dev.samy, -2, 2, 10, dev.samx, -5, 5, 10, exp_time=0.1, relative=False)
84
103
  ```
@@ -1,6 +1,12 @@
1
1
  (user.widgets.spiral_progress_bar)=
2
2
  # [Spiral Progress Bar](/api_reference/_autosummary/bec_widgets.cli.client.SpiralProgressBar)
3
- **Purpose** The Spiral Progress Bar widget is a circular progress bar that can be used to visualize the progress of a task. The widget is designed to be used in applications where the progress of a task is represented as a percentage. The Spiral Progress Bar widget is a part of the BEC Widgets library and can be controlled directly using its API, or hooked up to the progress of a device readback or scan.
3
+
4
+ **Purpose:**
5
+
6
+ The Spiral Progress Bar widget is a circular progress bar that can be used to visualize the progress of a task. The
7
+ widget is designed to be used in applications where the progress of a task is represented as a percentage. The Spiral
8
+ Progress Bar widget is a part of the BEC Widgets library and can be controlled directly using its API, or hooked up to
9
+ the progress of a device readback or scan.
4
10
 
5
11
  **Key Features:**
6
12
 
@@ -11,11 +17,38 @@
11
17
  **Example of Use:**
12
18
  ![SpiralProgressBar](./progress_bar.gif)
13
19
 
14
- **Code example**
15
- The following code snipped demonstrates how to create a 2D scatter plot using BEC Widgets within BEC.
20
+ **Code example:**
21
+
22
+ The following code snipped demonstrates how to create a `SpiralProgressBar` using BEC Widgets within BEC.
16
23
  ```python
17
24
  # adds a new dock with a spiral progress bar
18
25
  progress = gui.add_dock().add_widget("SpiralProgressBar")
19
26
  # customize the size of the ring
20
27
  progress.set_line_width(20)
21
- ```
28
+ ```
29
+
30
+ By default, the Spiral Progress Bar widget will display a single ring. To add more rings, use the add_ring method:
31
+
32
+ ```python
33
+ # adds a new dock with a spiral progress bar
34
+ progress.add_ring()
35
+ ```
36
+
37
+ To access rings and specify their properties, you can use `progress.rings` with an index specifying the ring index (
38
+ starting from 0):
39
+
40
+ ```python
41
+ progress.rings[0].set_line_width(20) # set the width of the first ring
42
+ progress.rings[1].set_line_width(10) # set the width of the second ring
43
+ ```
44
+
45
+ By default, the `SpiralProgressBar` widget is set with `progress.enable_auto_update(True)`, which will automatically
46
+ update the bars in the widget. To manually set updates for each progress bar, use the set_update method. Note that
47
+ manually updating a ring will disable the automatic update for the whole widget:
48
+
49
+ ```python
50
+ progress.rings[0].set_update("scan") # set the update of the first ring to be an overall scan progress
51
+ progress.rings[1].set_update("device",
52
+ "samx") # set the update of the second ring to be a device readback (in this case, samx)
53
+ ```
54
+
pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_widgets"
7
- version = "0.57.1"
7
+ version = "0.57.3"
8
8
  description = "BEC Widgets"
9
9
  requires-python = ">=3.10"
10
10
  classifiers = [
@@ -31,6 +31,7 @@ dev = [
31
31
  "pytest",
32
32
  "pytest-random-order",
33
33
  "pytest-timeout",
34
+ "pytest-xvfb",
34
35
  "coverage",
35
36
  "pytest-qt",
36
37
  "isort",
@@ -1,40 +1,55 @@
1
+ import random
2
+ import time
3
+ from contextlib import contextmanager
4
+
1
5
  import pytest
6
+ from bec_lib.endpoints import MessageEndpoints
2
7
 
8
+ from bec_widgets.cli.client_utils import _start_plot_process
3
9
  from bec_widgets.cli.rpc_register import RPCRegister
4
- from bec_widgets.cli.server import BECWidgetsCLIServer
5
10
  from bec_widgets.utils import BECDispatcher
6
11
  from bec_widgets.widgets import BECDockArea, BECFigure
7
12
 
8
13
 
14
+ # make threads check in autouse, **will be executed at the end**; better than
15
+ # having it in fixtures for each test, since it prevents from needing to
16
+ # 'manually' shutdown bec_client_lib (for example) to make it happy, whereas
17
+ # whereas in fact bec_client_lib makes its on cleanup
9
18
  @pytest.fixture(autouse=True)
10
- def rpc_register():
11
- yield RPCRegister()
12
- RPCRegister.reset_singleton()
19
+ def threads_check_fixture(threads_check):
20
+ return
21
+
22
+
23
+ @pytest.fixture
24
+ def gui_id():
25
+ return f"figure_{random.randint(0,100)}" # make a new gui id each time, to ensure no 'gui is alive' zombie key can perturbate
26
+
27
+
28
+ @contextmanager
29
+ def plot_server(gui_id, klass, client_lib):
30
+ dispatcher = BECDispatcher(client=client_lib) # Has to init singleton with fixture client
31
+ process, output_thread = _start_plot_process(
32
+ gui_id, klass, client_lib._client._service_config.redis
33
+ )
34
+ try:
35
+ while client_lib._client.connector.get(MessageEndpoints.gui_heartbeat(gui_id)) is None:
36
+ time.sleep(0.3)
37
+ yield gui_id
38
+ finally:
39
+ process.terminate()
40
+ process.wait()
41
+ output_thread.join()
42
+ dispatcher.disconnect_all()
43
+ dispatcher.reset_singleton()
13
44
 
14
45
 
15
46
  @pytest.fixture
16
- def rpc_server_figure(qtbot, bec_client_lib, threads_check):
17
- dispatcher = BECDispatcher(client=bec_client_lib) # Has to init singleton with fixture client
18
- server = BECWidgetsCLIServer(gui_id="figure", gui_class=BECFigure)
19
- qtbot.addWidget(server.gui)
20
- qtbot.waitExposed(server.gui)
21
- qtbot.wait(1000) # 1s long to wait until gui is ready
22
- yield server
23
- dispatcher.disconnect_all()
24
- server.client.shutdown()
25
- server.shutdown()
26
- dispatcher.reset_singleton()
47
+ def rpc_server_figure(gui_id, bec_client_lib):
48
+ with plot_server(gui_id, BECFigure, bec_client_lib) as server:
49
+ yield server
27
50
 
28
51
 
29
52
  @pytest.fixture
30
- def rpc_server_dock(qtbot, bec_client_lib, threads_check):
31
- dispatcher = BECDispatcher(client=bec_client_lib) # Has to init singleton with fixture client
32
- server = BECWidgetsCLIServer(gui_id="figure", gui_class=BECDockArea)
33
- qtbot.addWidget(server.gui)
34
- qtbot.waitExposed(server.gui)
35
- qtbot.wait(1000) # 1s long to wait until gui is ready
36
- yield server
37
- dispatcher.disconnect_all()
38
- server.client.shutdown()
39
- server.shutdown()
40
- dispatcher.reset_singleton()
53
+ def rpc_server_dock(gui_id, bec_client_lib):
54
+ with plot_server(gui_id, BECDockArea, bec_client_lib) as server:
55
+ yield server
@@ -1,3 +1,5 @@
1
+ import time
2
+
1
3
  import numpy as np
2
4
  import pytest
3
5
  from bec_lib.client import BECClient
@@ -8,24 +10,10 @@ from bec_widgets.cli.client import BECDockArea, BECFigure, BECImageShow, BECMoto
8
10
  from bec_widgets.utils import Colors
9
11
 
10
12
 
11
- @pytest.fixture(name="bec_client")
12
- def cli_bec_client(rpc_server_dock):
13
- """
14
- Fixture to create a BECClient instance that is independent of the GUI.
15
- """
16
- # pylint: disable=protected-access
17
- cli_client = BECClient(forced=True, config=rpc_server_dock.client._service_config)
18
- cli_client.start()
19
- yield cli_client
20
- cli_client.shutdown()
21
-
22
-
23
- def test_rpc_add_dock_with_figure_e2e(rpc_server_dock, qtbot):
24
- dock = BECDockArea(rpc_server_dock.gui_id)
25
- dock_server = rpc_server_dock.gui
26
-
13
+ def test_rpc_add_dock_with_figure_e2e(bec_client_lib, rpc_server_dock):
27
14
  # BEC client shortcuts
28
- client = rpc_server_dock.client
15
+ dock = BECDockArea(rpc_server_dock)
16
+ client = bec_client_lib
29
17
  dev = client.device_manager.devices
30
18
  scans = client.scans
31
19
  queue = client.queue
@@ -35,17 +23,18 @@ def test_rpc_add_dock_with_figure_e2e(rpc_server_dock, qtbot):
35
23
  d1 = dock.add_dock("dock_1")
36
24
  d2 = dock.add_dock("dock_2")
37
25
 
38
- assert len(dock_server.docks) == 3
39
-
26
+ dock_config = dock.config_dict
27
+ assert len(dock_config["docks"]) == 3
40
28
  # Add 3 figures with some widgets
41
29
  fig0 = d0.add_widget("BECFigure")
42
30
  fig1 = d1.add_widget("BECFigure")
43
31
  fig2 = d2.add_widget("BECFigure")
44
32
 
45
- assert len(dock_server.docks) == 3
46
- assert len(dock_server.docks["dock_0"].widgets) == 1
47
- assert len(dock_server.docks["dock_1"].widgets) == 1
48
- assert len(dock_server.docks["dock_2"].widgets) == 1
33
+ dock_config = dock.config_dict
34
+ assert len(dock_config["docks"]) == 3
35
+ assert len(dock_config["docks"]["dock_0"]["widgets"]) == 1
36
+ assert len(dock_config["docks"]["dock_1"]["widgets"]) == 1
37
+ assert len(dock_config["docks"]["dock_2"]["widgets"]) == 1
49
38
 
50
39
  assert fig1.__class__.__name__ == "BECFigure"
51
40
  assert fig1.__class__ == BECFigure
@@ -98,7 +87,7 @@ def test_rpc_add_dock_with_figure_e2e(rpc_server_dock, qtbot):
98
87
 
99
88
  # wait for scan to finish
100
89
  while not status.status == "COMPLETED":
101
- qtbot.wait(200)
90
+ time.sleep(0.2)
102
91
 
103
92
  # plot
104
93
  plt_last_scan_data = queue.scan_storage.storage[-1].data
@@ -110,7 +99,7 @@ def test_rpc_add_dock_with_figure_e2e(rpc_server_dock, qtbot):
110
99
  last_image_device = client.connector.get_last(MessageEndpoints.device_monitor("eiger"))[
111
100
  "data"
112
101
  ].data
113
- qtbot.wait(500)
102
+ time.sleep(0.5)
114
103
  last_image_plot = im.images[0].get_data()
115
104
  np.testing.assert_equal(last_image_device, last_image_plot)
116
105
 
@@ -129,40 +118,41 @@ def test_rpc_add_dock_with_figure_e2e(rpc_server_dock, qtbot):
129
118
  )
130
119
 
131
120
 
132
- def test_dock_manipulations_e2e(rpc_server_dock, qtbot):
133
- dock = BECDockArea(rpc_server_dock.gui_id)
134
- dock_server = rpc_server_dock.gui
121
+ def test_dock_manipulations_e2e(rpc_server_dock):
122
+ dock = BECDockArea(rpc_server_dock)
135
123
 
136
124
  d0 = dock.add_dock("dock_0")
137
125
  d1 = dock.add_dock("dock_1")
138
126
  d2 = dock.add_dock("dock_2")
139
- assert len(dock_server.docks) == 3
127
+ dock_config = dock.config_dict
128
+ assert len(dock_config["docks"]) == 3
140
129
 
141
130
  d0.detach()
142
131
  dock.detach_dock("dock_2")
143
- assert len(dock_server.docks) == 3
144
- assert len(dock_server.tempAreas) == 2
132
+ dock_config = dock.config_dict
133
+ assert len(dock_config["docks"]) == 3
134
+ assert len(dock.temp_areas) == 2
145
135
 
146
136
  d0.attach()
147
- assert len(dock_server.docks) == 3
148
- assert len(dock_server.tempAreas) == 1
137
+ dock_config = dock.config_dict
138
+ assert len(dock_config["docks"]) == 3
139
+ assert len(dock.temp_areas) == 1
149
140
 
150
141
  d2.remove()
151
- qtbot.wait(200)
142
+ dock_config = dock.config_dict
143
+ assert len(dock_config["docks"]) == 2
152
144
 
153
- assert len(dock_server.docks) == 2
154
- docks_list = list(dict(dock_server.docks).keys())
155
- assert ["dock_0", "dock_1"] == docks_list
145
+ assert ["dock_0", "dock_1"] == list(dock_config["docks"])
156
146
 
157
147
  dock.clear_all()
158
148
 
159
- assert len(dock_server.docks) == 0
160
- assert len(dock_server.tempAreas) == 0
149
+ dock_config = dock.config_dict
150
+ assert len(dock_config["docks"]) == 0
151
+ assert len(dock.temp_areas) == 0
161
152
 
162
153
 
163
154
  def test_spiral_bar(rpc_server_dock):
164
- dock = BECDockArea(rpc_server_dock.gui_id)
165
- dock_server = rpc_server_dock.gui
155
+ dock = BECDockArea(rpc_server_dock)
166
156
 
167
157
  d0 = dock.add_dock(name="dock_0")
168
158
 
@@ -173,49 +163,46 @@ def test_spiral_bar(rpc_server_dock):
173
163
  bar.set_colors_from_map("viridis")
174
164
  bar.set_value([10, 20, 30, 40, 50])
175
165
 
176
- bar_server = dock_server.docks["dock_0"].widgets[0]
166
+ bar_config = bar.config_dict
177
167
 
178
- expected_colors = Colors.golden_angle_color("viridis", 5, "RGB")
179
- bar_colors = [ring.color.getRgb() for ring in bar_server.rings]
180
- bar_values = [ring.config.value for ring in bar_server.rings]
168
+ expected_colors = [list(color) for color in Colors.golden_angle_color("viridis", 5, "RGB")]
169
+ bar_colors = [ring.config_dict["color"] for ring in bar.rings]
170
+ bar_values = [ring.config_dict["value"] for ring in bar.rings]
171
+ assert bar_config["num_bars"] == 5
181
172
  assert bar_values == [10, 20, 30, 40, 50]
182
173
  assert bar_colors == expected_colors
183
174
 
184
175
 
185
- def test_spiral_bar_scan_update(rpc_server_dock, qtbot):
186
- dock = BECDockArea(rpc_server_dock.gui_id)
187
- dock_server = rpc_server_dock.gui
176
+ def test_spiral_bar_scan_update(bec_client_lib, rpc_server_dock):
177
+ dock = BECDockArea(rpc_server_dock)
188
178
 
189
179
  d0 = dock.add_dock("dock_0")
190
180
 
191
- d0.add_widget("SpiralProgressBar")
181
+ bar = d0.add_widget("SpiralProgressBar")
192
182
 
193
- client = rpc_server_dock.client
183
+ client = bec_client_lib
194
184
  dev = client.device_manager.devices
185
+ dev.samx.tolerance.set(0)
186
+ dev.samy.tolerance.set(0)
195
187
  scans = client.scans
196
188
 
197
189
  status = scans.line_scan(dev.samx, -5, 5, steps=10, exp_time=0.05, relative=False)
190
+ status.wait()
198
191
 
199
- while not status.status == "COMPLETED":
200
- qtbot.wait(200)
201
-
202
- qtbot.wait(200)
203
- bar_server = dock_server.docks["dock_0"].widgets[0]
204
- assert bar_server.config.num_bars == 1
205
- np.testing.assert_allclose(bar_server.rings[0].config.value, 10, atol=0.1)
206
- np.testing.assert_allclose(bar_server.rings[0].config.min_value, 0, atol=0.1)
207
- np.testing.assert_allclose(bar_server.rings[0].config.max_value, 10, atol=0.1)
192
+ bar_config = bar.config_dict
193
+ assert bar_config["num_bars"] == 1
194
+ assert bar_config["rings"][0]["value"] == 10
195
+ assert bar_config["rings"][0]["min_value"] == 0
196
+ assert bar_config["rings"][0]["max_value"] == 10
208
197
 
209
198
  status = scans.grid_scan(dev.samx, -5, 5, 4, dev.samy, -10, 10, 4, relative=True, exp_time=0.1)
199
+ status.wait()
210
200
 
211
- while not status.status == "COMPLETED":
212
- qtbot.wait(200)
213
-
214
- qtbot.wait(200)
215
- assert bar_server.config.num_bars == 1
216
- np.testing.assert_allclose(bar_server.rings[0].config.value, 16, atol=0.1)
217
- np.testing.assert_allclose(bar_server.rings[0].config.min_value, 0, atol=0.1)
218
- np.testing.assert_allclose(bar_server.rings[0].config.max_value, 16, atol=0.1)
201
+ bar_config = bar.config_dict
202
+ assert bar_config["num_bars"] == 1
203
+ assert bar_config["rings"][0]["value"] == 16
204
+ assert bar_config["rings"][0]["min_value"] == 0
205
+ assert bar_config["rings"][0]["max_value"] == 16
219
206
 
220
207
  init_samx = dev.samx.read()["samx"]["value"]
221
208
  init_samy = dev.samy.read()["samy"]["value"]
@@ -226,23 +213,20 @@ def test_spiral_bar_scan_update(rpc_server_dock, qtbot):
226
213
  dev.samy.velocity.put(5)
227
214
 
228
215
  status = scans.umv(dev.samx, 5, dev.samy, 10, relative=True)
216
+ status.wait()
229
217
 
230
- while not status.status == "COMPLETED":
231
- qtbot.wait(200)
232
-
233
- qtbot.wait(200)
234
- assert bar_server.config.num_bars == 2
235
- np.testing.assert_allclose(bar_server.rings[0].config.value, final_samx, atol=0.1)
236
- np.testing.assert_allclose(bar_server.rings[1].config.value, final_samy, atol=0.1)
237
- np.testing.assert_allclose(bar_server.rings[0].config.min_value, init_samx, atol=0.1)
238
- np.testing.assert_allclose(bar_server.rings[1].config.min_value, init_samy, atol=0.1)
239
- np.testing.assert_allclose(bar_server.rings[0].config.max_value, final_samx, atol=0.1)
240
- np.testing.assert_allclose(bar_server.rings[1].config.max_value, final_samy, atol=0.1)
218
+ bar_config = bar.config_dict
219
+ assert bar_config["num_bars"] == 2
220
+ assert bar_config["rings"][0]["value"] == final_samx
221
+ assert bar_config["rings"][1]["value"] == final_samy
222
+ assert bar_config["rings"][0]["min_value"] == init_samx
223
+ assert bar_config["rings"][0]["max_value"] == final_samx
224
+ assert bar_config["rings"][1]["min_value"] == init_samy
225
+ assert bar_config["rings"][1]["max_value"] == final_samy
241
226
 
242
227
 
243
- def test_auto_update(rpc_server_dock, bec_client, qtbot):
244
- dock = BECDockArea(rpc_server_dock.gui_id)
245
- dock._client = bec_client
228
+ def test_auto_update(bec_client_lib, rpc_server_dock):
229
+ dock = BECDockArea(rpc_server_dock)
246
230
 
247
231
  AutoUpdates.enabled = True
248
232
  AutoUpdates.create_default_dock = True
@@ -253,16 +237,13 @@ def test_auto_update(rpc_server_dock, bec_client, qtbot):
253
237
  # we need to start the update script manually; normally this is done when the GUI is started
254
238
  dock._start_update_script()
255
239
 
256
- client = bec_client
240
+ client = bec_client_lib
257
241
  dev = client.device_manager.devices
258
242
  scans = client.scans
259
243
  queue = client.queue
260
244
 
261
245
  status = scans.line_scan(dev.samx, -5, 5, steps=10, exp_time=0.05, relative=False)
262
-
263
- # wait for scan to finish
264
- while not status.status == "COMPLETED":
265
- qtbot.wait(200)
246
+ status.wait()
266
247
 
267
248
  last_scan_data = queue.scan_storage.storage[-1].data
268
249
 
@@ -278,10 +259,7 @@ def test_auto_update(rpc_server_dock, bec_client, qtbot):
278
259
  status = scans.grid_scan(
279
260
  dev.samx, -10, 10, 5, dev.samy, -5, 5, 5, exp_time=0.05, relative=False
280
261
  )
281
-
282
- # wait for scan to finish
283
- while not status.status == "COMPLETED":
284
- qtbot.wait(200)
262
+ status.wait()
285
263
 
286
264
  plt = dock.auto_updates.get_default_figure()
287
265
  widgets = plt.widget_list