grbl1.1+Arduino CNCシールドV3.5+bCNCを使用中。
BluetoothモジュールおよびbCNCのPendant機能でスマホからもワイヤレス操作可能。
その他、電子工作・プログラミング、機械学習などもやっています。
MacとUbuntuを使用。

CNCマシン全般について:
国内レーザー加工機と中国製レーザー加工機の比較
中国製レーザーダイオードについて
CNCミリングマシンとCNCルーターマシンいろいろ
その他:
利用例や付加機能など:
CNCルーター関係:



*CNCマシンの制作記録は2016/04/10〜の投稿に書いてあります。


2018年4月19日木曜日

Jupyter Notebook/JupyterLab: matplotlib GIFアニメーション 作り方+読込み+表示方法

前回に引き続き、Jupyter NotebookとJupyterLabでのmatplotlibをつかったアニメーションについてのメモ。matplotlibは便利なのだけれども、個人的には使いづらく(覚えにくい)、特にアニメーションではかなりハマったので書いておきます。

環境:
Ubuntu 16.10
Python3.5
Jupyter Notebook 5.0.0
JupyterLab 0.32.0
matplotlib 2.2.2


まず、Jupyter NotebookやJupyterLab上に表示する際、
・%matplotlib inline (主に静止画)
・%matplotlib nbagg (動画、インタラクティブ)
この二種類のbackendsを用いるのですが、基本的にJupyterLabだと「nbagg」のほうはダメみたい。

次に、グラフをアニメーション化するには二つの方法があるようで、
・ArtistAnimation (比較的簡単)
・FuncAnimation (やや複雑)
パラメータが少なければどちらもそう難しくはないのですが、場合によっては表示されない場合もあります。表示されなくてもGIFアニメーションに保存はできることもあります。

これはつくったGIFアニメ。比較的簡単につくることができるけれども、JupyterLabに表示させるのが難しい。

表示方法(3種類):
そして、Jupyter上で表示する方法としては3つあり、

・nbaggを用いたインタラクティブウィンドウ(JupyterLabではエラー)
・inlineのままJavascript HTMLで出力する方法(JupyterLabではエラー)
・inlineのままHTML5 Videoとして出力する方法(エラーなし)

バックエンドをinlineのままJavascript HTMLやHTML5 Videoを使うためには、

from IPython.display import HTML


が必要となります。

以下がそれぞれの見た目です(キャプチャ画像なのでボタンを押しても動きません)。

バックエンド:%matplotlib nbagg
表示:animation.ArtistAnimation
JupyterLabでは表示不可


バックエンド:%matplotlib inline
変換表示:JavascriptHTML
JupyterLabでは表示不可
この中では一番多機能、逆再生・往復再生・スピード調整など可能

バックエンド:%matplotlib inline
変換表示:HTML5 Video
Jupyter Notebook、JupyterLabともに表示可能
動画ダウンロード・フルスクリーン表示可能


Jupyter Notebookではどれも大丈夫でしたが、JupterLabではJavascriptのエラーなのか最初の二つはダメそうです。HTML5 Video出力だと大丈夫だったので、これがおすすめかもしれません。
バグなのか、設定方法が悪いのか、インストールが不十分なのかわかりにくく結構手間取ってしまいました。

追記:
尚、ボタン付きのアニメーションとしてWebページに埋め込むには、以下を参考にしてください。
Matplotlib Animation embed on web page:アニメーションのWebページ上への埋め込み


今回試してみたこと:
・グラフをアニメーション化して再生、GIFアニメーション保存
・つくったGIFアニメーションを読み取って再生する方法
・Jupyter上にマークダウンとしてGIFを表示する方法

下準備(インストール:このサイト参照):
Nodejs
npm
ipympl
jupyter-widgets/jupyterlab-manager
(It requires matplotlib 2.0 or and ipywidgets 7.0 more recent.)



グラフのアニメーション化・再生・GIFアニメーション保存:
「ArtistAnimationを使った場合」

%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
from IPython.display import HTML

fig = plt.figure()
x = np.linspace(-5,5,100)
y = np.sin(x)

ims = [] # 動画要素を格納するリスト

for i in range(len(x)):
    ln = plt.plot(x,y, 'b--')     # 動かない線
    pt = plt.plot(x[i],y[i],'ro') # 移動する点
    ims.append(ln + pt)

ani = animation.ArtistAnimation(fig,ims,interval=50) # アニメ関数

ani.save("test.gif", writer = "imagemagick") # GIF保存

plt.close() #余計なウィンドウがでた場合消す

HTML(ani.to_html5_video()) # HTML5 Videoで表示
# HTML(ani.to_jshtml())    # Javascript HTMLで表示


アニメーション化するには、animation.ArtistAnimation()に最低でもfigとimsの二つの引数を渡し、imsに動画になる要素としてのplt.plot()を必要なだけ+を使って入れていくだけ。
その下のani.save()でGIFとして保存。Ubuntuの場合はwriterとしてimagemagickを指定したほうがいいらしい。
plt.close()については、バックエンドで%matplot inlineがはたらいているためか、HTML5 Videoのウィンドウとは別にもうひとつplt.figure()の空ウィンドウが出てくることがあって、それを消しています。
Jupyter Notebookなら、HTML(ani.to_jshtml)に切り替えても表示できました。nbaggを使わずinlineで済むのでお手軽です。ただし、HTMLをインポートしておく必要があります。
%matplotlib inlineは切り替えなければデフォルトになっているようなので、途中でnbaggなどに変えなければ記入する必要はありません。


「FuncAnimationを使った場合」

%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
from IPython.display import HTML

x = np.linspace(-5,5,100)
y = np.sin(x)

fig = plt.figure()
plt.axis([-5,5,-2,2])      # 座標範囲:[xmin,xmax,ymin,ymax]
b, = plt.plot([],[],'b--') # 固定描写(青破線)
t, = plt.plot([],[],'r-')  # 移動描写(赤丸)

# plt.axis('off') # 枠線なしの場合

def update(i):
    b.set_data(x,y)           # 固定描写(青破線)
    t.set_data(x[i], y[i])    # 現在の移動座標のみ表示(点描の場合)
    #t.set_data(x[:i], y[:i]) # 過去の軌跡座標も表示(線描の場合)

ani = animation.FuncAnimation(fig, update, interval=50) # アニメ関数

ani.save("test.gif", writer = "imagemagick") # GIF保存

plt.close()

HTML(ani.to_html5_video())   # HTML5 Videoで表示
# HTML(ani.to_jshtml())      # Javascript HTMLで表示


こちらの方法だとupdate(i)関数を作って、フレーム数となる引数iを入れてFuncAnimation()に渡すことになります。他の方法でも可能なのかもしれませんが、サンプルなどではline2Dオブジェクト用の変数(t, : 変数の後ろにコンマ付き)を用意し、set_data()で更新していくようです。やや複雑なことが可能なのかもしれません。subplotを追加するサンプルが多いですが、plt.plotだけを使って書いてみました。


バックエンド(inline/nbagg)切替方法・確認方法:
以下のコマンドで現在使用されているバックエンドを切替・確認できます。


%matplotlib inline
# %matplotlib nbagg

import matplotlib
matplotlib.get_backend()


この状態だと、inlineが使用されているので以下が出力されます。


'module://ipykernel.pylab.backend_inline'


通常の静止画グラフやHTML5 Video、JavascriptHTMLを使うならばinlineに設定。
また、コメントアウトでnbaggに切り替えれば、

'nbAgg'


が出力されて、nbaggがバックエンドとして使用されていることがわかります。ただし、JupyterLabではJavascriptエラーがでるかもしれません。


GIFアニメの読込と再生:
先ほどの方法でGIFアニメをつくることができますが、それを再生する方法についてです。
最初はそのままGIF画像を読み込めば表示できると思っていましたが、なかなかうまくいきませんでした。静止画であればすぐに表示できますが、動画として連続的に表示してくれずかなりハマりました。

まず、PILを使って以下のようなGIFファイルの内容を調べます。


from PIL import Image

img = Image.open("test.gif")

print("info: ",img.info)
print("animated: ",img.is_animated)
print("n_frames: ",img.n_frames)


そうすると、以下のような情報が出力されます。


info:  {'version': b'GIF89a', 'duration': 50, 'loop': 0, 'background': 0, 'extension': (b'NETSCAPE2.0', 803)}
animated:  True
n_frames:  100

このGIFファイルの場合はanimatedされているというのがわかります。またフレーム数が100あることもわかります。基本的にはこの二つの情報がわかれば大体大丈夫だと思います。
そのままこの二つの変数を使えば、異なるGIFファイルも読み込むことができると思います。


import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
from IPython.display import HTML
from PIL import Image

img = Image.open(filepath+"-test.gif") # PIL Imageで読込み

# GIFの情報を調べる(今回はフレーム数だけ)
# print("info: ",img.info)
# print("animated: ",img.is_animated)
print("n_frames: ",img.n_frames)       # フレーム数を取得

fig = plt.figure()

# plt.axis('off') # 枠線なしの場合

ims = []    # 各フレームを格納するリスト

for frame in range(0,img.n_frames):  # フレーム数だけループ処理
    img.seek(frame)                  # 順番に各フレーム画像を探し出す
    im = plt.imshow(img)
    ims.append([im])

ani = animation.ArtistAnimation(fig,ims,interval=50)

plt.close()

HTML(ani.to_html5_video())
# HTML(ani.to_jshtml())


まず、内包されているフレーム数を調べるために、PIL Imageをインポートしておきます。img=Image.Open()したあとに、img.n_framesでフレーム数がわかります。
あとはそのフレーム数だけループ処理をし、ひとつずつリストに格納していきますが、img.seek()という関数を使うことで内包されている何番目の画像を取り出すか指示できます。
img.seek(0)なら最初の画像、img.seek(1)ならその次の画像というような感じで次のplt.imshow()に入れていきます。GIFの場合はこのimg.seek()を使うといいようです。
もし連番のPNG画像を複数扱うならば、img.seek()は使わずに、ファイル名の番号を手がかりに読込みと表示をさせればいいことになります。

順番にひとつずつ取り出してはimg.show()やplt.imshow()で表示させてもいいと思ったのですが、そうするとJupyter上では次々と複数の静止画が出てきて動画として見ることができませんでした。出しては消すという方法ももあるのかもしれませんが、今回はArtistAnimation()に取り込んで動画として表示させることにしました。


Jupyter上にマークダウンでGIFアニメを表示する:
先ほどのやり方は、GIFアニメを一枚ずつ取り出して、何らかの加工をするときには使えるテクニックかもしれませんが、ただ出来栄えを確認したり表示するだけなら、マークダウンでJupyter上のセルに表示させればいいと思います。


### Animated GIF
<div class="pull-left">
![Animated GIF](test.gif)
<div>

Jupyter上のセルをマークダウンに切り替えてこんな感じで記入すれば、普通にGIFアニメとして表示されるはずです。この場合は普通の静止画と同じ読込み方です。


追記:ipympl0.1.0から0.1.1へアップデート:
その後、Anacondaでモジュールのインストールをし直したりしていたら、Jupyter用のモジュールである「ipympl」が0.1.0から0.1.1にバージョンアップしていました。
conda install ipympl
でインストールできます。
さっそくアップデートしてみると、0.1.0ではエラーがでていた「%matplotlib ipympl」でしたが、動くようになりました。
「ipympl」にすると、上画像のように「nbagg」がバックエンドとして動くようです。「nbagg」なので従来のインタラクティブウィンドウが使えます。


「%matplotlib ipympl」によるバックエンド「nbagg」の場合:

Jupyter Notebook:
通常のインタラクティブウィンドウ:
表示されるが閉じても止まらない(一部バグ?)。セルのアウトプットクリアで止めるしかない。以前の「%matplotlib nbagg」のほうがいいかも。

JavascriptHTML出力:
動く。エラー無し。

HTML5 Video:
動く。エラー無し。


JupyterLab:
通常のインタラクティブウィンドウ:
表示されるが閉じることができない。表示できるようになったところまではいい。あともう少し。

JavascriptHTML出力:
絵が出ない。まだ使えない。

HTML5 Video:
動く。エラー無し。結局これが一番使える。しかもバックエンドが「inline」でなくても映った。


まとめ:
ということから、少し進展ありましたが、やはりHTML5 Videoで出力するのが一番安定しているようです。またアップデートくれば直るかも。
Jupyter Notebookのほうが安定しているので、いまはNotebookを使ったほうがいいのかもしれません。それほど不便というわけでもないので。

関連:(少し違う作り方)

0 件のコメント:

コメントを投稿