相変わらず引きこもり生活な中の人です。今回はMatplotlibで散布図や折れ線グラフを高速描画する話です。
1.Matplotlibの描画の話
Pythonでグラフ描画を行う際によく使うのがMatplotlibです.Matlab-likeなインターフェースで扱いやすいのですが,リアルタイム描画を行おうとすると非常に遅いことがあります.
これに関してはいろいろな高速化のやり方があるのですが,この中でも手動更新のコードを自分で書くことで高速化を果たすやり方だと,データ数によっては100fps以上を狙うことができるらしいです.
参考:hukkumameo,【Python】matplotlibの手動で描画更新,俺言語。
2.手動更新について
描画の更新方法はいくつかあるのですが,今回は
- 描画領域を白く塗る
- データを描画する
- 描画領域のアップデート
- 画面の描画更新
でやってみました.(1.の参考に書いてあるcase4)
3.折れ線グラフ
折れ線グラフはhukkumameo氏がやった通りなのですが,少しいじってクラス化しました.まずは初期化から.
class Line:
def __init__(self, fig, ax, plot_area=(1000, 1000), len_points=100):
self.fig = fig
self.plot_area = plot_area
# axis setup
self.line_ax = ax
self.line_ax.set_xlim(0, plot_area[0])
self.line_ax.set_ylim(-plot_area[1], plot_area[1])
self.ydata = [0.0 for x in range(len_points)]
self.line, = self.line_ax.plot(self.ydata)
# show figure
self.fig.canvas.draw()
self.fig.show()
高速化のために自動での軸レンジの計算を手動に変更し,初期化部分で設定しています.描画するデータに関しては,専用のリスト(FIFO)を作成し,データ更新が走るたびに一番古いデータを破棄し,新しいデータを足していくようにしました.
次は更新周りです.
def update_data(self, points):
# draw background with white
self.line_ax.draw_artist(self.line_ax.patch)
# plot points
self.ydata.append(points)
self.ydata.pop(0)
self.line.set_ydata(self.ydata)
self.line_ax.draw_artist(self.line)
# update this graph
self.fig.canvas.blit(self.line_ax.bbox)
def plot(self, ydata):
self.update_data(ydata)
self.fig.canvas.flush_events()
自分で書いておいてなんですが,update_dataとplotの分割はあんまり意味は無さそうです...
4.散布図
折れ線ができたら散布図もやりたいということで,実際にやってみました.描画の基本方針は折れ線グラフとは変わらないのですが,やり方が少し変わってきます.このあたりの話に関しては,差分更新によるmatplotlibのアニメーションの高速化の記事によく書かれています.
class Scatter:
def __init__(self, fig, ax, plot_area=(1000, 1000), len_points=100, show_icon=False, icon_radius=100):
self.fig = fig
self.plot_area = plot_area
self.icon_radius = icon_radius if show_icon is True else None
# axis setup
self.pos_ax = ax
self.pos_ax.set_xlim(-plot_area[0], plot_area[0])
self.pos_ax.set_ylim(-plot_area[1], plot_area[1])
self.pos_points = self.pos_ax.scatter([], [])
self.xy = [[0.0, 0.0] for x in range(len_points)]
if show_icon is True:
self.agent_icon = mpatches.RegularPolygon(xy=(0, 0), numVertices=4, radius=self.icon_radius, orientation=0.0, ec="r", fill=False)
self.pos_ax.add_patch(self.agent_icon)
# show figure
self.fig.canvas.draw()
self.fig.show()
折れ線グラフと同じく,軸のレンジを決めFIFOの準備をしています.これに加え,最新のデータを示すためのポリゴン描画のコードが入っています.差分更新の記事ではmpatches.Circleの例が挙げられていますが,今回はポリゴンの方を使用し,描画に関してもdraw_artistを使用します.mpatchesに関しては公式が詳しいです.
def update_data(self, points, orientation=0.0):
# draw background with white
self.pos_ax.draw_artist(self.pos_ax.patch)
# plot points
self.xy.append(points)
self.xy.pop(0)
self.pos_points.set_offsets(self.xy)
self.pos_ax.draw_artist(self.pos_points)
# plot the icon
if self.icon_radius is not None:
self.agent_icon.xy = points
self.agent_icon.orientation = orientation
self.pos_ax.draw_artist(self.agent_icon)
# update this graph
self.fig.canvas.blit(self.pos_ax.bbox)
def plot(self, points, orientation=0.0):
self.update_data(points, orientation)
self.fig.canvas.flush_events()
基本的には折れ線グラフと一緒です.こっちもupdate_dataとplotはひとまとめにした方がよかったかもしれません.
参考:estshorter,差分更新によるmatplotlibのアニメーションの高速化,われがわログ
matplotlib公式,matplotlib.patches — Matplotlib 3.3.1 documentation,Matplotlib 3.3.1 documentation
5.動かす
とりあえず,[-1000, 1000]の範囲でランダムな点の組(X,Y)を生成し,グラフに打っていくことにしました.これを5000回繰り返して,平均フレームレートを計算します.実行コードはこちらから.
平均フレームレートは22.9[fps]で,目標の30[fps]には届きませんでした....
試しに一度に表示する点数を,折れ線グラフと散布図の両方で5000 -> 2500にしてみます.
平均フレームレートは31.0[fps]で,目標は達成したようです.一度の表示する点数を2500 -> 1250とさらに減らしてみます.
平均フレームレートは52.2[fps]とそこそこ速くなりました.どうやら表示するデータ数に応じてフレームレートが変動するようです.
6.結局どうなのよ
これは目標の30fps描画というべきかについてはいろいろ考える必要があると思いますは,とりあえず部分的には目標達成ということにしましょう.もやもやするけど.散布図のみだったり,グラフの更新を並列で走らせるとか,更新を隠蔽するとかすればもっとフレームレートは上がるはずなので,まだまだといったところでしょうか.
7.参考
hukkumameo,【Python】matplotlibの手動で描画更新,俺言語。
estshorter,差分更新によるmatplotlibのアニメーションの高速化,われがわログ
matplotlib公式,matplotlib.patches — Matplotlib 3.3.1 documentation,Matplotlib 3.3.1 documentation
©2020 shts All Right Reserved.