Python — быстрый график с использованием pyqtgraph (16 мс)?

Мне нужно построить непрерывный ввод с помощью pyqtgraph, поэтому я использую круговой буфер для хранения данных. Я использую deque с maxlen для выполнения этой работы. (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)

После этого я использую его так:

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

настроить сюжет как:

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')

обновить сюжет как:

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

Затем я вызываю его с помощью QTimer:

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

Вопрос:

1. Когда я рисую, кажется, что это намного медленнее, чем 16 мс. Есть идеи по ускорению?

2. Когда я определяю время update_plots() с помощью time.time() и вычисляю его среднее время выполнения (total_time/number_of_runs), оно постепенно увеличивается, я пытаюсь понять причина этого.

Есть предложения? Я новичок в Python, я могу сделать некоторые ошибки в коде, пожалуйста, не стесняйтесь указывать на это. Спасибо за вашу помощь заранее.

p.s. Я пробовал разные циклические буферы, как это предлагается в эффективном циклическом буфере?

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

Но в мой случай.

Я также пробовал это:

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

Он работает так же, как deque, поэтому я придерживаюсь deque.

РЕДАКТИРОВАТЬ: Извините, я сделал ошибку вчера. Я уже исправил это в коде.

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

person cityzz    schedule 15.04.2015    source источник
comment
Используйте xrange вместо диапазона, диапазон создает список. Если вы добавляете все data, просто используйте self.cbuffer[0].extend(data). Если вы добавляете фрагмент, используйте self.cbuffer[0].extend(itertools.islice(data,None,self.number_of_points))   -  person Padraic Cunningham    schedule 15.04.2015
comment
@PadraicCunningham Спасибо, я подумаю об этом. :D   -  person cityzz    schedule 15.04.2015
comment
@PadraicCunningham Как насчет метода update_plots()? Может ли это быть более эффективным?   -  person cityzz    schedule 15.04.2015
comment
Вы можете добавить ссылку на полный код?   -  person Padraic Cunningham    schedule 15.04.2015
comment
@PadraicCunningham Спасибо за вашу помощь, но я боюсь, что не могу связать их все, потому что это работает в рамках проприетарного кода. Я думаю, что все соответствующие части здесь. Ничто другое не должно влиять на скорость печати.   -  person cityzz    schedule 15.04.2015
comment
Откуда на самом деле берутся данные? Сколько времени занимает data_feeds?   -  person sebastian    schedule 15.04.2015
comment
@sebastian data_feeds поступают из другого потока, и для «Создания» 2 новых dict, data_points (длина 160 КБ), данных (длина 625) потребовалось менее 10 мс. Когда я замерил время update_cbuffer(), это заняло менее 1 мс.   -  person cityzz    schedule 16.04.2015


Ответы (1)


Я не уверен, что это полный ответ, но информация слишком длинная, чтобы превратить ее в комментарий, и я думаю, что она имеет решающее значение для вашего понимания проблемы.

Я думаю, что очень маловероятно, что ваш таймер будет срабатывать каждые 16 мс. Во-первых, если ваши методы self.update_cbuffer и self.update_plots выполняются дольше 16 мс, то QTimer пропустит срабатывание, когда должно, и сработает при следующем кратном 16 мс (например, если для запуска методов требуется 31 мс, ваш таймер должен сработать через 32 мс. Если для запуска методов потребуется 33 мс, следующий таймер сработает через 48 мс после предыдущего)

Кроме того, точность таймера зависит от платформы. В Windows таймеры имеют точность около 15 мс. В качестве доказательства я написал скрипт для тестирования на моей машине с Windows 8.1 (код включен в конце поста). На этом графике показано отклонение от ожидаемого времени ожидания в мс.ошибка в триггере времени ожидания

В этом случае мой пример срабатывал примерно на 12 мс раньше. Обратите внимание, что это не совсем правильно, поскольку я не думаю, что мой код учитывает время, необходимое для добавления ошибки в список ошибок. Однако это время должно быть намного меньше, чем смещение, которое вы видите на моем рисунке, и оно не объясняет большой разброс значений. Короче говоря, таймеры в окнах имеют точность, равную размеру вашего тайм-аута. Не очень хорошее сочетание.

Надеюсь, это хотя бы объясняет, почему код не делает то, что вы ожидаете. Однако без минималистического рабочего примера или самостоятельного всестороннего профилирования кода трудно понять, где находится узкое место в скорости.

Небольшое замечание: pyqtgraph, похоже, перестал обновлять мою гистограмму через некоторое время, когда тайм-аут в моем коде ниже был очень маленьким. Не уверен, почему это было.

Код для создания приведенного выше рисунка

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_())
person three_pineapples    schedule 15.04.2015
comment
Спасибо за ваше объяснение о QTimers. После того, как я замерил время update_cbuffer() и update_plots(), я обнаружил, что update_cbuffer() занимает меньше 1 мс, а update_plots() — это совсем другая история. Время выполнения update_plots() постоянно увеличивается, начиная с 13 мс. Поэтому я думаю, что могут возникнуть проблемы с self.curve[0].setData(), особенно с большим объемом данных (в моем случае 160 КБ). Как я могу узнать, откуда эта проблема? Есть ли другой более быстрый способ сделать это? - person cityzz; 16.04.2015
comment
@cityzz Вы имеете в виду 160 тысяч баллов? Это очень много. Учитывая, что у вас ограниченное разрешение на экране, вы можете сначала рассмотреть возможность понижения разрешения таким образом, чтобы сохранить функции (не уверен, что это обязательно сэкономит вам время). В противном случае вы могли бы углубиться в код PyQtGraph, чтобы узнать, что занимает больше всего времени (или использовать какой-либо инструмент профилирования), но я подозреваю, что самая медленная часть на самом деле рисует все данные на экране, которые вы не сможете ускорить . - person three_pineapples; 16.04.2015
comment
Спасибо, что указали на это, я сейчас делаю субдискретизацию, надеюсь, это ускорит графики. - person cityzz; 16.04.2015
comment
@cityzz Обратите внимание, я не знаю подробностей того, как вы понижаете дискретизацию, но, по моему опыту, самый быстрый способ сделать это - фактически выгрузить его в расширение C. Python обычно медленный при даунсэмплинге, и если скорость является проблемой, то вам следует помещать вещи в код C (вызываемый из Python), где это необходимо. Это, конечно, требует изучения расширений C, использования numpy из расширений C и способов их компиляции, а также фактического написания кода C. - person three_pineapples; 16.04.2015
comment
Я использую массив numpy для его понижения на данный момент, это кажется разумным быстро. _data = _data.reshape (-1, R). среднее (ось = 1) - person cityzz; 16.04.2015