Python
matplotlib
pillow

3D 散布図を回転 GIF アニメーションにする

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) で出力した