3Dviewerをスクリプトで動かす・録画する
序
以下、教科書2章4節の解説をさらに発展させた使い方である。
3DViewerはマウスを使ってぐりぐりと動かしながら扱うことがほとんどの場合だろう。とはいえ、スクリプトからも比較的容易にアクセスすることが可能であり、特に複雑な3次元画像のムービーを作るときにはスクリプトを書くことでその動きを細かく制御することができる。このページでは、Jythonを使って3DViewerを使う方法を紹介する。
Jythonの書き方がわからない、という方は以下のページで学んでほしい。
3Dviewerへのプロット
以下のコードを実行すれば、サンプル画像[Fly brain]が3Dviewerにプロットされる。まずは試してみて欲しい。
from ij import IJ
from ij3d import Image3DUniverse
flyurl = "http://imagej.nih.gov/ij/images/flybrain.zip"
imp = IJ.openImage(flyurl)
univ = Image3DUniverse()
univ.show()
c = univ.addVoltex(imp)
コードを解説しよう。
1、2行目:IJクラスと3DviewerのGUIのクラスをインポートしておく。なお、3DviewerのJavadocはこちらにある。
3行目:ImageJのウェブサイトにあるサンプル画像Fly brainのURLを変数として設定する。
4行目:上記の画像を、ImagePlusオブジェクトとして取得する。
5行目:Image3DUniverseをインスタンス化する。
6行目:このインスタンスをデスクトップ上に表示する。
7行目:flybrainのImagePlusオブジェクトを、3Dviewer上にプロットする。
3Dviewerのオブジェクトを動かす
さて、この3DViewerに描画された三次元オブジェクトを動かしてみよう。下のコードを上のコードに続けて貼り付けて実行すると、オブジェクトがぐるぐるとまわる。
from ij3d.behaviors import ViewPlatformTransformer
from javax.media.j3d import Transform3D
from javax.vecmath import Vector3d
import time
vtf = ViewPlatformTransformer(univ, univ)
x1z1 = Vector3d(1, 0, 1)
univ.rotateToPositiveXY()
for zm in range(1,3000, 10):
vtf.zoomTo(zm)
vtf.rotate(x1z1, 0.03)
time.sleep(0.01)
for zm in range(3000, 1000, -10):
vtf.zoomTo(zm)
vtf.rotate(x1z1, 0.03)
time.sleep(0.01)
コードを解説する。
最初の1-4行はインポート文で、必要なクラスをここで宣言する。
6行目: ViewPlatformTransformerをインスタンス化(オブジェクトを生成)する。引数はふたつあるが、両方共に現在表示しているImage3DUniverseのインスタンスを指定する。
7行目: 回転の方向を指定する3次元ベクトルをVector3dクラスをインスタンス化して生成する。引数は三次元の座標になる。ここではx=1, y = 0, z = 1のベクトルを指定した。これはx軸を中心とする回転と、z軸を中心とする回転の組み合わせになる。x軸、y軸はモニターの平面を構成する座標軸であり、z軸は画面に垂直な方向に伸びる軸である、と想像するとわかりやい。z軸を中心にまわる、ということはすなわち、描画した構造が時計の針のようにくるくるとまわる、という印象になる。上のコードが動くことが確認できたら、このベクトルを(1, 0, 0)にしたり(0, 0, 1)にしたりすると、どのような動きをどの項が実現するのかがわかるだろう。
8行目: Image3DUniverseのメソッドを使って、デフォルトの位置にオブジェクトの配置を戻す。今回はスクラッチから描画するスクリプトなのであまり意味はないが、手で動かしてから実行する場合には開始のポジションを固定したほうがよいので、この一行をいれる。
9行目: 回転とズームの動きを構成するためのループの開始。pythonの標準関数rangeを使って1から3000まで10づつ大きくなっていくようなリストを作成し、このリストをループすることになる。変数はzmに順番に使われていく。
10行目: ViewPlatformTransformerのインスタンスに対して、ズームの倍率を指定する… この場合にはzmで指定をしているので、ループにともなってすこしずつ大きくなっていく、といってもレンズの倍率とはことなり、観察者の視点からの距離を指定する。0は観察者の目にぴったりくっついていることになるが、1になると少し離れる。今回は3000が最大値であり、今回の動きでは一番の遠点にオブジェクトを位置させることになる。オブジェクトは徐々に離れていくような印象になる。
11行目: ViewPlatformTransformerのインスタンスに対して、回転を指定する。第一引数がループの前に作成した3次元ベクトルのオブジェクト、第二引数が回転する角度(ラジアン)。
12行目: 関数timeをつかってスクリプトの進行を0.01msec止める。これがないとあっというまに動きが終わってしまう。
13行目からは、逆方向のループ。内容は同じである。
3Dviewerのオブジェクトを動きを動画にする
以上で3DViewer上でオブジェクトを動かすスクリプトの書き方はわかったと思うのだが、これをムービーにして保存するには要所要所でスナップショットを撮影し、スタックに保存することが必要になる。以下のような感じである。
imp = univ.takeSnapshot()
stk = ImageStack(imp.width, imp.height)
stk.addSlice(imp.getProcessor())
Image3DUniverseのメソッドのひとつにtakeSnapshot()がある。返り値がImagePlusオブジェクトであり、これをこのままスタックに保持していけば良い。二行目で空のスタックオブジェクトを用意し、三行目でスナップショットのImageProcessorインスタンスを加えている。実際のスクリプトでは、ループ毎に一枚ずつ撮影し、スタックにキープする、という形になる。以下はここまでのすべてを組み合わせたスクリプトである。
2020年10月21日追記
回転の際の一番目の引数で使われる三次元ベクトルのクラスが、Java純正からScijavaのクラスに変更されていることに気がついた。これに伴い、上のコードはエラーを返す。訂正したものが以下になる。
from ij import IJ
from ij3d import Image3DUniverse
from ij3d.behaviors import ViewPlatformTransformer
from javax.media.j3d import Transform3D
#from javax.vecmath import Vector3d
from org.scijava.vecmath import Vector3d
import time
from ij import IJ, ImageStack
def addSnapShot(univ, stk):
imp = univ.takeSnapshot()
#stk = ImageStack(imp.width, imp.height)
stk.addSlice(imp.getProcessor())
flyurl = "http://imagej.nih.gov/ij/images/flybrain.zip"
imp = IJ.openImage(flyurl)
snapshotstk = ImageStack(imp.width, imp.height)
univ = Image3DUniverse()
univ.show()
c = univ.addVoltex(imp)
vtf = ViewPlatformTransformer(univ, univ)
x1z1 = Vector3d(1, 0, 1)
univ.rotateToPositiveXY()
for zm in range(1,3000, 10):
vtf.zoomTo(zm)
vtf.rotate(x1z1, 0.03)
time.sleep(0.01)
for zm in range(3000, 1000, -10):
vtf.zoomTo(zm)
vtf.rotate(x1z1, 0.03)
time.sleep(0.01)
このコードは以下にある。
出力されるスタックをmpeg4圧縮のムービーに変換したものが以下の動画である 。スタックを [File > SaveAs > Avi...]でエクスポートし(ファイル名はてきとうにout-2.aviとした)、ffmepeg を使って次のようなコマンドで変換した。
ffmpeg -i out-2.avi -vcodec mpeg4 -qscale 0 out-2.mov