diff --git a/docs/assets/widget_screenshots/scatter_waveform.png b/docs/assets/widget_screenshots/scatter_waveform.png new file mode 100644 index 00000000..ab198870 Binary files /dev/null and b/docs/assets/widget_screenshots/scatter_waveform.png differ diff --git a/docs/developer/widget_development/widget_tutorial.md b/docs/developer/widget_development/widget_tutorial.md index e947a408..17b02b56 100644 --- a/docs/developer/widget_development/widget_tutorial.md +++ b/docs/developer/widget_development/widget_tutorial.md @@ -28,7 +28,7 @@ from qtpy.QtWidgets import QWidget, QLabel, QDoubleSpinBox, QPushButton, QVBoxLa class MotorControlWidget(QWidget): - def __init__(self, motor_name: str, parent=None): + def __init__(self, parent=None, motor_name: str = ""): super().__init__(parent) self.motor_name = motor_name @@ -70,18 +70,17 @@ from [`BECWidget`](https://bec.readthedocs.io/projects/bec-widgets/en/latest/api pass the motor name to the widget, and use `get_bec_shortcuts` to access BEC services. ```python -from qtpy.QtCore import Slot -from qtpy.QtWidgets import QWidget, QLabel, QDoubleSpinBox, QPushButton, QVBoxLayout - from bec_lib.endpoints import MessageEndpoints +from qtpy.QtWidgets import QDoubleSpinBox, QLabel, QPushButton, QVBoxLayout, QWidget + from bec_widgets.utils.bec_widget import BECWidget +from bec_widgets.utils.error_popups import SafeSlot class MotorControlWidget(BECWidget, QWidget): - def __init__(self, motor_name: str, parent=None, *args, **kwargs): - super().__init__(*args, **kwargs) - QWidget.__init__(self, parent) + def __init__(self, parent=None, motor_name: str = "", **kwargs): + super().__init__(parent=parent, **kwargs) self.motor_name = motor_name @@ -114,13 +113,13 @@ class MotorControlWidget(BECWidget, QWidget): self.on_motor_update, MessageEndpoints.device_readback(self.motor_name) ) - @Slot() + @SafeSlot() def move_motor(self): target_position = self.spin_box.value() self.dev[self.motor_name].move(target_position) print(f"Commanding motor {self.motor_name} to move to {target_position}") - @Slot(dict, dict) + @SafeSlot(dict, dict) def on_motor_update(self, msg_content, metadata): position = msg_content.get("signals", {}).get(self.motor_name, {}).get("value", "N/A") self.label.setText(f"{self.motor_name} : {round(position, 2)}") @@ -132,19 +131,18 @@ Next, we’ll set up an RPC interface to allow remote control of the widget from the `BECIPythonClient`. We’ll expose a method that allows changing the motor name through CLI commands. ```python -from qtpy.QtCore import Slot -from qtpy.QtWidgets import QWidget, QLabel, QDoubleSpinBox, QPushButton, QVBoxLayout - from bec_lib.endpoints import MessageEndpoints +from qtpy.QtWidgets import QDoubleSpinBox, QLabel, QPushButton, QVBoxLayout, QWidget + from bec_widgets.utils.bec_widget import BECWidget +from bec_widgets.utils.error_popups import SafeSlot class MotorControlWidget(BECWidget, QWidget): USER_ACCESS = ["change_motor"] - def __init__(self, motor_name: str, parent=None, *args, **kwargs): - super().__init__(*args, **kwargs) - QWidget.__init__(self, parent) + def __init__(self, parent=None, motor_name: str = "", **kwargs): + super().__init__(parent=parent, **kwargs) self.motor_name = motor_name @@ -177,13 +175,13 @@ class MotorControlWidget(BECWidget, QWidget): self.on_motor_update, MessageEndpoints.device_readback(self.motor_name) ) - @Slot() + @SafeSlot() def move_motor(self): target_position = self.spin_box.value() self.dev[self.motor_name].move(target_position) print(f"Commanding motor {self.motor_name} to move to {target_position}") - @Slot(dict, dict) + @SafeSlot(dict, dict) def on_motor_update(self, msg_content, metadata): position = msg_content.get("signals", {}).get(self.motor_name, {}).get("value", "N/A") self.label.setText(f"{self.motor_name} : {round(position, 2)}") @@ -203,11 +201,11 @@ class MotorControlWidget(BECWidget, QWidget): ``` ```{warning} -After implementing an RPC method, you must run the `cli/generate_cli.py` script to update the CLI commands for `BECIPythonClient`. This script generates the necessary command-line interface bindings, ensuring that your RPC method can be accessed and controlled remotely. +After implementing an RPC method, you must run the `bw-generate-cli --target ` script to update the CLI commands for `BECIPythonClient`, e.g. `bw-generate-cli --target csaxs_bec`. This script generates the necessary command-line interface bindings, ensuring that your RPC method can be accessed and controlled remotely. ``` ```{note} -In this tutorial, we used the @Slot decorator from QtCore to mark methods as slots for signals. This decorator ensures that the connected methods are treated as slots by the Qt framework, which can be connected to signals. It’s a best practice to use the @Slot decorator to clearly indicate which methods are intended to handle signal events with correct argument signatures. +In this tutorial, we used the @SafeSlot decorator from BEC Widgets to mark methods as slots for signals. This decorator ensures that the connected methods are treated as slots by the Qt framework, which can be connected to signals. It’s a best practice to use the @SafeSlot decorator to clearly indicate which methods are intended to handle signal events with correct argument signatures. @SafeSlot also provides error handling and logging capabilities, making it more robust and easier to debug. ``` ## Step 4: Running the Widget diff --git a/docs/user/widgets/buttons_appearance/buttons_appearance.md b/docs/user/widgets/buttons_appearance/buttons_appearance.md index 31af3c8a..32b40060 100644 --- a/docs/user/widgets/buttons_appearance/buttons_appearance.md +++ b/docs/user/widgets/buttons_appearance/buttons_appearance.md @@ -63,12 +63,12 @@ from qtpy.QtWidgets import QWidget, QVBoxLayout from bec_widgets.widgets.buttons import DarkModeButton class MyGui(QWidget): - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super().__init__(parent=parent) self.setLayout(QVBoxLayout(self)) # Create and add the DarkModeButton to the layout - self.dark_mode_button = DarkModeButton() + self.dark_mode_button = DarkModeButton(parent=self) self.layout().addWidget(self.dark_mode_button) # Example of how this custom GUI might be used: @@ -83,12 +83,12 @@ from qtpy.QtWidgets import QWidget, QVBoxLayout from bec_widgets.widgets.buttons import ColorButton class MyGui(QWidget): - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super().__init__(parent=parent) self.setLayout(QVBoxLayout(self)) # Create and add the ColorButton to the layout - self.color_button = ColorButton() + self.color_button = ColorButton(self) self.layout().addWidget(self.color_button) # Example of how this custom GUI might be used: @@ -103,12 +103,12 @@ from qtpy.QtWidgets import QWidget, QVBoxLayout from bec_widgets.widgets.buttons import ColormapSelector class MyGui(QWidget): - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super().__init__(parent=parent) self.setLayout(QVBoxLayout(self)) # Create and add the ColormapSelector to the layout - self.colormap_selector = ColormapSelector() + self.colormap_selector = ColormapSelector(self) self.layout().addWidget(self.colormap_selector) # Example of how this custom GUI might be used: @@ -123,12 +123,12 @@ from qtpy.QtWidgets import QWidget, QVBoxLayout from bec_widgets.widgets.buttons import ColormapButton class MyGui(QWidget): - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super().__init__(parent=parent) self.setLayout(QVBoxLayout(self)) # Create and add the ColormapButton to the layout - self.colormap_button = ColormapButton() + self.colormap_button = ColormapButton(self) self.layout().addWidget(self.colormap_button) # Connect the signal to handle colormap changes diff --git a/docs/user/widgets/device_input/device_input.md b/docs/user/widgets/device_input/device_input.md index c289d554..7469880e 100644 --- a/docs/user/widgets/device_input/device_input.md +++ b/docs/user/widgets/device_input/device_input.md @@ -45,12 +45,12 @@ from bec_lib.device import ReadoutPriority from bec_widgets.widgets.base_classes.device_input_base import BECDeviceFilter class MyGui(QWidget): - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super().__init__(parent=parent) self.setLayout(QVBoxLayout(self)) # Initialize the layout for the widget # Create and add the DeviceLineEdit to the layout - self.device_line_edit = DeviceLineEdit(device_filter=BECDeviceFilter.POSITIONER, readout_priority_filter=ReadoutPriority.BASELINE) + self.device_line_edit = DeviceLineEdit(parent=self, device_filter=BECDeviceFilter.POSITIONER, readout_priority_filter=ReadoutPriority.BASELINE) self.layout().addWidget(self.device_line_edit) # Example of how this custom GUI might be used: @@ -71,12 +71,12 @@ from bec_lib.device import ReadoutPriority from bec_widgets.widgets.base_classes.device_input_base import BECDeviceFilter class MyGui(QWidget): - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super().__init__(parent=parent) self.setLayout(QVBoxLayout(self)) # Initialize the layout for the widget # Create and add the DeviceComboBox to the layout - self.device_combobox = DeviceComboBox(device_filter=BECDeviceFilter.POSITIONER, readout_priority_filter=ReadoutPriority.BASELINE) + self.device_combobox = DeviceComboBox(parent=self, device_filter=BECDeviceFilter.POSITIONER, readout_priority_filter=ReadoutPriority.BASELINE) self.layout().addWidget(self.device_combobox) # Example of how this custom GUI might be used: diff --git a/docs/user/widgets/lmfit_dialog/lmfit_dialog.md b/docs/user/widgets/lmfit_dialog/lmfit_dialog.md index a3157e0a..8c0607cd 100644 --- a/docs/user/widgets/lmfit_dialog/lmfit_dialog.md +++ b/docs/user/widgets/lmfit_dialog/lmfit_dialog.md @@ -4,14 +4,14 @@ ````{tab} Overview -The [`LMFit Dialog`](/api_reference/_autosummary/bec_widgets.widgets.lmfit_dialog.lmfit_dialog.LMFitDialog) is a widget that is developed to be used together with the [`BECWaveformWidget`](/api_reference/_autosummary/bec_widgets.widgets.waveform.waveform_widget.BECWaveformWidget). The `BECWaveformWidget` allows user to submit a fit request to BEC's [DAP server](https://bec.readthedocs.io/en/latest/developer/getting_started/architecture.html) choosing from a selection of [LMFit models](https://lmfit.github.io/lmfit-py/builtin_models.html#) to fit monitored data sources. The `LMFit Dialog` provides an interface to monitor these fits, including statistics and fit parameters in real time. -Within the `BECWaveformWidget`, the dialog is accessible via the toolbar and will be automatically linked to the current waveform widget. For a more customised use, we can embed the `LMFit Dialog` in a larger GUI using the *BEC Designer*. In this case, one has to connect the [`update_summary_tree`](/api_reference/_autosummary/bec_widgets.widgets.lmfit_dialog.lmfit_dialog.LMFitDialog.rst#bec_widgets.widgets.lmfit_dialog.lmfit_dialog.LMFitDialog.update_summary_tree) slot of the LMFit Dialog to the [`dap_summary_update`](/api_reference/_autosummary/bec_widgets.widgets.waveform.waveform_widget.BECWaveformWidget.rst#bec_widgets.widgets.waveform.waveform_widget.BECWaveformWidget.dap_summary_update) signal of the BECWaveformWidget to ensure its functionality. +The [`LMFit Dialog`](/api_reference/_autosummary/bec_widgets.widgets.dap.lmfit_dialog.lmfit_dialog.LMFitDialog) is a widget that is developed to be used together with the [`Waveform`](/api_reference/_autosummary/bec_widgets.widgets.plots.waveform.waveform_widget.Waveform) widget. The `Waveform` widget allows user to submit a fit request to BEC's [DAP server](https://bec.readthedocs.io/en/latest/developer/getting_started/architecture.html) choosing from a selection of [LMFit models](https://lmfit.github.io/lmfit-py/builtin_models.html#) to fit monitored data sources. The `LMFit Dialog` provides an interface to monitor these fits, including statistics and fit parameters in real time. +Within the `Waveform` widget, the dialog is accessible via the toolbar and will be automatically linked to the current waveform widget. For a more customised use, we can embed the `LMFit Dialog` in a larger GUI using the *BEC Designer*. In this case, one has to connect the [`update_summary_tree`](/api_reference/_autosummary/bec_widgets.widgets.dap.lmfit_dialog.lmfit_dialog.LMFitDialog.rst#bec_widgets.widgets.lmfit_dialog.lmfit_dialog.LMFitDialog.update_summary_tree) slot of the LMFit Dialog to the [`dap_summary_update`](/api_reference/_autosummary/bec_widgets.widgets.plots.waveform.waveform_widget.Waveform.rst#bec_widgets.widgets.plots.waveform.waveform_widget.Waveform.dap_summary_update) signal of the BECWaveformWidget to ensure its functionality. ## Key Features: - **Fit Summary**: Display updates on LMFit DAP processes and fit statistics. - **Fit Parameter**: Display current fit parameter. -- **BECWaveformWidget Integration**: Directly connect to BECWaveformWidget to display fit statistics and parameters. +- **`Waveform` Widget Integration**: Directly connect to `Waveform` widget to display fit statistics and parameters. ```{figure} /assets/widget_screenshots/lmfit_dialog.png --- name: lmfit_dialog @@ -20,12 +20,12 @@ LMFit Dialog ``` ```` ````{tab} Connect in BEC Designer -The `LMFit Dialog` widget can be connected to a `BECWaveformWidget` to display fit statistics and parameters from the LMFit DAP process hooked up to the waveform widget. You can use the signal/slot editor from the BEC Designer to connect the `dap_summary_update` signal of the BECWaveformWidget to the `update_summary_tree` slot of the LMFit Dialog. +The `LMFit Dialog` widget can be connected to a `Waveform` widget to display fit statistics and parameters from the LMFit DAP process hooked up to the waveform widget. You can use the signal/slot editor from the BEC Designer to connect the `dap_summary_update` signal of the `Waveform` widget to the `update_summary_tree` slot of the LMFit Dialog. ```{figure} /assets/widget_screenshots/lmfit_dialog_connect.png ```` ````{tab} Connect in Python -It is also possible to directly connect the `dap_summary_update` signal of the BECWaveformWidget to the `update_summary_tree` slot of the LMFit Dialog in Python. +It is also possible to directly connect the `dap_summary_update` signal of the `Waveform` widget to the `update_summary_tree` slot of the LMFit Dialog in Python. ```python waveform = BECWaveformWidget(...) diff --git a/docs/user/widgets/positioner_box/positioner_box.md b/docs/user/widgets/positioner_box/positioner_box.md index ff19570c..7c4e05c8 100644 --- a/docs/user/widgets/positioner_box/positioner_box.md +++ b/docs/user/widgets/positioner_box/positioner_box.md @@ -27,12 +27,12 @@ from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget from bec_widgets.widgets.positioner_box import PositionerBox class MyGui(QWidget): - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super().__init__(parent=parent) self.setLayout(QVBoxLayout(self)) # Initialize the layout for the widget # Create and add the PositionerBox to the layout - self.positioner_box = PositionerBox(device="motor1") + self.positioner_box = PositionerBox(parent=self, device="motor1") self.layout().addWidget(self.positioner_box) # Example of how this custom GUI might be used: diff --git a/docs/user/widgets/positioner_box/positioner_box_2d.md b/docs/user/widgets/positioner_box/positioner_box_2d.md index bdaed4ab..db1c9507 100644 --- a/docs/user/widgets/positioner_box/positioner_box_2d.md +++ b/docs/user/widgets/positioner_box/positioner_box_2d.md @@ -1,10 +1,10 @@ (user.widgets.positioner_box_2d)= -# Positioner Box Widget +# Positioner Box 2D Widget ````{tab} Overview -The [`PositionerBox2D`](/api_reference/_autosummary/bec_widgets.cli.client.PositionerBox2D) widget is very similar to the ['PositionerBox'](/user/widgets/positioner_box/positioner_box) but allows controlling two positioners at the same time, in a horizontal and vertical orientation respectively. It is intended primarily for controlling axes which have a perpendicular relationship like that. In other cases, it may be better to use a `PositionerGroup` instead. +The [`PositionerBox2D`](/api_reference/_autosummary/bec_widgets.cli.client.PositionerBox2D) widget is very similar to the [`PositionerBox`](/user/widgets/positioner_box/positioner_box) but allows controlling two positioners at the same time, in a horizontal and vertical orientation respectively. It is intended primarily for controlling axes which have a perpendicular relationship like that. In other cases, it may be better to use a `PositionerGroup` instead. The `PositionerBox2D` has the same features as the standard `PositionerBox`, but additionally, step buttons which move the positioner by the selected step size, and tweak buttons which move by a tenth of the selected step size. @@ -23,12 +23,12 @@ from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget from bec_widgets.widgets.positioner_box import PositionerBox2D class MyGui(QWidget): - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super().__init__(parent=parent) self.setLayout(QVBoxLayout(self)) # Initialize the layout for the widget # Create and add the PositionerBox to the layout - self.positioner_box_2d = PositionerBox(device_hor="horizontal_motor", device_ver="vertical_motor") + self.positioner_box_2d = PositionerBox(parent=self, device_hor="horizontal_motor", device_ver="vertical_motor") self.layout().addWidget(self.positioner_box_2d) # Example of how this custom GUI might be used: diff --git a/docs/user/widgets/progress_bar/ring_progress_bar.md b/docs/user/widgets/progress_bar/ring_progress_bar.md index 782d7593..a86ec75a 100644 --- a/docs/user/widgets/progress_bar/ring_progress_bar.md +++ b/docs/user/widgets/progress_bar/ring_progress_bar.md @@ -24,7 +24,8 @@ In this example, we demonstrate how to add a `RingProgressBar` widget to a `BECD ```python # Add a new dock with a RingProgressBar widget -progress = gui.add_dock().add_widget("RingProgressBar") +dock_area = gui.new('my_new_dock_area') # Create a new dock area +progress = dock_area.new().new(gui.available_widgets.RingProgressBar) # Customize the size of the progress ring progress.set_line_widths(20) diff --git a/docs/user/widgets/waveform/scatter_2D.gif b/docs/user/widgets/scatter_waveform/scatter_2D.gif similarity index 100% rename from docs/user/widgets/waveform/scatter_2D.gif rename to docs/user/widgets/scatter_waveform/scatter_2D.gif diff --git a/docs/user/widgets/scatter_waveform/scatter_waveform.md b/docs/user/widgets/scatter_waveform/scatter_waveform.md new file mode 100644 index 00000000..aa699c97 --- /dev/null +++ b/docs/user/widgets/scatter_waveform/scatter_waveform.md @@ -0,0 +1,39 @@ +(user.widgets.scatter_waveform_widget)= + +# Scatter Waveform Widget + +````{tab} Overview +The 2D scatter plot widget is designed for more complex data visualization. It employs a false color map to represent a third dimension (z-axis), making it an ideal tool for visualizing multidimensional data sets. + +## Key Features: +- **Real-Time Data Visualization**: Display 2D scatter plots with a third dimension represented by color. +- **Flexible Integration**: Can be integrated into [`BECDockArea`](user.widgets.bec_dock_area), or used as an individual component in your application through `BECDesigner`. + +```` + +````{tab} Examples - CLI + +`ScatterWaveform` widget can be embedded in [`BECDockArea`](user.widgets.bec_dock_area), or used as an individual component in your application through `BECDesigner`. The command-line API is consistent across these contexts. + +## Example + +```python +# Add a new dock_area, a new dock and a BECWaveForm to the dock +plt = gui.new().new().new(gui.available_widgets.ScatterWaveform) +plt.plot(x_name='samx', y_name='samy', z_name='bpm4i') + +``` + +![Scatter 2D](./scatter_2D.gif) + + +```{note} +The ScatterWaveform widget only plots the data points if both x and y axis motors are moving. Or more generally, if all signals are of readout type *monitored*. +``` +```` + +````{tab} API +```{eval-rst} +.. include:: /api_reference/_autosummary/bec_widgets.cli.client.ScatterWaveform.rst +``` +```` diff --git a/docs/user/widgets/toggle/toggle.md b/docs/user/widgets/toggle/toggle.md index 41bab87c..06b067ec 100644 --- a/docs/user/widgets/toggle/toggle.md +++ b/docs/user/widgets/toggle/toggle.md @@ -27,12 +27,12 @@ from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget from bec_widgets.widgets.toggle_switch import ToggleSwitch class MyGui(QWidget): - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super().__init__(parent=parent) self.setLayout(QVBoxLayout(self)) # Initialize the layout for the widget # Create and add the ToggleSwitch to the layout - self.toggle_switch = ToggleSwitch() + self.toggle_switch = ToggleSwitch(parent=self) self.layout().addWidget(self.toggle_switch) # Example of how this custom GUI might be used: diff --git a/docs/user/widgets/waveform/waveform_widget.md b/docs/user/widgets/waveform/waveform_widget.md index e15daf69..c5422b35 100644 --- a/docs/user/widgets/waveform/waveform_widget.md +++ b/docs/user/widgets/waveform/waveform_widget.md @@ -36,9 +36,9 @@ plt1.plot(x_name='samx', y_name='bpm4i') plt2.plot(x_name='samx', y_name='bpm3i') # set axis labels -plt1.set_title("Gauss plots vs. samx") -plt1.set_x_label("Motor X") -plt1.set_y_label("Gauss Signal (A.U.") +plt1.title = "Gauss plots vs. samx" +plt1.x_label = "Motor X" +plt1.y_label = "Gauss Signal (A.U.)" ``` @@ -97,25 +97,6 @@ print(dap_bpm3a.dap_params) ![Waveform 1D_DAP](./bec_figure_dap.gif) -## Example 3 - 2D ScatterWaveform plot - -The 2D scatter plot widget is designed for more complex data visualization. It employs a false color map to represent a third dimension (z-axis), making it an ideal tool for visualizing multidimensional data sets. - -```python -# Add a new dock_area, a new dock and a BECWaveForm to the dock -plt = gui.new().new().new(gui.available_widgets.ScatterWaveform) -plt.plot(x_name='samx', y_name='samy', z_name='bpm4i') - -plt = gui.add_dock().add_widget('BECFigure').add_plot(x_name='samx', y_name='samy', z_name='bpm4i') -``` - -![Scatter 2D](./scatter_2D.gif) - - -```{note} -The ScatterWaveform widget only plots the data points if both x and y axis motors are moving. Or more generally, if all signals are of readout type *monitored*. -``` - ```` ````{tab} API diff --git a/docs/user/widgets/widgets.md b/docs/user/widgets/widgets.md index 6898c901..21ebd4c8 100644 --- a/docs/user/widgets/widgets.md +++ b/docs/user/widgets/widgets.md @@ -45,7 +45,15 @@ Display 1D detector signals. Display multiple 1D waveforms. ``` -```{grid-item-card} Image widget +```{grid-item-card} Scatter Waveform Widget +:link: user.widgets.scatter_waveform_widget +:link-type: ref +:img-top: /assets/widget_screenshots/scatter_waveform.png + +Display a 1D waveforms with a third device on the z-axis. +``` + +```{grid-item-card} Image Widget :link: user.widgets.image_widget :link-type: ref :img-top: /assets/widget_screenshots/image_widget.png @@ -256,6 +264,7 @@ hidden: true dock_area/bec_dock_area.md waveform/waveform_widget.md +scatter_waveform/scatter_waveform.md multi_waveform/multi_waveform.md image/image_widget.md motor_map/motor_map.md