俺言語。

自分にしか理解できない言語で書かれた備忘録

【Python】matplotlibの手動で描画更新

matplotlibでアニメーションほど早い更新周期ではないものの,

イベント(たとえばグラフをダブルクリック)でグラフを更新(変化)させたい場合など,

グラフ更新の関数を実行してあげる必要がある。


最も一般的なのは 

canvas.draw()

しかしこれがすごく遅い。

Figureクラスの中の関数のためかfigure全体を更新してしまうためらしい。


色々試した結果,

bg = canvas.copy_from_bbox(self.ax.bbox) # bboxのエリアを背景(というか戻したい状態)として保存

canvas.restore_region(bg) # 戻したい状態へ描画

ax.draw_artist(artist) # 描画したいartist(LineやPatch)を登録

canvas.blit(ax.bbox) # bboxのエリアだけ描画更新 以降②へ戻る

※ ④ canvas.blitよりcanvas.update()の方がメモリ使用が少なく早い情報もあるが
   ケースによるのとqt4aggでしか動かなかった。


でやると更新したい場所のみ変更するのでmatplotlibとは思えないほど早い。

bboxのエリアを前に保存した状態に戻す手順とその効果はペイントみたいに感じる。


参考にしたサイトにあったcanvas.draw()と他のもっと早い手順との速度比較を試してみた。

import matplotlib.pyplot as plt
import numpy as np
import time

from scipy.stats._continuous_distns import t_gen


def case1():

    fig, ax = plt.subplots()

    t_start = time.time()
    num_plot = 0

    while time.time() - t_start < 1:
        ax.clear()
        ax.plot(np.random.randn(100))
        plt.pause(0.001)

        num_plot += 1

    return num_plot


def case2():

    fig, ax = plt.subplots()
    line, = ax.plot(np.random.randn(100))

    t_start = time.time()
    num_plot = 0

    while time.time() - t_start < 1:
        line.set_ydata(np.random.randn(100))
        plt.pause(0.001)

        num_plot += 1

    return num_plot


def case3():

    fig, ax = plt.subplots()
    line, = ax.plot(np.random.randn(100))
    fig.canvas.draw()
    fig.show()

    t_start = time.time()
    num_plot = 0

    while time.time() - t_start < 1:
        line.set_ydata(np.random.randn(100))
        fig.canvas.draw()
        fig.canvas.flush_events() # <-これがないと画面に描画されない。

        num_plot += 1

    return num_plot

def case4():

    fig, ax = plt.subplots()
    line, = ax.plot(np.random.randn(100))
    fig.canvas.draw()
    fig.show()

    t_start = time.time()
    num_plot = 0

    while time.time() - t_start < 1:
        line.set_ydata(np.random.randn(100))

        ax.draw_artist(ax.patch)
        ax.draw_artist(line)
        #fig.canvas.update()
        fig.canvas.blit(ax.bbox)

        fig.canvas.flush_events()
        num_plot += 1

    return num_plot

def case5():

    fig, ax = plt.subplots()
    fig.canvas.draw()

    bg = fig.canvas.copy_from_bbox(ax.bbox)

    line, = ax.plot(np.random.randn(100))
    fig.show()

    t_start = time.time()
    num_plot = 0

    while time.time() - t_start < 1:
        line.set_ydata(np.random.randn(100))

        fig.canvas.restore_region(bg)
        ax.draw_artist(line)

        #fig.canvas.update()
        fig.canvas.blit(ax.bbox)

        fig.canvas.flush_events()
        num_plot += 1

    return num_plot


def case6():

    fig, ax = plt.subplots()
    fig.canvas.draw()

    bg = fig.canvas.copy_from_bbox(ax.bbox)

    line, = ax.plot(np.random.randn(100))
    fig.show()

    t_start = time.time()
    num_plot = 0

    while time.time() - t_start < 1:
        line.set_ydata(np.random.randn(100))

        fig.canvas.restore_region(bg)
        ax.draw_artist(line)

        fig.canvas.update()

        fig.canvas.flush_events()
        num_plot += 1

    return num_plot


if __name__ == "__main__":
    print("case1: " + str(case1()) + "fps")
    print("case1: " + str(case2()) + "fps")
    print("case3: " + str(case3()) + "fps")
    print("case4: " + str(case4()) + "fps")
    print("case5: " + str(case5()) + "fps")
    print("case6: " + str(case6()) + "fps")

Case1: 最も原始的。Lineを書いては消して。Pauseを使うことで画面を更新。

Case2: Case1からLineをいちいち消すのではなくLine1のデータを書き換えて更新

Case3: Case2 + canvas.draw() これが基準になる

Case4: 背景を白で描画する(ax.draw_artist(ax.patch))ことで前の描画をクリア。再描画はcanvas.blit()を使う

Case5: Case4から前描画クリアをcanvas.restore_region()に変更

Case6: Case5から再描画をcanvas.update()に変更


いろんなケースを試したけど要は

Case3基準に対してCase5がどれだけ速いか。
Case5とCase6どっちが速いか。

結果は

case1: 5fps

case1: 7fps

case3: 44fps

case4: 227fps

case5: 218fps

case6: 183fps

Case3に対してCase5は約5倍速い。

Case5よりCase6の方が約1.2倍速い結果となった。

意外にCase4が遅くない。。

Speeding up Matplotlib