🌊

リアルタイム波形表示アプリケーションの作成vol.1

2024/06/20に公開

はじめに

先日,私はある記事でPyQtGraphというライブラリについて知りました.PyQtGraphは,PyQtPySideを使用してデータの可視化やGUIアプリケーションの開発を行うためのライブラリです.
そこで,PyQtGraphとNumpyを組み合わせれば,面白いアプリケーションが作れるのではないかと考えました.PyQtGraphの可視化機能とNumpyの数値計算機能を組み合わせることで,データの生成からリアルタイムの可視化まで,一貫したワークフローを実現できると思ったのです.
そこで今回は,NumpyでランダムなデータをリアルタイムでPyQtGraphを使って波形として表示するアプリケーションを作成してみました.

実行環境

  • Windows(10 or 11)
  • Windows用ソースコードエディター(VS codeなど)
  • Python 3.12.1(またはPython 3.10.9)
  • PyQtGraph 0.13.7
  • Numpy 1.26.4
  • PyQt5 5.15.10(またはPySide2)

実装

  1. 必要なライブラリをインポートします.
pip install pyqtgraph
pip install PyQt5
pip install numpy
  1. Numpyでデータを生成し,波形変換していきます.
import numpy as np
import pyqtgraph as pg
from PySide2 import QtWidgets, QtCore
app = QtWidgets.QApplication([])
win = pg.GraphicsLayoutWidget(show=True, title="リアルタイム波形表示")
plot = win.addPlot(title="Waveform")

data = np.random.normal(size=100)
curve = plot.plot(data)

def update():
    global data, curve
    data[:-1] = data[1:]
    data[-1] = np.random.normal()
    curve.setData(data)

timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(50)

if __name__ == '__main__':
    QtWidgets.QApplication.instance().exec_()

始めにQtWidgets.QApplication([])を使用して
QApplicationをインスタンス化し,pg.GraphicsLayoutWidgetを作成しています.
これにより,ウィンドウとプロットエリアが作成されます.
次にnp.random.normal(size=100)を使用して,サイズが100の正規分布に従う乱数データを生成し,plot.plot(data)でそのデータをプロットしています.
そしてupdate関数では,グローバル変数dataとcurveを使用して実際のデータ更新を行っています.data[:-1] = data[1:]は,dataの最後の要素を除く全ての要素を1つずつずらし,最後の要素に新しい乱数を代入しています.そして,curve.setData(data)でプロットを更新しています.
最後に,QtCore.QTimer()を使用してタイマーを作成し,timer.timeout.connect(update)でタイマーのタイムアウトイベントにupdate関数を接続し,timer.start(50)で50ミリ秒ごとにupdate関数が呼び出されるように設定しています.
上記のコードを実行するとこのようになります.


発展

より発展的なものにするために複数の波形の同時表示や,サンプリングレートを調整するためのスライダーを追加したりなどを組み合わせることで,リアルタイム波形表示をより高度で実用的なものにすることができると考えました.
今回は3つの波形を同時に表示し,ボタンによる波形更新の制御とサンプリングレートを調整するためのスライダーを追加します.

import numpy as np
import pyqtgraph as pg
from PyQt5 import QtWidgets, QtCore

class RealTimeWaveformPlotter(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("リアルタイム波形表示")
        self.central_widget = QtWidgets.QWidget()
        self.setCentralWidget(self.central_widget)
        self.layout = QtWidgets.QVBoxLayout(self.central_widget)

        self.plot_widget = pg.PlotWidget()
        self.layout.addWidget(self.plot_widget)

        self.start_stop_button = QtWidgets.QPushButton("Start")
        self.start_stop_button.clicked.connect(self.start_stop)
        self.layout.addWidget(self.start_stop_button)

        self.sample_rate_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        self.sample_rate_slider.setRange(1, 100)
        self.sample_rate_slider.setValue(50)
        self.sample_rate_slider.valueChanged.connect(self.update_sample_rate)
        self.layout.addWidget(self.sample_rate_slider)

        self.curves = []
        self.data = []
        for _ in range(3):
            data = np.zeros(100)
            self.data.append(data)
            curve = self.plot_widget.plot(data)
            self.curves.append(curve)

        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update_plot)

    def start_stop(self):
        if self.timer.isActive():
            self.timer.stop()
            self.start_stop_button.setText("Start")
        else:
            self.timer.start()
            self.start_stop_button.setText("Stop")

    def update_sample_rate(self, value):
        self.timer.setInterval(value)

    def update_plot(self):
        for i in range(len(self.curves)):
            data = self.data[i]
            data[:-1] = data[1:]
            data[-1] = np.random.normal()
            self.curves[i].setData(data)

if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    plotter = RealTimeWaveformPlotter()
    plotter.show()
    app.exec_()

QtWidgets.QMainWindowを継承したRealTimeWaveformPlotterクラス

__init__メソッドでは,ウィンドウのタイトルを設定し,中央のウィジェットとレイアウトを作成しています.
start_stop_buttonは,"Start"ボタンを作成し,クリック時にstart_stopメソッドを呼び出します.
curvesとdataは,3つの曲線とそれに対応するデータを保持するためのリストです.

start_stopメソッド

ボタンがクリックされたときに,タイマーを開始または停止し,ボタンのテキストを変更します.

update_sample_rateメソッド

サンプルレートが変更されたときに,タイマーのインターバルを更新します.

update_plotメソッド

曲線のデータを更新し,プロットをリアルタイムで更新します.
上記のコードを実行するとこのようになります.

このようにSTART/STOPボタンとサンプリングレートを調整するためのスライダーが追加されました.
ただこれだと,どれがどの波形がわかりませんね.
これでは波形を増やした意味がありません.
色分けやラベルを追加すればより見やすくなると思います.
これについては次回にしたいと思います.
次回の記事は下記のリンクから是非お読みください!

https://zenn.dev/miguel/articles/f6952a8ad4e942

まとめ

このチュートリアルでは,PyQtGraphとNumpyを使って,リアルタイムに波形を表示するアプリケーションを作成しました.発展で波形の更新速度を調整するためのスライダーと,波形の更新を開始・停止するためのボタンも用意しましたが,見にくい表示でしたので色分けなどの追加実装が必要です.

その他リソース

Discussion