Python - Fast ploting using pyqtgraph (16ms)?

梦想与她 提交于 2020-01-30 06:44:28

问题


I need to plot an continuous input using pyqtgraph, so I use a circular buffer to hold the data. I use deque with maxlen to do the job. (Python 2.7, numpy 1.9.2, pyqtgraph 0.9.10)

from collections import deque
def create_cbuffer(self):
    buffer_len = self.BUFFER_LEN*self.number_of_points
    data = [0]*buffer_len # buffer_len = 160k
    self.cbuffer[0] = deque(data, maxlen=buffer_len)
    buffer_len = self.BUFFER_LEN
    data = [0]*buffer_len
    self.cbuffer[1] = deque(data, maxlen=buffer_len)

After that I use it like this:

import time
def update_cbuffer(self):
    data_points, data = data_feeds()  # data get every 16ms as lists
    start_t = time.time()
    self.cbuffer[0].extend(data_points) # Thanks to @PadraicCunningham
    # for k in xrange(0, self.number_of_points):
    #     self.cbuffer[0].append(data_points[k])
    self.cbuffer[1].append(data)
    fin_t = time.time() - start_t

setup plot as:

self.curve[0] = self.plots[0].plot(self.X_AXIS, 
                [0]*self.BUFFER_LEN*self.number_of_points,
                pen=pg.intColor(color_idx_0),name='plot1')
self.curve[1] = self.plots[1].plot(self.X_AXIS_2, [0]*self.BUFFER_LEN,
                pen=pg.intColor(color_idx_1),name='plot2')

update plot as:

def update_plots(self):
    self.curve[0].setData(self.X_AXIS, self.cbuffer[0])
    self.curve[0].setPos(self.ptr, 0)
    self.curve[1].setData(self.X_AXIS_2, self.cbuffer[1])
    self.curve[1].setPos(self.ptr, 0)
    self.ptr += 0.016

Then I call it using QTimer:

self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.update_cbuffer)
self.timer.timeout.connect(self.update_plots)
self.timer.start(16)

The Question is:

1. When I plot it, it seems to be much slower than 16ms. Any ideas to speed it up?

2. When I time the update_plots() using time.time() and calculate its average run time (total_time/number_of_runs), it increases gradually, I trying to understand the reason behind it.

Any suggestions? I am new to Python, I could make some mistakes in the code, please do not hesitate to point it out. Thank you for your help in advance.

p.s I've try different circular buffers as suggested in efficient circular buffer?

class Circular_Buffer():
    def __init__(self, buffer_len, data_type='float'):
        if data_type == 'int':
            self.__buffer = np.zeros(buffer_len, dtype=int)
        else:
            self.__buffer = np.zeros(buffer_len)
        self.__counter = 0

    def append(self, data):
        self.__buffer = np.roll(self.__buffer, -1)
        self.__buffer[-1] = data

    def get(self):
        return self.__buffer

But it turns out to be much slower in my case.

I've also try this:

class CB_list():
    def __init__(self, buffer_len):
        self.__buffer = [0]*buffer_len

    def append(self, data):
        self.__buffer = self.__buffer[1:]
        self.__buffer.append(data)

    def get(self):
        return self.__buffer

It performs similar as deque, so I stick with deque.

EDIT: Sorry I made a mistake yesterday. I've already correct it on the code.

data = [0]*buffer_len # buffer_len = 16k  <--- Should be 160k instead

回答1:


I'm not sure this is a complete answer, but the information is too long to turn into a comment, and I think it is critical for your understanding of the problem.

I think it is very unlikely you will get your timer to fire every 16 ms. Firstly, if your methods self.update_cbuffer and self.update_plots take longer than 16 ms to run, then the QTimer will skip firing when it should, and fire on the next multiple of 16 ms (eg if the methods take 31 ms to run, your timer should fire after 32 ms. If the methods then take 33 ms to run, the timer will next fire 48 ms after the previous one)

Furthermore, the accuracy of the timer is platform dependent. On windows, timers are only accurate to around 15 ms. As proof of this, I wrote a script to test on my windows 8.1 machine (code included at the end of the post). This graph shows the deviation from the expected timeout in ms.

In this case, my example was firing around 12ms early. Note that this isn't quite correct, as I don't think my code takes into account the length of time it takes to append the error to the list of errors. However, that time should be much less than the offset you see in my figure, nor does it account for the large spread of values. In short, timers on windows have an accuracy around the size of your timeout. Not a good combination.

Hopefully this at least explains why the code isn't doing what you expect. Without a minimilistic working example though, or comprehensive profiling of the code by yourself, it is difficult to know where the bottleneck in speed is.

As a small aside, pyqtgraph seemed to stop updating my histogram after a while when the timeout in my code below was very small. Not sure why that was.

Code to produce the above figure

from PyQt4 import QtGui, QtCore
import sys
import time
import pyqtgraph as pg
import numpy as np

start_time = time.time()

timeout = 0.16 # this is in SECONDS. Change to vary how often the QTimer fires
time_list = []

def method():
    global start_time
    time_list.append((timeout-(time.time()-start_time))*1000)
    start_time = time.time()

def update_plot():
    y,x = np.histogram(time_list, bins=np.linspace(-15, 15, 40))
    plt1.plot(x, y, stepMode=True, fillLevel=0, brush=(0,0,255,150))

app = QtGui.QApplication(sys.argv)

win = pg.GraphicsWindow()
win.resize(800,350)
win.setWindowTitle('Histogram')
plt1 = win.addPlot()
y,x = np.histogram(time_list, bins=np.linspace(-15, 15, 40))
plt1.plot(x, y, stepMode=True, fillLevel=0, brush=(0,0,255,150))
win.show()

timer = QtCore.QTimer()
timer.timeout.connect(method)
timer.timeout.connect(update_plot)
timer.start(timeout*1000)

sys.exit(app.exec_())


来源:https://stackoverflow.com/questions/29646053/python-fast-ploting-using-pyqtgraph-16ms

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!