mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-03-04 16:02:51 +01:00
feat(heatmap): add interpolation and oversampling UI components
This commit is contained in:
@@ -1564,6 +1564,48 @@ class Heatmap(RPCBase):
|
||||
Enable the full colorbar.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def interpolation_method(self) -> "str":
|
||||
"""
|
||||
The interpolation method used for the heatmap.
|
||||
"""
|
||||
|
||||
@interpolation_method.setter
|
||||
@rpc_call
|
||||
def interpolation_method(self) -> "str":
|
||||
"""
|
||||
The interpolation method used for the heatmap.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def oversampling_factor(self) -> "float":
|
||||
"""
|
||||
The oversampling factor for grid resolution.
|
||||
"""
|
||||
|
||||
@oversampling_factor.setter
|
||||
@rpc_call
|
||||
def oversampling_factor(self) -> "float":
|
||||
"""
|
||||
The oversampling factor for grid resolution.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def enforce_interpolation(self) -> "bool":
|
||||
"""
|
||||
Whether to enforce interpolation even for grid scans.
|
||||
"""
|
||||
|
||||
@enforce_interpolation.setter
|
||||
@rpc_call
|
||||
def enforce_interpolation(self) -> "bool":
|
||||
"""
|
||||
Whether to enforce interpolation even for grid scans.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def fft(self) -> "bool":
|
||||
@@ -1649,12 +1691,32 @@ class Heatmap(RPCBase):
|
||||
y_entry: "None | str" = None,
|
||||
z_entry: "None | str" = None,
|
||||
color_map: "str | None" = "plasma",
|
||||
label: "str | None" = None,
|
||||
validate_bec: "bool" = True,
|
||||
interpolation: "Literal['linear', 'nearest'] | None" = None,
|
||||
enforce_interpolation: "bool | None" = None,
|
||||
oversampling_factor: "float | None" = None,
|
||||
lock_aspect_ratio: "bool | None" = None,
|
||||
show_config_label: "bool | None" = None,
|
||||
reload: "bool" = False,
|
||||
):
|
||||
"""
|
||||
Plot the heatmap with the given x, y, and z data.
|
||||
|
||||
Args:
|
||||
x_name (str): The name of the x-axis signal.
|
||||
y_name (str): The name of the y-axis signal.
|
||||
z_name (str): The name of the z-axis signal.
|
||||
x_entry (str | None): The entry for the x-axis signal.
|
||||
y_entry (str | None): The entry for the y-axis signal.
|
||||
z_entry (str | None): The entry for the z-axis signal.
|
||||
color_map (str | None): The color map to use for the heatmap.
|
||||
validate_bec (bool): Whether to validate the entries against BEC signals.
|
||||
interpolation (Literal["linear", "nearest"] | None): The interpolation method to use.
|
||||
enforce_interpolation (bool | None): Whether to enforce interpolation even for grid scans.
|
||||
oversampling_factor (float | None): Factor to oversample the grid resolution.
|
||||
lock_aspect_ratio (bool | None): Whether to lock the aspect ratio of the image.
|
||||
show_config_label (bool | None): Whether to show the configuration label in the heatmap.
|
||||
reload (bool): Whether to reload the heatmap with new data.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import json
|
||||
from typing import Literal
|
||||
|
||||
@@ -11,7 +10,11 @@ from bec_lib.endpoints import MessageEndpoints
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from qtpy.QtCore import QTimer, Signal
|
||||
from qtpy.QtGui import QTransform
|
||||
from scipy.interpolate import LinearNDInterpolator
|
||||
from scipy.interpolate import (
|
||||
CloughTocher2DInterpolator,
|
||||
LinearNDInterpolator,
|
||||
NearestNDInterpolator,
|
||||
)
|
||||
from scipy.spatial import cKDTree
|
||||
from toolz import partition
|
||||
|
||||
@@ -44,6 +47,19 @@ class HeatmapConfig(ConnectionConfig):
|
||||
color_bar: Literal["full", "simple"] | None = Field(
|
||||
None, description="The type of the color bar."
|
||||
)
|
||||
interpolation: Literal["linear", "nearest", "clough"] = Field(
|
||||
"linear", description="The interpolation method for the heatmap."
|
||||
)
|
||||
oversampling_factor: float = Field(
|
||||
1.0,
|
||||
description="Factor to oversample the grid resolution (1.0 = no oversampling, 2.0 = 2x resolution).",
|
||||
)
|
||||
show_config_label: bool = Field(
|
||||
True, description="Whether to show the configuration label in the heatmap."
|
||||
)
|
||||
enforce_interpolation: bool = Field(
|
||||
False, description="Whether to use the interpolation mode even for grid scans."
|
||||
)
|
||||
lock_aspect_ratio: bool = Field(
|
||||
False, description="Whether to lock the aspect ratio of the image."
|
||||
)
|
||||
@@ -119,6 +135,12 @@ class Heatmap(ImageBase):
|
||||
"enable_simple_colorbar.setter",
|
||||
"enable_full_colorbar",
|
||||
"enable_full_colorbar.setter",
|
||||
"interpolation_method",
|
||||
"interpolation_method.setter",
|
||||
"oversampling_factor",
|
||||
"oversampling_factor.setter",
|
||||
"enforce_interpolation",
|
||||
"enforce_interpolation.setter",
|
||||
"fft",
|
||||
"fft.setter",
|
||||
"log",
|
||||
@@ -141,8 +163,19 @@ class Heatmap(ImageBase):
|
||||
|
||||
def __init__(self, parent=None, config: HeatmapConfig | None = None, **kwargs):
|
||||
if config is None:
|
||||
config = HeatmapConfig(widget_class=self.__class__.__name__)
|
||||
super().__init__(parent=parent, config=config, **kwargs)
|
||||
config = HeatmapConfig(
|
||||
widget_class=self.__class__.__name__,
|
||||
parent_id=None,
|
||||
color_map="plasma",
|
||||
color_bar=None,
|
||||
interpolation="linear",
|
||||
oversampling_factor=1.0,
|
||||
lock_aspect_ratio=False,
|
||||
x_device=None,
|
||||
y_device=None,
|
||||
z_device=None,
|
||||
)
|
||||
super().__init__(parent=parent, config=config, theme_update=True, **kwargs)
|
||||
self._image_config = config
|
||||
self.scan_id = None
|
||||
self.old_scan_id = None
|
||||
@@ -150,9 +183,16 @@ class Heatmap(ImageBase):
|
||||
self.status_message = None
|
||||
self._grid_index = None
|
||||
self.heatmap_dialog = None
|
||||
bg_color = pg.mkColor((240, 240, 240, 150))
|
||||
self.config_label = pg.LegendItem(
|
||||
labelTextColor=(0, 0, 0), offset=(-30, 1), brush=pg.mkBrush(bg_color), horSpacing=0
|
||||
)
|
||||
self.config_label.setParentItem(self.plot_item.vb)
|
||||
self.config_label.setVisible(False)
|
||||
self.reload = False
|
||||
self.bec_dispatcher.connect_slot(self.on_scan_status, MessageEndpoints.scan_status())
|
||||
self.bec_dispatcher.connect_slot(self.on_scan_progress, MessageEndpoints.scan_progress())
|
||||
self.heatmap_property_changed.connect(lambda: self.sync_signal_update.emit())
|
||||
|
||||
self.proxy_update_sync = pg.SignalProxy(
|
||||
self.sync_signal_update, rateLimit=5, slot=self.update_plot
|
||||
@@ -168,6 +208,7 @@ class Heatmap(ImageBase):
|
||||
"image_colorbar",
|
||||
"image_processing",
|
||||
"axis_popup",
|
||||
"interpolation_info",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -180,6 +221,23 @@ class Heatmap(ImageBase):
|
||||
# Widget Specific GUI interactions
|
||||
################################################################################
|
||||
|
||||
@SafeSlot(str)
|
||||
def apply_theme(self, theme: str):
|
||||
"""
|
||||
Apply the current theme to the heatmap widget.
|
||||
"""
|
||||
super().apply_theme(theme)
|
||||
if theme == "dark":
|
||||
brush = pg.mkBrush(pg.mkColor(50, 50, 50, 150))
|
||||
color = pg.mkColor(255, 255, 255)
|
||||
else:
|
||||
brush = pg.mkBrush(pg.mkColor(240, 240, 240, 150))
|
||||
color = pg.mkColor(0, 0, 0)
|
||||
if hasattr(self, "config_label"):
|
||||
self.config_label.setBrush(brush)
|
||||
self.config_label.setLabelTextColor(color)
|
||||
self.redraw_config_label()
|
||||
|
||||
@SafeSlot(popup_error=True)
|
||||
def plot(
|
||||
self,
|
||||
@@ -190,12 +248,32 @@ class Heatmap(ImageBase):
|
||||
y_entry: None | str = None,
|
||||
z_entry: None | str = None,
|
||||
color_map: str | None = "plasma",
|
||||
label: str | None = None,
|
||||
validate_bec: bool = True,
|
||||
interpolation: Literal["linear", "nearest"] | None = None,
|
||||
enforce_interpolation: bool | None = None,
|
||||
oversampling_factor: float | None = None,
|
||||
lock_aspect_ratio: bool | None = None,
|
||||
show_config_label: bool | None = None,
|
||||
reload: bool = False,
|
||||
):
|
||||
"""
|
||||
Plot the heatmap with the given x, y, and z data.
|
||||
|
||||
Args:
|
||||
x_name (str): The name of the x-axis signal.
|
||||
y_name (str): The name of the y-axis signal.
|
||||
z_name (str): The name of the z-axis signal.
|
||||
x_entry (str | None): The entry for the x-axis signal.
|
||||
y_entry (str | None): The entry for the y-axis signal.
|
||||
z_entry (str | None): The entry for the z-axis signal.
|
||||
color_map (str | None): The color map to use for the heatmap.
|
||||
validate_bec (bool): Whether to validate the entries against BEC signals.
|
||||
interpolation (Literal["linear", "nearest"] | None): The interpolation method to use.
|
||||
enforce_interpolation (bool | None): Whether to enforce interpolation even for grid scans.
|
||||
oversampling_factor (float | None): Factor to oversample the grid resolution.
|
||||
lock_aspect_ratio (bool | None): Whether to lock the aspect ratio of the image.
|
||||
show_config_label (bool | None): Whether to show the configuration label in the heatmap.
|
||||
reload (bool): Whether to reload the heatmap with new data.
|
||||
"""
|
||||
if validate_bec:
|
||||
x_entry = self.entry_validator.validate_signal(x_name, x_entry)
|
||||
@@ -207,12 +285,33 @@ class Heatmap(ImageBase):
|
||||
if x_name is None or y_name is None or z_name is None:
|
||||
raise ValueError("x, y, and z names must be provided.")
|
||||
|
||||
if interpolation is None:
|
||||
interpolation = self._image_config.interpolation
|
||||
|
||||
if oversampling_factor is None:
|
||||
oversampling_factor = self._image_config.oversampling_factor
|
||||
|
||||
if enforce_interpolation is None:
|
||||
enforce_interpolation = self._image_config.enforce_interpolation
|
||||
|
||||
if lock_aspect_ratio is None:
|
||||
lock_aspect_ratio = self._image_config.lock_aspect_ratio
|
||||
|
||||
if show_config_label is None:
|
||||
show_config_label = self._image_config.show_config_label
|
||||
|
||||
self._image_config = HeatmapConfig(
|
||||
parent_id=self.gui_id,
|
||||
x_device=HeatmapDeviceSignal(name=x_name, entry=x_entry),
|
||||
y_device=HeatmapDeviceSignal(name=y_name, entry=y_entry),
|
||||
z_device=HeatmapDeviceSignal(name=z_name, entry=z_entry),
|
||||
color_map=color_map,
|
||||
color_bar=None,
|
||||
interpolation=interpolation,
|
||||
oversampling_factor=oversampling_factor,
|
||||
enforce_interpolation=enforce_interpolation,
|
||||
lock_aspect_ratio=lock_aspect_ratio,
|
||||
show_config_label=show_config_label,
|
||||
)
|
||||
self.color_map = color_map
|
||||
self.reload = reload
|
||||
@@ -230,7 +329,6 @@ class Heatmap(ImageBase):
|
||||
self.scan_item = self.client.history[-1]
|
||||
self.scan_id = self.client.history._scan_ids[-1]
|
||||
self.old_scan_id = None
|
||||
self.update_plot()
|
||||
|
||||
def update_labels(self):
|
||||
"""
|
||||
@@ -279,6 +377,19 @@ class Heatmap(ImageBase):
|
||||
if name not in ["image_processing_fft", "image_processing_log"]:
|
||||
action().action.setVisible(False)
|
||||
|
||||
self.toolbar.add_action(
|
||||
"interpolation_info",
|
||||
MaterialIconAction(
|
||||
icon_name="info", tooltip="Show Interpolation Info", checkable=True, parent=self
|
||||
),
|
||||
)
|
||||
self.toolbar.components.get_action("interpolation_info").action.triggered.connect(
|
||||
self.toggle_interpolation_info
|
||||
)
|
||||
self.toolbar.components.get_action("interpolation_info").action.setChecked(
|
||||
self._image_config.show_config_label
|
||||
)
|
||||
|
||||
def show_heatmap_settings(self):
|
||||
"""
|
||||
Show the heatmap settings dialog.
|
||||
@@ -289,7 +400,7 @@ class Heatmap(ImageBase):
|
||||
self.heatmap_dialog = SettingsDialog(
|
||||
self, settings_widget=heatmap_settings, window_title="Heatmap Settings", modal=False
|
||||
)
|
||||
self.heatmap_dialog.resize(620, 200)
|
||||
self.heatmap_dialog.resize(700, 350)
|
||||
# When the dialog is closed, update the toolbar icon and clear the reference
|
||||
self.heatmap_dialog.finished.connect(self._heatmap_dialog_closed)
|
||||
self.heatmap_dialog.show()
|
||||
@@ -300,6 +411,16 @@ class Heatmap(ImageBase):
|
||||
self.heatmap_dialog.activateWindow()
|
||||
heatmap_settings_action.setChecked(True) # keep it toggled
|
||||
|
||||
def toggle_interpolation_info(self):
|
||||
"""
|
||||
Toggle the visibility of the interpolation info label.
|
||||
"""
|
||||
self._image_config.show_config_label = not self._image_config.show_config_label
|
||||
self.toolbar.components.get_action("interpolation_info").action.setChecked(
|
||||
self._image_config.show_config_label
|
||||
)
|
||||
self.redraw_config_label()
|
||||
|
||||
def _heatmap_dialog_closed(self):
|
||||
"""
|
||||
Slot for when the heatmap settings dialog is closed.
|
||||
@@ -397,6 +518,7 @@ class Heatmap(ImageBase):
|
||||
scan_id = metadata["scan_id"]
|
||||
scan_name = metadata["scan_name"]
|
||||
scan_type = metadata["scan_type"]
|
||||
scan_number = metadata["scan_number"]
|
||||
request_inputs = metadata["request_inputs"]
|
||||
if "arg_bundle" in request_inputs and isinstance(request_inputs["arg_bundle"], str):
|
||||
# Convert the arg_bundle from a JSON string to a dictionary
|
||||
@@ -408,6 +530,7 @@ class Heatmap(ImageBase):
|
||||
status=status,
|
||||
scan_id=scan_id,
|
||||
scan_name=scan_name,
|
||||
scan_number=scan_number,
|
||||
scan_type=scan_type,
|
||||
request_inputs=request_inputs,
|
||||
info={"positions": positions},
|
||||
@@ -420,6 +543,9 @@ class Heatmap(ImageBase):
|
||||
return
|
||||
self.status_message = scan_msg
|
||||
|
||||
if self._image_config.show_config_label:
|
||||
self.redraw_config_label()
|
||||
|
||||
img, transform = self.get_image_data(x_data=x_data, y_data=y_data, z_data=z_data)
|
||||
if img is None:
|
||||
logger.warning("Image data is None; skipping update.")
|
||||
@@ -434,6 +560,25 @@ class Heatmap(ImageBase):
|
||||
if self.crosshair is not None:
|
||||
self.crosshair.update_markers_on_image_change()
|
||||
|
||||
def redraw_config_label(self):
|
||||
scan_msg = self.status_message
|
||||
if scan_msg is None:
|
||||
return
|
||||
if not self._image_config.show_config_label:
|
||||
self.config_label.setVisible(False)
|
||||
return
|
||||
self.config_label.setVisible(True)
|
||||
self.config_label.clear()
|
||||
self.config_label.addItem(self.plot_item, f"Scan: {scan_msg.scan_number}")
|
||||
self.config_label.addItem(self.plot_item, f"Scan Name: {scan_msg.scan_name}")
|
||||
if scan_msg.scan_name != "grid_scan" or self._image_config.enforce_interpolation:
|
||||
self.config_label.addItem(
|
||||
self.plot_item, f"Interpolation: {self._image_config.interpolation}"
|
||||
)
|
||||
self.config_label.addItem(
|
||||
self.plot_item, f"Oversampling: {self._image_config.oversampling_factor}x"
|
||||
)
|
||||
|
||||
def get_image_data(
|
||||
self,
|
||||
x_data: list[float] | None = None,
|
||||
@@ -458,7 +603,7 @@ class Heatmap(ImageBase):
|
||||
logger.warning("x, y, or z data is None; skipping update.")
|
||||
return None, None
|
||||
|
||||
if msg.scan_name == "grid_scan":
|
||||
if msg.scan_name == "grid_scan" and not self._image_config.enforce_interpolation:
|
||||
# We only support the grid scan mode if both scanning motors
|
||||
# are configured in the heatmap config.
|
||||
device_x = self._image_config.x_device.entry
|
||||
@@ -571,7 +716,16 @@ class Heatmap(ImageBase):
|
||||
grid_x, grid_y, transform = self.get_image_grid(xy_data)
|
||||
|
||||
# Interpolate the z data onto the grid
|
||||
interp = LinearNDInterpolator(xy_data, z_data)
|
||||
if self._image_config.interpolation == "linear":
|
||||
interp = LinearNDInterpolator(xy_data, z_data)
|
||||
elif self._image_config.interpolation == "nearest":
|
||||
interp = NearestNDInterpolator(xy_data, z_data)
|
||||
elif self._image_config.interpolation == "clough":
|
||||
interp = CloughTocher2DInterpolator(xy_data, z_data)
|
||||
else:
|
||||
raise ValueError(
|
||||
"Interpolation method must be either 'linear', 'nearest', or 'clough'."
|
||||
)
|
||||
grid_z = interp(grid_x, grid_y)
|
||||
|
||||
return grid_z, transform
|
||||
@@ -587,20 +741,24 @@ class Heatmap(ImageBase):
|
||||
Returns:
|
||||
tuple[np.ndarray, np.ndarray, QTransform]: The grid x and y coordinates and the QTransform.
|
||||
"""
|
||||
base_width, base_height = self.estimate_image_resolution(positions)
|
||||
|
||||
width, height = self.estimate_image_resolution(positions)
|
||||
# Apply oversampling factor
|
||||
factor = self._image_config.oversampling_factor
|
||||
|
||||
# Create a grid of points for interpolation
|
||||
# Apply oversampling
|
||||
width = int(base_width * factor)
|
||||
height = int(base_height * factor)
|
||||
|
||||
# Create grid
|
||||
grid_x, grid_y = np.mgrid[
|
||||
min(positions[:, 0]) : max(positions[:, 0]) : width * 1j,
|
||||
min(positions[:, 1]) : max(positions[:, 1]) : height * 1j,
|
||||
]
|
||||
|
||||
# Calculate the QTransform to put (0,0) at the axis origin
|
||||
x_min = min(positions[:, 0])
|
||||
y_min = min(positions[:, 1])
|
||||
x_max = max(positions[:, 0])
|
||||
y_max = max(positions[:, 1])
|
||||
# Calculate transform
|
||||
x_min, x_max = min(positions[:, 0]), max(positions[:, 0])
|
||||
y_min, y_max = min(positions[:, 1]), max(positions[:, 1])
|
||||
x_range = x_max - x_min
|
||||
y_range = y_max - y_min
|
||||
x_scale = x_range / width
|
||||
@@ -670,7 +828,7 @@ class Heatmap(ImageBase):
|
||||
# Optionally fetch the latest from history if nothing is set
|
||||
# self.update_with_scan_history(-1)
|
||||
if self.scan_item is None:
|
||||
logger.info("No scan executed so far; skipping device curves categorisation.")
|
||||
logger.info("No scan executed so far; skipping update.")
|
||||
return "none", "none"
|
||||
|
||||
if hasattr(self.scan_item, "live_data"):
|
||||
@@ -688,6 +846,62 @@ class Heatmap(ImageBase):
|
||||
self.crosshair.reset()
|
||||
super().reset()
|
||||
|
||||
@SafeProperty(str)
|
||||
def interpolation_method(self) -> str:
|
||||
"""
|
||||
The interpolation method used for the heatmap.
|
||||
"""
|
||||
return self._image_config.interpolation
|
||||
|
||||
@interpolation_method.setter
|
||||
def interpolation_method(self, value: str):
|
||||
"""
|
||||
Set the interpolation method for the heatmap.
|
||||
Args:
|
||||
value(str): The interpolation method, either 'linear' or 'nearest'.
|
||||
"""
|
||||
if value not in ["linear", "nearest"]:
|
||||
raise ValueError("Interpolation method must be either 'linear' or 'nearest'.")
|
||||
self._image_config.interpolation = value
|
||||
self.heatmap_property_changed.emit()
|
||||
|
||||
@SafeProperty(float)
|
||||
def oversampling_factor(self) -> float:
|
||||
"""
|
||||
The oversampling factor for grid resolution.
|
||||
"""
|
||||
return self._image_config.oversampling_factor
|
||||
|
||||
@oversampling_factor.setter
|
||||
def oversampling_factor(self, value: float):
|
||||
"""
|
||||
Set the oversampling factor for grid resolution.
|
||||
Args:
|
||||
value(float): The oversampling factor (1.0 = no oversampling, 2.0 = 2x resolution).
|
||||
"""
|
||||
if value <= 0:
|
||||
raise ValueError("Oversampling factor must be greater than 0.")
|
||||
self._image_config.oversampling_factor = value
|
||||
self.heatmap_property_changed.emit()
|
||||
|
||||
@SafeProperty(bool)
|
||||
def enforce_interpolation(self) -> bool:
|
||||
"""
|
||||
Whether to enforce interpolation even for grid scans.
|
||||
"""
|
||||
return self._image_config.enforce_interpolation
|
||||
|
||||
@enforce_interpolation.setter
|
||||
def enforce_interpolation(self, value: bool):
|
||||
"""
|
||||
Set whether to enforce interpolation even for grid scans.
|
||||
|
||||
Args:
|
||||
value(bool): Whether to enforce interpolation.
|
||||
"""
|
||||
self._image_config.enforce_interpolation = value
|
||||
self.heatmap_property_changed.emit()
|
||||
|
||||
################################################################################
|
||||
# Post Processing
|
||||
################################################################################
|
||||
@@ -768,6 +982,6 @@ if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
heatmap = Heatmap()
|
||||
heatmap.plot(x_name="samx", y_name="samy", z_name="bpm4i")
|
||||
heatmap.plot(x_name="samx", y_name="samy", z_name="bpm4i", oversampling_factor=5.0)
|
||||
heatmap.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
@@ -27,7 +27,7 @@ class HeatmapPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
||||
return DOM_XML
|
||||
|
||||
def group(self):
|
||||
return ""
|
||||
return "Plot Widgets"
|
||||
|
||||
def icon(self):
|
||||
return designer_material_icon(Heatmap.ICON_NAME)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from qtpy.QtWidgets import QFrame, QScrollArea, QVBoxLayout
|
||||
|
||||
@@ -6,6 +9,11 @@ from bec_widgets.utils import UILoader
|
||||
from bec_widgets.utils.error_popups import SafeSlot
|
||||
from bec_widgets.utils.settings_dialog import SettingWidget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_widgets.widgets.control.device_input.signal_combobox.signal_combobox import (
|
||||
SignalComboBox,
|
||||
)
|
||||
|
||||
|
||||
class HeatmapSettings(SettingWidget):
|
||||
def __init__(self, parent=None, target_widget=None, popup=False, *args, **kwargs):
|
||||
@@ -46,6 +54,8 @@ class HeatmapSettings(SettingWidget):
|
||||
if popup is False:
|
||||
self.ui.button_apply.clicked.connect(self.accept_changes)
|
||||
|
||||
self.ui.x_name.setFocus()
|
||||
|
||||
@SafeSlot()
|
||||
def fetch_all_properties(self):
|
||||
"""
|
||||
@@ -85,31 +95,66 @@ class HeatmapSettings(SettingWidget):
|
||||
if hasattr(self.ui, "x_name"):
|
||||
self.ui.x_name.set_device(x_name)
|
||||
if hasattr(self.ui, "x_entry") and x_entry is not None:
|
||||
self.ui.x_entry.setText(x_entry)
|
||||
self.ui.x_entry.set_to_obj_name(x_entry)
|
||||
|
||||
if hasattr(self.ui, "y_name"):
|
||||
self.ui.y_name.set_device(y_name)
|
||||
if hasattr(self.ui, "y_entry") and y_entry is not None:
|
||||
self.ui.y_entry.setText(y_entry)
|
||||
self.ui.y_entry.set_to_obj_name(y_entry)
|
||||
|
||||
if hasattr(self.ui, "z_name"):
|
||||
self.ui.z_name.set_device(z_name)
|
||||
if hasattr(self.ui, "z_entry") and z_entry is not None:
|
||||
self.ui.z_entry.setText(z_entry)
|
||||
self.ui.z_entry.set_to_obj_name(z_entry)
|
||||
|
||||
if hasattr(self.ui, "interpolation"):
|
||||
self.ui.interpolation.setCurrentText(
|
||||
getattr(self.target_widget._image_config, "interpolation", "linear")
|
||||
)
|
||||
if hasattr(self.ui, "oversampling_factor"):
|
||||
self.ui.oversampling_factor.setValue(
|
||||
getattr(self.target_widget._image_config, "oversampling_factor", 1.0)
|
||||
)
|
||||
if hasattr(self.ui, "enforce_interpolation"):
|
||||
self.ui.enforce_interpolation.setChecked(
|
||||
getattr(self.target_widget._image_config, "enforce_interpolation", False)
|
||||
)
|
||||
|
||||
def _get_signal_name(self, signal: SignalComboBox) -> str:
|
||||
"""
|
||||
Get the signal name from the signal combobox.
|
||||
Args:
|
||||
signal (SignalComboBox): The signal combobox to get the name from.
|
||||
Returns:
|
||||
str: The signal name.
|
||||
"""
|
||||
device_entry = signal.currentText()
|
||||
index = signal.findText(device_entry)
|
||||
if index == -1:
|
||||
return device_entry
|
||||
|
||||
device_entry_info = signal.itemData(index)
|
||||
if device_entry_info:
|
||||
device_entry = device_entry_info.get("obj_name", device_entry)
|
||||
|
||||
return device_entry if device_entry else ""
|
||||
|
||||
@SafeSlot()
|
||||
def accept_changes(self):
|
||||
"""
|
||||
Apply all properties from the settings widget to the target widget.
|
||||
"""
|
||||
x_name = self.ui.x_name.text()
|
||||
x_entry = self.ui.x_entry.text()
|
||||
y_name = self.ui.y_name.text()
|
||||
y_entry = self.ui.y_entry.text()
|
||||
z_name = self.ui.z_name.text()
|
||||
z_entry = self.ui.z_entry.text()
|
||||
x_name = self.ui.x_name.currentText()
|
||||
x_entry = self._get_signal_name(self.ui.x_entry)
|
||||
y_name = self.ui.y_name.currentText()
|
||||
y_entry = self._get_signal_name(self.ui.y_entry)
|
||||
z_name = self.ui.z_name.currentText()
|
||||
z_entry = self._get_signal_name(self.ui.z_entry)
|
||||
validate_bec = self.ui.validate_bec.checked
|
||||
color_map = self.ui.color_map.colormap
|
||||
interpolation = self.ui.interpolation.currentText()
|
||||
oversampling_factor = self.ui.oversampling_factor.value()
|
||||
enforce_interpolation = self.ui.enforce_interpolation.isChecked()
|
||||
|
||||
self.target_widget.plot(
|
||||
x_name=x_name,
|
||||
@@ -120,6 +165,9 @@ class HeatmapSettings(SettingWidget):
|
||||
z_entry=z_entry,
|
||||
color_map=color_map,
|
||||
validate_bec=validate_bec,
|
||||
interpolation=interpolation,
|
||||
oversampling_factor=oversampling_factor,
|
||||
enforce_interpolation=enforce_interpolation,
|
||||
reload=True,
|
||||
)
|
||||
|
||||
@@ -136,3 +184,5 @@ class HeatmapSettings(SettingWidget):
|
||||
self.ui.z_name.deleteLater()
|
||||
self.ui.z_entry.close()
|
||||
self.ui.z_entry.deleteLater()
|
||||
self.ui.interpolation.close()
|
||||
self.ui.interpolation.deleteLater()
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>604</width>
|
||||
<height>166</height>
|
||||
<width>826</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -17,20 +17,162 @@
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Validate BEC</string>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Interpolation</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="1" column="2" alignment="Qt::AlignmentFlag::AlignRight">
|
||||
<widget class="ToggleSwitch" name="enforce_interpolation">
|
||||
<property name="toolTip">
|
||||
<string>Use the interpolation mode even for grid scans</string>
|
||||
</property>
|
||||
<property name="checked" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="toolTip">
|
||||
<string>Use the interpolation mode even for grid scans</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enforce Interpolation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<widget class="QDoubleSpinBox" name="oversampling_factor">
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>10.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QComboBox" name="interpolation">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>26</height>
|
||||
</size>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>linear</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>nearest</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>clough</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Oversampling</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Interpolation Method</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ToggleSwitch" name="validate_bec"/>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="BECColorMapWidget" name="color_map"/>
|
||||
<item alignment="Qt::AlignmentFlag::AlignRight">
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>General</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Validate BEC</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3" alignment="Qt::AlignmentFlag::AlignRight">
|
||||
<widget class="ToggleSwitch" name="validate_bec"/>
|
||||
</item>
|
||||
<item row="3" column="3" alignment="Qt::AlignmentFlag::AlignRight">
|
||||
<widget class="BECColorMapWidget" name="color_map">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Colormap</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
@@ -46,9 +188,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="DeviceLineEdit" name="x_name"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
@@ -56,8 +195,22 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="DeviceComboBox" name="x_name">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="set_first_element_as_empty" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="x_entry"/>
|
||||
<widget class="SignalComboBox" name="x_entry">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@@ -75,9 +228,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="DeviceLineEdit" name="y_name"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
@@ -85,8 +235,22 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="DeviceComboBox" name="y_name">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="set_first_element_as_empty" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="y_entry"/>
|
||||
<widget class="SignalComboBox" name="y_entry">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@@ -111,11 +275,22 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="z_entry"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="DeviceLineEdit" name="z_name"/>
|
||||
<widget class="DeviceComboBox" name="z_name">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="set_first_element_as_empty" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SignalComboBox" name="z_entry">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@@ -126,9 +301,14 @@
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>DeviceLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>device_line_edit</header>
|
||||
<class>DeviceComboBox</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>device_combobox</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>SignalComboBox</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>signal_combo_box</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ToggleSwitch</class>
|
||||
@@ -143,59 +323,109 @@
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>x_name</tabstop>
|
||||
<tabstop>x_entry</tabstop>
|
||||
<tabstop>y_name</tabstop>
|
||||
<tabstop>y_entry</tabstop>
|
||||
<tabstop>z_name</tabstop>
|
||||
<tabstop>x_entry</tabstop>
|
||||
<tabstop>y_entry</tabstop>
|
||||
<tabstop>z_entry</tabstop>
|
||||
<tabstop>interpolation</tabstop>
|
||||
<tabstop>oversampling_factor</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>x_name</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<signal>device_reset()</signal>
|
||||
<receiver>x_entry</receiver>
|
||||
<slot>clear()</slot>
|
||||
<slot>reset_selection()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>134</x>
|
||||
<y>95</y>
|
||||
<x>254</x>
|
||||
<y>226</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>138</x>
|
||||
<y>128</y>
|
||||
<x>254</x>
|
||||
<y>267</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>x_name</sender>
|
||||
<signal>currentTextChanged(QString)</signal>
|
||||
<receiver>x_entry</receiver>
|
||||
<slot>set_device(QString)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>254</x>
|
||||
<y>226</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>254</x>
|
||||
<y>267</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>y_name</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<signal>device_reset()</signal>
|
||||
<receiver>y_entry</receiver>
|
||||
<slot>clear()</slot>
|
||||
<slot>reset_selection()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>351</x>
|
||||
<y>91</y>
|
||||
<x>526</x>
|
||||
<y>226</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>349</x>
|
||||
<y>121</y>
|
||||
<x>526</x>
|
||||
<y>267</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>y_name</sender>
|
||||
<signal>currentTextChanged(QString)</signal>
|
||||
<receiver>y_entry</receiver>
|
||||
<slot>set_device(QString)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>526</x>
|
||||
<y>226</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>526</x>
|
||||
<y>267</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>z_name</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<signal>device_reset()</signal>
|
||||
<receiver>z_entry</receiver>
|
||||
<slot>clear()</slot>
|
||||
<slot>reset_selection()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>520</x>
|
||||
<y>98</y>
|
||||
<x>798</x>
|
||||
<y>226</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>522</x>
|
||||
<y>127</y>
|
||||
<x>798</x>
|
||||
<y>267</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>z_name</sender>
|
||||
<signal>currentTextChanged(QString)</signal>
|
||||
<receiver>z_entry</receiver>
|
||||
<slot>set_device(QString)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>798</x>
|
||||
<y>226</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>798</x>
|
||||
<y>267</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
|
||||
@@ -1,204 +1,374 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>233</width>
|
||||
<height>427</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>427</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_apply">
|
||||
<property name="text">
|
||||
<string>Apply</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="BECColorMapWidget" name="color_map"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Validate BEC</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ToggleSwitch" name="validate_bec"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>X Device</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="DeviceLineEdit" name="x_name"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Signal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="x_entry"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Y Device</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="DeviceLineEdit" name="y_name"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Signal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="y_entry"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Z Device</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="DeviceLineEdit" name="z_name"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Signal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="z_entry"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>305</width>
|
||||
<height>629</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>629</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_apply">
|
||||
<property name="text">
|
||||
<string>Apply</string>
|
||||
</property>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>DeviceLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>device_line_edit</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ToggleSwitch</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>toggle_switch</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>BECColorMapWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>bec_color_map_widget</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>x_name</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>x_entry</receiver>
|
||||
<slot>clear()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>156</x>
|
||||
<y>123</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>158</x>
|
||||
<y>157</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>y_name</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>y_entry</receiver>
|
||||
<slot>clear()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>116</x>
|
||||
<y>229</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>116</x>
|
||||
<y>251</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>z_name</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>z_entry</receiver>
|
||||
<slot>clear()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>110</x>
|
||||
<y>326</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>110</x>
|
||||
<y>352</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="BECColorMapWidget" name="color_map"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Validate BEC</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ToggleSwitch" name="validate_bec">
|
||||
<property name="checked" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>X Device</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Signal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="DeviceComboBox" name="x_name">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="set_first_element_as_empty" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SignalComboBox" name="x_entry">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Y Device</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Signal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="DeviceComboBox" name="y_name">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="set_first_element_as_empty" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SignalComboBox" name="y_entry">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Z Device</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="DeviceComboBox" name="z_name">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="set_first_element_as_empty" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="SignalComboBox" name="z_entry">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Signal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Interpolation</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Interpolation Method</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="interpolation">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>linear</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>nearest</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Enforce Interpolation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" alignment="Qt::AlignmentFlag::AlignRight">
|
||||
<widget class="ToggleSwitch" name="enforce_interpolation">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Oversampling</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDoubleSpinBox" name="oversampling_factor">
|
||||
<property name="minimum">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>10.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>DeviceComboBox</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>device_combobox</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>SignalComboBox</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>signal_combo_box</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ToggleSwitch</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>toggle_switch</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>BECColorMapWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>bec_color_map_widget</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>x_name</tabstop>
|
||||
<tabstop>y_name</tabstop>
|
||||
<tabstop>z_name</tabstop>
|
||||
<tabstop>button_apply</tabstop>
|
||||
<tabstop>x_entry</tabstop>
|
||||
<tabstop>y_entry</tabstop>
|
||||
<tabstop>z_entry</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>x_name</sender>
|
||||
<signal>device_reset()</signal>
|
||||
<receiver>x_entry</receiver>
|
||||
<slot>reset_selection()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>113</x>
|
||||
<y>178</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>110</x>
|
||||
<y>183</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>x_name</sender>
|
||||
<signal>currentTextChanged(QString)</signal>
|
||||
<receiver>x_entry</receiver>
|
||||
<slot>set_device(QString)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>160</x>
|
||||
<y>178</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>159</x>
|
||||
<y>188</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>y_name</sender>
|
||||
<signal>device_reset()</signal>
|
||||
<receiver>y_entry</receiver>
|
||||
<slot>reset_selection()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>92</x>
|
||||
<y>278</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>92</x>
|
||||
<y>287</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>y_name</sender>
|
||||
<signal>currentTextChanged(QString)</signal>
|
||||
<receiver>y_entry</receiver>
|
||||
<slot>set_device(QString)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>136</x>
|
||||
<y>277</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>135</x>
|
||||
<y>290</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>z_name</sender>
|
||||
<signal>device_reset()</signal>
|
||||
<receiver>z_entry</receiver>
|
||||
<slot>reset_selection()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>106</x>
|
||||
<y>376</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>112</x>
|
||||
<y>397</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>z_name</sender>
|
||||
<signal>currentTextChanged(QString)</signal>
|
||||
<receiver>z_entry</receiver>
|
||||
<slot>set_device(QString)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>164</x>
|
||||
<y>376</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>168</x>
|
||||
<y>389</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
|
||||
BIN
docs/assets/widget_screenshots/heatmap_widget.png
Normal file
BIN
docs/assets/widget_screenshots/heatmap_widget.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
BIN
docs/user/widgets/heatmap/heatmap_fermat_scan.gif
Normal file
BIN
docs/user/widgets/heatmap/heatmap_fermat_scan.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 MiB |
BIN
docs/user/widgets/heatmap/heatmap_grid_scan.gif
Normal file
BIN
docs/user/widgets/heatmap/heatmap_grid_scan.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 825 KiB |
106
docs/user/widgets/heatmap/heatmap_widget.md
Normal file
106
docs/user/widgets/heatmap/heatmap_widget.md
Normal file
@@ -0,0 +1,106 @@
|
||||
(user.widgets.heatmap_widget)=
|
||||
|
||||
# Heatmap widget
|
||||
|
||||
````{tab} Overview
|
||||
|
||||
The Heatmap widget is a specialized plotting tool designed for visualizing 2D grid data with color mapping for the z-axis. It excels at displaying data from grid scans or arbitrary step scans, automatically interpolating scattered data points into a coherent 2D image. Directly integrated with the `BEC` framework, it can display live data streams from scanning experiments within the current `BEC` session.
|
||||
|
||||
## Key Features:
|
||||
- **Flexible Integration**: The widget can be integrated into [`BECDockArea`](user.widgets.bec_dock_area), or used as an individual component in your application through `BEC Designer`.
|
||||
- **Live Grid Scan Visualization**: Real-time plotting of grid scan data with automatic positioning and color mapping based on scan parameters.
|
||||
- **Dual Scan Support**: Handles both structured grid scans (with pre-allocated grids) and unstructured step scans (with interpolation).
|
||||
- **Intelligent Data Interpolation**: For arbitrary step scans, the widget automatically interpolates scattered (x, y, z) data points into a smooth 2D heatmap using various interpolation methods.
|
||||
- **Oversampling**: Supports oversampling to enhance the appearance of the heatmap, allowing for smoother transitions and better visual representation of data. Especially useful the for nearest-neighbor interpolation.
|
||||
- **Customizable Color Maps**: Wide variety of color maps available for data visualization, with support for both simple and full color bars.
|
||||
- **Real-time Image Processing**: Apply real-time processing techniques such as FFT and logarithmic scaling to enhance data visualization.
|
||||
- **Interactive Controls**: Comprehensive toolbar with settings for heatmap configuration, crosshair tools, mouse interaction, and data export capabilities.
|
||||
|
||||
|
||||
```{figure} ./heatmap_grid_scan.gif
|
||||
:width: 60%
|
||||
|
||||
Real-time heatmap visualization of a 2D grid scan showing motor positions and detector intensity
|
||||
```
|
||||
|
||||
```{figure} ./heatmap_fermat_scan.gif
|
||||
:width: 80%
|
||||
|
||||
Real-time heatmap visualization of an (not path-optimized) scan following Fermat's spiral pattern. On the left, the heatmap widget is shown with the oversampling option set to 10 and the interpolation method set to nearest neighbor. On the right, the scatter waveform widget is shown with the same data.
|
||||
```
|
||||
|
||||
|
||||
````
|
||||
|
||||
````{tab} Examples - CLI
|
||||
|
||||
`HeatmapWidget` can be embedded in [`BECDockArea`](user.widgets.bec_dock_area), or used as an individual component in your application through `BEC Designer`. The command-line API is the same for all cases.
|
||||
|
||||
## Example 1 - Visualizing Grid Scan Data
|
||||
|
||||
In this example, we demonstrate how to add a `HeatmapWidget` to visualize live data from a 2D grid scan with motor positions and detector readout.
|
||||
|
||||
```python
|
||||
# Add a new dock with HeatmapWidget
|
||||
dock_area = gui.new()
|
||||
heatmap_widget = dock_area.new().new(gui.available_widgets.Heatmap)
|
||||
|
||||
# Plot a heatmap with x and y motor positions and z detector signal
|
||||
heatmap_widget.plot(
|
||||
x_name='samx', # X-axis motor
|
||||
y_name='samy', # Y-axis motor
|
||||
z_name='bpm4i', # Z-axis detector signal
|
||||
color_map='plasma'
|
||||
)
|
||||
heatmap_widget.title = "Grid Scan - Sample Position vs BPM Intensity"
|
||||
```
|
||||
|
||||
## Example 2 - Step Scan with Custom Entries
|
||||
|
||||
This example shows how to visualize data from an arbitrary step scan by specifying custom data entries for each axis.
|
||||
|
||||
```python
|
||||
# Add a new dock with HeatmapWidget
|
||||
dock_area = gui.new()
|
||||
heatmap_widget = dock_area.new().new(gui.available_widgets.Heatmap)
|
||||
|
||||
# Plot heatmap with specific data entries
|
||||
heatmap_widget.plot(
|
||||
x_name='motor1',
|
||||
y_name='motor2',
|
||||
z_name='detector1',
|
||||
x_entry='RBV', # Use readback value for x
|
||||
y_entry='RBV', # Use readback value for y
|
||||
z_entry='value', # Use main value for z
|
||||
color_map='viridis',
|
||||
reload=True # Force reload of data
|
||||
)
|
||||
```
|
||||
|
||||
## Example 3 - Real-time Processing and Customization
|
||||
|
||||
The `Heatmap` widget provides real-time processing capabilities and extensive customization options for enhanced data visualization.
|
||||
|
||||
```python
|
||||
# Configure heatmap appearance and processing
|
||||
heatmap_widget.color_map = 'plasma'
|
||||
heatmap_widget.lock_aspect_ratio = True
|
||||
|
||||
# Apply real-time processing
|
||||
heatmap_widget.fft = True # Apply FFT to the data
|
||||
heatmap_widget.log = True # Use logarithmic scaling
|
||||
|
||||
# Configure color bar and range
|
||||
heatmap_widget.enable_full_colorbar = True
|
||||
heatmap_widget.v_min = 0
|
||||
heatmap_widget.v_max = 1000
|
||||
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
````{tab} API
|
||||
```{eval-rst}
|
||||
.. include:: /api_reference/_autosummary/bec_widgets.widgets.plots.heatmap.heatmap.Heatmap.rst
|
||||
```
|
||||
````
|
||||
@@ -61,6 +61,14 @@ Display a 1D waveforms with a third device on the z-axis.
|
||||
Display signal from 2D detector.
|
||||
```
|
||||
|
||||
```{grid-item-card} Heatmap Widget
|
||||
:link: user.widgets.heatmap_widget
|
||||
:link-type: ref
|
||||
:img-top: /assets/widget_screenshots/heatmap_widget.png
|
||||
|
||||
Display 2D grid data with color mapping.
|
||||
```
|
||||
|
||||
```{grid-item-card} Motor Map Widget
|
||||
:link: user.widgets.motor_map
|
||||
:link-type: ref
|
||||
@@ -275,6 +283,7 @@ waveform/waveform_widget.md
|
||||
scatter_waveform/scatter_waveform.md
|
||||
multi_waveform/multi_waveform.md
|
||||
image/image_widget.md
|
||||
heatmap/heatmap_widget.md
|
||||
motor_map/motor_map.md
|
||||
scan_control/scan_control.md
|
||||
progress_bar/ring_progress_bar.md
|
||||
|
||||
@@ -3,6 +3,7 @@ from unittest import mock
|
||||
import numpy as np
|
||||
import pytest
|
||||
from bec_lib import messages
|
||||
from bec_lib.scan_history import ScanHistory
|
||||
|
||||
from bec_widgets.widgets.plots.heatmap.heatmap import Heatmap, HeatmapConfig, HeatmapDeviceSignal
|
||||
|
||||
@@ -323,7 +324,43 @@ def test_heatmap_settings_popup_show_settings(heatmap_widget, qtbot):
|
||||
|
||||
# Check that the ui elements are correctly initialized
|
||||
assert dialog.widget.ui.color_map.colormap == heatmap_widget.color_map
|
||||
assert dialog.widget.ui.x_name.text() == heatmap_widget._image_config.x_device.name
|
||||
assert dialog.widget.ui.x_name.currentText() == heatmap_widget._image_config.x_device.name
|
||||
|
||||
dialog.reject()
|
||||
qtbot.waitUntil(lambda: heatmap_widget.heatmap_dialog is None)
|
||||
|
||||
|
||||
def test_heatmap_widget_reset(heatmap_widget):
|
||||
"""
|
||||
Test that the reset method clears the plot.
|
||||
"""
|
||||
heatmap_widget.scan_item = create_dummy_scan_item()
|
||||
heatmap_widget.plot(x_name="samx", y_name="samy", z_name="bpm4i")
|
||||
|
||||
heatmap_widget.reset()
|
||||
assert heatmap_widget._grid_index is None
|
||||
assert heatmap_widget.main_image.raw_data is None
|
||||
|
||||
|
||||
def test_heatmap_widget_update_plot_with_scan_history(heatmap_widget, grid_scan_history_msg, qtbot):
|
||||
"""
|
||||
Test that the update_plot method updates the plot with scan history.
|
||||
"""
|
||||
heatmap_widget.client.history = ScanHistory(heatmap_widget.client, False)
|
||||
heatmap_widget.client.history._scan_data[grid_scan_history_msg.scan_id] = grid_scan_history_msg
|
||||
heatmap_widget.client.history._scan_ids.append(grid_scan_history_msg.scan_id)
|
||||
heatmap_widget.client.queue.scan_storage.current_scan = None
|
||||
heatmap_widget.plot(
|
||||
x_name="samx",
|
||||
y_name="samy",
|
||||
z_name="bpm4i",
|
||||
x_entry="samx",
|
||||
y_entry="samy",
|
||||
z_entry="bpm4i",
|
||||
)
|
||||
qtbot.waitUntil(lambda: heatmap_widget.main_image.raw_data is not None)
|
||||
qtbot.waitUntil(lambda: heatmap_widget.main_image.raw_data.shape == (10, 10))
|
||||
|
||||
heatmap_widget.enforce_interpolation = True
|
||||
heatmap_widget.oversampling_factor = 2.0
|
||||
qtbot.waitUntil(lambda: heatmap_widget.main_image.raw_data.shape == (20, 20))
|
||||
|
||||
Reference in New Issue
Block a user