Repository: pymeasure/pymeasure

Issue:
Revert temporary line width fix
Whenever the pyqtgraph issue (https://github.com/pyqtgraph/pyqtgraph/issues/533) is revolved, the temporary fix to improve plotting speed by restricting the line width to 1 (https://github.com/pymeasure/pymeasure/pull/494) should be reverted.


Discussion:
Greeting from pyqtgraph land.  Fix is merged into master, it will be in the next release.  Given the number of projects that asked for this "feature", I'm tempted to do a release sooner than later, but before doing so, I would like to hear if thick lines works for you as you think it should, or if you're stumbling across some edge case.

There are some caveats.

1. Line update speed with pens > 1 pixel are still ~4-5x slower than our current 1 pixel or less width (we recently underwent some significant performance improvements with our 1 pixel pen's so even a 4-5x hit would not be that far off from performance in <= 0.12.0
2. The pens need to be "solid" style
3. The pen color requires alpha to be 1 (no transparency)
4. If the pen is > 4 pixels, there is another penalty attached (but it's relatively minor)

Hope this addresses your use case!
I think, we should consider that even with fix in pyqtgraph, linewidth>1 still carry a penalty in performance (4-5 times slower). A possibility is to leave default to 1 and allow users to change it, if they wish
There is another option, and that's to enable the openGL renderer.  That one suffers no performance penalty but there are a series of other known issues and PlotCurveItem isn't as feature complete.

Last I checked the openGL renderer was slower than the raster renderer; but that's likely due to pyqtgraph maintainers not knowing much about openGL so we likely have some very inefficient code.
Thanks @j9ac9k for the work and the heads-up!! I've tried the updated master and it indeed works a lot smoother.
I'm not entirely sure what the best approach would be, to either leave the linewidth at 1 and add an option to change it, to set the default to 2 (and also add an option to change it), or to do either of the above and by default turn on openGL rendering (I know too little of this matter). I'm inclined to vote for keeping the linewidth at 1 and adding the option to change it and a note that users can turn on openGL.
>  I'm inclined to vote for keeping the linewidth at 1 and adding the option to change it and a note that users can turn on openGL.

This sounds best to me, too.
My personal recommendation would be you all stick with line width = 1 as a default as well.  It's the most performant config for the time being.  The `enableOpenGL=True` variant is not on feature parity with the standard renderer, that we generally don't recommend it unless you know what you're doing (and oddly enough, I don't think any of the currently active maintainers of pyqtgraph know much about openGL, so we would exclude ourselves from that group 😆 ).

Regarding turning on/off OpenGL, we recently attempted to make a toggle for this in one of our benchmarks; once openGL was enabled, we could not disable it in the same instance.  The code had no errors, but the refusal to disabling the openGL renderer was silent; I only noticed it when doing profiling and realizing that despite openGL being disabled, we were getting openGL-like performance results.

Resolving patch:
diff --git a/pymeasure/display/plotter.py b/pymeasure/display/plotter.py
--- a/pymeasure/display/plotter.py
+++ b/pymeasure/display/plotter.py
@@ -45,14 +45,15 @@ class Plotter(StoppableThread):
             A tutorial and example on using the Plotter and PlotterWindow.
     """
 
-    def __init__(self, results, refresh_time=0.1):
+    def __init__(self, results, refresh_time=0.1, linewidth=1):
         super(Plotter, self).__init__()
         self.results = results
         self.refresh_time = refresh_time
+        self.linewidth = linewidth
 
     def run(self):
         app = QtGui.QApplication(sys.argv)
-        window = PlotterWindow(self, refresh_time=self.refresh_time)
+        window = PlotterWindow(self, refresh_time=self.refresh_time, linewidth=self.linewidth)
         self.setup_plot(window.plot)
         app.aboutToQuit.connect(window.quit)
         window.show()
diff --git a/pymeasure/display/widgets.py b/pymeasure/display/widgets.py
--- a/pymeasure/display/widgets.py
+++ b/pymeasure/display/widgets.py
@@ -204,11 +204,12 @@ class PlotWidget(TabWidget, QtGui.QWidget):
     """
 
     def __init__(self, name, columns, x_axis=None, y_axis=None, refresh_time=0.2,
-                 check_status=True, parent=None):
+                 check_status=True, linewidth=1, parent=None):
         super().__init__(name, parent)
         self.columns = columns
         self.refresh_time = refresh_time
         self.check_status = check_status
+        self.linewidth = linewidth
         self._setup_ui()
         self._layout()
         if x_axis is not None:
@@ -266,9 +267,7 @@ def sizeHint(self):
 
     def new_curve(self, results, color=pg.intColor(0), **kwargs):
         if 'pen' not in kwargs:
-            # The pyqtgraph pen width was changed to 1 (originally: 2) to circumvent plotting slowdown.
-            # Once the issue (https://github.com/pyqtgraph/pyqtgraph/issues/533) is resolved it can be reverted
-            kwargs['pen'] = pg.mkPen(color=color, width=1)
+            kwargs['pen'] = pg.mkPen(color=color, width=self.linewidth)
         if 'antialias' not in kwargs:
             kwargs['antialias'] = False
         curve = ResultsCurve(results,
@@ -298,10 +297,9 @@ def remove(self, curve):
         self.plot.removeItem(curve)
 
     def set_color(self, curve, color):
-        """ Remove curve from widget """
-        # The pyqtgraph pen width was changed to 1 (originally: 2) to circumvent plotting slowdown.
-        # Once the issue (https://github.com/pyqtgraph/pyqtgraph/issues/533) is resolved it can be reverted
-        curve.setPen(pg.mkPen(color=color, width=1))
+        """ Change the color of the pen of the curve """
+        curve.pen.setColor(color)
+        curve.updateItems(styleUpdate=True)
 
 class ImageWidget(TabWidget, QtGui.QWidget):
     """ Extends the ImageFrame to allow different columns
diff --git a/pymeasure/display/windows.py b/pymeasure/display/windows.py
--- a/pymeasure/display/windows.py
+++ b/pymeasure/display/windows.py
@@ -71,7 +71,7 @@ class PlotterWindow(QtGui.QMainWindow):
     .. pyqtgraph.PlotItem: http://www.pyqtgraph.org/documentation/graphicsItems/plotitem.html
 
     """
-    def __init__(self, plotter, refresh_time=0.1, parent=None):
+    def __init__(self, plotter, refresh_time=0.1, linewidth=1, parent=None):
         super().__init__(parent)
         self.plotter = plotter
         self.refresh_time = refresh_time
@@ -97,7 +97,8 @@ def __init__(self, plotter, refresh_time=0.1, parent=None):
         hbox.addWidget(self.file)
         vbox.addLayout(hbox)
 
-        self.plot_widget = PlotWidget("Plotter", columns, refresh_time=self.refresh_time, check_status=False)
+        self.plot_widget = PlotWidget("Plotter", columns, refresh_time=self.refresh_time,
+                                      check_status=False, linewidth=linewidth)
         self.plot = self.plot_widget.plot
 
         vbox.addWidget(self.plot_widget)
@@ -107,10 +108,8 @@ def __init__(self, plotter, refresh_time=0.1, parent=None):
         self.main.show()
         self.resize(800, 600)
 
-        # The pyqtgraph pen width was changed to 1 (originally: 2) to circumvent plotting slowdown.
-        # Once the issue (https://github.com/pyqtgraph/pyqtgraph/issues/533) is resolved it can be reverted
         self.curve = ResultsCurve(plotter.results, columns[0], columns[1],
-                                  pen=pg.mkPen(color=pg.intColor(0), width=1), antialias=False)
+                                  pen=pg.mkPen(color=pg.intColor(0), width=linewidth), antialias=False)
         self.plot.addItem(self.curve)
 
         self.plot_widget.updated.connect(self.check_stop)
@@ -617,65 +616,53 @@ class ManagedWindow(ManagedWindowBase):
         Tutorial :ref:`tutorial-managedwindow`
             A tutorial and example on the basic configuration and usage of ManagedWindow.
 
+    :param procedure_class: procedure class describing the experiment (see :class:`~pymeasure.experiment.procedure.Procedure`)
+    :param x_axis: the initial data-column for the x-axis of the plot
+    :param y_axis: the initial data-column for the y-axis of the plot
+    :param linewidth: linewidth for the displayed curves, default is 1
+    :param \**kwargs: optional keyword arguments that will be passed to :class:`~pymeasure.display.windows.ManagedWindowBase`
+
     """
 
-    def __init__(self, procedure_class, inputs=(), displays=(), x_axis=None, y_axis=None,
-                 log_channel='', log_level=logging.INFO, parent=None, sequencer=False,
-                 sequencer_inputs=None, sequence_file=None, inputs_in_scrollarea=False, directory_input=False,
-                 wdg_list=(), hide_groups=True):
+    def __init__(self, procedure_class, x_axis=None, y_axis=None, linewidth=1, **kwargs):
         self.x_axis = x_axis
         self.y_axis = y_axis
         self.log_widget = LogWidget("Experiment Log")
-        self.plot_widget = PlotWidget("Results Graph", procedure_class.DATA_COLUMNS, self.x_axis, self.y_axis)
+        self.plot_widget = PlotWidget("Results Graph", procedure_class.DATA_COLUMNS, self.x_axis, self.y_axis,
+                                      linewidth=linewidth)
         self.plot_widget.setMinimumSize(100, 200)
-        super().__init__(
-            procedure_class=procedure_class,
-            widget_list=wdg_list+(self.plot_widget, self.log_widget),
-            inputs=inputs,
-            displays=displays,
-            parent=parent,
-            sequencer=sequencer,
-            sequencer_inputs=sequencer_inputs,
-            sequence_file=sequence_file,
-            inputs_in_scrollarea=inputs_in_scrollarea,
-            directory_input=directory_input,
-            hide_groups=hide_groups,
-        )
+
+        if "widget_list" not in kwargs:
+            kwargs["widget_list"] = ()
+        kwargs["widget_list"] = kwargs["widget_list"] + (self.plot_widget, self.log_widget)
+
+        super().__init__(procedure_class, **kwargs)
 
         # Setup measured_quantities once we know x_axis and y_axis
         self.browser_widget.browser.measured_quantities = [self.x_axis, self.y_axis]
 
         logging.getLogger().addHandler(self.log_widget.handler)  # needs to be in Qt context?
-        log.setLevel(log_level)
+        log.setLevel(self.log_level)
         log.info("ManagedWindow connected to logging")
 
 class ManagedImageWindow(ManagedWindow):
     """
     Display experiment output with an :class:`~pymeasure.display.widget.ImageWidget` class.
 
+    :param procedure_class: procedure class describing the experiment (see :class:`~pymeasure.experiment.procedure.Procedure`)
+    :param x_axis: the data-column for the x-axis of the plot, cannot be changed afterwards for the image-plot
+    :param y_axis: the data-column for the y-axis of the plot, cannot be changed afterwards for the image-plot
+    :param z_axis: the initial data-column for the z-axis of the plot, can be changed afterwards
+    :param \**kwargs: optional keyword arguments that will be passed to :class:`~pymeasure.display.windows.ManagedWindow`
+
     """
 
-    def __init__(self, procedure_class, x_axis, y_axis, z_axis=None, inputs=(), displays=(),
-                 log_channel='', log_level=logging.INFO, parent=None, sequencer=False,
-                 sequencer_inputs=None, sequence_file=None, inputs_in_scrollarea=False, directory_input=False,
-                 hide_groups=True):
+    def __init__(self, procedure_class, x_axis, y_axis, z_axis=None, **kwargs):
         self.z_axis = z_axis
         self.image_widget = ImageWidget("Image", procedure_class.DATA_COLUMNS, x_axis, y_axis, z_axis)
-        wdg_list = (self.image_widget, )
-        super().__init__(
-            procedure_class = procedure_class,
-            inputs=inputs,
-            displays=displays,
-            x_axis=x_axis,
-            y_axis=y_axis,
-            log_channel=log_channel,
-            log_level=log_level,
-            parent=parent,
-            sequencer=sequencer,
-            sequencer_inputs=sequencer_inputs,
-            sequence_file=sequence_file,
-            inputs_in_scrollarea=inputs_in_scrollarea,
-            directory_input=directory_input,
-            wdg_list=wdg_list,
-            hide_groups=hide_groups,
-        )
+
+        if "widget_list" not in kwargs:
+            kwargs["widget_list"] = ()
+        kwargs["widget_list"] = kwargs["widget_list"] + (self.image_widget, )
+
+        super().__init__(procedure_class, x_axis=x_axis, y_axis=y_axis, **kwargs)


Test patch: