TypeScriptと戯れる、constructorでのthisの変更

使ってない機能についてネット上のサンプルをもとにちょっと勉強

class Base {
    x: number = 0;
    constructor() {
        // `this`ではなく、新しいオブジェクトを返す
        return {
            x: 1,
        };
    }
}
class Derived extends Base {
    constructor() {
        super();
        this.x += 1;
    }
}

var derved = new Derived();
derved.x;// 2
derved instanceof Base; //false
derved instanceof Derived; //false

これ使うのは最終手段的な感じだなぁ。

TypeScriptと戯れる、クラス拡張と匿名継承

似たような処理をするクラス群で微妙に違うものを定義したい。
クラスとしても使えるし、コード補完で定義済みも用意されてる処理を実現したい。
のでちょっと実験してみた。

クラス中のクラス宣言はできなかったのだが
同名namespaceを利用してclassを拡張する事ができる
のを知った。

普通に行うとコンストラクタを利用して定義済みインスタンスを用意すればいいのだが
匿名クラスを直接実体化することで必要によりメソッドのオーバーライドも可能な実装が実現できた。
プロパティーが多い時にも対応できる。

TypeScriptでは定義さえも処理されることで実現されるため
定義と処理を混在させる事ができるため座標を少しづつずらした定義なのも簡単に実現できる。

以下テストコード※実用するにはImageが読み終わるまで待つなどの処理が必要

    export class ImageResource{
        public Image: HTMLImageElement;
        public Location: { x: number, y: number };
        public Size: { w: number, h: number };
    }
    export namespace ImageResource {
        let ImageA = new Image();
        ImageA.src = "resource.png";
        class DefaultResource extends ImageResource { Image = ImageA; Location = { x: 0, y: 0 }; Size = { w: 64, h: 64 }; }
        let x = 0, w = 64;
        export let Image1: ImageResource = new class extends DefaultResource { Location = { x: x, y: 0 } }; x += w;
        export let Image2: ImageResource = new class extends DefaultResource { Location = { x: x, y: 0 } }; x += w;
        export let Image3: ImageResource = new class extends DefaultResource { Location = { x: x, y: 0 } }; x += w;
    }
//定義済み
    ImageResource.Image2;//VSでのコード補完での選択確認
//生成
    let img=new ImageResource();
//img.でクラスメンバの候補のみ出てくるのを確認

ブラウザのデバッガを使ってのデータ検索

ブラウザのJavaScriptはwindowオブジェクトをルートとして変数が定義されるので
windowオブジェクトから再帰的に指定した値を検索し
見つけたオブジェクト階層を出力することができるのではないかと思い
簡単なスクリプトを書いてみた。
※感覚的にはバイナリエディタでデータ値を検索しアドレスを見つける感じで。


(function(root,data,lvl){let logmax=99,cntmax=10000000;let cnt=0,a=[],r=[],b=[];function f(o){ if(b.indexOf(o)>=0)return;b.push(o);for(let k in o){if(!logmax || cntmax<=cnt)return;a.push(k);cnt++;if(o[k]===data){r.push(a.concat());logmax--;}if(lvl>a.length){if(o[k]) try{f(o[k]);}catch(e){}}a.pop();}};f(root);r.push("search "+cnt+" objects");return r;})(window,"find data",4);

最後の引数はそれぞれ
ルートオブジェクト,検索値,検索最大階層
となっている。

アクセスしたらだめなオブジェクトがあったりして使用時はwindowオブジェクト以外のオブジェクト指定か
ちょっとした改造が必要かもしれない。

動作確認はChromeでDeveloperツールを起動し停止後
Consoleに張り付けて行った。
そのさいconsole.logをつぶされているサイトがあったため使用しないようにした。

Activiz.NETを使ったDicomからのボリュームレンダリング

Activiz.NETを使うとC#でVTKを使用した開発ができる。
VisualStudioからNuGetを使用してさくっとC#でのVTKの開発環境が作れるが欠点もある
Activiz.NETは2012年1月30日に更新されたっきりでVTKのバージョンが古くネット上に多くあるサンプルが使えないことが多い。
その上でどこまでできるかやってみた。
ここまでのものが試行錯誤で2日ほどでできたので案外実用的かもしれない。
フォルダからDicomファイル一式を読み込むのも以下の処理で済むのは驚異的。

            vtkImageData data = new vtkImageData();
            using (vtkDICOMImageReader reader = new vtkDICOMImageReader())
            {
                reader.SetDirectoryName(dir);
                reader.Update();
                data.ShallowCopy(reader.GetOutput());
            }
            data.Update();


ボリュームレンダリングの領域に画像を割り込ませる事ができるので結構よさげ。
左パネルにボリュームレンダリングを
右4分割パネルにそれぞれAxial Sagittal Coronal Oblique断面を表示
分割パネルのほうをクリックで左パネルに断面を表示。
分割パネルのホイール操作で断面の移動。
ボリュームレンダリング側では表示された断面をつかんで動かせる。
TestDataフォルダの中を入れ替えればほかの画像も開けるはず。

MipMprDemo.zip

ボリュームレンダリング実験

色空間の調整により頭骨から髪まで観察することができる。

ただし血管を外部から透視するには頭骨が邪魔になるので色空間(WL)外の色を透過するように変更※左画像
if (src > 1) src = 0;

それでもうまく表示できないので
一定の濃さが蓄積されるまでは透視できるようしてみた。※右画像
シェーダー

float src = (_VolumeData[id] + ws) / _WindowWidth;
if (src > 1) src = 0;

float a = saturate(src);//透明度
if (rayopa > 0) {
    rayopa -= a;
    a = 0;
}

静止画で見るとわかりづらいがなかなか面白い結果が得られた。

ComputeBufferによるボリュームレンダリング

CPU部分

        _volumeData = new ComputeBuffer(volumeSizeX * volumeSizeY * volumeSizeZ, 4);
        _volumeData.SetData(volumeColors);
        _rayMarchMaterial.SetInt("_VolumeSizeX", volumeSizeX);
        _rayMarchMaterial.SetInt("_VolumeSizeY", volumeSizeY);
        _rayMarchMaterial.SetInt("_VolumeSizeZ", volumeSizeZ);
        _rayMarchMaterial.SetBuffer("_VolumeData", _volumeData);

GPU部分

	#define STEP_CNT 512
	#define STEP_SIZE 1 / STEP_CNT
	half4 raymarch(v2f i, float offset) 
	{
		float3 frontPos = tex2D(_FrontTex, i.uv[1]).xyz;		
		float3 backPos = tex2D(_BackTex, i.uv[1]).xyz;				
		float3 dir = backPos - frontPos;
		float3 pos = frontPos;
		float4 dst = 0;
		float3 stepDist = dir * STEP_SIZE;
		float ws = _WindowCenter - _WindowWidth/2;

//外側ならすべてが1になるみたい。
		if (pos.x >= 1.0f && pos.y >= 1.0f && pos.z >= 1.0f)
		{
			return dst;
		}
		for(int k = 0; k < STEP_CNT; k++)
		{
			int3 size = { _VolumeSizeX ,_VolumeSizeY ,_VolumeSizeZ};
			int3 index = (int3)(pos.xyz*size);
			int id = index.x + (index.y * size.x) + (index.z * (size.x * size.y));
			float src = (_VolumeData[id] + ws) / _WindowWidth;
			float a = saturate(src);

			// clipping
			float border = step(1 - _ClipDims.x, pos.x);
			border *= step(pos.y, _ClipDims.y);
			border *= step(pos.z, _ClipDims.z);
			border *= step(0, dot(_ClipPlane, float4(pos - 0.5, 1)) + _ClipPlane.w);

			// Standard blending	        
			a *= saturate(_Opacity * border);

			src *= a;
			dst.r = (1.0f - dst.a) * src + dst.r;
			dst.a = (1.0f - dst.a) * a + dst.a;
			pos += stepDist;
//一定の不透明度を超えたら演算中断
			if (dst.a >= 0.8) {
				break;
			}
		}
		dst.g = dst.b = dst.r / 2;
//黒い部分にも色を付けたい
		dst.b += 0.1;
		dst.g += 0.1;
		return dst;
	}

前回失敗していたのは
_volumeData = new ComputeBuffer(volumeSizeX * volumeSizeY * volumeSizeZ, 4);
での引数の4部分が小さかったためだった。
実機でTexture3Dの倍の512*512*120ほどのボクセルデータが読み込めるようになった。
もっといけるはずだが今のとこ描画が追いつかない・・。

ボリュームレンダリングの実験

Twitterだと書ききれないのでこちらに。
試行錯誤の独り言です。

Unityの3dテクスチャによるボリュームレンダリングは参考になるサイトもそれなりにあって表示もうまく行ったのだけどAPIが初期化時に実際の8倍以上のメモリを要求する。
部分初期化はサポートしてないようだ。

対策としてComputeBufferを使って処理しようとしたらたしか、4の倍数で2048以下でないとダメというエラーがでたので早々に諦めて
テクスチャを数枚z軸に対して積み重ねるようにしてみた。
例えばzが0.5以下なら0番目のテクスチャを
z値2倍で使う
それより上なら1番目のテクスチャをz値0.5差し引いた上で2倍で使う。
この試みは一応の成功、
問題点としてはつなぎ目がおかしくなる
これはボリューム内のz軸一枚分それぞれ余分に持ち計算式を少し変えれば解決できそうだ。
他にもシェーダー変更後Unityをアクティブにした瞬間Unityがなぜかフリーズというレベルで固まる。
10分以上応答がなくおそらく変換処理をしてるのだろうけど重すぎる。
これにより知識が足りない故のトライアンドエラーがうまくいかなくなる。
テクスチャを3枚以上同じ方法でつかうと
実行する前にループ文にフラグつけろとのエラーがでてつけてみたけど変わらない。(もしかしてマテリアルにシェーダー設定されてる時点で内部実行されてる?)
Unityはさらにフリーズするようになる。

とりあえず次回は
https://developer.microsoft.com/en-us/windows/mixed-reality/volume_rendering
を真似てやはりComputeBufferをつかう方法を再度チャレンジ

A-Bikeカスタマイズ ハンドルのバタつき

ハンドルのバタつきは標準でゴム紐により対応しているがこれにより折りたたみ時のハンドルの引き抜きが困難になっている。
あと使っているうちに外れたので
今回は簡易的に対応する事に
引っ張る力が必要で磁石なら接着するほど近くないといけない。
今回は微妙に離れているのと折りたたみ時の移動でも問題ないようにしたい。
リールキーホルダーを使えば近距離で引く力が得られるし、片方の固定で使う時に引っかければよい感じになる。
ゴムバンドでは片方の固定だけだとぶら下がって邪魔になるのでこちらの方が利点が大きい。、


今回はもともと付いてたクリップで対応したけど
時間ある時に裏にネジ止めしたいところ。

A-Bike 用カバー作成

折りたたみ状態で転がせるようになったので
移動時用の簡易カバーが欲しいので
100円ショップのパーツで試作して見た。
ハサミあればできるのでらくちん。
専用バッグは入れるのが困難な上に転がせない
かさばるなどの欠点があるので
そこに特化してみる。
袋の片面が切れた形状で
被せた後、切れた内側中央と端の二箇所をとめると
袋状になるのでそのまま袋外側を縛るかわりに
上下二箇所をマジックテープでとめるだけでよい。
コストもかからず作るのが楽なので
耐久性は気にしない。
IMG_3659

IMG_3661

IMG_3662

HoloLens用のコントローラーブックマークレット

試作版を公開します。

iPhoneのSafariでの動作はできなかったのでiPhoneではとりあえずChrome使ってください。

Solica HoloLensTools
※マルウェア検知されますね・・回避方法はまたあとで考えますが危険なコードは含まれてないです。

インストール方法:
Hololens device portalに接続し
File ExplorerによりDocumentsに
solica.hololens.js
user.hololens.js
をアップします。

スマホやPCのブラウザに
bookmarklet.txtの内容で適当な名前を付けて登録します。
スマホの場合ブックマークの名前はシンプルなものにしてください。

使い方:
接続方法
まずHololens device portalに接続します。
そのページを開いた状態で
PCならブックマークを開きます。
スマホならアドレスバーに先ほど登録したブックマーク名を入力していくと一覧に登録したものが出てくるのでそこで選んで開きます。

起動後
現在の版では下のテキストボックスに文字を入力しソフトウェアキーボードのEnterをすると送信されます。
テキストがない場合そのコードが入力されます。
例えばテキストボックスにカーソルがある状態でバックスペースなどをするとそのまま送信されます。
マウスかタッチにより
上のパネルでドラッグした方向に該当するカーソルキーが入力されます。
キー入力が確定後タッチをやめるなどの解除処理をするまで定期的に処理されます。
3本指で下に動かすとWindowsキーが入力されホーム画面が現れます。
その他詳細は
user.hololens.js
を読み、改造してください。

スクロールやジェスチャーを送信するREST APIも見つけましたが今のところ動作を確認できませんでした。
せめてクリックができればモチベが上がるのですが。

備考:httpsの上クロスサイトドメインの回避の為にブックマークレットによる実装となりました。REST APIをつついてみるのにもよさそうですね。

追記:
書き忘れていたのでですがiPhone Chromeのポップアップブロックを解除する必要もあり。
追記2:
ドラッグ(スワイプ)とフリックが排他制御入ってるせいで操作性が悪くてスクリプト作りづらいですね。
次のバージョンでは修正します。