Wizard Notes

Python, JavaScript を使った音楽信号分析の技術録、作曲活動に関する雑記

Python:PyQtGraphで散布図グラフをリアルタイムプロット

matplotlibよりも滑らかなリアルタイムプロットができる PyQtGraph で散布図をリアルタイムプロットしてみました。

1000点でアルファ値(透明度)を設定していますが、約60fpsでリアルタイムプロットできていることを確認しました。

こちらの散布図の実装方法とソースコードを紹介したいと思います。

PyQtGraphにおける散布図リアルタイムプロットの流れ

抽象的なコードで流れを説明します。

# -*- coding: utf-8 -*-
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg

# (1) PyQtのウインドウ生成
app = QtGui.QApplication([])
# (2) GraphicsLayoutWidged オブジェクト生成
win = pg.GraphicsLayoutWidget()
win.show()
# (3) PlotItem オブジェクト生成
plot = win.addPlot(title="real-time scatter plot") 
# (4) PlotDataItemオブジェクト生成
curve = plot.plot(pen=...) 

# 初期データ(numpy.ndarrayオブジェクト)生成
n_data = 1000
x = ...
y = ...

# (5) 更新処理を記述した関数
def update():
    global x, y, curve, plot

    # x, y を更新
    x += ...
    y += ...
    # データ更新
    curve.setData(x, y)
    
# (6) 定期的に(5)の関数を実行
fps = 60
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(1/fps * 1000)


# アプリケーション実行
if __name__ == '__main__':
    import sys
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

要点だけまとめると、

  1. PyQtのウインドウ生成
    • PyQt5.QtWidgets.QApplication オブジェクト生成
  2. GraphicsLayoutWidgedオブジェクト生成
  3. PlotItem オブジェクト生成
  4. PlotDataItemオブジェクト生成
  5. 更新処理を記述した関数を実装
  6. 定期的に(5)の関数を実行

という流れになります。

1~4までで、

  • QApplication
  • GraphicsLayoutWidged
  • PlotItem
  • PlotDataItem

という4種類の異なるクラスのオブジェクトが生成されています。

これらの関係は、以下の図が参考になります。

なお、GraphicsViewクラスはPlotItemの親クラスでもあります。

f:id:Kurene:20210610231744p:plain:w400
https://pyqtgraph.readthedocs.io/en/latest/plotting.html

すなわち、それぞれのクラスは以下のような役割を持っています。

  • GraphicsLayoutWidged
    • 複数のプロットのレイアウトを設定する
  • PlotItem
    • 標準の 2D プロット領域
    • 軸、ラベル、インタラクティブなグラフ操作に関する処理を扱う
  • PlotDataItem
    • プロットするデータ、属性、プロット方法を扱う

散布図の属性

よく使う散布図の属性は、以下のように設定します。

ただし、

p = win.addPlot() # PlotItem オブジェクト
curve = p.plot(pen=None) # PlotDataItem オブジェクト

とします。

  • 散布図の線
    • p.plot(pen=None) でシンボルのみのグラフ=散布図ができる
    • 折れ線グラフの線に関する属性をセットできる 参考ページ
  • シンボル
    • matplotlib だと marker
    • p.plot(symbol="o")
  • シンボルの外周の色,
    • p.plot(symbolPen='b')
  • シンボルの内部の色
    • p.plot(symbolBrush='c')
  • シンボルのサイズ
    • p.plot(symbolSize=10)
  • 軸の範囲
    • X軸: p.setXRange(-5, 5)
    • Y軸: p.setYRange(-5, 5)
    • XY軸のオートスケールOn/Off: p.enableAutoRange('xy', False)
  • プロットするデータの更新
    • curve.setData(x, y) アルファ値(透過度)
    • curve.setAlpha(0.5, False)

ラベルなど軸関係は以下のWebページが参考になります

AxisItem — pyqtgraph 0.12.1 documentation

実装例・ソースコード

f:id:Kurene:20210610233143g:plain:w400

# -*- coding: utf-8 -*-
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
import time


app = QtGui.QApplication([])# PyQtのアプリ生成
win = pg.GraphicsLayoutWidget() # GraphicsWindow() オブジェクト生成
win.resize(500, 500)
win.show()

p = win.addPlot(title="real-time scatter plot") # PlotItem オブジェクト生成
curve = p.plot(pen=None, symbol="o", symbolPen='b', symbolSize=10, symbolBrush='c') # PlotDataItemオブジェクト生成。pen=Noneで点のみ
pg.setConfigOptions(antialias=True)

n_data = 1000
x = np.random.normal(0.0, 1.0, n_data)
y = np.random.normal(0.0, 1.0, n_data)
p.setXRange(-5, 5)
p.setYRange(-5, 5)

iter = 0
pretime = time.time()
def update():
    global iter, p, x, y, curve
    global pretime
    
    # x, y座標を変更(アニメーション)
    x += np.random.normal(0.0, 0.1, n_data) + 0.01*np.cos(2*np.pi*iter/300)
    y += np.random.normal(0.0, 0.1, n_data) + 0.01*np.sin(2*np.pi*iter/120)

    if iter > 0:
        p.enableAutoRange('xy', False) # x, y軸のスケール固定
    curve.setData(x, y)
    curve.setAlpha(0.5, False)
    
    curtime = time.time()
    fps = 1.0 / (curtime - pretime + 1e-16)
    p.setTitle(f"fps: {fps:0.1f} Hz")
    pretime = curtime
    iter += 1
    
fps = 60
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(1/fps * 1000)


if __name__ == '__main__':
    import sys
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

参考

Pythonにおける リアルタイムプロットは matplotlib ではなく PyQtGraph がオススメである理由は、以下の記事をご覧ください。

www.wizard-notes.com