使用 pyqtgraph 和线程的实时绘图
问题描述
这有点长,第一部分只是对问题的描述,第二部分是我的修复"是否正确的问题.
this is a bit longer, the first part is just a description of the problem, the second one the question if my "fix" is correct.
我从 python 编程开始.我创建了一个程序,它与读取我们熔化实验室熔炉温度的 Arduino 进行通信.然后在 PID 算法中使用温度,并将输出设置为 Arduino.通信是通过 pyserial 完成的.到目前为止,一切都有效,包括实时绘制温度信号、PID 变量等.该脚本包括一个主循环和 3 个线程(串行通信、从串行端口读取的数据移位器、来自 QWidget 的设定温度和 PID 算法的输出.这些值用于创建一个数组以在 pyqtgraph 中显示.最后,第三个线程将数据从 datashifter 转移到 QWidget.
I started with python programming. I created a program that communicates with an Arduino that reads the temperature of a furnace of our melting lab. The temperature is then used in a PID algorithm and an output is set to the Arduino. The communication is done via pyserial. So far, everthing works, including live plotting of the temperature signals, PID-variables and so on. The script includes a the main loop and 3 threads (serial communication, a datashifter that reads from serialport, the set temperature from the QWidget and the output of the PID algorithm. This values are used to create an array for displaying within pyqtgraph. Finally, the third thread shifts the data from the datashifter to the QWidget.
当使用我的 Linux 笔记本时,一切正常,并且 GUI 从未停止更新.相比之下,当使用任何 Windows 主机时,我会遇到一些 pyqtgraphs 停止刷新的问题.这种行为很奇怪,因为我或多或少地同时设置了所有数据,使用相同的 numpy 数组(只是不同的列)——有些图刷新时间更长(小时),有些更早停止(分钟).在搜索了或多或少的漏洞之后 ;-) 我认为我发现了问题:它是从线程到 GUI 的数据传递.一些虚拟代码来解释发生了什么:
When using my Linux-Notebook, everything works fine, and the GUI never stops updating. In contrast, when using any Windows-Host, i have the problem that some pyqtgraphs stop to refresh. The behavior is strange, because i set all data at more or less the same time, with the same numpy array (just different columns) - some plots refresh longer (hours), some stop earlier (minutes). After searching more or less the hole internet ;-) I think that I found the problem: Its the passing of data from from a thread to the GUI. Some dummy code to explain what's going on:
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 上).
Neither calling self.gui.update() nor processEvents() has any influence on the outcome: The plots stop redrawing after a while (on windows).
现在我有一个非常简单的例子,只是想确定我是否正确使用了线程.它工作正常,但我有一些问题:
Now i have a very simple example, and just want to make sure if I'm using the threading-stuff correctly. It works fine, but I have some questions:
- 信号槽方法是否复制传递的数据?
- 为什么不需要调用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_())
解决方案
信号槽方法是否复制传递的数据? 信号是线程安全的,并且在传输数据时会进行复制,所以数据之前的线程和消费它的线程(GUI线程)不会有冲突
Does the signal-slot approach copy the passed data? The signals are thread-safe and when transferring data they make a copy so the thread that precedes the data and the thread that consumes it (GUI Thread) will not have conflicts
为什么不需要调用QWidget的update()方法? 其实pyqtgraph调用的是update方法,plot是一个PlotDataItem,所以如果我们查看setData() 方法,它调用 updateItems() 方法,在该方法中的 setData() 方法曲线 或 scatter 属性(根据图形的类型),在曲线的情况 setData() 方法调用 updateData() 和 updateData() 方法调用更新,在分散的情况下,它的 setData() 方法调用 addpoint() 和 addPoints() 调用 invalidate(),而这个 invalidate() 方法调用 update().
Why is it not necessary to call the update() method of the QWidget? Actually pyqtgraph calls the update method, plot is a PlotDataItem, so if we check the source code of setData() method, it calls the updateItems() method, in that method the setData() method of the curve or scatter attribute is called (according to the type of graphics), in the case of curve its setData() method calls updateData(), and the updateData() method calls update, and in the case of the scatter its setData() method calls addpoint(), and addPoints() calls invalidate(), and this invalidate() method calls update().
在使用信号时我必须使用任何类型的锁吗?不需要,因为信号是线程安全的,所以 Qt 已经设置了保护以避免冲突.
Do I have to use any kind of locks when using signals? No, as the signals are thread-safe so Qt already has the guards set to avoid the collision.
相关文章