簡體   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