[Python, Julia] Jupyter で 3D 表示 - Mayavi ライブラリ

  • 20
    いいね
  • 5
    コメント

 Jupyter Advent calendar 2016 12日目の記事です。
 Jupyter で 3Dグラフィックスを表示できます。Python の Mayavi ライブラリを使います。
 Mayavi は、Python用の 3次元グラフィックス表示ライブラリです。科学技術分野の可視化を念頭に開発された vtk をグラフィックスエンジンとしています。デモを見て頂くと、どのような絵が描けるか分かるでしょう。-> デモ, MRI example

お膳立て

 Mayaviライブラリの出力を Jupyter で表示するためには、以下の二つの条件が整っている必要があります。

  • Jupyter から呼ばれる Python の kernel において、Mayavi パッケージが利用可能である。
  • Mayavi 向けの nbextension が、jupyter にインストールされている。

 実は、このお膳立てが一番難しいです。表示結果に興味がある方は、この節の続きは飛ばして頂いて結構です。
以下では、Mac OSX 上で、conda 環境を用いて、環境構築してみます。

(1) Python2 と Mayavi インストール

 Mayavi の anaconda パッケージは、python 2 でのみ提供されていますので、現時点の最新版 Python 2.7 をインストールします。
 Python の環境構築の概要は、データサイエンティストを目指す人のpython環境構築 2016 にまとまっています。 複数の conda 環境と付き合うには注意が必要ですが、pyenvとanacondaを共存させる時のactivate衝突問題の回避策3種類 の一つめ、すなわち、「pyenv を用いて anaconda をインストールしたあと、pyenv を用いない」という方針で私は運用しています。当該記事の通り pyenv と anaconda をインストールしたら、Mayavi 向けの conda環境 (以下では py27mayavi とします) を作ります。

conda install -n py27mayavi numpy jupyter mayavi=4.4
source activate py27mayavi

 Mayavi は、用いるライブラリのバージョンに敏感です。Mayavi の最新版は 4.5 ですが、一つ前の 4.4 が安定しているように、私は感じています。
 Mayavi を追加したら、Python から Mayavi が動くことを確かめておきます。3次元の絵が表示されたら成功です。

from mayavi import mlab
mlab.test_plot3d()

(2) Jupyter で conda 環境を使えるようにする。

 (1)で作成した conda環境 py27mayavi を、Jupyter から呼べるようにします。
 起動中の Jupyter から複数の kernel (conda 環境)を切り替えて使うための設定方法が、記事 Condaで作ってる仮想環境の切り替えをJupyter上で簡単に行う方法 で紹介されています。例えば、下図のように、New Notebook で起動する kernel を選べるので、便利です。
スクリーンショット 2016-12-10 17.13.55.png

(3) Jupyter に nbextension を追加

 nbextensions も、conda を用いてインストールします。 こちらを参考に -> https://github.com/ipython-contrib/jupyter_contrib_nbextensions

conda install -n py27mayavi -c conda-forge jupyter_contrib_nbextensions

Using Mayavi in Jupyter notebooks によると、Mayavi 向けの nbextensions をインストールするために、

jupyter nbextension install --py mayavi --user

と打ち込めと書いてありますが、これはうまく動きませんでした。しかし、Jupyter 内から、mlab.init_notebook を起動すると (後述)、Mayavi 向けの nbextensions が存在していますので、結果オーライとしましょう。

最も簡単な例 Python

 シェル (コマンドライン, ターミナル)から Jupyter を起動します。

jupyter notebook

 Mayavi をインストールした conda環境を kernel として、New notebook します。Mayavi を呼び出してみましょう。

 下の Jupyter Notebook: https://gist.github.com/anonymous/2c85c265cbee3c4485cfe39239593a11

from mayavi import mlab
mlab.init_notebook()
s=mlab.test_plot3d()
s

 Mayavi でオブジェクトを表示するには、mayavi.mlab モジュールを通常用います。mlab.init_notebook() は Jupyter 上に Mayavi 画像を出力するための「おまじない」です。

スクリーンショット 2016-12-10 17.28.32.png

 出力セル Out[*] に、3次元表示向けの窓が開き、3次元オブジェクトが表示されました。左ボタンを押しながらのマウス操作でオブジェクトを回転・拡大縮小でき、Shiftキーと左ボタンを押しながらのマウス操作でオブジェクトを移動できます。

表示の仕組み

 3D表示の仕組みを、およそ以下のようになっていると思います。
 mlab.init_notebook() したあとに、Notebook initialized with x3d backend 「notebook は 3次元シーンを x3d 形式で扱うように設定された」というメッセージが出力されましたが、x3d は、3次元シーンを記述するためのフォーマット VRML (Virtual Reality Modeling Language) の後継です。Mayaviが3次元シーンを x3d 形式で出力するとすると、Jupyter notebook が WEB ブラウザに3次元画像を描画します。 HTML5 に準拠するmodern browser は、WebGL、すなわち、3次元グラフィックス規格 OpenGL のサブセットを表示する機能を備えていますから、これを利用するわけです。

鉄則 : Mayavi オブジェクトを出力せよ

 次に、簡単なプログラムを書いてみます。 表面を描く関数 surfテストプログラム です。

 下の Jupyter Notebook: https://gist.github.com/anonymous/954fdf1bebc9802a3e035092c1ac50ff

import numpy as np

def f(x, y):
    return np.sin(x + y) + np.sin(2 * x - y) + np.cos(3 * x + 4 * y)

x, y = np.mgrid[-7.:7.05:0.1, -5.:5.05:0.05]

from mayavi import mlab
mlab.init_notebook()
s = mlab.surf(x, y, f)

s

スクリーンショット 2016-12-10 18.31.01.png

 関数 f の様子が描画されました。
 この描画結果を得るには、最後の行に書かれた s が必要です。 これを削除すると何も描画されません。つまり、Jupyter の出力セルに Mayavi のオブジェクトを出力すると、それが 3次元に描画される仕組みになっているようです。

それを踏まえて、次の例です。

 下の Jupyter Notebook: https://gist.github.com/anonymous/f55d3291bf778bd5fef98e0741d82d65

import numpy as np
np.random.seed(12345)
x = 4 * (np.random.random(500) - 0.5)
y = 4 * (np.random.random(500) - 0.5)
z = np.exp(-(x ** 2 + y ** 2))

from mayavi import mlab
mlab.init_notebook()

mlab.figure(1, fgcolor=(0, 0, 0), bgcolor=(1, 1, 1))
pts = mlab.points3d(x, y, z, z, scale_mode='none', scale_factor=0.2)
mesh = mlab.pipeline.delaunay2d(pts)
surf = mlab.pipeline.surface(mesh)

mlab.view(47, 57, 8.2, (0.1, 0.15, 0.14))
mlab.show()

mesh
surf

スクリーンショット 2016-12-10 18.49.50.png

 3次元描画を得るためにには、プログラム末尾の 2行 meshsurf が必要です。プログラム末尾で Mayavi オブジェクトを出力させると、Jupyter が、これらを拾って 3次元描画するわけです。

Julia でも Mayavi on Jupyter

 Julia は、対話型で実行できるのに、極めて高い数値計算の実行性能を享受できる、新しいプログラミング言語です。
 先週書いた拙文で、Mayavi を Julia で使う方法と、conda環境を Julia から操作する方法を解説しました。
* 3Dプロットライブラリ MayaVi を Julia から使う
* conda パッケージを Juliaから追加しよう

最も簡単な例 Julia

 Jupyter 内部で Mayavi を呼び出すための、最も簡単な例を紹介します。
 
 下の Jupyter Notebook:
https://gist.github.com/anonymous/b991896af47ac1ef3fed630b0922bd7a

using PyCall
@pyimport mayavi.mlab as mlab
mlab.init_notebook()

mlab.test_points3d()

スクリーンショット 2016-12-11 9.36.59.png

 Julia から Python モジュールを使うためには using PyCall してから、使いたい Pythonモジュールを@pyimport で読み込みます。 Pythonの import命令は、Juliaの @pyimport 命令に相当します。 @pyimport で読み込まれたオブジェクトは Juliaの型がつくので、Julia内からそのまま呼び出すことができます。

import mayavi.mlab as mlab     # Python
@pyimport mayavi.mlab as mlab  # Julia

Spherical Harmonics

 拙文 で紹介した Spherical Harmonics (球面調和関数)の を、Jupyter で実行してみます。

 下の Jupyter Notebook:

phi   = [ u1 for u1 in linspace(0,pi,101), v1 in linspace(0,2*pi,101) ]
theta = [ v1 for u1 in linspace(0,pi,101), v1 in linspace(0,2*pi,101) ]

r = 0.3
x = r * sin(phi) .* cos(theta)
y = r * sin(phi) .* sin(theta)
z = r * cos(phi)

using PyCall
@pyimport scipy.special as spe
@pyimport mayavi.mlab as mlab
mlab.init_notebook("x3d")

mlab.figure(1, bgcolor=(1, 1, 1), fgcolor=(0,0,0), size=(400, 300))
mlab.clf()
mlab.view(90, 70, 6.2, (-1.3, -2.9, 0.25))

u=false
for n in 1:6-1, m in 0:n-1
    s = real( spe.sph_harm(m, n, theta, phi) )
    mlab.mesh(x - m, y - n, z, scalars=s, colormap="jet")
    s[s .< 0] *= 0.97
    s /= maximum(s)
    u = mlab.mesh(s .* x - m, s .* y - n, s .* z + 1.3, 
        scalars=s, colormap="Spectral" )
    u
end
u

スクリーンショット 2016-12-11 9.27.38.png
スクリーンショット 2016-12-11 9.29.22のコピー.png

 ループ内で mlab.mesh は Mayavi オブジェクトを出力しますが、Jupyter は表示しません。ループの外で Mayavi オブジェクトを出力させてみたところ、3次元オブジェクトが表示されました。 u には、最後に作成された Mayavi オブジェクトだけ入っていますが、全景が描かれました。3次元描画のきっかけが存在すればよいように感じます。

Mayavi 以外から 3D表示 (妄想中)

 3Dデータが埋め込まれた Jupyter notebook は、データ交換形式として大変魅力的です。Mayavi 以外からも利用できたらうれしいです。まだ方法を確立できていませんが、二点コメントします。
 一つ目、x3d形式の修正です。 Jupyter notebook (拡張子 .ipynb) をテキスト・エディタで開いてみると、x3d 形式のデータが埋め込まれています。(x3d で表現される 3Dオブジェクトは、線と面だけですから、ちょっとしたシーンでも長いテキストになります)

  "outputs": [
   {
    "data": {
     "text/html": [
      "\n",
      "    <?xml version=\"1.0\" encoding =\"UTF-8\"?>\n",
      "\n",
      "<X3D profile=\"Immersive\" version=\"3.0\" id=\"scene_1\" >\n",
      "  <head>\n",
      "    <meta name=\"filename\" content=\"Stream\"/>\n",
      "    <meta name=\"generator\" content=\"Visualization ToolKit X3D exporter v0.9.1\"/>\n",
      "    <meta name=\"numberofelements\" content=\"1\"/>\n",
      "  </head>\n",
      "  <Scene>\n",
      "    <Background skyColor=\"0.5 0.5 0.5\"/>\n",

この x3d scene をエディタで修正して、Jupyter notebook で再び読み込むと、その通り 3D シーンが変わりました。ですから、別のプログラムから、x3d 形式でテキストを導入すれば、Mayavi 以外からも 3D 画像を表示できるはずです。
 もう一点。 Jupyter notebook をHTML形式で保存したファイルを、Juopyterがインストールされていない環境でも 3次元描画すれば便利ですね。
さて、HTML形式で保存したファイル Jupyter notebook は x3d データを含んでいますが、これをブラウザで開いても 3D は描画されません。x3d 描画を指示する部分は、ここですが、ローカルファイルを指しているため、描画されなかったのでしょう。

<script type="text/javascript">
require(["/nbextensions/mayavi/x3d/x3dom.js"], function(x3dom) { /**/
    var x3dom_css = document.getElementById("x3dom-css"); 
    if (x3dom_css === null) {
        var l = document.createElement("link");
        l.setAttribute("rel", "stylesheet");
        l.setAttribute("type", "text/css");
        l.setAttribute("href", "/nbextensions/mayavi/x3d/x3dom.css"); /**/
        l.setAttribute("id", "x3dom-css");
        $("head").append(l);
    }
    if (typeof x3dom != 'undefined') {
        x3dom.reload();
    }
    else if (typeof window.x3dom != 'undefined') {
        window.x3dom.reload();
    }
})
</script>

 nbextension 内の Mayavi プラグインへの参照を、x3dom.org に書き直してみます。
* /nbextensions/mayavi/x3d/x3dom.jshttp://www.x3dom.org/download/x3dom.js に修正
* /nbextensions/mayavi/x3d/x3dom.csshttp://www.x3dom.org/download/x3dom.css に修正

スクリーンショット 2016-12-12 17.10.33.png

すると、3Dの描画エリアは描かれるようになったのですが、まだ 3Dオブジェクトは描かれていません。
 以上の2点、もう少し考えてみます。うまくいったら、追記したいです。
 最後は、妄想で終わってごめんなさい。

この投稿は jupyter notebook Advent Calendar 201612日目の記事です。