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 DATMerge 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 TOPHSV to RGB TOP がありますが、それの単色版と考えることができます。

こちらのサンプルは、Circle SOP で作成した円に対して Texture SOPTexture 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 モジュールも利用しており、インターネット上から過去一日の間に起った地震のデータをダウンロードしてきて、その緯度経度情報からのビジュアライズを表示します。

urllib3OpenCVnumpy と同じくTouchDesignerにデフォルトで入っているサードパーティのライブラリで、HTTPを使ったデータのダウンロードをとても簡単に実装できます。

OpenCVnumpy を使う

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移植版の実装であるpyflannpip を使ってインストールして利用します。

Pythonで近似最近傍探索を試したいときはpyflannがちょうど良い - 洋食の日記

上記の記事のように、pip install git+ssh://git@github.com/EdsterG/pyflann.git として pyflann をインストールできます。実際に使って実装した例が /project1/USE_VENV/ANN にあります。

CPlusPlus XOPを使う

これまで外部のPythonライブラリやnumpyを使ってきましたが、Pythonでプログラムを書いている以上、かなり気をつけないとパフォーマンスの問題にすぐに直面してしまいます。

なぜPythonはこんなにも遅いのか? | POSTD

上記の記事によると、Pythonが遅い原因は自由度を犠牲にしていないからと結論づけられています。TouchDesignerの柔軟な設計もPythonの自由度の上に成り立っている部分もあるのでしょうがない部分ではあります。

速度面の改善は、ある程度はパフォーマンスチューニングで何とかできますが、根本的にPythonを使わない、という選択肢もあります。

パフォーマンスチューニングでprofiler使わないのは損してると思う - Qiita
100万倍速いプログラムを書く - Qiita

このセクションでは、C++を使ってのTouchDesignerのカスタムオペレーターの実装にフォーカスします。

CPlusPlus XOPのサンプルのありか

まずはC++オペレーターのサンプルを見てみましょう。TouchDesignerがインストールされているフォルダの Samples\CPlusPlus 以下にいくつかサンプルがあります。

Visual Studio 2017Xcode がインストールされた環境であればコンパイルして試すことかできます。

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を使ったインタラクションも相俟ってとにかく物理的な動きの気持ちよさと、レンダリングの綺麗さがすごいです。

もちろん基本的な機能だけでも勿論相当なことはできますが、この作品のようにどんどん新しい要素を貪欲に取り入れていったほうがおもしろい結果になりやすいのではないか、と僕は考えます。なので、何か面白いものを作りたいと思うのであれば、新しいこと、難しいことにひるまずに、どんどん自分のできる事をアップデートし続ける姿勢が大事なのではないか、と思ったのがこのワークショップの題材を決めたきっかけです。

人と違うことをするには人と違うことをしなければならない