mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-13 11:11:49 +02:00
feat(waveform): original Ivans waveform ported from PyQt5 with plugin
This commit is contained in:
0
bec_widgets/widgets/ivan_waveform/__init__.py
Normal file
0
bec_widgets/widgets/ivan_waveform/__init__.py
Normal file
154
bec_widgets/widgets/ivan_waveform/ivan_waveform.py
Normal file
154
bec_widgets/widgets/ivan_waveform/ivan_waveform.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import itertools
|
||||||
|
from threading import RLock
|
||||||
|
|
||||||
|
import pyqtgraph as pg
|
||||||
|
from bec_lib.endpoints import MessageEndpoints
|
||||||
|
from bec_lib.logger import bec_logger
|
||||||
|
from qtpy.QtCore import Property as pyqtProperty, Slot as pyqtSlot
|
||||||
|
|
||||||
|
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
||||||
|
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
pg.setConfigOptions(background="w", foreground="k", antialias=True)
|
||||||
|
COLORS = ["#fd7f6f", "#7eb0d5", "#b2e061", "#bd7ebe", "#ffb55a"]
|
||||||
|
|
||||||
|
|
||||||
|
class BECScanPlot(pg.GraphicsView):
|
||||||
|
def __init__(self, parent=None, background="default"):
|
||||||
|
super().__init__(parent, background)
|
||||||
|
BECDispatcher().connect_slot(self.on_scan_segment, MessageEndpoints.scan_segment())
|
||||||
|
|
||||||
|
self.view = pg.PlotItem()
|
||||||
|
self.setCentralItem(self.view)
|
||||||
|
|
||||||
|
self._scanID = None
|
||||||
|
self._scanID_lock = RLock()
|
||||||
|
|
||||||
|
self._x_channel = ""
|
||||||
|
self._y_channel_list = []
|
||||||
|
|
||||||
|
self.scan_curves = {}
|
||||||
|
self.dap_curves = {}
|
||||||
|
|
||||||
|
self.x_channel = "samx"
|
||||||
|
self.y_channel_list = ["bpm3y", "bpm6y"]
|
||||||
|
|
||||||
|
def reset_plots(self, _scan_segment, _metadata):
|
||||||
|
for plot_curve in {**self.scan_curves, **self.dap_curves}.values():
|
||||||
|
plot_curve.setData(x=[], y=[])
|
||||||
|
|
||||||
|
@pyqtSlot(dict, dict)
|
||||||
|
def on_scan_segment(self, scan_segment, metadata):
|
||||||
|
# reset plots on scanID change
|
||||||
|
with self._scanID_lock:
|
||||||
|
scan_id = scan_segment["scan_id"]
|
||||||
|
if self._scanID != scan_id:
|
||||||
|
self._scanID = scan_id
|
||||||
|
self.reset_plots(scan_segment, metadata)
|
||||||
|
|
||||||
|
if not self.x_channel:
|
||||||
|
return
|
||||||
|
|
||||||
|
data = scan_segment["data"]
|
||||||
|
|
||||||
|
if self.x_channel not in data:
|
||||||
|
logger.warning(f"Unknown channel `{self.x_channel}` for X data in {self.objectName()}")
|
||||||
|
return
|
||||||
|
|
||||||
|
x_new = data[self.x_channel][self.x_channel]["value"]
|
||||||
|
for chan, plot_curve in self.scan_curves.items():
|
||||||
|
if not chan:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if chan not in data:
|
||||||
|
logger.warning(f"Unknown channel `{chan}` for Y data in {self.objectName()}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
y_new = data[chan][chan]["value"]
|
||||||
|
x, y = plot_curve.getData() # TODO: is it a good approach?
|
||||||
|
if x is None:
|
||||||
|
x = []
|
||||||
|
if y is None:
|
||||||
|
y = []
|
||||||
|
|
||||||
|
plot_curve.setData(x=[*x, x_new], y=[*y, y_new])
|
||||||
|
|
||||||
|
@pyqtSlot(dict, dict)
|
||||||
|
def redraw_dap(self, content, _metadata):
|
||||||
|
data = content["data"]
|
||||||
|
for chan, plot_curve in self.dap_curves.items():
|
||||||
|
if not chan:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if chan not in data:
|
||||||
|
logger.warning(f"Unknown channel `{chan}` for DAP data in {self.objectName()}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
x_new = data[chan]["x"]
|
||||||
|
y_new = data[chan]["y"]
|
||||||
|
|
||||||
|
plot_curve.setData(x=x_new, y=y_new)
|
||||||
|
|
||||||
|
@pyqtProperty("QStringList")
|
||||||
|
def y_channel_list(self):
|
||||||
|
return self._y_channel_list
|
||||||
|
|
||||||
|
@y_channel_list.setter
|
||||||
|
def y_channel_list(self, new_list):
|
||||||
|
bec_dispatcher = BECDispatcher()
|
||||||
|
# TODO: do we want to care about dap/not dap here?
|
||||||
|
chan_removed = [chan for chan in self._y_channel_list if chan not in new_list]
|
||||||
|
if chan_removed and chan_removed[0].startswith("dap."):
|
||||||
|
chan_removed = chan_removed[0].partition("dap.")[-1]
|
||||||
|
chan_removed_ep = MessageEndpoints.processed_data(chan_removed)
|
||||||
|
bec_dispatcher.disconnect_slot(self.redraw_dap, chan_removed_ep)
|
||||||
|
|
||||||
|
self._y_channel_list = new_list
|
||||||
|
|
||||||
|
# Prepare plot for a potentially different list of y channels
|
||||||
|
self.view.clear()
|
||||||
|
|
||||||
|
self.view.addLegend()
|
||||||
|
colors = itertools.cycle(COLORS)
|
||||||
|
|
||||||
|
for y_chan in new_list:
|
||||||
|
if y_chan.startswith("dap."):
|
||||||
|
y_chan = y_chan.partition("dap.")[-1]
|
||||||
|
curves = self.dap_curves
|
||||||
|
y_chan_ep = MessageEndpoints.processed_data(y_chan)
|
||||||
|
bec_dispatcher.connect_slot(self.redraw_dap, y_chan_ep)
|
||||||
|
else:
|
||||||
|
curves = self.scan_curves
|
||||||
|
|
||||||
|
curves[y_chan] = self.view.plot(
|
||||||
|
x=[], y=[], pen=pg.mkPen(color=next(colors), width=2), name=y_chan
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(new_list) == 1:
|
||||||
|
self.view.setLabel("left", new_list[0])
|
||||||
|
|
||||||
|
@pyqtProperty(str)
|
||||||
|
def x_channel(self):
|
||||||
|
return self._x_channel
|
||||||
|
|
||||||
|
@x_channel.setter
|
||||||
|
def x_channel(self, new_val):
|
||||||
|
self._x_channel = new_val
|
||||||
|
self.view.setLabel("bottom", new_val)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from qtpy.QtWidgets import QApplication
|
||||||
|
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
|
plot = BECScanPlot()
|
||||||
|
plot.x_channel = "samx"
|
||||||
|
plot.y_channel_list = ["bpm3y", "bpm6y"]
|
||||||
|
|
||||||
|
plot.show()
|
||||||
|
|
||||||
|
sys.exit(app.exec())
|
@ -0,0 +1 @@
|
|||||||
|
{'files': ['ivan_waveform.py']}
|
56
bec_widgets/widgets/ivan_waveform/ivan_waveform_plugin.py
Normal file
56
bec_widgets/widgets/ivan_waveform/ivan_waveform_plugin.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from qtpy.QtGui import QIcon
|
||||||
|
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||||
|
|
||||||
|
from bec_widgets.widgets.ivan_waveform.ivan_waveform import BECScanPlot
|
||||||
|
|
||||||
|
|
||||||
|
class BECScanPlotPlugin(QDesignerCustomWidgetInterface):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self._initialized = False
|
||||||
|
|
||||||
|
def initialize(self, formEditor):
|
||||||
|
if self._initialized:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._initialized = True
|
||||||
|
|
||||||
|
def isInitialized(self):
|
||||||
|
return self._initialized
|
||||||
|
|
||||||
|
def createWidget(self, parent):
|
||||||
|
return BECScanPlot(parent)
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return "BECScanPlot"
|
||||||
|
|
||||||
|
def group(self):
|
||||||
|
return "BEC widgets"
|
||||||
|
|
||||||
|
def icon(self):
|
||||||
|
return QIcon()
|
||||||
|
|
||||||
|
def toolTip(self):
|
||||||
|
return "BEC plot for scans"
|
||||||
|
|
||||||
|
def whatsThis(self):
|
||||||
|
return "BEC plot for scans"
|
||||||
|
|
||||||
|
def isContainer(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def domXml(self):
|
||||||
|
return (
|
||||||
|
'<widget class="BECScanPlot" name="BECScanPlot">\n'
|
||||||
|
' <property name="toolTip" >\n'
|
||||||
|
" <string>BEC plot for scans</string>\n"
|
||||||
|
" </property>\n"
|
||||||
|
' <property name="whatsThis" >\n'
|
||||||
|
" <string>BEC plot for scans in Python using PyQt.</string>\n"
|
||||||
|
" </property>\n"
|
||||||
|
"</widget>\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
def includeFile(self):
|
||||||
|
return "scan_plot"
|
15
bec_widgets/widgets/ivan_waveform/register_ivan_waveform.py
Normal file
15
bec_widgets/widgets/ivan_waveform/register_ivan_waveform.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
def main(): # pragma: no cover
|
||||||
|
from qtpy import PYSIDE6
|
||||||
|
|
||||||
|
if not PYSIDE6:
|
||||||
|
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
||||||
|
return
|
||||||
|
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||||
|
|
||||||
|
from bec_widgets.widgets.ivan_waveform.ivan_waveform_plugin import BECScanPlotPlugin
|
||||||
|
|
||||||
|
QPyDesignerCustomWidgetCollection.addCustomWidget(BECScanPlotPlugin())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
main()
|
Reference in New Issue
Block a user