带PyQ的精确计时器

2024-04-27 01:05:29 发布

您现在位置:Python中文网/ 问答频道 /正文

我用pyqtgraph来绘制从传感器接收到的大量数据。你知道吗

为此,我创建了一个线程来获取数据并放入队列。为了绘制数据,我会定期使用计时器检查队列是否为空。 问题是计时器(QTimer)的精确度似乎很差。我的意思是,当测量线程中的负载较低(休眠1000/100ms)时,精度相当好,但当负载增加(休眠10ms)时,用于绘制数据的更新函数不会在同一时间段内回调。你知道吗

下面是一个示例代码:

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

我希望计时器与测量线程上的负载同步。我试图降低前一个线程的优先级,但没有解决问题。你知道吗


Tags: importselfdatatimequeueinitdefnp
1条回答
网友
1楼 · 发布于 2024-04-27 01:05:29

QTimer documentation部分回答了您的问题:

All timer types may time out later than expected if the system is busy or unable to provide the requested accuracy. In such a case of timeout overrun, Qt will emit timeout() only once, even if multiple timeouts have expired, and then will resume the original interval.

问题是,在超时调用_update之后,Qt将需要一些时间来处理self.drawplot.setData()之后发生的事情,基本上就是计算图形信息并实际将其绘制在屏幕上。
你不能得到1ms的延迟,因为Qt不能工作那么快。你知道吗

即使QTimer可以在另一个线程中工作(“异步”,但要注意这个词的含义),它总是依赖于它创建或驻留的线程(QTimer不能从与其不同的线程启动或停止)。因此,既然您已经在窗口线程(Qt主事件循环)中创建了计时器,那么它的超时精度取决于该循环处理所有事件的能力,而且由于许多事件都与GUI绘制有关(在我们看来,GUI绘制似乎很快,但实际上很慢,因为它对CPU的要求很高),你很容易理解为什么你永远不会得到1毫秒的间隔。不要忘记一个事实,即使pyqtgraph非常快,我们仍然在谈论Python。你知道吗

虽然1ms的QTimer可以达到更高的精度(为它创建一个单独的线程),但无论如何,你也不会从中得到任何好处:即使使用速度非常快的计算机,你实质上要求的是以1000Hz的频率更新屏幕,而大多数图形硬件的速度都不能超过100-200Hz;这意味着如果你拥有一个高端系统,你不会得到超过一个更新每4ms

如果每次有新数据可用时都需要更新绘图,则最好使用信号和插槽,这样也可以避免对队列进行任何不必要的检查,并确保根据需要更新绘图:

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)。你知道吗

关于QTimerhere,还有一个有趣的答案,解释了创建一个QTimer和它超时时基本上发生的事情。你知道吗

相关问题 更多 >