From db274c644f643f830c35b6a92edd328bf7e24f59 Mon Sep 17 00:00:00 2001 From: Klaus Wakonig Date: Tue, 11 Jul 2023 17:58:07 +0200 Subject: [PATCH] feat: added config plotter --- bec_widgets/config_plotter.py | 127 ++++++++++++++++++++++++++++++++++ setup.py | 1 + tests/test_config_plotter.py | 45 ++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 bec_widgets/config_plotter.py create mode 100644 tests/test_config_plotter.py diff --git a/bec_widgets/config_plotter.py b/bec_widgets/config_plotter.py new file mode 100644 index 00000000..dc01923c --- /dev/null +++ b/bec_widgets/config_plotter.py @@ -0,0 +1,127 @@ +from typing import List + +import numpy as np +import pyqtgraph as pg +from pyqtgraph import mkPen +from pyqtgraph.Qt import QtCore, QtWidgets + + +class ConfigPlotter(pg.GraphicsWidget): + """ + ConfigPlotter is a widget that can be used to plot data from multiple channels + in a grid layout. The layout is specified by a list of dicts, where each dict + specifies the position of the plot in the grid, the channels to plot, and the + type of plot to use. The plot type is specified by the name of the pyqtgraph + item to use. For example, to plot a single channel in a PlotItem, the config + would look like this: + + config = [ + { + "cols": 1, + "rows": 1, + "y": 0, + "x": 0, + "config": {"channels": ["a"], "label_xy": ["", "a"], "item": "PlotItem"}, + } + ] + + """ + + def __init__(self, configs: List[dict], parent=None): + super().__init__(parent) + self.configs = configs + self.plots = {} + self._init_ui() + self._init_plots() + + def _init_ui(self): + pg.setConfigOption("background", "w") + pg.setConfigOption("foreground", "k") + self.pen = mkPen(color=(56, 76, 107), width=4, style=QtCore.Qt.SolidLine) + + self.view = pg.GraphicsView() + self.view.setAntialiasing(True) + self.view.show() + + self.layout = pg.GraphicsLayout() + self.view.setCentralWidget(self.layout) + + def _init_plots(self): + for config in self.configs: + channels = config["config"]["channels"] + for channel in channels: + item = pg.PlotItem() + self.layout.addItem( + item, + row=config["y"], + col=config["x"], + rowspan=config["rows"], + colspan=config["cols"], + ) + + # call the corresponding init function, e.g. init_plotitem + init_func = getattr(self, f"init_{config['config']['item']}") + init_func(channel, config["config"], item) + + # self.init_ImageItem(channel, config["config"], item) + + def init_PlotItem(self, channel: str, config: dict, item: pg.GraphicsItem): + """ + Initialize a PlotItem + + Args: + channel(str): channel to plot + config(dict): config dict for the channel + item(pg.GraphicsItem): PlotItem to plot the data + """ + # pylint: disable=invalid-name + plot_data = item.plot(np.random.rand(100), pen=self.pen) + item.setLabel("left", channel) + self.plots[channel] = {"item": item, "plot_data": plot_data} + + def init_ImageItem(self, channel: str, config: dict, item: pg.GraphicsItem): + """ + Initialize an ImageItem + + Args: + channel(str): channel to plot + config(dict): config dict for the channel + item(pg.GraphicsItem): ImageItem to plot the data + """ + # pylint: disable=invalid-name + img = pg.ImageItem() + item.addItem(img) + img.setImage(np.random.rand(100, 100)) + self.plots[channel] = {"item": item, "plot_data": img} + + +if __name__ == "__main__": + import sys + + CONFIG = [ + { + "cols": 1, + "rows": 1, + "y": 0, + "x": 0, + "config": {"channels": ["a"], "label_xy": ["", "a"], "item": "PlotItem"}, + }, + { + "cols": 1, + "rows": 1, + "y": 1, + "x": 0, + "config": {"channels": ["b"], "label_xy": ["", "b"], "item": "PlotItem"}, + }, + { + "cols": 1, + "rows": 2, + "y": 0, + "x": 1, + "config": {"channels": ["c"], "label_xy": ["", "c"], "item": "ImageItem"}, + }, + ] + + app = QtWidgets.QApplication(sys.argv) + win = ConfigPlotter(CONFIG) + pg.exec() diff --git a/setup.py b/setup.py index f82baba4..5688c4d7 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ if __name__ == "__main__": setup( install_requires=[ "pyqt5", + "pyqtgraph", ], extras_require={"dev": ["pytest", "pytest-random-order", "coverage", "pytest-qt"]}, version=__version__, diff --git a/tests/test_config_plotter.py b/tests/test_config_plotter.py new file mode 100644 index 00000000..87f4b1e8 --- /dev/null +++ b/tests/test_config_plotter.py @@ -0,0 +1,45 @@ +import pyqtgraph as pg +from pytestqt import qtbot + +from bec_widgets import config_plotter + + +def test_config_plotter(qtbot): + """Test ConfigPlotter""" + + config = [ + { + "cols": 1, + "rows": 1, + "y": 0, + "x": 0, + "config": {"channels": ["a"], "label_xy": ["", "a"], "item": "PlotItem"}, + } + ] + plotter = config_plotter.ConfigPlotter(config) + + assert isinstance(plotter.plots["a"]["item"], pg.PlotItem) + + +def test_config_plotter_image(qtbot): + """Test ConfigPlotter""" + + config = [ + { + "cols": 1, + "rows": 1, + "y": 0, + "x": 0, + "config": {"channels": ["a"], "label_xy": ["", "a"], "item": "PlotItem"}, + }, + { + "cols": 1, + "rows": 1, + "y": 1, + "x": 0, + "config": {"channels": ["b"], "label_xy": ["", "b"], "item": "ImageItem"}, + }, + ] + plotter = config_plotter.ConfigPlotter(config) + + assert isinstance(plotter.plots["a"]["item"], pg.PlotItem)