Python にて 3D 散布図を描画して回転する GIF アニメーションとして出力したい、ということをやって少しハマったので、最終的にできた方法をまとめる。
GIF アニメに変換する方法としては ImageMagick や FFmpeg を使う方法などがあるが、今回は Pillow を使った。
データ
今回はサンプルデータとして、ランダムな3次元データを生成したものをプロットすることにする。
import numpy as np
data = np.random.multivariate_normal(np.zeros(3), np.eye(3), 2**10).transpose()
data.shape # => (3, 1024)
3D 散布図を描画する
Matplotlib で 3D 散布図を描画して、それを PIL Image に変換する。
from io import BytesIO
from PIL import Image
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
def render_frame(angle):
"""data の 3D 散布図を PIL Image に変換して返す"""
global data
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(*data)
ax.view_init(30, angle)
plt.close()
# 軸の設定
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim(-3, 3)
ax.set_ylim(-3, 3)
ax.set_zlim(-3, 3)
# PIL Image に変換
buf = BytesIO()
fig.savefig(buf, bbox_inches='tight', pad_inches=0.0)
return Image.open(buf)
render_frame(30)
GIF アニメを生成する
角度を変えながら全フレーム分の画像を生成して、GIF アニメとして保存すれば完成。
images = [render_frame(angle) for angle in range(360)]
images[0].save('output.gif', save_all=True, append_images=images[1:], duration=100, loop=0)
※ range(360)
だと出力された画像が重すぎて Qiita にアップロードできなかったので、上記画像は range(180)
で出力した