繁体   English   中英

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

[英]Using pre-downsampled data when plotting large time series in PyQtGraph

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

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

我怎样才能做到这一点?

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

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

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

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

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

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

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


对性能的补充意见

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM