0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 03:31:50 +02:00

feat(image_widget): image_widget added

This commit is contained in:
2024-07-19 18:55:01 +02:00
committed by wyzula_j
parent 95e515114a
commit 6a9317facd
4 changed files with 348 additions and 0 deletions

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
<path d="M283.85-289.91h67.41l42.32-112.66h172.59l43.33 112.66h68.85l-164-428h-67.5l-163 428Zm127.74-165.72 67.34-182.87H481l68.41 182.87H411.59Zm68.53 381.61q-86.32 0-160.51-31t-128.89-85.7q-54.7-54.7-85.7-128.89-31-74.19-31-160.51 0-85.31 30.94-159.4t85.7-128.9q54.76-54.8 128.95-86.3t160.51-31.5q85.31 0 159.42 31.47 74.1 31.47 128.91 86.27 54.82 54.8 86.29 128.88 31.48 74.08 31.48 159.6 0 86.2-31.5 160.39-31.5 74.19-86.3 128.95-54.81 54.76-128.9 85.7-74.09 30.94-159.4 30.94ZM480-480Zm-.04 337.85q144.08 0 240.99-96.74 96.9-96.74 96.9-241.07 0-144.32-96.86-241.11-96.86-96.78-240.95-96.78-144.08 0-240.99 96.74-96.9 96.74-96.9 241.07 0 144.32 96.86 241.11 96.86 96.78 240.95 96.78Z"/>
</svg>

After

Width:  |  Height:  |  Size: 809 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
<path d="M636.17-32.59 480.35-188.41l45.82-46.07 77.13 77.37v-137.32H356.93q-28.1 0-46.8-18.55-18.7-18.54-18.7-46.95V-600.3H74.02v-65.27h217.41v-137.32l-77.36 77.37-45.59-45.83 155.59-155.82 156.06 155.82-46.06 45.83-77.14-77.37v442.96h529.29v65.5H669.04v137.32l77.13-77.37L792-188.41 636.17-32.59ZM603.3-419.93V-600.3H416.93v-65.27H603.3q28.37 0 47.06 18.56 18.68 18.55 18.68 46.71v180.37H603.3Z"/>
</svg>

After

Width:  |  Height:  |  Size: 518 B

View File

View File

@ -0,0 +1,342 @@
import sys
from typing import Literal, Optional
import pyqtgraph as pg
from qtpy.QtWidgets import QWidget, QVBoxLayout
from bec_widgets.qt_utils.error_popups import WarningPopupUtility, SafeSlot
from bec_widgets.qt_utils.settings_dialog import SettingsDialog
from bec_widgets.qt_utils.toolbar import (
ModularToolBar,
SeparatorAction,
IconAction,
DeviceSelectionAction,
)
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.widgets.device_combobox.device_combobox import DeviceComboBox
from bec_widgets.widgets.figure import BECFigure
from bec_widgets.widgets.figure.plots.axis_settings import AxisSettings
from bec_widgets.widgets.figure.plots.image.image import ImageConfig
from bec_widgets.widgets.figure.plots.image.image_item import BECImageItem
# TODO move the actions to general place
class BECImageWidget(BECWidget, QWidget):
USER_ACCESS = []
def __init__(
self,
parent: QWidget | None = None,
config: ImageConfig | dict = None,
client=None,
gui_id: str | None = None,
) -> None:
if config is None:
config = ImageConfig(widget_class=self.__class__.__name__)
else:
if isinstance(config, dict):
config = ImageConfig(**config)
super().__init__(client=client, gui_id=gui_id)
QWidget.__init__(self, parent)
self.layout = QVBoxLayout(self)
self.layout.setSpacing(0)
self.layout.setContentsMargins(0, 0, 0, 0)
self.fig = BECFigure()
self.toolbar = ModularToolBar(
actions={
"monitor": DeviceSelectionAction(
"Monitor:", DeviceComboBox(device_filter="Device")
),
"connect": IconAction(icon_path="connection.svg", tooltip="Connect Motors"),
"separator_0": SeparatorAction(),
"save": IconAction(icon_path="save.svg", tooltip="Open Export Dialog"),
"separator_1": SeparatorAction(),
"drag_mode": IconAction(
icon_path="drag_pan_mode.svg", tooltip="Drag Mouse Mode", checkable=True
),
"rectangle_mode": IconAction(
icon_path="rectangle_mode.svg", tooltip="Rectangle Zoom Mode", checkable=True
),
"auto_range": IconAction(icon_path="auto_range.svg", tooltip="Autorange Plot"),
"auto_range_image": IconAction(
icon_path="image_autorange.svg",
tooltip="Autorange Image Intensity",
checkable=True,
),
"separator_2": SeparatorAction(),
"FFT": IconAction(icon_path="transform.svg", tooltip="Toggle FFT", checkable=True),
"separator_3": SeparatorAction(),
"axis_settings": IconAction(
icon_path="settings.svg", tooltip="Open Configuration Dialog"
),
},
target_widget=self,
)
self.layout.addWidget(self.toolbar)
self.layout.addWidget(self.fig)
self.warning_util = WarningPopupUtility(self)
self.image = self.fig.image()
self.image.apply_config(config)
self.config = config
self._hook_actions()
# TODO test commands
self.image.add_monitor_image("eiger")
def _hook_actions(self):
self.toolbar.widgets["connect"].action.triggered.connect(self._connect_action)
self.toolbar.widgets["save"].action.triggered.connect(self.export)
self.toolbar.widgets["drag_mode"].action.triggered.connect(self.enable_mouse_pan_mode)
self.toolbar.widgets["rectangle_mode"].action.triggered.connect(
self.enable_mouse_rectangle_mode
)
self.toolbar.widgets["auto_range"].action.triggered.connect(self._auto_range_from_toolbar)
self.toolbar.widgets["auto_range_image"].action.triggered.connect(
self.toogle_image_autorange
)
self.toolbar.widgets["FFT"].action.triggered.connect(self.toogle_fft)
self.toolbar.widgets["axis_settings"].action.triggered.connect(self.show_axis_settings)
###################################
# Dialog Windows
###################################
@SafeSlot(popup_error=True)
def _connect_action(self):
monitor_combo = self.toolbar.widgets["monitor"].device_combobox
monitor_name = monitor_combo.currentText()
self.add_monitor_image(monitor_name)
monitor_combo.setStyleSheet("QComboBox { background-color: " "; }")
def show_axis_settings(self):
dialog = SettingsDialog(
self,
settings_widget=AxisSettings(),
window_title="Axis Settings",
config=self._config_dict["axis"],
)
dialog.exec()
###################################
# User Access Methods from image
###################################
@SafeSlot(popup_error=True)
def add_monitor_image(
self,
monitor: str,
color_map: Optional[str] = "magma",
color_bar: Optional[Literal["simple", "full"]] = "full",
downsample: Optional[bool] = True,
opacity: Optional[float] = 1.0,
vrange: Optional[tuple[int, int]] = None,
# post_processing: Optional[PostProcessingConfig] = None,
**kwargs,
) -> BECImageItem:
return self.image.add_monitor_image(
monitor,
color_map=color_map,
color_bar=color_bar,
downsample=downsample,
opacity=opacity,
vrange=vrange,
**kwargs,
)
def set_fft(self, enable: bool = False, name: str = None):
"""
Set the FFT of the image.
If name is not specified, then set FFT for all images.
Args:
enable(bool): Whether to perform FFT on the monitor data.
name(str): The name of the image. If None, apply to all images.
"""
self.image.set_fft(enable, name)
###################################
# User Access Methods from Plotbase
###################################
def set(self, **kwargs):
"""
Set the properties of the plot widget.
Args:
**kwargs: Keyword arguments for the properties to be set.
Possible properties:
- title: str
- x_label: str
- y_label: str
- x_scale: Literal["linear", "log"]
- y_scale: Literal["linear", "log"]
- x_lim: tuple
- y_lim: tuple
- legend_label_size: int
"""
self.image.set(**kwargs)
def set_title(self, title: str):
"""
Set the title of the plot widget.
Args:
title(str): Title of the plot.
"""
self.image.set_title(title)
def set_x_label(self, x_label: str):
"""
Set the x-axis label of the plot widget.
Args:
x_label(str): Label of the x-axis.
"""
self.image.set_x_label(x_label)
def set_y_label(self, y_label: str):
"""
Set the y-axis label of the plot widget.
Args:
y_label(str): Label of the y-axis.
"""
self.image.set_y_label(y_label)
def set_x_scale(self, x_scale: Literal["linear", "log"]):
"""
Set the scale of the x-axis of the plot widget.
Args:
x_scale(Literal["linear", "log"]): Scale of the x-axis.
"""
self.image.set_x_scale(x_scale)
def set_y_scale(self, y_scale: Literal["linear", "log"]):
"""
Set the scale of the y-axis of the plot widget.
Args:
y_scale(Literal["linear", "log"]): Scale of the y-axis.
"""
self.image.set_y_scale(y_scale)
def set_x_lim(self, x_lim: tuple):
"""
Set the limits of the x-axis of the plot widget.
Args:
x_lim(tuple): Limits of the x-axis.
"""
self.image.set_x_lim(x_lim)
def set_y_lim(self, y_lim: tuple):
"""
Set the limits of the y-axis of the plot widget.
Args:
y_lim(tuple): Limits of the y-axis.
"""
self.image.set_y_lim(y_lim)
def set_legend_label_size(self, legend_label_size: int):
"""
Set the size of the legend labels of the plot widget.
Args:
legend_label_size(int): Size of the legend labels.
"""
self.image.set_legend_label_size(legend_label_size)
@SafeSlot()
def _auto_range_from_toolbar(self):
"""
Set the auto range of the plot widget from the toolbar.
"""
self.image.set_auto_range(True, "xy")
def set_grid(self, x_grid: bool, y_grid: bool):
"""
Set the grid visibility of the plot widget.
Args:
x_grid(bool): Visibility of the x-axis grid.
y_grid(bool): Visibility of the y-axis grid.
"""
self.image.set_grid(x_grid, y_grid)
def lock_aspect_ratio(self, lock: bool):
"""
Lock the aspect ratio of the plot widget.
Args:
lock(bool): Lock the aspect ratio.
"""
self.image.lock_aspect_ratio(lock)
###################################
# Toolbar Actions
###################################
@SafeSlot()
def toogle_fft(self):
checked = self.toolbar.widgets["FFT"].action.isChecked()
self.set_fft(checked)
@SafeSlot()
def toogle_image_autorange(self):
"""
Enable the auto range of the image intensity.
"""
checked = self.toolbar.widgets["auto_range_image"].action.isChecked()
self.image.set_autorange(checked)
# self.autorange_checked = checked
# if self.autorange_checked:
# print("Auto range is enabled")
# self.image.set_autorange(True)
# else:
# print("Auto range is disabled")
# self.image.set_autorange(False)
@SafeSlot()
def enable_mouse_rectangle_mode(self):
self.toolbar.widgets["rectangle_mode"].action.setChecked(True)
self.toolbar.widgets["drag_mode"].action.setChecked(False)
self.image.plot_item.getViewBox().setMouseMode(pg.ViewBox.RectMode)
@SafeSlot()
def enable_mouse_pan_mode(self):
self.toolbar.widgets["drag_mode"].action.setChecked(True)
self.toolbar.widgets["rectangle_mode"].action.setChecked(False)
self.image.plot_item.getViewBox().setMouseMode(pg.ViewBox.PanMode)
def export(self):
"""
Show the export dialog for the plot widget.
"""
self.image.export()
def cleanup(self):
self.fig.cleanup()
self.client.shutdown()
return super().cleanup()
def main(): # pragma: no cover
from qtpy.QtWidgets import QApplication
app = QApplication(sys.argv)
widget = BECImageWidget()
widget.show()
sys.exit(app.exec_())
if __name__ == "__main__": # pragma: no cover
main()