Extending TouchDesigner @MUTEK.JP
プロジェクトファイル: Extending-TouchDesigner-MUTEK.zip
はじめに
このワークショップでは、内容を時間内に100%理解してもらう事を目的としていません。
標準の機能でやりたい事が実現できなかった時、どうやって解決へと漕ぎ着けるかのサバイバルマップ的な位置付けになればと思っています。 内容はWebでも公開しますので、無理してメモなどを取らないでも大丈夫です。概念的な所を持ち帰って下さい。
なるべく飛躍がないように気をつけますが、「これは何?」という所があったらすぐ質問してください。あなたが分からないことはみんなもわからない可能性が高いです!
通常のオペレーターを使う
/project1/USE_BASIC_OPERATOR
まずは通常のようにオペレーターを使う方法です。これは皆さん日常的にやっていると思います。
ここが全ての基本になります。
ネットワークエディタの利点は見通しのよさ、作業内容を視覚的に追っていける所です。 このセッションで学ぶ色々な方法も最終的にはこのネットワークエディタに戻ってきます。
Text DATを使う
次にScript CHOP、Sctipt SOPなどのスクリプト系のオペレーター TouchDesginerでPythonを扱う際に必ず使うことになります
まずは基本的な操作をみていきつつ、TouchDesigner内でのPythonのTIPSをいつくか紹介していきます
TouchDesigner上でテキストを編集する
外部エディタでText DATを編集する
Pythonスクリプトを書くにあたって、TouchDesignerのText DAT
はお手軽なのですが不便なこともあります。Preferences
メニュー > DATs
タブ > Text Editor
を指定することで、任意のテキストエディタを使うことができます。
外部のテキストエディタを使うことで、コードを書く際に書式を間違っていた時にエラーを出してくれたり、その他色々な便利な機能があるのでなるべく外部エディタを使ったほうがいいでしょう。僕はいつも Visual Studio Code を使っています。
Preference
> DATs
> Table Editor
のほうも指定していると、Table DAT
を開いた時にも外部エディタで開いてくれます。この場合、テーブルは タブ文字
区切りのテキストとして表現されます。エディタの設定でスペースが入ってしまう場合は設定を切り替えてタブ文字
が入るようにしてください。空白文字を表示するオプションを有効にしておくと、タブ文字とスペースの見分けがつくようになるので便利です。
Text DATの構造化
プロジェクトがある程度の大きさになってくると、一枚のText DAT
に入りきる規模ではなくなってくる時があります。そのような時は関数やクラスなど機能単位ごとにText DAT
を分割して、再利用しやすいように構造化すると便利です。
TouchDesignerには、Pythonスクリプトを構造化するためのいくつかの機能があります。
import
を使う
同一階層にあるText DAT
は、import
を使ってPythonモジュールのように取り込むことができます。
import my_common
my_common.my_func()
my_common.my_class()
mod()
を使う
同様にして、TouchDesignerの機能である mod
関数を使うと別の階層にあるText DAT
も読み込むことができます。こちらは文字列から対象のText DAT
を指定できるため、より柔軟性の高い構造化が可能です。
ただ、柔軟性が高い = より処理負荷が高い、といった側面も往々にしてあるので、ループの中で使うなどの場合は注意が必要です。
my_common = mod('my_common')
my_common.my_func()
my_common.my_class()
Merge DAT
を使う
Select DAT
や Merge DAT
を組み合わせることでモジュールシステムに似た構造をオペレーターレベルで実現することも可能です。この場合は文字列レベルのコピーになってしまい、元のText DAT
の変数の状態などは参照されないため、関数やクラスの定義を共有する用途では使えないことに注意してください。
COMP Extension
を使う
今まで3つの手法を紹介してきましたが、Pythonスクリプトの構造化という点においてはこの COMP Extension
が一番安定しています。
COMP Extension
は、COMPをPythonで拡張してカスタムメソッドや変数を追加できるようにする機能です。1つのPythonクラスを定義するようにしてCOMPに新たな機能を付加するように働くため、__init__
メソッドを使った初期化処理が書けます。この特徴によって初期化の順番などがコントロールしやすくなり、より堅牢に構造化できます。
クラスの中で、大文字で初まるメンバ変数やメソッドは外部に公開され、Pythonから参照できるようになります。
他の方法と比べて使う手順や知っておくべき知識が多いのでとっつきにくいですが、一度覚えてしまうと他の手法よりも使いやすく、ハマり所が少ないのでオススメです。
TouchDesigner Comp Extensions の使い方 - Qiita
標準ライブラリを使う
TouchDesignerに搭載されているPythonはほとんど制限なく、全ての機能を使うことができます。
Pythonの標準ライブラリはこちらにまとまっていますが、その中でも有用そうなものをピックアップしていくつかサンプルを作成しました。Python 標準ライブラリ - Python 3.6.5 ドキュメント
colorsys
/project1/STANDARD_LIBRARIES/colorsys
colorsys
は、RGBやHSV、HSLといった色空間を相互変換するモジュールです。TouchDesignerにも RGB to HSV TOP
や HSV to RGB TOP
がありますが、それの単色版と考えることができます。
こちらのサンプルは、Circle SOP
で作成した円に対して Texture SOP
の Texture Type
> Edge Length
を指定して線の長さを0~1のu座標に割り当てた値を利用してHSVの色の値からカラフルな色の円のジオメトリを生成しています。
csv
/project1/STANDARD_LIBRARIES/csv
csv
は、コンマで区切られたテキストデータを処理するためのモジュールです。Microsoft Excelのような表計算ソフトでよく扱われます。TouchDesignerは、デフォルトでタブ区切りのtsvを扱うことができますがcsvはそのまま読み込むことができません。Pythonモジュールを使えば標準でサポートされていないファイル形式でも扱うことができます。
こちらのサンプルは、インターネットからダウンロードできるcsvファイルを使って簡単なデータビジュアライズを試してみた結果です。世界中の6万近くの空港の緯度経度データのcsvファイルをSOPに立ち上げて表示するといったものです。
json
/project1/STANDARD_LIBRARIES/json_http
json
は、jsonフォーマットの文字列をパースしてPythonオブジェクトに変換するモジュールです。このサンプルは urllib3
モジュールも利用しており、インターネット上から過去一日の間に起った地震のデータをダウンロードしてきて、その緯度経度情報からのビジュアライズを表示します。
urllib3
も OpenCV
や numpy
と同じくTouchDesignerにデフォルトで入っているサードパーティのライブラリで、HTTPを使ったデータのダウンロードをとても簡単に実装できます。
OpenCV
、numpy
を使う
Python標準ライブラリ以外にもTouchDesignerに標準で組込まれている外部モジュールがあります。OpenCV、numpyなどです。
/project1/OpenCV
では、2018.25850 で追加された、TOPクラスの numpyArray()
メソッドを使って画像データを取得し、OpenCVの画像解析の1つである特徴点抽出を使ってビジュアルエフェクトとして利用しています。
ネットワークが多少複雑に見えますが、大部分はレンダリングに必要なアスペクト等を計算している部分なので、本質的な所は以下の OP Execute DAT
の部分のみです。
import numpy as np
import cv2
feature_params = dict(
maxCorners=300, qualityLevel=0.1, minDistance=2, blockSize=2)
def onPostCook(changeOp):
arr = changeOp.numpyArray(delayed=True)
gray = arr[:,:,0]
pts = cv2.goodFeaturesToTrack(gray, mask = None, **feature_params)
script1 = op('script1')
script1.clear()
script1.numSamples = len(pts)
tx = script1.appendChan('tx')
ty = script1.appendChan('ty')
for i in range(script1.numSamples):
pt = pts[i][0]
tx[i] = pt[0]
ty[i] = pt[1]
TOPのクラスの .numpyArray(delayed=True)
のメソッドを呼び出すと numpy.ndarray
の画像データが取得できます。cv2.goodFeaturesToTrack
にはグレースケールの画像を入れないといけないので、 gray = arr[:,:,0]
の部分で赤チャンネルだけ抜きだして、グレースケールにしています。delayed
引数は、フレーム遅延を許容するかわりに処理を高速化するオプションです。
arr = changeOp.numpyArray(delayed=True)
gray = arr[:,:,0]
続けて、cv2.goodFeaturesToTrack
で画像から特徴点の配列を計算します。feature_params
には特徴点の最大数、信頼性のフィルタ係数などのパラメーターを指定します。
pts = cv2.goodFeaturesToTrack(gray, mask = None, **feature_params)
cv2.goodFeaturesToTrack
から返ってくる特徴点の配列はピクセル座標なので、Render TOP
でレンダリングできるように適当なスケールに変更して、Projection
モードを Otrhographic
にしたカメラで撮影して元画像に重ねる、といった具合です。
OpenCVの関数は色々な面白い効果を出せる物が多いのですが、リアルタイムで実行するには処理速度が遅いものもあります。その場合は解像度を落とすなりして、処理負荷との兼ね合いを探る必要があります。
外部のPythonモジュールを使う
前述の OpenCV
や、numpy
以外にも、世界中にはPythonから使える様々なモジュール、ライブラリがあります。このセクションでは、外部のPythonモジュールを使うことでTouchDesignerの機能を大幅にアップデートする方法を示します。
例えば、Awesome Python:素晴らしい Python フレームワーク・ライブラリ・ソフトウェア・リソースの数々 - Qiita には大小様々な有用なライブラリが沢山リストされています。この大量のリソースをTouchDesignerで使えるとさらに表現の幅が広がります。
この作業を行なうには、まず、システムにPythonがインストールされていることが前提となります。 Download Python から、現在のTouchDesignerで使われている Python 3.5
系のインストーラーをダウンロードしてインストールしましょう。
Pythonのバージョン付けは以下のルールで決められています。
Python のバージョン番号の仕組みはどうなっているのですか?
Python のバージョン番号は A.B.C や A.B のように付けられています。 A はメジャーバージョン番号で、言語の本当に重要な変更の時のみ上げられます。 B はマイナーバージョン番号で、そこまでは大きくない変更の時に上げられます。 C はマイクロレベルで、バグフィックスリリースの度に上げられます。
なので、今回の場合は Python 3.5.X
の一番新しいものをインストールしましょう。(3.5系はもうあまりアップデートされていないようで、Windows版の最新のインストーラーは 3.5.4
までしかありませんでした)
外部ライブラリのインストール
Pythonのライブラリをインストールするには pip
コマンドを使うのが簡単です。pip
コマンドは、Pythonのパッケージマネジメントをするためのもので、使いたいライブラリを依存ライブラリも含めて簡単にインストールできます。例えば pip install opencv-python
とするとopencvがインストールできます。
PyPI – the Python Package Index では、pip
コマンドでインストール可能な全てのライブラリを検索できます。
注意: 昔のバージョンのPythonにインストールされている pip
コマンドはバージョンが古く、Could not fetch URL https://pypi.python.org/simple/pip/: ...
のようなエラーメッセージが出て上手く動かない場合があります。その場合は https://bootstrap.pypa.io/get-pip.py をダウンロードしてきて Pythonで実行すると最新版にアップデートできます。
方法1: Preferenceから設定する
外部のPythonモジュールを使う一番簡単な方法は、TouchDesignerの Preference
> General
> Python 64-bit Module Path
にインストールしたPythonの site-packages
フォルダを指定する方法です。
僕の環境だと、C:\Python35\Lib\site-packages\
を指定しましたが、インストール時の設定によって変わっている可能性があります。 【Python】site-packagesのパスを確認する方法【pip install】 - Qiita
site-packages
は、Pythonに pip
コマンド等で外部から何かしらのライブラリをインストールした時のインストール先のフォルダです。なので、それを指定しておくことで、システムのPythonで使えるPythonモジュールはTouchDesignerでも使えるという状態にできます。
なのですが、この方法は個人的にはあまりオススメしません。ライブラリのインストールに管理者権限が必要になったり、同じプロジェクトを他のPCへ移動したい場合、Pythonにインストールされているライブラリを調べて新しい環境で再度インストール、といった手間が発生するためです。
方法2: モジュール探索パスの設定
もう少しお手軽に済ますこともできます。
例えば、以下のようなスクリプトを実行すると、.toe
のあるフォルダの python
フォルダがモジュール探索パスに追加されて、そこに入れてあるモジュールを import
できるようになります。sys.path
がモジュール探索パスの配列になっているためです。
import sys, os
path = os.path.join(os.path.abspath('.'), 'python')
if not path in sys.path:
sys.path.append(path)
print('Append new module search path:', path)
こうしておくことで、.toe
と同階層の python
フォルダに依存ライブラリが集約でき、プロジェクト管理が非常に楽になります。ただ、この方法だと全てのライブラリを手動で管理しないといけなくなり、依存が複雑なパッケージをインストールしようとすると結構大変な作業になります。
cjlano/svg のような簡単なライブラリであればこの方法でも必要十分です。
方法3: venv
で仮想環境を作成、pipでインストール
venv
を使うと、pip
コマンドの簡単なパッケージ管理の恩恵を受けながらプロジェクトローカルフォルダでスクリプトを管理できます。この方法が今の所一番オススメ です。 これは、Pythonに 仮想環境 と呼ばれる機能を追加するための標準ライブラリで、ライブラリやPythonのバージョンなどの状態を環境ごとに管理する仕組みです。
仮想環境を作るには、コマンドプロンプトやターミナルから、.toe
のある階層で以下のコマンドを入力します。完了すると、 .venv
という新しいフォルダが作成され、その中に仮想環境が構築されます。
python -m venv .venv
pip
コマンドでライブラリをインストールする場合は、一旦仮想環境を有効化する必要があります。
.venv\Scripts\activate.bat
を実行してください。OSXの場合は、
source .venv/bin/activate
になります。コマンドプロンプトの表示に (.venv)
と追加されれば仮想環境の有効化は成功です。
この状態で pip
コマンドを使ってインストールしたライブラリは .vnev\Lib\site-packages
にインストールされます。前述の sys.path.append()
を使った手法でこのフォルダを指定すると pip
コマンドを活用しつつ、プロジェクトローカルにライブラリをインストールできます。
Windowsでのセットアップの手順
OSXでのセットアップの手順
最近傍探索
仮想環境の準備ができたので、手頃な例として最近傍探索を取り上げます。
最近傍探索は、点群の中から任意の点に対して距離的に近い点をいかに効率よく検索するかという問題です。ビジュアルエフェクトへの応用としては、After EffectsのプラグインのPlexus
が最近傍探索を使ったものの代表的な例として挙げられます。
通常、Plexusのような、ある程度近い点同士を結ぶといった効果を生み出すには、パーティクルの数 ^ 2 / 2
の計算量が必要になり、単純な実装だと数が増えるほどに指数的に計算量が増えていきます。そこでC++で実装された FLANN
のような、最近某探索を高速にするためのライブラリを利用することになります。今回はFLANN
のPython移植版の実装であるpyflann
を pip
を使ってインストールして利用します。
Pythonで近似最近傍探索を試したいときはpyflannがちょうど良い - 洋食の日記
上記の記事のように、pip install git+ssh://git@github.com/EdsterG/pyflann.git
として pyflann
をインストールできます。実際に使って実装した例が /project1/USE_VENV/ANN
にあります。
CPlusPlus XOPを使う
これまで外部のPythonライブラリやnumpy
を使ってきましたが、Pythonでプログラムを書いている以上、かなり気をつけないとパフォーマンスの問題にすぐに直面してしまいます。
上記の記事によると、Pythonが遅い原因は自由度を犠牲にしていないからと結論づけられています。TouchDesignerの柔軟な設計もPythonの自由度の上に成り立っている部分もあるのでしょうがない部分ではあります。
速度面の改善は、ある程度はパフォーマンスチューニングで何とかできますが、根本的にPythonを使わない、という選択肢もあります。
パフォーマンスチューニングでprofiler使わないのは損してると思う - Qiita
100万倍速いプログラムを書く - Qiita
このセクションでは、C++を使ってのTouchDesignerのカスタムオペレーターの実装にフォーカスします。
CPlusPlus XOPのサンプルのありか
まずはC++オペレーターのサンプルを見てみましょう。TouchDesignerがインストールされているフォルダの Samples\CPlusPlus
以下にいくつかサンプルがあります。
Visual Studio 2017
や Xcode
がインストールされた環境であればコンパイルして試すことかできます。
Hello World
サンプルを見ながら実装していくのが基本となりますが、satoruhiga/TouchDesigner-Plugin-Template に僕が作った、オペレーターを作るまでをなるべく簡単にするためのテンプレートがあるので、今回はこちらを使って簡単なオペレーターを書いていきます。
このテンプレートは CMake というビルドシステムを使っていて、オプションを指定することで各オペレータータイプを簡単に書き始められるよう工夫したものです。
Windowsでのビルド手順
OSXでのビルド手順
Scatter SOP
/project1/USE_CPLUSPLUS
には上記のテンプレートを使って実装したサンプルがあります。
三角ポリゴンで構成されたメッシュの表面に任意の数のポイントをランダムで分布させる、といった機能のSOPオペレーターですが、同じアルゴリズムで実装したScript SOP
を使った実装よりも80倍近く高速に動作します。
実装の詳細は 3次元空間、複数三角形内に均一に、点をばらまく - Qiita を参考にしました。こうやってC++を使ったオペレータを開発することで、openFrameworksやCinderといった環境と同程度の、CPUに負荷がかかりがちな処理を高速に実装できます。
TouchDesignerを拡張する意義
ここまでいかにしてTouchDesignerを拡張するかについて書いてきましたが、なぜ基本的な機能だけではいけないのか、という事に触れていませんでした。その答えは、例えばこの作品を見ていただければわかると思います。
技術的にはnVidiaがリリースした流体物理演算ライブラリのFlexをCHOPから扱えるようにした FlexCHOP を使っていて、水のようなレンダリングはおそらく http://developer.download.nvidia.com/presentations/2010/gdc/Direct3D_Effects.pdf のレンダリング手法を使っているのだと思いますが、Kinectを使ったインタラクションも相俟ってとにかく物理的な動きの気持ちよさと、レンダリングの綺麗さがすごいです。
もちろん基本的な機能だけでも勿論相当なことはできますが、この作品のようにどんどん新しい要素を貪欲に取り入れていったほうがおもしろい結果になりやすいのではないか、と僕は考えます。なので、何か面白いものを作りたいと思うのであれば、新しいこと、難しいことにひるまずに、どんどん自分のできる事をアップデートし続ける姿勢が大事なのではないか、と思ったのがこのワークショップの題材を決めたきっかけです。
人と違うことをするには人と違うことをしなければならない