Использование данных с предварительной субдискретизацией при построении больших временных рядов в PyQtGraph

Мне нужно построить большой временной ряд в PyQtGraph (миллионы точек). Начертить его как есть практически невозможно, и при включении опций оптимизации (понижающая дискретизация с использованием setDownsampling и отсечение с использованием setClipToView) он по-прежнему едва пригоден для использования при уменьшении масштаба (только при увеличении он становится быстрым благодаря отсечению).

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

Как я могу этого добиться?


person Andrzej Pronobis    schedule 28.05.2015    source источник


Ответы (2)


Я сделал что-то подобное в проекте, над которым работаю, под названием runviewer. Общая идея заключается в повторной выборке данных всякий раз, когда изменяется x-диапазон графика. Приблизительный метод, который мы используем:

  • Подключите метод к сигналу sigXRangeChanged сигнала PlotWidget, который устанавливает логический флаг, указывающий, что данные должны быть передискретизированы.

  • Запустите поток, который опрашивает логический флаг каждые x секунд (мы выбрали 0,5 секунды), чтобы увидеть, нужно ли выполнять повторную выборку данных. Если да, данные передискретизируются с использованием алгоритма по вашему выбору (мы написали свой собственный на C). Затем эти данные отправляются обратно в основной поток (например, используйте QThread и отправляйте сигнал обратно в основной поток), где выполняется вызов pyqtgraph для обновления данных на графике (обратите внимание, вы можете вызывать методы pyqtgraph только из основная тема!)

Мы используем логический флаг, чтобы отделить события изменения x-диапазона от повторной выборки. Вы не хотите передискретизировать каждый раз, когда изменяется x-диапазон, так как сигнал запускается много раз, когда вы увеличиваете масштаб с помощью мыши, и вы не хотите генерировать очередь вызовов передискретизации, поскольку передискретизация выполняется медленно, даже с C !

Вы также должны убедиться, что ваш поток передискретизации немедленно устанавливает логический флаг в False, если он обнаруживает, что он имеет значение True, а затем запускает алгоритм передискретизации. Это делается для того, чтобы последующие события изменения x-диапазона во время текущей передискретизации приводили к последующей передискретизации.

Вы также, вероятно, могли бы улучшить это, не опрашивая флаг, а используя какое-то потоковое событие/условие.

Обратите внимание, что повторная выборка с помощью Python очень, очень медленная, поэтому мы решили написать алгоритм повторной выборки C и вызывать его из Python. numpy в основном на C, так что будет быстро. Однако я не думаю, что у них была функция, сохраняющая алгоритм передискретизации. Большинство людей, выполняющих повторную выборку, — это просто стандартная понижающая выборка, при которой вы берете каждую N-ю точку, но мы хотели по-прежнему иметь возможность видеть наличие функций, меньших размера выборки при уменьшении масштаба.


Дополнительные комментарии по производительности

Я подозреваю, что часть проблемы производительности со встроенным методом pyqtgraph заключается в том, что понижение частоты дискретизации выполняется в основном потоке. Таким образом, понижающая дискретизация должна быть завершена до того, как график снова станет реагировать на пользовательский ввод. Наш метод позволяет этого избежать. Наш подход также ограничивает количество раз, когда происходит понижение частоты дискретизации, максимум раз в the length of time it takes to down-sample + the poll delay секунды. Таким образом, с задержкой, которую мы используем, мы снижаем частоту дискретизации только каждые 0,5-1 секунду, сохраняя при этом отзывчивость основного потока (и, следовательно, пользовательского интерфейса). Это означает, что пользователь может увидеть данные с грубой выборкой, если быстро увеличить масштаб, но это исправляется не более чем за 2 итерации повторной выборки (то есть задержка не более 1-2 секунд). Кроме того, поскольку на исправление требуется короткое время, обновление/перерисовка с использованием новых выборочных данных часто выполняется после того, как пользователь закончил взаимодействие с пользовательским интерфейсом, поэтому он не замечает зависаний во время перерисовки.

Очевидно, что время, которое я цитирую, полностью зависит от скорости передискретизации и задержки опроса!

person three_pineapples    schedule 28.05.2015
comment
Спасибо за ваше предложение. Чем это решение отличается от опции autoDownsample, встроенной в PyQtGraph (pyqtgraph.org/documentation/graphicsItems /plotdataitem.html)? Я уже использую это, и постоянное понижение частоты дискретизации каждый раз, когда я увеличиваю масштаб, делает все очень медленным. - person Andrzej Pronobis; 29.05.2015
comment
@Andrzej Andrzej Я добавил несколько дополнительных комментариев о производительности этого подхода в редактировании моего ответа (это было слишком долго для комментария!). Дайте мне знать, если вам нужна дополнительная информация или у вас есть дополнительные вопросы. - person three_pineapples; 29.05.2015
comment
Спасибо за ваши полезные предложения @three_pineapples! Как я уже сказал в своем ответе, я решил пойти другим путем, поскольку мои статические данные позволили мне предварительно вычислить сигналы с пониженной частотой дискретизации и таким образом еще больше ускорить процесс. - person Andrzej Pronobis; 30.05.2015

Ответ @three_pineapples описывает действительно хорошее улучшение по сравнению с понижением частоты дискретизации по умолчанию в PyQtGraph, но оно по-прежнему требует выполнения понижения частоты дискретизации на лету, что в моем случае проблематично.

Поэтому я решил реализовать другую стратегию, а именно предварительно проредить данные, а затем выбрать либо уже прореженные данные, либо исходные данные в зависимости от «уровня масштабирования».

Я комбинирую этот подход со стратегией автоматического понижения дискретизации по умолчанию, изначально используемой PyQtGraph, для дальнейшего повышения скорости (которую можно улучшить с помощью предложений @three_pineapples).

Таким образом, PyQtGraph всегда начинает с данных гораздо меньшей размерности, что делает масштабирование и панорамирование мгновенными даже при очень большом количестве выборок.

Мой подход резюмируется в этом коде, который исправляет метод getData PlotDataItem.

# Downsample data
downsampled_data = downsample(data, 100)

# Replacement for the default getData function
def getData(obj):
    # Calculate the visible range
    range = obj.viewRect()
    if range is not None:
        dx = float(data[-1, 0] - data[0, 0]) / (data.size[0] - 1)
        x0 = (range.left() - data[0, 0]) / dx
        x1 = (range.right() - data[0, 0]) / dx
    # Decide whether to use downsampled or original data
    if (x1 - x0) > 20000:
        obj.xData = downsampled_data[:, 0]
        obj.yData = downsampled_data[:, 1]
    else:
        obj.xData = data[:, 0]
        obj.yData = data[:, 1]
    # Run the original getData of PlotDataItem
    return PlotDataItem.getData(obj)

# Replace the original getData with our getData
plot_data_item.getData = types.MethodType(getData, plot_data_item)
person Andrzej Pronobis    schedule 29.05.2015