Preoperative simulation for laparoscopic hepatectomy using Unity & Synapse Vincent

#背景
　肝切除術の術前シミュレーションとしては、DICOMビューワーによる軸位断や冠状断による観察、Synapse Vincentを用いた三次元画像作成と観察が、用いられている。開腹手術においては、術中超音波が術中の腫瘍や脈管の観察に非常に有用であり、また直接触知することにより術中にオリエンテーションを得ることは比較的容易である。
　しかしながら腹腔鏡手術では、術中超音波の操作性や性能は開腹のものに劣り、また触覚がないこともあり、術中オリエンテーションを得ることが困難な場合がある。そのため、術前シミュレーションは、相対的に重要となっている。
　術前オリエンテーションは、Synapse Vincentにより行われるが、短所として


- 肝臓の透明度が1つのみ
- 対象（肝臓）を動かすが、視点（腹腔鏡）を動かすわけではなく、実際の観察と乖離している
- 3次元ではない

などが挙げられる。

#目的
　Synapse Vincentの短所を改善して、術前シミュレーション向上のために、Unityを使用した画像処理、観察プログラムを作成する。

#方法
##材料
DICOMデータ：術前CTのボリュームデータ
##使用ソフト
Synapse Vincent
Horos
Blender
Unity
Windows10
##方法
###１　Synapse Vincentによる肝臓解析（自力で抽出する場合は、Horosからでも可能）
　術前ＣＴのボリュームデータをSynapse Vincentに取り込み、肝臓解析を行う。4相で撮影している場合は、自動的に、早期肝動脈相、晩期肝動脈相、門脈相、平衡相に分けられるが、平衡相（静脈相）は不要なことが多いので、3相で取り込むことが多い。肝臓の位置合わせは非剛体で行う。肝動脈、門脈は適宜抽出し、胆管・胆嚢も必要に応じて抽出する。
　肝臓観察画面において、右下の領域抽出より、抽出したそれぞれをDICOMファイルとして書き出す。Synapse Vincentのバージョンによっては、直接stlファイルとして書き出すことができる（ようである）。
###2　Horosによるstlへの変換
　Synapse Vincentで抽出してDICOMとして書き出したファイルを、DICOMビューワーのHorosで読み込む。3Dのサーフェイスレンダリングを行い、skinを選択すると、抽出した領域の3D画像が描出される。これをexportにてstlファイルで書き出す。この操作を抽出したそれぞれのファイルに対して行う。
　Synapse Vincentがない場合は、Horosを用いて自力でROIを作成して、領域抽出を行うことは可能である。
###3　Blenderによる処理（スキップ可能）
　stlファイルをBlenderに取り込んで、平滑化などの画像処理を行う。肝切離線がおおよそ決まっていれば、肝切離をシミュレーションするために、肝臓画像を変形させる。ファイルはBlender形式で保存する。
###4　Unityへの取り込み
　BlenderファイルはそのままUnityに取り込むことができるので、新規シーンを開いて、Assetへドラッグ＆ドロップして取り込む。
###5　Unityの設定
　カメラポートが原点(0, 0, 0)になるように配置を行う。1目盛りが1cmになるように換算している。肝臓の中心は(-5, -10, 20)あたりになる。腹腔鏡はフレキシブルスコープを模しており、フレキシブルスコープの曲がる付け根をcenterとして、ゲームオブジェクトを一つおいておく。その3cm先にメインカメラを配置している。照明はスポットライトをメインカメラの子オブジェクトとして配置している。

```scope.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Scope6: MonoBehaviour {
    // leng is the length between camera port and the center

    public float leng = 10;

    // dia is the length between the center and the main camera

    public float dia = 3f;

    // arz, elev, carz, celev are angles
    // arz is pan of the center from camera port (0,0,0), elev is pitch of the center
    // carz is pan of the camera, celev is pitch of the camera

    float arz = 0;
    float elev = 0;
    float carz = 0;
    float celev = 0;

    // scp is the initial position of the scope, cmp is camera

    public Vector3 scp = new Vector3(0, 0, 0);
    public Vector3 cmp = new Vector3(0, 0, 0);

   
    //public GameObject prefab = new GameObject();


    // Use this for initialization
    void Start () {

	}

    // Update is called once per frame
    void Update()
    {

        // How to move the scope and camera.

        if (Input.GetKey(KeyCode.S) && leng < 30)
        {
            leng += 0.1f;
        }
        if (Input.GetKey(KeyCode.X) && leng > -20)
        {
            leng -= 0.1f;
        }

        if (Input.GetKey(KeyCode.D) && arz < 1.5)
        {
            arz += 0.005f;
        }

        if (Input.GetKey(KeyCode.A) && arz > -1.5)
        {
            arz -= 0.005f;
        }

        if (Input.GetKey(KeyCode.W) && elev < 0.5)
        {
            elev += 0.005f;
        }

        if (Input.GetKey(KeyCode.Z) && elev > -1.5)
        {
            elev -= 0.005f;
        }

        if (Input.GetKey(KeyCode.RightArrow) && carz < 1.7)
        {
            carz += 0.005f;
        }

        if (Input.GetKey(KeyCode.LeftArrow) && carz > -1.7)
        {
            carz -= 0.005f;
        }

        if (Input.GetKey(KeyCode.UpArrow) && celev < 1.7)
        {
            celev += 0.005f;
        }

        if (Input.GetKey(KeyCode.DownArrow) && celev > -1.7)
        {
            celev -= 0.005f;
        }

        if (Input.GetKey(KeyCode.M))
        {
            carz = 0;
            celev = 0;
        }

        if (Input.GetKey(KeyCode.N))
        {
            arz = 0;
            elev = 0;
            carz = 0;
            celev = 0;
        }

        // the moved position of the scope is decided by leng, elev, arz

        scp.x = leng * Mathf.Cos(elev) * Mathf.Sin(arz);
        scp.y = leng * Mathf.Sin(elev);
        scp.z = leng * Mathf.Cos(elev) * Mathf.Cos(arz);


        // the moved position of the main camera is decided by leng, elev, arz

        cmp.x = leng * Mathf.Cos(elev) * Mathf.Sin(arz) + dia * Mathf.Cos(elev + celev) * Mathf.Sin(arz + carz);
        cmp.y = leng * Mathf.Sin(elev) + dia * Mathf.Sin(elev + celev);
        cmp.z = leng * Mathf.Cos(elev) * Mathf.Cos(arz) + dia * Mathf.Cos(elev + celev) * Mathf.Cos(arz + carz);

        transform.position = scp;
        Camera.main.transform.position = cmp;

        // the orientation of the camera

        Vector3 ori = cmp + (cmp - scp);
        Camera.main.transform.LookAt(ori);

        // visualization of the scope and the camera
        LineRenderer scopeln = gameObject.GetComponent<LineRenderer>();
        scopeln.SetPosition(0, cmp);
        scopeln.SetPosition(1, scp);
        scopeln.SetPosition(2, Vector3.zero);
    }
 }
```

#結果

キーボードでスコープとカメラの向きを操作できる。
##スコープ
s：スコープの挿入
x：スコープの引き抜き
w：上向き
z：下向き
a：左向き
d：右向き
n：すべての角度リセット

##カメラ
上矢印：上向く鳥
下矢印：下向く鳥
右矢印：右向く鳥
左矢印：左向く鳥
m：カメラの角度リセット
#考察
　以上の画像処理をすることで、実際の腹腔鏡に似た視野、および操作性で、肝臓の観察が可能となる。下記のようなスクリプトを肝臓につけることで、肝臓の透過性を変化させることで、腫瘍や血管の位置を確認することができる。

```transparent.cs
public class Transparent : MonoBehaviour {
    Renderer render;
    Color color;
    float d = -0.01f;
	// Use this for initialization
	void Start () {
        render = GetComponent<Renderer>();
        color = render.material.color;
	}
	
	// Update is called once per frame
	void Update () {
        bool down = Input.GetKey(KeyCode.Space);
        if (down)
        {
            if (color.a <= 0)
            {
                d = 0.01f;
            }
            if (color.a >= 1)
            {
                d = -0.01f;
            }
            color.a += d;
            render.material.color = color;
        }
	}
}
```