在 PyQtGraph 中绘制大时间序列时使用预下采样数据

Posted

技术标签:

【中文标题】在 PyQtGraph 中绘制大时间序列时使用预下采样数据【英文标题】:Using pre-downsampled data when plotting large time series in PyQtGraph 【发布时间】:2015-08-10 10:56:33 【问题描述】:

我需要在 PyQtGraph 中绘制一个大的时间序列(数百万个点)。按原样绘制它实际上是不可能的,并且在打开优化选项时(使用setDownsampling 进行下采样并使用setClipToView 进行裁剪)它在缩小时仍然几乎不可用(只有在放大时它会因为裁剪而变得更快)。

不过我有个主意。我可以对我的数据进行预下采样,因为它们是静态的。然后,我可以在缩小时使用缓存的下采样数据,在放大时使用原始数据。

我怎样才能做到这一点?

【问题讨论】:

【参考方案1】:

我在一个名为runviewer 的项目中做过类似的事情。一般的想法是在绘图的 x 范围发生变化时重新采样数据。我们使用的大致方法是:

将方法连接到PlotWidgetsigXRangeChanged 信号,该信号设置一个布尔标志,指示数据需要重新采样。

启动一个线程,每 x 秒(我们选择 0.5 秒)轮询布尔标志,以查看是否需要对数据进行重新采样。如果是,则使用您选择的算法对数据进行重新采样(我们用 C 编写了自己的算法)。然后将此数据发送回主线程(例如,使用QThread 并向主线程发出信号),其中调用 pyqtgraph 以更新图中的数据(注意,您只能调用 pyqtgraph 方法来自主线程!)

我们使用布尔标志将 x 范围变化事件与重采样分离。您不想在每次 x 范围更改时重新采样,因为当您使用鼠标缩放时会多次触发信号,并且您不想生成重新采样调用队列,因为重新采样很慢,即使使用 C !

您还需要确保您的重采样线程在检测到布尔标志为 True 时立即将其设置为 False,然后运行重采样算法。这样一来,当前重采样期间的后续 x 范围更改事件会导致后续重采样。

您也可以通过不轮询标志,而是使用某种线程事件/条件来改善这一点。

请注意,使用 Python 进行重采样非常非常慢,这就是我们选择编写重采样算法 C 并从 Python 调用它的原因。 numpy 主要是在 C 中,所以会很快。但是我认为他们没有保留特征的重采样算法。大多数人所做的重采样只是标准的下采样,即每第 N 个点取一次,但我们希望在缩小时仍然能够看到小于采样大小的特征的存在。


关于性能的其他 cmets

我怀疑pyqtgraph内置方法的部分性能问题是下采样是在主线程中完成的。因此,必须在图形再次响应用户输入之前完成下采样。我们的方法避免了这种情况。我们的方法还将下采样发生的次数限制为最多每the length of time it takes to down-sample + the poll delay 秒一次。因此,通过我们使用的延迟,我们仅每 0.5-1 秒进行一次下采样,同时保持主线程(以及 UI)响应。这确实意味着如果用户快速放大,他们可能会看到粗略采样的数据,但这会在最多 2 次重新采样迭代中得到纠正(因此最多延迟 1-2 秒)。此外,由于需要很短的时间来纠正,使用新采样数据的更新/重绘通常是在用户完成与 UI 的交互之后完成的,因此他们不会注意到重绘过程中的任何无响应。

显然,我引用的时间完全取决于重新采样的速度和轮询延迟!

【讨论】:

感谢您的建议。该解决方案与 PyQtGraph (pyqtgraph.org/documentation/graphicsItems/plotdataitem.html) 中内置的 autoDownsample 选项有何不同?我已经在使用那个了,每次缩放时不断的下采样会让事情变得超级慢。 @Andrzej 我在编辑我的答案时添加了一些关于这种方法性能的额外 cmets(评论太长了!)。如果您想了解更多详情或有其他问题,请告诉我。 感谢您的有用建议@three_pineapples!正如我所说,在我的回答中,我决定采用另一种方式,因为我的静态数据允许我预先计算下采样信号并以这种方式加快速度。【参考方案2】:

@three_pineapples 的回答描述了对 PyQtGraph 中默认下采样的一个非常好的改进,但它仍然需要动态执行下采样,这在我的情况下是有问题的。

因此,我决定实施不同的策略,即对数据进行预下采样,然后根据“缩放级别”选择已经下采样的数据或原始数据。

我将该方法与 PyQtGraph 原生采用的默认自动下采样策略相结合,以进一步提高速度(可以通过 @three_pineapples 的建议进一步改进)。

这样,PyQtGraph 总是从低维数的数据开始,这使得即使有大量样本也可以立即进行缩放和平移。

这段代码总结了我的方法,猴子修补了PlotDataItem的getData方法。

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

【讨论】:

以上是关于在 PyQtGraph 中绘制大时间序列时使用预下采样数据的主要内容,如果未能解决你的问题,请参考以下文章

Python 3.3 pyqtgraph 无法绘制点

如何在pyqtgraph中绘制十字准线并绘制鼠标位置?

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

如何在 pyqtgraph 远程视图上绘制切片的 numpy 数据数组

使用 GLMeshItem pyqtgraph 绘制时制作透明项目

用PyQtGraph绘制可视化数据图表