Skip to content

Commit 8517cc9

Browse files
Merge pull request #4 from Dennis-van-Gils/investigate
Investigate PyQtGraph & OpenGL performance
2 parents 7e41633 + 39d6a32 commit 8517cc9

File tree

5 files changed

+81
-45
lines changed

5 files changed

+81
-45
lines changed

README.rst

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@
88
.. image:: https://img.shields.io/badge/License-MIT-purple.svg
99
:target: https://github.com/Dennis-van-Gils/DvG_Arduino_PyQt_multithread_demo/blob/master/LICENSE.txt
1010

11-
DvG_Arduino_PyQt_multithread_demo
12-
=================================
11+
Arduino PyQt multithread demo
12+
=============================
1313

1414
Demonstration of multithreaded communication, real-time plotting and logging of live Arduino data using PyQt/PySide and PyQtGraph.
1515

1616
This demo needs just a bare Arduino(-like) device that, for demonstration purposes, will act as a numerical waveform generator. The source files are included and they should compile over a wide range of Arduino boards. Connect the Arduino to any USB port on your computer and run this Python demo. It should automatically find the Arduino and show you a fully functioning GUI with live data at a stable acquisition rate of 100 Hz.
1717

18-
Alternatively, you can simulate the Arduino by running ``python demo_A_GUI_full.py simulate``.
18+
::
19+
20+
Alternatively, you can simulate the Arduino by running `python demo_A_GUI_full.py simulate`.
1921

20-
It features a graphical user-interface, with a PyQtGraph plot for fast real-time plotting of data. The main thread handles the GUI and redrawing of the plot, another thread deals with acquiring data from the Arduino at a fixed rate and a third thread maintains a thread-safe queue where messages to be sent out to the Arduino are managed.
22+
It features a graphical user-interface, with a PyQtGraph plot for fast real-time plotting of data. The main thread handles the GUI and redrawing of the plot, another thread deals with acquiring data (DAQ) from the Arduino at a fixed rate and a third thread maintains a thread-safe queue where messages to be sent out to the Arduino are managed.
2123

2224

2325
.. image:: /images/Arduino_PyQt_demo_with_multithreading.PNG
@@ -29,7 +31,21 @@ Other depencies you'll need for this demo can be installed by running::
2931

3032
pip install -r requirements.txt
3133

32-
PyQtGraph performance
33-
=====================
34+
PyQtGraph & OpenGL performance
35+
==============================
36+
37+
Enabling OpenGL for plotting within PyQtGraph has major benefits as the drawing will get offloaded to the GPU, instead of being handled by the CPU. However, when PyOpenGL is installed in the Python environment and OpenGL acceleration is enabled inside PyQtGraph as such in order to get full OpenGL support::
38+
39+
import pyqtgraph as pg
40+
pg.setConfigOptions(useOpenGL=True)
41+
pg.setConfigOptions(enableExperimental=True)
42+
43+
the specific version of PyQtGraph will have a major influence on the timing stability of the DAQ routine, even though it is running in a separate dedicated thread. This becomes visible as a fluctuating time stamp in the recorded log file. Remarkably, I observe that ``PyQtGraph==0.11.1 leads to a superior timing stability`` of +/- 1 ms in the recorded time stamps, whereas ``0.12.x`` and ``0.13.1`` are very detrimental to the stability with values of +/- 20 ms. The cause for this lies in the different way that PyQtGraph v0.12+ handles `PlotDataItem()` with PyOpenGL. I suspect that the Python GIL (Global Interpreter Lock) is responsible for this, somehow. There is nothing I can do about that and hopefully this gets resolved in future PyQtGraph versions.
44+
45+
Recommendation
46+
--------------
47+
48+
``Stick with pyqtgraph==0.11.1`` when OpenGL is needed and when consistent and high (> 10 Hz) DAQ rates are required. Unfortunately, ``0.11.1`` only supports PyQt5 or PySide2, not PyQt6 or PySide6 which get supported from of version ``0.12+``.
49+
50+
Note: ``pyqtgraph==0.11.0`` has a line width issue with OpenGL curves and is stuck at 1 pixel, unless you apply `my monkeypatch <https://github.com/Dennis-van-Gils/python-dvg-pyqtgraph-monkeypatch>`_.
3451

35-
The specific version of PyQtGraph *can* have major influence on the timing stability of the DAQ routine, visible as a fluctuating time stamp in a recorded log file. I observe that ``PyQtGraph==0.11`` leads to a great timing stability of +/- 1 ms, whereas ``0.12.4`` and ``0.13.1`` are very detrimental to the stability with values of +/- 20 ms. The reason for this is still unknown. I have to investigate further.

demo_A_GUI_full.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
__author__ = "Dennis van Gils"
77
__authoremail__ = "vangils.dennis@gmail.com"
88
__url__ = "https://github.com/Dennis-van-Gils/DvG_Arduino_PyQt_multithread_demo"
9-
__date__ = "12-10-2022"
10-
__version__ = "8.1"
9+
__date__ = "13-10-2022"
10+
__version__ = "8.2"
1111
# pylint: disable=bare-except, broad-except, unnecessary-lambda
1212

1313
import os
@@ -22,6 +22,7 @@
2222
# fmt: on
2323

2424
# Global flags
25+
TRY_USING_OPENGL = True
2526
USE_LARGER_TEXT = False # For demonstration on a beamer
2627
USE_PC_TIME = True # Use Arduino time or PC time?
2728
SIMULATE_ARDUINO = False # Simulate an Arduino, instead?
@@ -82,7 +83,6 @@
8283
QT_VERSION = (
8384
QtCore.QT_VERSION_STR if QT_LIB in (PYQT5, PYQT6) else QtCore.__version__
8485
)
85-
print(f"{QT_LIB} {QT_VERSION}")
8686

8787
# pylint: enable=import-error, no-name-in-module, c-extension-no-member
8888
# \end[Mechanism to support both PyQt and PySide]
@@ -92,6 +92,9 @@
9292
import numpy as np
9393
import pyqtgraph as pg
9494

95+
print(f"{QT_LIB:9s} {QT_VERSION}")
96+
print(f"PyQtGraph {pg.__version__}")
97+
9598
from dvg_debug_functions import tprint, dprint, print_fancy_traceback as pft
9699
from dvg_pyqtgraph_threadsafe import HistoryChartCurve, PlotManager
97100
from dvg_pyqt_filelogger import FileLogger
@@ -105,17 +108,21 @@
105108
from dvg_devices.Arduino_protocol_serial import Arduino
106109
from dvg_qdeviceio import QDeviceIO
107110

108-
109-
try:
110-
import OpenGL.GL as gl # pylint: disable=unused-import
111-
except:
112-
print("OpenGL acceleration: Disabled")
113-
print("To install: `conda install pyopengl` or `pip install pyopengl`")
111+
if TRY_USING_OPENGL:
112+
try:
113+
import OpenGL.GL as gl # pylint: disable=unused-import
114+
from OpenGL.version import __version__ as gl_version
115+
except:
116+
print("PyOpenGL not found")
117+
print("To install: `conda install pyopengl` or `pip install pyopengl`")
118+
else:
119+
print(f"PyOpenGL {gl_version}")
120+
pg.setConfigOptions(useOpenGL=True)
121+
pg.setConfigOptions(antialias=True)
122+
pg.setConfigOptions(enableExperimental=True)
114123
else:
115-
print("OpenGL acceleration: Enabled")
116-
pg.setConfigOptions(useOpenGL=True)
117-
pg.setConfigOptions(antialias=True)
118-
pg.setConfigOptions(enableExperimental=True)
124+
print("PyOpenGL disabled")
125+
119126

120127
# Global pyqtgraph configuration
121128
# pg.setConfigOptions(leftButtonPan=False)

demo_B_GUI_minimal.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
__author__ = "Dennis van Gils"
77
__authoremail__ = "vangils.dennis@gmail.com"
88
__url__ = "https://github.com/Dennis-van-Gils/DvG_Arduino_PyQt_multithread_demo"
9-
__date__ = "12-09-2022"
10-
__version__ = "8.1"
9+
__date__ = "13-09-2022"
10+
__version__ = "8.2"
1111
# pylint: disable=bare-except, broad-except
1212

1313
import os
@@ -22,6 +22,7 @@
2222
# fmt: on
2323

2424
# Global flags
25+
TRY_USING_OPENGL = True
2526
SIMULATE_ARDUINO = False # Simulate an Arduino, instead?
2627
if sys.argv[-1] == "simulate":
2728
SIMULATE_ARDUINO = True
@@ -80,7 +81,6 @@
8081
QT_VERSION = (
8182
QtCore.QT_VERSION_STR if QT_LIB in (PYQT5, PYQT6) else QtCore.__version__
8283
)
83-
print(f"{QT_LIB} {QT_VERSION}")
8484

8585
# pylint: enable=import-error, no-name-in-module, c-extension-no-member
8686
# \end[Mechanism to support both PyQt and PySide]
@@ -90,23 +90,30 @@
9090
import numpy as np
9191
import pyqtgraph as pg
9292

93+
print(f"{QT_LIB:9s} {QT_VERSION}")
94+
print(f"PyQtGraph {pg.__version__}")
95+
9396
from dvg_debug_functions import dprint, print_fancy_traceback as pft
9497
from dvg_pyqtgraph_threadsafe import HistoryChartCurve
9598

9699
from dvg_fakearduino import FakeArduino
97100
from dvg_devices.Arduino_protocol_serial import Arduino
98101
from dvg_qdeviceio import QDeviceIO
99102

100-
try:
101-
import OpenGL.GL as gl # pylint: disable=unused-import
102-
except:
103-
print("OpenGL acceleration: Disabled")
104-
print("To install: `conda install pyopengl` or `pip install pyopengl`")
103+
if TRY_USING_OPENGL:
104+
try:
105+
import OpenGL.GL as gl # pylint: disable=unused-import
106+
from OpenGL.version import __version__ as gl_version
107+
except:
108+
print("PyOpenGL not found")
109+
print("To install: `conda install pyopengl` or `pip install pyopengl`")
110+
else:
111+
print(f"PyOpenGL {gl_version}")
112+
pg.setConfigOptions(useOpenGL=True)
113+
pg.setConfigOptions(antialias=True)
114+
pg.setConfigOptions(enableExperimental=True)
105115
else:
106-
print("OpenGL acceleration: Enabled")
107-
pg.setConfigOptions(useOpenGL=True)
108-
pg.setConfigOptions(antialias=True)
109-
pg.setConfigOptions(enableExperimental=True)
116+
print("PyOpenGL disabled")
110117

111118
# Global pyqtgraph configuration
112119
# pg.setConfigOptions(leftButtonPan=False)

demo_C_singlethread_for_comparison.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
__author__ = "Dennis van Gils"
1313
__authoremail__ = "vangils.dennis@gmail.com"
1414
__url__ = "https://github.com/Dennis-van-Gils/DvG_Arduino_PyQt_multithread_demo"
15-
__date__ = "12-10-2022"
16-
__version__ = "8.1"
15+
__date__ = "13-10-2022"
16+
__version__ = "8.2"
1717
# pylint: disable=bare-except, broad-except, unnecessary-lambda
1818

1919
import os
@@ -28,6 +28,7 @@
2828
# fmt: on
2929

3030
# Global flags
31+
TRY_USING_OPENGL = True
3132
USE_PC_TIME = True # Use Arduino time or PC time?
3233
SIMULATE_ARDUINO = False # Simulate an Arduino, instead?
3334
if sys.argv[-1] == "simulate":
@@ -87,7 +88,6 @@
8788
QT_VERSION = (
8889
QtCore.QT_VERSION_STR if QT_LIB in (PYQT5, PYQT6) else QtCore.__version__
8990
)
90-
print(f"{QT_LIB} {QT_VERSION}")
9191

9292
# pylint: enable=import-error, no-name-in-module, c-extension-no-member
9393
# \end[Mechanism to support both PyQt and PySide]
@@ -97,6 +97,9 @@
9797
import numpy as np
9898
import pyqtgraph as pg
9999

100+
print(f"{QT_LIB:9s} {QT_VERSION}")
101+
print(f"PyQtGraph {pg.__version__}")
102+
100103
from dvg_debug_functions import tprint, dprint, print_fancy_traceback as pft
101104
from dvg_pyqtgraph_threadsafe import HistoryChartCurve, PlotManager
102105
from dvg_pyqt_filelogger import FileLogger
@@ -109,17 +112,20 @@
109112
from dvg_fakearduino import FakeArduino
110113
from dvg_devices.Arduino_protocol_serial import Arduino
111114

112-
113-
try:
114-
import OpenGL.GL as gl # pylint: disable=unused-import
115-
except:
116-
print("OpenGL acceleration: Disabled")
117-
print("To install: `conda install pyopengl` or `pip install pyopengl`")
115+
if TRY_USING_OPENGL:
116+
try:
117+
import OpenGL.GL as gl # pylint: disable=unused-import
118+
from OpenGL.version import __version__ as gl_version
119+
except:
120+
print("PyOpenGL not found")
121+
print("To install: `conda install pyopengl` or `pip install pyopengl`")
122+
else:
123+
print(f"PyOpenGL {gl_version}")
124+
pg.setConfigOptions(useOpenGL=True)
125+
pg.setConfigOptions(antialias=True)
126+
pg.setConfigOptions(enableExperimental=True)
118127
else:
119-
print("OpenGL acceleration: Enabled")
120-
pg.setConfigOptions(useOpenGL=True)
121-
pg.setConfigOptions(antialias=True)
122-
pg.setConfigOptions(enableExperimental=True)
128+
print("PyOpenGL disabled")
123129

124130
# Global pyqtgraph configuration
125131
# pg.setConfigOptions(leftButtonPan=False)
2.37 KB
Loading

0 commit comments

Comments
 (0)