我正在使用pyqtgraph绘制从传感器接收的大量数据。
为此,我创建了一个线程来获取数据并将其放入队列。为了绘制数据,我定期使用计时器检查队列是否不为空。问题在于计时器(QTimer)的准确性似乎确实很差。我的意思是,当测量线程中的负载较低(睡眠状态为1000/100毫秒)时,精度非常好,但是当负载增加(睡眠状态为10毫秒)时,用于绘制数据的更新函数不会以相同的方式回调期。
这里是示例代码:
import sys
import time
from queue import Queue
from random import random
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtWidgets
data_queue = Queue()
class WorkerThread(QtCore.QThread):
def __init__(self, parent):
super(WorkerThread, self).__init__(parent=parent)
def run(self):
t_init = time.time()
while True:
# Generating random data
values = [(time.time()-t_init, random()) for _ in range(200)]
data_queue.put(values)
print("adding data")
self.msleep(10)
class GraphPlot(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(GraphPlot, self).__init__(parent)
self.mainbox = QtWidgets.QWidget()
self.setCentralWidget(self.mainbox)
self.mainbox.setLayout(QtWidgets.QVBoxLayout())
self.canvas = pg.GraphicsLayoutWidget()
self.mainbox.layout().addWidget(self.canvas)
self.analogPlot = self.canvas.addPlot(title='Real-time data')
self.drawplot = self.analogPlot.plot(pen='r')
numPoints = 20000
self.t = np.zeros(numPoints, dtype=int)
self.x = np.zeros(numPoints, dtype=int)
self.worker = WorkerThread(self)
self.worker.start()
self.timer = pg.QtCore.QTimer()
self.timer.setTimerType(QtCore.Qt.PreciseTimer)
self.timer.timeout.connect(self._update)
self.timer.start(1)
def _update(self):
print('start:', time.time())
size = data_queue.qsize()
if size > 0:
for _ in range(size):
values = data_queue.get()
for v in values:
self.t = np.append(self.t[1:], v[0])
self.x = np.append(self.x[1:], v[1])
self.drawplot.setData(self.t, self.x)
print('end:', time.time())
app = QtWidgets.QApplication(sys.argv)
plot = GraphPlot()
plot.show()
sys.exit(app.exec_())
输出摘录:
start: 1572893919.9067862
adding data
end: 1572893919.9217482 <--
adding data
start: 1572893919.9586473 <-- there should be 1ms of difference with last 'end'
actually, there is around 37ms
无论测量线程上的负载如何,我都希望计时器与同一时间段同步。我试图降低前一个线程的优先级,但未能解决问题。
QTimer documentation部分回答了您的问题:
所有计时器类型可能如果系统繁忙,则超时时间比预期的要晚或无法提供所需的准确性。在这种情况下溢出,Qt将仅发出timeout()一次,即使有多个超时已过期,然后将恢复原始间隔。
问题是,从超时中调用_update
之后,Qt将需要一些时间来处理self.drawplot.setData()
之后发生的事情,这基本上是在计算图形信息并将其实际绘制在屏幕上。您没有得到1ms的延迟,因为Qt不能这么快地工作。
即使QTimer可以在另一个线程中工作(“异步”,但要注意该词的含义),它始终取决于它创建或驻留的线程(无法从其他线程启动或停止QTimer)比它的一个)。因此,由于您已经在窗口线程(Qt主事件循环)中创建了计时器,因此其超时精度取决于该循环处理其[[all事件的能力,并且由于许多事件与GUI相关绘画(在我们看来似乎很快,但实际上对CPU的要求很高,[
class WorkerThread(QtCore.QThread):
newData = QtCore.pyqtSignal(object)
def __init__(self, parent):
super(WorkerThread, self).__init__(parent=parent)
def run(self):
t_init = time.time()
while True:
# Generating random data
values = [(time.time()-t_init, random()) for _ in range(200)]
print("adding data")
self.newData.emit(values)
self.msleep(10)
class GraphPlot(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(GraphPlot, self).__init__(parent)
self.mainbox = QtWidgets.QWidget()
self.setCentralWidget(self.mainbox)
self.mainbox.setLayout(QtWidgets.QVBoxLayout())
self.canvas = pg.GraphicsLayoutWidget()
self.mainbox.layout().addWidget(self.canvas)
self.analogPlot = self.canvas.addPlot(title='Real-time data')
self.drawplot = self.analogPlot.plot(pen='r')
numPoints = 20000
self.t = np.zeros(numPoints, dtype=int)
self.x = np.zeros(numPoints, dtype=int)
self.worker = WorkerThread(self)
self.worker.newData.connect(self.newData)
self.worker.start()
def newData(self, data):
print('start:', time.time())
for v in data:
self.t = np.append(self.t[1:], v[0])
self.x = np.append(self.x[1:], v[1])
self.drawplot.setData(self.t, self.x)
print('end:', time.time())
您将不会获得1ms的更新,但是无论如何都不需要它;另外,请记住,以该速度打印将始终以某种方式影响性能。
最后,以1ms的间隔设置PreciseTimer
是没有优势的,因为无论如何,大多数平台上的计时器精度约为1ms(如文档linked before的同一段开头所述),并且设置仅在更长的时间间隔内才需要精度(我说至少25-50ms)。关于QTimer here,还有一个有趣的答案,它解释了每次创建一个超时都会发生的基本情况。