mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 11:41:49 +02:00
WIP ROI manager for Waveform, however DAP do not update correctly, DO NOT PUT INTO PLOTBASE MR
This commit is contained in:
@ -20,6 +20,7 @@ from bec_widgets.widgets.plots_next_gen.toolbar_bundles.mouse_interactions impor
|
||||
MouseInteractionToolbarBundle,
|
||||
)
|
||||
from bec_widgets.widgets.plots_next_gen.toolbar_bundles.plot_export import PlotExportBundle
|
||||
from bec_widgets.widgets.plots_next_gen.toolbar_bundles.roi_bundle import ROIBundle
|
||||
from bec_widgets.widgets.plots_next_gen.toolbar_bundles.save_state import SaveStateBundle
|
||||
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
|
||||
|
||||
@ -118,18 +119,14 @@ class PlotBase(BECWidget, QWidget):
|
||||
self.plot_export_bundle = PlotExportBundle("plot_export", target_widget=self)
|
||||
self.mouse_bundle = MouseInteractionToolbarBundle("mouse_interaction", target_widget=self)
|
||||
self.state_export_bundle = SaveStateBundle("state_export", target_widget=self)
|
||||
self.roi_bundle = ROIBundle("roi", target_widget=self)
|
||||
|
||||
# Add elements to toolbar
|
||||
self.toolbar.add_bundle(self.plot_export_bundle, target_widget=self)
|
||||
self.toolbar.add_bundle(self.state_export_bundle, target_widget=self)
|
||||
self.toolbar.add_bundle(self.mouse_bundle, target_widget=self)
|
||||
self.toolbar.add_bundle(self.roi_bundle, target_widget=self)
|
||||
|
||||
self.toolbar.add_action("separator_0", SeparatorAction(), target_widget=self)
|
||||
self.toolbar.add_action(
|
||||
"crosshair",
|
||||
MaterialIconAction(icon_name="point_scan", tooltip="Show Crosshair", checkable=True),
|
||||
target_widget=self,
|
||||
)
|
||||
self.toolbar.add_action("separator_1", SeparatorAction(), target_widget=self)
|
||||
self.toolbar.add_action(
|
||||
"fps_monitor",
|
||||
@ -141,7 +138,6 @@ class PlotBase(BECWidget, QWidget):
|
||||
self.toolbar.widgets["fps_monitor"].action.toggled.connect(
|
||||
lambda checked: setattr(self, "enable_fps_monitor", checked)
|
||||
)
|
||||
self.toolbar.widgets["crosshair"].action.toggled.connect(self.toggle_crosshair)
|
||||
|
||||
def add_side_menus(self):
|
||||
"""Adds multiple menus to the side panel."""
|
||||
|
@ -0,0 +1,32 @@
|
||||
from bec_widgets.qt_utils.toolbar import MaterialIconAction, ToolbarBundle
|
||||
|
||||
|
||||
class ROIBundle(ToolbarBundle):
|
||||
"""
|
||||
A bundle of actions that are hooked in this constructor itself,
|
||||
so that you can immediately connect the signals and toggle states.
|
||||
|
||||
This bundle is for a toolbar that controls crosshair and ROI interaction.
|
||||
"""
|
||||
|
||||
def __init__(self, bundle_id="roi", target_widget=None, **kwargs):
|
||||
super().__init__(bundle_id=bundle_id, actions=[], **kwargs)
|
||||
self.target_widget = target_widget
|
||||
|
||||
# Create each MaterialIconAction with a parent
|
||||
# so the signals can fire even if the toolbar isn't added yet.
|
||||
crosshair = MaterialIconAction(
|
||||
icon_name="point_scan", tooltip="Show Crosshair", checkable=True
|
||||
)
|
||||
roi = MaterialIconAction(
|
||||
icon_name="align_justify_space_between",
|
||||
tooltip="Add ROI region for DAP",
|
||||
checkable=True,
|
||||
)
|
||||
|
||||
# Add them to the bundle
|
||||
self.add_action("crosshair", crosshair)
|
||||
self.add_action("roi_linear", roi)
|
||||
|
||||
# Immediately connect signals
|
||||
crosshair.action.toggled.connect(self.target_widget.toggle_crosshair)
|
@ -0,0 +1,83 @@
|
||||
import pyqtgraph as pg
|
||||
from qtpy.QtCore import QObject, Signal, Slot
|
||||
from bec_widgets.utils.linear_region_selector import LinearRegionWrapper
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
|
||||
|
||||
class WaveformROIManager(QObject):
|
||||
"""
|
||||
A reusable helper class that manages a single linear ROI region on a given plot item.
|
||||
It provides signals to notify about region changes and active state.
|
||||
"""
|
||||
|
||||
roi_changed = Signal(tuple) # Emitted when the ROI (left, right) changes
|
||||
roi_active = Signal(bool) # Emitted when ROI is enabled or disabled
|
||||
|
||||
def __init__(self, plot_item: pg.PlotItem, parent=None):
|
||||
super().__init__(parent)
|
||||
self._plot_item = plot_item
|
||||
self._roi_wrapper: LinearRegionWrapper | None = None
|
||||
self._roi_region: tuple[float, float] | None = None
|
||||
self._accent_colors = get_accent_colors()
|
||||
|
||||
@property
|
||||
def roi_region(self) -> tuple[float, float] | None:
|
||||
return self._roi_region
|
||||
|
||||
@roi_region.setter
|
||||
def roi_region(self, value: tuple[float, float] | None):
|
||||
self._roi_region = value
|
||||
if self._roi_wrapper is not None and value is not None:
|
||||
self._roi_wrapper.linear_region_selector.setRegion(value)
|
||||
|
||||
@Slot(bool)
|
||||
def toggle_roi(self, enabled: bool) -> None:
|
||||
if enabled:
|
||||
self._enable_roi()
|
||||
else:
|
||||
self._disable_roi()
|
||||
|
||||
@Slot(tuple)
|
||||
def select_roi(self, region: tuple[float, float]):
|
||||
# If ROI not present, enabling it
|
||||
if self._roi_wrapper is None:
|
||||
self.toggle_roi(True)
|
||||
self.roi_region = region
|
||||
|
||||
def _enable_roi(self):
|
||||
if self._roi_wrapper is not None:
|
||||
# Already enabled
|
||||
return
|
||||
color = self._accent_colors.default
|
||||
color.setAlpha(int(0.2 * 255))
|
||||
hover_color = self._accent_colors.default
|
||||
hover_color.setAlpha(int(0.35 * 255))
|
||||
|
||||
self._roi_wrapper = LinearRegionWrapper(
|
||||
self._plot_item, color=color, hover_color=hover_color, parent=self
|
||||
)
|
||||
self._roi_wrapper.add_region_selector()
|
||||
self._roi_wrapper.region_changed.connect(self._on_region_changed)
|
||||
|
||||
# If we already had a region, apply it
|
||||
if self._roi_region is not None:
|
||||
self._roi_wrapper.linear_region_selector.setRegion(self._roi_region)
|
||||
else:
|
||||
self._roi_region = self._roi_wrapper.linear_region_selector.getRegion()
|
||||
|
||||
self.roi_active.emit(True)
|
||||
|
||||
def _disable_roi(self):
|
||||
if self._roi_wrapper is not None:
|
||||
self._roi_wrapper.region_changed.disconnect(self._on_region_changed)
|
||||
self._roi_wrapper.cleanup()
|
||||
self._roi_wrapper.deleteLater()
|
||||
self._roi_wrapper = None
|
||||
|
||||
self._roi_region = None
|
||||
self.roi_active.emit(False)
|
||||
|
||||
@Slot(tuple)
|
||||
def _on_region_changed(self, region: tuple[float, float]):
|
||||
self._roi_region = region
|
||||
self.roi_changed.emit(region)
|
@ -18,6 +18,7 @@ from bec_widgets.utils.bec_signal_proxy import BECSignalProxy
|
||||
from bec_widgets.utils.colors import Colors, set_theme
|
||||
from bec_widgets.widgets.plots_next_gen.plot_base import PlotBase
|
||||
from bec_widgets.widgets.plots_next_gen.waveform.curve import Curve, CurveConfig, DeviceSignal
|
||||
from bec_widgets.widgets.plots_next_gen.waveform.utils.roi_manager import WaveformROIManager
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
@ -56,8 +57,9 @@ class Waveform(PlotBase):
|
||||
new_scan = Signal()
|
||||
new_scan_id = Signal(str)
|
||||
|
||||
# roi_changed = Signal(tuple)
|
||||
# roi_active = Signal(bool)
|
||||
roi_changed = Signal(tuple)
|
||||
roi_active = Signal(bool)
|
||||
|
||||
# request_dap_refresh = Signal() #TODO probably replaced by request_dap_update
|
||||
def __init__(
|
||||
self,
|
||||
@ -93,6 +95,9 @@ class Waveform(PlotBase):
|
||||
"label_suffix": "",
|
||||
} # TODO decide which one to use
|
||||
|
||||
# Specific GUI elements
|
||||
self._init_roi_manager()
|
||||
|
||||
# Scan status update loop
|
||||
self.bec_dispatcher.connect_slot(self.on_scan_status, MessageEndpoints.scan_status())
|
||||
self.bec_dispatcher.connect_slot(self.on_scan_progress, MessageEndpoints.scan_progress())
|
||||
@ -118,6 +123,53 @@ class Waveform(PlotBase):
|
||||
# ) # TODO implement
|
||||
self.scan_history(-1)
|
||||
|
||||
################################################################################
|
||||
# Widget Specific GUI interactions
|
||||
################################################################################
|
||||
|
||||
def _init_roi_manager(self):
|
||||
"""
|
||||
Initialize the ROI manager for the Waveform widget.
|
||||
"""
|
||||
self._roi_manager = WaveformROIManager(self.plot_item, parent=self)
|
||||
|
||||
# Connect manager signals -> forward them via Waveform's own signals
|
||||
self._roi_manager.roi_changed.connect(self.roi_changed)
|
||||
self._roi_manager.roi_active.connect(self.roi_active)
|
||||
|
||||
# Example: connect ROI changed to re-request DAP
|
||||
self.roi_changed.connect(self._on_roi_changed_for_dap)
|
||||
self.toolbar.widgets["roi_linear"].action.toggled.connect(self._roi_manager.toggle_roi)
|
||||
|
||||
@property
|
||||
def roi_region(self) -> tuple[float, float] | None:
|
||||
"""
|
||||
Allows external code to get/set the ROI region easily via Waveform.
|
||||
"""
|
||||
return self._roi_manager.roi_region
|
||||
|
||||
@roi_region.setter
|
||||
def roi_region(self, value: tuple[float, float] | None):
|
||||
self._roi_manager.roi_region = value
|
||||
|
||||
def select_roi(self, region: tuple[float, float]):
|
||||
"""
|
||||
Public method if you want the old `select_roi` style.
|
||||
"""
|
||||
self._roi_manager.select_roi(region)
|
||||
|
||||
# If you want the old toggle_roi style:
|
||||
def toggle_roi(self, enabled: bool):
|
||||
self._roi_manager.toggle_roi(enabled)
|
||||
|
||||
def _on_roi_changed_for_dap(self, region: tuple[float, float]):
|
||||
"""
|
||||
Whenever the ROI changes, you might want to re-request DAP with the new x_min, x_max.
|
||||
"""
|
||||
logger.info(f"ROI region changed to {region}, requesting new DAP fit.")
|
||||
# Example: you could store these in a local property, or directly call request_dap_update
|
||||
self.request_dap_update.emit()
|
||||
|
||||
################################################################################
|
||||
# Widget Specific Properties
|
||||
################################################################################
|
||||
@ -810,8 +862,6 @@ class Waveform(PlotBase):
|
||||
# @SafeSlot() #FIXME type error
|
||||
def request_dap(self):
|
||||
"""Request new fit for data"""
|
||||
print("Request DAP") # TODO change to logger
|
||||
|
||||
for dap_curve in self._dap_curves:
|
||||
parent_label = getattr(dap_curve.config, "parent_label", None)
|
||||
if not parent_label:
|
||||
@ -825,6 +875,13 @@ class Waveform(PlotBase):
|
||||
x_data, y_data = parent_curve.get_data()
|
||||
model_name = dap_curve.config.signal.dap
|
||||
model = getattr(self.dap, model_name)
|
||||
try:
|
||||
x_min, x_max = self.roi_region
|
||||
except TypeError:
|
||||
x_min = None
|
||||
x_max = None
|
||||
|
||||
print(f"x_min: {x_min}, x_max: {x_max}")
|
||||
|
||||
# TODO implement DAP logic
|
||||
msg = messages.DAPRequestMessage(
|
||||
@ -832,7 +889,12 @@ class Waveform(PlotBase):
|
||||
dap_type="on_demand",
|
||||
config={
|
||||
"args": [],
|
||||
"kwargs": {"data_x": x_data, "data_y": y_data}, # TODO add xmin,xmax as before
|
||||
"kwargs": {
|
||||
"data_x": x_data,
|
||||
"data_y": y_data,
|
||||
"x_min": x_min,
|
||||
"x_max": x_max,
|
||||
}, # TODO add xmin,xmax as before -> so far do not work
|
||||
"class_args": model._plugin_info["class_args"],
|
||||
"class_kwargs": model._plugin_info["class_kwargs"],
|
||||
"curve_label": dap_curve.name(),
|
||||
|
Reference in New Issue
Block a user