2020年9月11日金曜日

Matplotlibで散布図や折れ線グラフの30fps描画を実現したい




相変わらず引きこもり生活な中の人です。今回はMatplotlibで散布図や折れ線グラフを高速描画する話です。 



1.Matplotlibの描画の話


Pythonでグラフ描画を行う際によく使うのがMatplotlibです.Matlab-likeなインターフェースで扱いやすいのですが,リアルタイム描画を行おうとすると非常に遅いことがあります.
これに関してはいろいろな高速化のやり方があるのですが,この中でも手動更新のコードを自分で書くことで高速化を果たすやり方だと,データ数によっては100fps以上を狙うことができるらしいです.

参考:hukkumameo,【Python】matplotlibの手動で描画更新,俺言語。

2.手動更新について


描画の更新方法はいくつかあるのですが,今回は
  1. 描画領域を白く塗る
  2. データを描画する
  3. 描画領域のアップデート
  4. 画面の描画更新
でやってみました.(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はひとまとめにした方がよかったかもしれません.

   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.参考


matplotlib公式,matplotlib.patches — Matplotlib 3.3.1 documentation,Matplotlib 3.3.1 documentation




©2020 shts All Right Reserved.

2020年9月10日木曜日

このブログを参照するときに気を付けてほしいこと


インターン準備が大変すぎて,おめめぐるぐる中の人です.さて,今回はブログの利用に関しての話です.弊ブログも来月で7年目に突入し,だんだんとほかのブログなどから参照されること増えてきました. 今のところ目立ったトラブルもないのですが,もしかしたら今後何か起きるかもしれないので,その前に手を打っていくのがこの記事の趣旨です.



1.このブログの趣旨


このブログは,中の人が試してみてうまくいった・うまくいかなかった事例をまとめたものです.

2.情報の内容と免責


当然,開発環境・アップデートなどにより,このブログ内で紹介した方法も動かなくなることがあります.うまくいかない場合にはほかの方法を試してください.また,ブログ内で紹介した方法で何か起こっても(物損など)責任は負えないのでご了承ください.

3.ほかの媒体での利用


基本的に他の媒体(ブログ・Twitter・同人誌など)での使用に関して,商用・非商用問わず,基本的にNGはないのですが,元にしたブログ記事のリンクを貼ってほしいです.許可は取らなくてもいいです.
諸事情により元記事が消えることもあるのでご了承ください.


4.その他


この記事は状況に合わせてアップデートします.そのため途中で内容が変わるかもしれません.




©2020 shts All Right Reserved.