読者です 読者をやめる 読者になる 読者になる

俺言語。

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

【Python】グラフ枠(Spines)の色・太さ変更

グラフ枠(Spines)を一度描画した後に変更する方法。


グラフ枠は

ax.Spines{"Bottom"}
ax.Spines{"Top"}
ax.Spines{"Left"}
ax.Spines{"Right"}

に枠線のインスタンスがあり,

色変更は ax.Spines{"**"}.set_color(色)

太さ変更は ax.Spines{"**"}.set_linewidth(太さ)

で変更する。

問題は枠線(Spines)がAXESエリアのぎりぎり外側にいるため,

背景を保存する際や再描画エリアを指定する際に使う

ax.bboxでは範囲に含まれないこと。


解決方法として

ax.bbox.extended(縦?倍率,横?倍率)でax.bboxの範囲を少し広げてやる。

 問題点はAxesサイズが変わると拡張される面積も変わるためプログラム毎で調整が必要になってしまう。


ax.xaxis.get_tightbbox(ax.get_renderer_cache())でtickラベルを含むaxisのbboxを取得し,

 ax.bbox.union([結合したいbbox, 結合したいbbox])でbboxをつなげる。

 注意点はax.xaxis.get_tightbbox()がほんとにtightでtickラベルぎりぎりのbboxを返す。
 
 そのためtickラベルの桁数が変わる場合など簡単にエリアをはみ出すので注意。



グラフをダブルクリックしてグラフ枠を赤&太く -> 黒&細くを繰り返す。

def event_datacheck(self, event):
    if event.dblclick:

        if event.inaxes.spines["bottom"].get_edgecolor()[0] == 0:

            # 赤線を引く前にbgを保存
            # キーをイベントのaxesインスタンスにしてクリックされたaxesにbgを引き当て
            # key:axインスタンス value:bg
            self.ax_dict[event.inaxes] = event.canvas.copy_from_bbox(event.inaxes.bbox.expanded(1.1, 1.1))

            event.inaxes.spines["bottom"].set_color("red")
            event.inaxes.spines["top"].set_color("red")
            event.inaxes.spines["left"].set_color("red")
            event.inaxes.spines["right"].set_color("red")

            event.inaxes.spines["bottom"].set_linewidth(2)
            event.inaxes.spines["top"].set_linewidth(2)
            event.inaxes.spines["left"].set_linewidth(2)
            event.inaxes.spines["right"].set_linewidth(2)

            print("Axes was SELECTED")

        else:
            # イベント内のaxesに対応するbgを使って画面をbgに戻す
            event.canvas.restore_region(self.ax_dict[event.inaxes])

            event.inaxes.spines["bottom"].set_color("black")
            event.inaxes.spines["top"].set_color("black")
            event.inaxes.spines["left"].set_color("black")
            event.inaxes.spines["right"].set_color("black")

            event.inaxes.spines["bottom"].set_linewidth(0.5)
            event.inaxes.spines["top"].set_linewidth(0.5)
            event.inaxes.spines["left"].set_linewidth(0.5)
            event.inaxes.spines["right"].set_linewidth(0.5)

            print("Axes was RELEASED")

        event.inaxes.draw_artist(event.inaxes.spines["bottom"])
        event.inaxes.draw_artist(event.inaxes.spines["top"])
        event.inaxes.draw_artist(event.inaxes.spines["left"])
        event.inaxes.draw_artist(event.inaxes.spines["right"])

        # これで更新
        event.inaxes.figure.canvas.blit(event.inaxes.bbox.expanded(1.2, 1.2))
        # event.inaxes.figure.canvas.update() <- wxaggだと動かない

【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

【Python】win32clipboardが無いと言われる

新しい環境にpipでpywin32をインストールした時,過去に動いていたプログラムで

ImportError: DLL load failed when importing win32clipboard とエラーが発生。
(実際は日本語のエラー)

.dllにパスが通っていなのか、などいろいろ試したが

exe版のpywin32でインストールすると動くようになった。

【Python】Pycharm + wx3 でエラー

PycharmのPython Console使用時に発生したエラー。

おそらくbackednをpyqt4からwxに変更したことも関係あると思われる。

C:\Program Files (x86)\JetBrains\PyCharm Community Edition 2017.1\helpers\pydev\pydev_ipython\inputhookwx.py

def inputhook_wx3():
    """Run the wx event loop by processing pending events only.

    This is like inputhook_wx1, but it keeps processing pending events
    until stdin is ready.  After processing all pending events, a call to
    time.sleep is inserted.  This is needed, otherwise, CPU usage is at 100%.
    This sleep time should be tuned though for best performance.
    """
    # We need to protect against a user pressing Control-C when IPython is
    # idle and this is running. We trap KeyboardInterrupt and pass.
    try:
        app = wx.GetApp()  # @UndefinedVariable
        if app is not None:
            assert wx.IsMainThread()  # @UndefinedVariable

            # The import of wx on Linux sets the handler for signal.SIGINT
            # to 0.  This is a bug in wx or gtk.  We fix by just setting it
            # back to the Python default.
            if not callable(signal.getsignal(signal.SIGINT)):
                signal.signal(signal.SIGINT, signal.default_int_handler)

            evtloop = wx.GUIEventLoop()  # @UndefinedVariable
            ea = wx.EventLoopActivator(evtloop)  # @UndefinedVariable
            t = clock()
            while not stdin_ready():
                while evtloop.Pending():
                    t = clock()
                    evtloop.Dispatch()
                #app.ProcessIdle()
                wx.WakeUpIdle()

wx3になって
wx.Thread_IsMain() が wx.IsMainThread に変更されているのに
プログラム内ではwx2のままが原因。

ほかにも
wx.EventLoop() が wx.GUIEventLoop() を使うよう推奨されていたので変更した。

【Python】Pycharmアップデート後にPythonクラッシュ多発

Pycharmはずっと2016.3バージョンを使っていたが知らぬ間に2017.1という新バージョンに。

機能もデバッグの高速化とPandasの可視化と魅力的だったため
アップデートしたところ,前は普通に動いていたプログラムが立て続けに
Pythonのcrashで動かず。。

その時のエラーは
コンソールに
Process finished with exit code -1073741819 (0xC0000005)

"Pythonは動作を停止しました"
というウィンドウ。

色々原因を調べていくとmatplotlibをインポートするところで
エラーが発生。

インストールされていたmatplotlibのバージョンが1.*で
2.0.0が新たに公開されていたので試しに更新したところ直った!

【Python】リスト内包表記の戻り値はリスト

知らなかった。

連続で処理したものをリストでもっておきたい時に便利。
たとえば グラフウィンドウ内にgridspecを使って大量にaxesを設定する場合

row = 4
line = 4
gs = gridspec.Gridspec(row, line)
ax = [fig.addsubplot(gs[num]) for num in row * line]

ax[0].plot(...) 

辞書内包表記もあるのでそちらの戻り値はおそらくディクショナリ。

【Python】wxFormBuilderがwxPython3に対応できていない部分

wxFormBuilderで作ったGUIはwxPython2までしか対応していないせいか
wxPython3だとエラーでうまく動かいない場合があり。

変更(修正)が必要な項目のまとめ。

【バージョン】
wxformbuilder:3.5.1-RC1(Unicode)
wxPython-Phoenix: 3.0.3dev2749+f803d20


【要変更箇所】
・グリッドのセル中身を変更したときに発生するイベント
wx.grid.EVT_GRID_CELL_CHANGE → wx.grid.EVT_GRID_CELL_CHANGED


<追記>
下記にwx2からwx3になった際に変更になったクラスと関数のリスト。
何故か上記は含まれず。

https://wxpython.org/Phoenix/docs/html/classic_vs_phoenix.html#introduction