0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 11:41:49 +02:00

fix(examples): outdated examples removed (mca_plot.py, stream_plot.py, motor_example.py)

This commit is contained in:
2024-05-29 20:45:21 +02:00
parent 855be3551a
commit ddc9510c2b
12 changed files with 20 additions and 2253 deletions

View File

@ -2,18 +2,16 @@ import os
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
from pyqtgraph.Qt import QtWidgets import qdarktheme
from qtconsole.inprocess import QtInProcessKernelManager from qtconsole.inprocess import QtInProcessKernelManager
from qtconsole.rich_jupyter_widget import RichJupyterWidget from qtconsole.rich_jupyter_widget import RichJupyterWidget
from qtpy.QtCore import QSize from qtpy.QtCore import QSize
from qtpy.QtGui import QIcon from qtpy.QtGui import QIcon
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
from bec_widgets.cli.rpc_register import RPCRegister
from bec_widgets.utils import BECDispatcher, UILoader from bec_widgets.utils import BECDispatcher, UILoader
from bec_widgets.widgets import BECFigure from bec_widgets.widgets import BECFigure
from bec_widgets.widgets.dock.dock_area import BECDockArea from bec_widgets.widgets.dock.dock_area import BECDockArea
from bec_widgets.widgets.spiral_progress_bar.spiral_progress_bar import SpiralProgressBar
class JupyterConsoleWidget(RichJupyterWidget): # pragma: no cover: class JupyterConsoleWidget(RichJupyterWidget): # pragma: no cover:
@ -26,7 +24,6 @@ class JupyterConsoleWidget(RichJupyterWidget): # pragma: no cover:
self.kernel_client.start_channels() self.kernel_client.start_channels()
self.kernel_manager.kernel.shell.push({"np": np, "pg": pg}) self.kernel_manager.kernel.shell.push({"np": np, "pg": pg})
# self.set_console_font_size(70)
def shutdown_kernel(self): def shutdown_kernel(self):
self.kernel_client.stop_channels() self.kernel_client.stop_channels()
@ -46,27 +43,22 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
self.ui.splitter.setSizes([200, 100]) self.ui.splitter.setSizes([200, 100])
self.safe_close = False self.safe_close = False
# self.figure.clean_signal.connect(self.confirm_close)
self.register = RPCRegister()
self.register.add_rpc(self.figure)
# console push # console push
self.console.kernel_manager.kernel.shell.push( self.console.kernel_manager.kernel.shell.push(
{ {
"fig": self.figure, "fig": self.figure,
"register": self.register,
"dock": self.dock, "dock": self.dock,
"w1": self.w1, "w1": self.w1,
"w2": self.w2, "w2": self.w2,
"w3": self.w3, "w3": self.w3,
"d0": self.d0,
"d1": self.d1, "d1": self.d1,
"d2": self.d2, "d2": self.d2,
"d3": self.d3, "fig0": self.fig0,
"fig1": self.fig1,
"fig2": self.fig2,
"bar": self.bar, "bar": self.bar,
"b2a": self.button_2_a,
"b2b": self.button_2_b,
"b2c": self.button_2_c,
"bec": self.figure.client, "bec": self.figure.client,
"scans": self.figure.client.scans, "scans": self.figure.client.scans,
"dev": self.figure.client.device_manager.devices, "dev": self.figure.client.device_manager.devices,
@ -111,25 +103,22 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
self.c1 = self.w1.get_config() self.c1 = self.w1.get_config()
def _init_dock(self): def _init_dock(self):
self.button_1 = QtWidgets.QPushButton("Button 1 ")
self.button_2_a = QtWidgets.QPushButton("Button to be added at place 0,0 in d3")
self.button_2_b = QtWidgets.QPushButton("button after without postions specified")
self.button_2_c = QtWidgets.QPushButton("button super late")
self.button_3 = QtWidgets.QPushButton("Button above Figure ")
self.bar = SpiralProgressBar()
self.label_2 = QtWidgets.QLabel("label which is added separately") self.d0 = self.dock.add_dock(name="dock_0")
self.label_3 = QtWidgets.QLabel("Label above figure") self.fig0 = self.d0.add_widget_bec("BECFigure")
self.fig0.image("eiger", vrange=(0, 100))
self.d1 = self.dock.add_dock(widget=self.button_1, position="left") self.d1 = self.dock.add_dock(name="dock_1", position="right")
self.d1.addWidget(self.label_2) self.fig1 = self.d1.add_widget_bec("BECFigure")
self.d2 = self.dock.add_dock(widget=self.bar, position="right") self.fig1.plot(x_name="samx", y_name="bpm4i")
self.d3 = self.dock.add_dock(name="figure") self.fig1.plot(x_name="samx", y_name="bpm3a")
self.fig_dock3 = BECFigure()
self.fig_dock3.plot(x_name="samx", y_name="bpm4d") self.d2 = self.dock.add_dock(name="dock_2", position="bottom")
self.d3.add_widget(self.label_3) self.fig2 = self.d2.add_widget_bec("BECFigure", row=0, col=0)
self.d3.add_widget(self.button_3) self.fig2.motor_map(x_name="samx", y_name="samy")
self.d3.add_widget(self.fig_dock3) self.fig2.plot(x_name="samx", y_name="bpm4i")
self.bar = self.d2.add_widget_bec("SpiralProgressBar", row=0, col=1)
self.bar.set_diameter(200)
self.dock.save_state() self.dock.save_state()
@ -155,6 +144,7 @@ if __name__ == "__main__": # pragma: no cover
app = QApplication(sys.argv) app = QApplication(sys.argv)
app.setApplicationName("Jupyter Console") app.setApplicationName("Jupyter Console")
app.setApplicationDisplayName("Jupyter Console") app.setApplicationDisplayName("Jupyter Console")
qdarktheme.setup_theme("auto")
icon = QIcon() icon = QIcon()
icon.addFile(os.path.join(module_path, "assets", "terminal_icon.png"), size=QSize(48, 48)) icon.addFile(os.path.join(module_path, "assets", "terminal_icon.png"), size=QSize(48, 48))
app.setWindowIcon(icon) app.setWindowIcon(icon)

View File

@ -1,159 +0,0 @@
# import simulation_progress as SP
import numpy as np
import pyqtgraph as pg
from bec_lib import messages
from bec_lib.endpoints import MessageEndpoints
from qtpy.QtCore import Signal as pyqtSignal
from qtpy.QtCore import Slot as pyqtSlot
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
class StreamApp(QWidget):
update_signal = pyqtSignal()
new_scan_id = pyqtSignal(str)
def __init__(self, device, sub_device):
super().__init__()
pg.setConfigOptions(background="w", foreground="k")
self.init_ui()
self.setWindowTitle("MCA readout")
self.data = None
self.scan_id = None
self.stream_consumer = None
self.device = device
self.sub_device = sub_device
self.start_device_consumer()
# self.start_device_consumer(self.device) # for simulation
self.new_scan_id.connect(self.create_new_stream_consumer)
self.update_signal.connect(self.plot_new)
def init_ui(self):
# Create layout and add widgets
self.layout = QVBoxLayout()
self.setLayout(self.layout)
# Create plot
self.glw = pg.GraphicsLayoutWidget()
self.layout.addWidget(self.glw)
# Create Plot and add ImageItem
self.plot_item = pg.PlotItem()
self.plot_item.setAspectLocked(False)
self.imageItem = pg.ImageItem()
# self.plot_item1D = pg.PlotItem()
# self.plot_item.addItem(self.imageItem)
# self.plot_item.addItem(self.plot_item1D)
# Setting up histogram
# self.hist = pg.HistogramLUTItem()
# self.hist.setImageItem(self.imageItem)
# self.hist.gradient.loadPreset("magma")
# self.update_hist()
# Adding Items to Graphical Layout
self.glw.addItem(self.plot_item)
# self.glw.addItem(self.hist)
@pyqtSlot(str)
def create_new_stream_consumer(self, scan_id: str):
print(f"Creating new stream consumer for scan_id: {scan_id}")
self.connect_stream_consumer(scan_id, self.device)
def connect_stream_consumer(self, scan_id, device):
if self.stream_consumer is not None:
self.stream_consumer.shutdown()
self.stream_consumer = connector.stream_consumer(
topics=MessageEndpoints.device_async_readback(scan_id=scan_id, device=device),
cb=self._streamer_cb,
parent=self,
)
self.stream_consumer.start()
def start_device_consumer(self):
self.device_consumer = connector.consumer(
topics=MessageEndpoints.scan_status(), cb=self._device_cv, parent=self
)
self.device_consumer.start()
# def start_device_consumer(self, device): #for simulation
# self.device_consumer = connector.consumer(
# topics=MessageEndpoints.device_status(device), cb=self._device_cv, parent=self
# )
#
# self.device_consumer.start()
def plot_new(self):
print(f"Printing data from plot update: {self.data}")
self.plot_item.plot(self.data[0])
# self.imageItem.setImage(self.data, autoLevels=False)
@staticmethod
def _streamer_cb(msg, *, parent, **_kwargs) -> None:
msgMCS = msg.value
print(msgMCS)
row = msgMCS.content["signals"][parent.sub_device]
metadata = msgMCS.metadata
# Check if the current number of rows is odd
# if parent.data is not None and parent.data.shape[0] % 2 == 1:
# row = np.flip(row) # Flip the row
print(f"Printing data from callback update: {row}")
parent.data = np.array([row])
# if parent.data is None:
# parent.data = np.array([row])
# else:
# parent.data = np.vstack((parent.data, row))
parent.update_signal.emit()
@staticmethod
def _device_cv(msg, *, parent, **_kwargs) -> None:
print("Getting ScanID")
msgDEV = msg.value
current_scan_id = msgDEV.content["scan_id"]
if parent.scan_id is None:
parent.scan_id = current_scan_id
parent.new_scan_id.emit(current_scan_id)
print(f"New scan_id: {current_scan_id}")
if current_scan_id != parent.scan_id:
parent.scan_id = current_scan_id
# parent.data = None
# parent.imageItem.clear()
parent.new_scan_id.emit(current_scan_id)
print(f"New scan_id: {current_scan_id}")
if __name__ == "__main__":
import argparse
from bec_lib.redis_connector import RedisConnector
parser = argparse.ArgumentParser(description="Stream App.")
parser.add_argument("--port", type=str, default="pc15543:6379", help="Port for RedisConnector")
parser.add_argument("--device", type=str, default="mcs", help="Device name")
parser.add_argument("--sub_device", type=str, default="mca4", help="Sub-device name")
args = parser.parse_args()
connector = RedisConnector(args.port)
app = QApplication([])
streamApp = StreamApp(device=args.device, sub_device=args.sub_device)
streamApp.show()
app.exec()

View File

@ -1,28 +0,0 @@
import time
from bec_lib import messages
from bec_lib.endpoints import MessageEndpoints
from bec_lib.redis_connector import RedisConnector
connector = RedisConnector("localhost:6379")
metadata = {}
scan_id = "ScanID1"
metadata.update(
{"scan_id": scan_id, "async_update": "append"} # this will be different for each scan
)
for ii in range(20):
data = {"mca1": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "mca2": [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]}
msg = messages.DeviceMessage(signals=data, metadata=metadata).dumps()
connector.xadd(
topic=MessageEndpoints.device_async_readback(
scan_id=scan_id, device="mca"
), # scan_id will be different for each scan
msg={"data": msg}, # TODO should be msg_dict
expire=1800,
)
print(f"Sent {ii}")
time.sleep(0.5)

View File

@ -1,17 +0,0 @@
selected_motors:
motor_x: "samx"
motor_y: "samy"
plot_motors:
max_points: 1000
num_dim_points: 100
scatter_size: 5
precision: 3
mode_lock: False # "Individual" or "Start/Stop". False to unlock
extra_columns:
- sample name: "sample 1"
- step_x [mu]: 25
- step_y [mu]: 25
- exp_time [s]: 1
- start: 1
- tilt [deg]: 0

View File

@ -1,10 +0,0 @@
redis:
host: pc15543
port: 6379
mongodb:
host: localhost
port: 27017
scibec:
host: http://localhost
port: 3030
beamline: MyBeamline

View File

@ -1,17 +0,0 @@
selected_motors:
motor_x: "samx"
motor_y: "samy"
plot_motors:
max_points: 1000
num_dim_points: 100
scatter_size: 5
precision: 3
mode_lock: Start/Stop # "Individual" or "Start/Stop"
extra_columns:
- sample name: "sample 1"
- step_x [mu]: 25
- step_y [mu]: 25
- exp_time [s]: 1
- start: 1
- tilt [deg]: 0

File diff suppressed because it is too large Load Diff

View File

@ -1,148 +0,0 @@
<?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>845</width>
<height>635</height>
</rect>
</property>
<property name="windowTitle">
<string>Line Plot</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QSplitter" name="splitter_plot">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="glw_plot_placeholder" native="true"/>
<widget class="QWidget" name="glw_image_placeholder" native="true"/>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,1,1,15">
<item>
<widget class="QPushButton" name="pushButton_generate">
<property name="text">
<string>Generate 1D and 2D data without stream</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>1st angle of azimutal segment (deg)</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QDoubleSpinBox" name="doubleSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="singleStep">
<double>0.250000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>f1amp</string>
</property>
</item>
<item>
<property name="text">
<string>f2amp</string>
</property>
</item>
<item>
<property name="text">
<string>f2 phase</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Precision</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBox_precision">
<property name="value">
<number>4</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="cursor_table">
<property name="textElideMode">
<enum>Qt::ElideMiddle</enum>
</property>
<column>
<property name="text">
<string>Display</string>
</property>
</column>
<column>
<property name="text">
<string>X</string>
</property>
</column>
<column>
<property name="text">
<string>Y</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,342 +0,0 @@
import os
import threading
import time
import numpy as np
import pyqtgraph
import pyqtgraph as pg
from bec_lib import messages
from bec_lib.endpoints import MessageEndpoints
from bec_lib.redis_connector import RedisConnector
from pyqtgraph import mkBrush, mkPen
from pyqtgraph.Qt import QtCore, QtWidgets
from qtpy.QtCore import Signal, Slot
from qtpy.QtWidgets import QTableWidgetItem, QVBoxLayout
from bec_widgets.utils import Colors, Crosshair, UILoader
from bec_widgets.utils.bec_dispatcher import BECDispatcher
class StreamPlot(QtWidgets.QWidget):
update_signal = Signal()
roi_signal = Signal(tuple)
def __init__(self, name="", y_value_list=["gauss_bpm"], client=None, parent=None) -> None:
"""
Basic plot widget for displaying scan data.
Args:
name (str, optional): Name of the plot. Defaults to "".
y_value_list (list, optional): List of signals to be plotted. Defaults to ["gauss_bpm"].
"""
# Client and device manager from BEC
self.client = BECDispatcher().client if client is None else client
super(StreamPlot, self).__init__()
# Set style for pyqtgraph plots
pg.setConfigOption("background", "w")
pg.setConfigOption("foreground", "k")
current_path = os.path.dirname(__file__)
self.ui = UILoader().load_ui(os.path.join(current_path, "line_plot.ui"), self)
self._idle_time = 100
self.connector = RedisConnector(["localhost:6379"])
self.y_value_list = y_value_list
self.previous_y_value_list = None
self.plotter_data_x = []
self.plotter_data_y = []
self.plotter_scan_id = None
self._current_proj = None
self._current_metadata_ep = "px_stream/projection_{}/metadata"
self.proxy_update = pg.SignalProxy(self.update_signal, rateLimit=25, slot=self.update)
self._data_retriever_thread_exit_event = threading.Event()
self.data_retriever = threading.Thread(
target=self.on_projection, args=(self._data_retriever_thread_exit_event,), daemon=True
)
self.data_retriever.start()
##########################
# UI
##########################
self.init_ui()
self.init_curves()
self.hook_crosshair()
def close(self):
super().close()
self._data_retriever_thread_exit_event.set()
self.data_retriever.join()
def init_ui(self):
"""Setup all ui elements"""
##########################
# 1D Plot
##########################
# LabelItem for ROI
self.label_plot = pg.LabelItem(justify="center")
self.glw_plot_layout = QVBoxLayout(self.ui.glw_plot_placeholder)
self.glw_plot = pg.GraphicsLayoutWidget()
self.glw_plot_layout.addWidget(self.glw_plot)
self.glw_plot.addItem(self.label_plot)
self.label_plot.setText("ROI region")
# ROI selector - so far from [-1,1] #TODO update to scale with xrange
self.roi_selector = pg.LinearRegionItem([-1, 1])
self.glw_plot.nextRow() # TODO update of cursor
self.label_plot_moved = pg.LabelItem(justify="center")
self.glw_plot.addItem(self.label_plot_moved)
self.label_plot_moved.setText("Actual coordinates (X, Y)")
# Label for coordinates clicked
self.glw_plot.nextRow()
self.label_plot_clicked = pg.LabelItem(justify="center")
self.glw_plot.addItem(self.label_plot_clicked)
self.label_plot_clicked.setText("Clicked coordinates (X, Y)")
# 1D PlotItem
self.glw_plot.nextRow()
self.plot = pg.PlotItem()
self.plot.setLogMode(True, True)
self.glw_plot.addItem(self.plot)
self.plot.addLegend()
##########################
# 2D Plot
##########################
# Label for coordinates moved
self.label_image_moved = pg.LabelItem(justify="center")
self.glw_image_layout = QVBoxLayout(self.ui.glw_image_placeholder)
self.glw_image = pg.GraphicsLayoutWidget()
self.glw_plot_layout.addWidget(self.glw_image)
self.glw_image.addItem(self.label_image_moved)
self.label_image_moved.setText("Actual coordinates (X, Y)")
# Label for coordinates clicked
self.glw_image.nextRow()
self.label_image_clicked = pg.LabelItem(justify="center")
self.glw_image.addItem(self.label_image_clicked)
self.label_image_clicked.setText("Clicked coordinates (X, Y)")
# TODO try to lock aspect ratio with view
# # Create a window
# win = pg.GraphicsLayoutWidget()
# win.show()
#
# # Create a ViewBox
# view = win.addViewBox()
#
# # Lock the aspect ratio
# view.setAspectLocked(True)
# # Create an ImageItem
# image_item = pg.ImageItem(np.random.random((100, 100)))
#
# # Add the ImageItem to the ViewBox
# view.addItem(image_item)
# 2D ImageItem
self.glw_image.nextRow()
self.plot_image = pg.PlotItem()
self.glw_image.addItem(self.plot_image)
def init_curves(self):
# init of 1D plot
self.plot.clear()
self.curves = []
self.pens = []
self.brushs = []
self.color_list = Colors.golden_angle_color(colormap="CET-R2", num=len(self.y_value_list))
for ii, y_value in enumerate(self.y_value_list):
pen = mkPen(color=self.color_list[ii], width=2, style=QtCore.Qt.DashLine)
brush = mkBrush(color=self.color_list[ii])
curve = pg.PlotDataItem(symbolBrush=brush, pen=pen, skipFiniteCheck=True, name=y_value)
self.plot.addItem(curve)
self.curves.append(curve)
self.pens.append(pen)
self.brushs.append(brush)
# check if roi selector is in the plot
if self.roi_selector not in self.plot.items:
self.plot.addItem(self.roi_selector)
# init of 2D plot
self.plot_image.clear()
self.img = pg.ImageItem()
self.plot_image.addItem(self.img)
# hooking signals
self.hook_crosshair()
self.init_table()
def splitter_sizes(self): ...
def hook_crosshair(self):
self.crosshair_1d = Crosshair(self.plot, precision=4)
self.crosshair_1d.coordinatesChanged1D.connect(
lambda x, y: self.label_plot_moved.setText(f"Moved : ({x}, {y})")
)
self.crosshair_1d.coordinatesClicked1D.connect(
lambda x, y: self.label_plot_clicked.setText(f"Moved : ({x}, {y})")
)
self.crosshair_1d.coordinatesChanged1D.connect(
lambda x, y: self.update_table(table_widget=self.cursor_table, x=x, y_values=y)
)
self.crosshair_2D = Crosshair(self.plot_image)
self.crosshair_2D.coordinatesChanged2D.connect(
lambda x, y: self.label_image_moved.setText(f"Moved : ({x}, {y})")
)
self.crosshair_2D.coordinatesClicked2D.connect(
lambda x, y: self.label_image_clicked.setText(f"Moved : ({x}, {y})")
)
# ROI
self.roi_selector.sigRegionChangeFinished.connect(self.get_roi_region)
def get_roi_region(self):
"""For testing purpose now, get roi region and print it to self.label as tuple"""
region = self.roi_selector.getRegion()
self.label_plot.setText(f"x = {(10 ** region[0]):.4f}, y ={(10 ** region[1]):.4f}")
return_dict = {
"horiz_roi": [
np.where(self.plotter_data_x[0] > 10 ** region[0])[0][0],
np.where(self.plotter_data_x[0] < 10 ** region[1])[0][-1],
]
}
msg = messages.DeviceMessage(signals=return_dict).dumps()
self.connector.set_and_publish("px_stream/gui_event", msg=msg)
self.roi_signal.emit(region)
def init_table(self):
# Init number of rows in table according to n of devices
self.ui.cursor_table.setRowCount(len(self.y_value_list))
# self.table.setHorizontalHeaderLabels(["(X, Y) - Moved", "(X, Y) - Clicked"]) #TODO can be dynamic
self.ui.cursor_table.setVerticalHeaderLabels(self.y_value_list)
self.ui.cursor_table.resizeColumnsToContents()
def update_table(self, table_widget, x, y_values):
for i, y in enumerate(y_values):
table_widget.setItem(i, 1, QTableWidgetItem(str(x)))
table_widget.setItem(i, 2, QTableWidgetItem(str(y)))
table_widget.resizeColumnsToContents()
def update(self):
"""Update the plot with the new data."""
# check if QTable was initialised and if list of devices was changed
# if self.y_value_list != self.previous_y_value_list:
# self.setup_cursor_table()
# self.previous_y_value_list = self.y_value_list.copy() if self.y_value_list else None
self.curves[0].setData(self.plotter_data_x[0], self.plotter_data_y[0])
@staticmethod
def flip_even_rows(arr):
arr_copy = np.copy(arr) # Create a writable copy
arr_copy[1::2, :] = arr_copy[1::2, ::-1]
return arr_copy
@staticmethod
def remove_curve_by_name(plot: pyqtgraph.PlotItem, name: str) -> None:
# def remove_curve_by_name(plot: pyqtgraph.PlotItem, checkbox: QtWidgets.QCheckBox, name: str) -> None:
"""Removes a curve from the given plot by the specified name.
Args:
plot (pyqtgraph.PlotItem): The plot from which to remove the curve.
name (str): The name of the curve to remove.
"""
# if checkbox.isChecked():
for item in plot.items:
if isinstance(item, pg.PlotDataItem) and getattr(item, "opts", {}).get("name") == name:
plot.removeItem(item)
return
# else:
# return
def on_projection(self, exit_event):
while not exit_event.is_set():
if self._current_proj is None:
time.sleep(0.1)
continue
endpoint = f"px_stream/projection_{self._current_proj}/data"
msgs = self.client.connector.lrange(topic=endpoint, start=-1, end=-1)
data = msgs
if not data:
continue
with np.errstate(divide="ignore", invalid="ignore"):
self.plotter_data_y = [
np.sum(
np.sum(data[-1].content["signals"]["data"] * self._current_norm, axis=1)
/ np.sum(self._current_norm, axis=0),
axis=0,
).squeeze()
]
self.update_signal.emit()
@Slot(dict, dict)
def on_dap_update(self, data: dict, metadata: dict):
flipped_data = self.flip_even_rows(data["data"]["z"])
self.img.setImage(flipped_data)
@Slot(dict, dict)
def new_proj(self, content: dict, _metadata: dict):
proj_nr = content["signals"]["proj_nr"]
endpoint = f"px_stream/projection_{proj_nr}/metadata"
msg_raw = self.client.connector.get(topic=endpoint)
msg = messages.DeviceMessage.loads(msg_raw)
self._current_q = msg.content["signals"]["q"]
self._current_norm = msg.content["signals"]["norm_sum"]
self._current_metadata = msg.content["signals"]["metadata"]
self.plotter_data_x = [self._current_q]
self._current_proj = proj_nr
if __name__ == "__main__":
import argparse
# from bec_widgets import ctrl_c # TODO uncomment when ctrl_c is ready to be compatible with qtpy
parser = argparse.ArgumentParser()
parser.add_argument(
"--signals", help="specify recorded signals", nargs="+", default=["gauss_bpm"]
)
# default = ["gauss_bpm", "bpm4i", "bpm5i", "bpm6i", "xert"],
value = parser.parse_args()
print(f"Plotting signals for: {', '.join(value.signals)}")
# Client from dispatcher
bec_dispatcher = BECDispatcher()
client = bec_dispatcher.client
app = QtWidgets.QApplication([])
# ctrl_c.setup(app) # TODO uncomment when ctrl_c is ready to be compatible with qtpy
plot = StreamPlot(y_value_list=value.signals, client=client)
bec_dispatcher.connect_slot(plot.new_proj, "px_stream/proj_nr")
bec_dispatcher.connect_slot(
plot.on_dap_update, MessageEndpoints.processed_data("px_dap_worker")
)
plot.show()
# client.callbacks.register("scan_segment", plot, sync=False)
app.exec()

View File

@ -1,158 +0,0 @@
# pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
import threading
from unittest import mock
import numpy as np
import pytest
from bec_lib import messages
from bec_lib.redis_connector import RedisConnector
from pytestqt import qtbot
from bec_widgets.examples.stream_plot.stream_plot import StreamPlot
@pytest.fixture(scope="function")
def stream_app(qtbot):
"""Helper function to set up the StreamPlot widget."""
client = mock.MagicMock()
widget = StreamPlot(client=client)
qtbot.addWidget(widget)
qtbot.waitExposed(widget)
yield widget
widget.close()
def test_roi_signals_emitted(qtbot, stream_app):
region = (0.1, 0.9)
with qtbot.waitSignal(stream_app.roi_signal, timeout=1000) as blocker:
stream_app.roi_signal.emit(region)
assert blocker.signal_triggered
assert blocker.args == [region]
def test_update_signals_emitted(qtbot, stream_app):
# Mimic data coming from the data stream
stream_app.plotter_data_x = [list(range(10))] # Replace with the actual x data
stream_app.plotter_data_y = [list(range(10))] # Replace with the actual y data
# Initialize curves
stream_app.init_curves()
with qtbot.waitSignal(stream_app.update_signal, timeout=1000) as blocker:
stream_app.update_signal.emit()
assert blocker.signal_triggered
def test_ui_initialization(qtbot, stream_app):
"""Checking the UI creation."""
# Check if UI elements are initialized correctly
assert stream_app.label_plot is not None
assert stream_app.label_plot_moved is not None
assert stream_app.label_plot_clicked is not None
assert stream_app.label_image_moved is not None
assert stream_app.label_image_clicked is not None
# Check if plots are initialized correctly
assert stream_app.plot is not None
assert stream_app.plot_image is not None
# Check if ROI selector is initialized correctly
assert stream_app.roi_selector is not None
def test_1d_plotting_data(qtbot, stream_app):
# Set up some mock data
x_data = [list(range(10))]
y_data = [list(range(10))]
# Manually set the data attributes
stream_app.plotter_data_x = x_data
stream_app.plotter_data_y = y_data
stream_app.y_value_list = ["Curve 1"]
# Initialize curves and update the plot
stream_app.init_curves()
stream_app.update() # This should update the plot with the new data
# Check the data on the plot
for idx, curve in enumerate(stream_app.curves):
np.testing.assert_array_equal(curve.xData, x_data[0]) # Access the first list of x_data
np.testing.assert_array_equal(
curve.yData, y_data[idx]
) # Access the list of y_data for each curve without additional indexing
def test_flip_even_rows(qtbot, stream_app):
# Create a numpy array with some known data
original_array = np.array(
[[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20]]
)
# Call flip_even_rows on the original array
flipped_array = stream_app.flip_even_rows(original_array)
# Expected array flipped along the rows with even indices
expected_array = np.array(
[[1, 2, 3, 4, 5], [10, 9, 8, 7, 6], [11, 12, 13, 14, 15], [20, 19, 18, 17, 16]]
)
# Check that flip_even_rows returned the expected result
np.testing.assert_array_equal(flipped_array, expected_array)
def test_on_dap_update(qtbot, stream_app):
"""2D image rendering by dap update"""
# Create some mock data to be "received" by the slot
data_dict = {"data": {"z": np.random.rand(10, 10)}}
metadata_dict = {}
# Trigger the slot
stream_app.on_dap_update(data_dict, metadata_dict)
# Apply the same transformation to the test data
expected_data = stream_app.flip_even_rows(data_dict["data"]["z"])
# Now check the state of the StreamPlot object
# For example, check the data of the image plot:
np.testing.assert_array_equal(stream_app.img.image, expected_data)
####################
# Until Here
####################
# def test_new_proj(qtbot, stream_app): #TODO this test is not working, does it make sense testing even?
# # Create some mock content to be "received" by the slot
# content_dict = {"signals": {"proj_nr": 1}}
# metadata_dict = {}
#
# # Manually create some mock data that new_proj would work with
# # This step may need to be adjusted to fit the actual behavior of new_proj
# mock_data = {
# "q": np.array([1, 2, 3, 4, 5]),
# "norm_sum": np.array([6, 7, 8, 9, 10]),
# "metadata": "some_metadata",
# }
#
# # Assume the RedisConnector client would return this data when new_proj is called
# mock_message = mock.MagicMock(spec=messages.DeviceMessage)
# mock_message.__getitem__.side_effect = lambda key: mock_data[key]
# stream_app.client.producer.get = mock.MagicMock(return_value=mock_message.dumps())
#
# # Trigger the slot
# stream_app.new_proj(content_dict, metadata_dict)
#
# # Now check the state of the StreamPlot object
# # For example, check that the plotter_data_x attribute was updated correctly:
# np.testing.assert_array_equal(stream_app.plotter_data_x, [mock_data["q"]])
# assert stream_app._current_proj == 1
# assert stream_app._current_q == mock_data["q"]
# assert stream_app._current_norm == mock_data["norm_sum"]
# assert stream_app._current_metadata == mock_data["metadata"]
# def test_connection_creation(qtbot, stream_app): #TODO maybe test connections in a different way?
# assert isinstance(stream_app.producer, RedisConnector)
# assert isinstance(stream_app.data_retriever, threading.Thread)
# assert stream_app.data_retriever.is_alive()