使用 pyqtgraph 和线程进行实时绘图

Posted

技术标签:

【中文标题】使用 pyqtgraph 和线程进行实时绘图【英文标题】:Real-Time-Plotting using pyqtgraph and threading 【发布时间】:2019-07-24 12:08:43 【问题描述】:

这有点长,第一部分只是对问题的描述,第二部分是我的“修复”是否正确的问题。

我从 python 编程开始。我创建了一个程序,它与读取我们熔化实验室熔炉温度的 Arduino 进行通信。然后在 PID 算法中使用温度,并将输出设置为 Arduino。通信是通过 pyserial 完成的。到目前为止,一切都有效,包括实时绘制温度信号、PID 变量等。该脚本包括一个主循环和 3 个线程(串行通信、一个从串行端口读取的数据移位器、来自 QWidget 的设定温度和 PID 算法的输出。这些值用于创建一个数组以在 pyqtgraph 中显示。最后,第三个线程将数据从 datashifter 转移到 QWidget。

当使用我的 Linux 笔记本时,一切正常,并且 GUI 从未停止更新。相比之下,当使用任何 Windows 主机时,我会遇到一些 pyqtgraphs 停止刷新的问题。这种行为很奇怪,因为我或多或少地同时设置了所有数据,使用相同的 numpy 数组(只是不同的列) - 有些图刷新时间更长(小时),有些更早停止(分钟)。在搜索了或多或少的漏洞之后 ;-) 我认为我发现了问题:它是从线程到 GUI 的数据传递。一些虚拟代码来解释发生了什么:

DataUpdaterToGUI(QThread):

#sets the QWidget from main loop
def setGUI(self, gui):
    self.gui = gui

def run()
    while True:
        with lock(): # RLock() Instance
           copyArray = self.dataArray[:] # copy the array from the shifter
           self.gui.plot1.copyArray(dataArray[:, 0], copyArray[:, 1])
           self.gui.plot2.copyArray(dataArray[:, 0], copyArray[:, 2])
           # self.gui.update()
           # QApplication.instance().processEvents() 

调用 self.gui.update() 和 processEvents() 都不会对结果产生任何影响:绘图会在一段时间后停止重绘(在 Windows 上)。

现在我有一个非常简单的例子,只是想确定我是否正确使用了线程。它工作正常,但我有一些问题:

信号槽方法是否复制传递的数据? 为什么不需要调用QWidget的update()方法? 使用信号时是否必须使用任何类型的锁?
class Main(QWidget):
    def __init__(self):
        super().__init__()

        self.layout = QGridLayout(self)
        self.graph = pg.PlotWidget()
        self.graph.setYRange(0,1000)
        self.plot = self.graph.plot()
        self.layout.addWidget(self.graph,0,0)
        self.show()

    def make_connection(self, data_object):
        data_object.signal.connect(self.grab_data)

    @pyqtSlot(object)
    def grab_data(self, data):
        print(data)
        self.plot.setData(data)


class Worker(QThread):
    signal = pyqtSignal(object)

    def __init__(self):
        super().__init__()

    def run(self):
        self.data = [0, 1]
        i = 2
        while True:
            self.data[1] = i
            self.signal.emit(self.data)
            time.sleep(0.01)
            i += 1

if __name__ == "__main__": 
    app = QApplication(sys.argv)

    widget = Main()
    worker = Worker()
    widget.make_connection(worker)
    worker.start()

    sys.exit(app.exec_())

【问题讨论】:

【参考方案1】:

信号槽方法是否复制传递的数据? 信号是线程安全的,并且在传输数据时会进行复制,因此数据之前的线程和使用它的线程(GUI Thread) 不会有冲突

为什么不需要调用QWidget的update()方法? 其实pyqtgraph调用的是update方法,plot是一个PlotDataItem,所以如果我们查看setData()方法的源码, 它调用updateItems() 方法,在该方法中调用curve 或scatter 属性的setData() 方法(根据图形的类型),在曲线的情况下其setData() 方法调用updateData (),updateData() 方法调用 update,在 scatter 的情况下,它的 setData() 方法调用 addpoint(),addPoints() 调用 invalidate(),而这个 invalidate() 方法调用 update()。

在使用信号时我必须使用任何类型的锁吗?不需要,因为信号是线程安全的,所以 Qt 已经设置了保护以避免冲突。

【讨论】:

非常感谢。我更改了脚本中的代码以通过信号传输数据。它工作(更好和更长),但是,在 Windows 上它停止(这一次,所有绘图同时停止)在 GUI 中的一些选项卡后停止。按键事件仍会更新 QLabel。你知道为什么会这样吗?编辑:重新标记并再次更新绘图一次。 @Leichti 如果没有minimal reproducible example,很难说出问题出在哪里,如果您需要提供 MCVE 的帮助 你是对的,对不起。但是,我最后的评论似乎不正确,程序现在运行没有任何问题!所以,对于所有其他有类似问题的人:使用信号和插槽!

以上是关于使用 pyqtgraph 和线程进行实时绘图的主要内容,如果未能解决你的问题,请参考以下文章

在 PyQt4 #2 中使用 PyQtGraph 进行实时绘图

pyQtgraph 绘图延迟串行绘图

PyQtGraph PlotWidget:如何强制每次绘制(更改范围)以进行“实时”绘图

即使设备断开连接,使用 PyQtGraph 的实时绘图仍在绘制

重新启动实时绘图时,pyqtgraph实时绘图中断

用Python串口实时显示数据并绘图pyqtgraph(详细教程)