5.4 Jaw/MLC外接距離のチェック

目的

x方向のJawの位置と、最大開度のMLCの位置が規定の距離になっているかを確認します。

必要な情報

JawのX方向の位置(X1, X2)、MLCの位置

与えられている引数

PlanSetupクラスのインスタンスplan

必要な情報へのアクセス方法

JawとMLCの位置は、各フィールド(Beamクラス)のコントロールポイントに定義されています。 Jawの位置情報は、VMS.TPS.Common.Model.Typs 名前空間の VRect 構造体で定義されており、この構造体はX1, X2, Y1, Y2の4つのプロパティを持っています。

フィールドの最初のコントロールポイントのJawの位置は以下のようにして取り出すことができます。

1
2
3
4
5
6
7
8
foreach(var beam in plan.Beams)
{
    // 最初のコントロールポイントのJaw位置
    var jawPositions = beam.ControlPoints.ElementAt(0).JawPositions;

    // X1座標を取り出す場合
    var x1 = jawPositions.X1;
}

同じく、MLC位置情報は以下のようにアクセスできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
foreach(var beam in plan.Beams)
{
    // MLCがない場合はアクセスしない
    if (beam.MLC != null)
    {
        var leafPositions = beam.ControlPoints.ElementAt(0).LeafPositions;
        // Leaf対の数を数える
        var leafPairs = leafPositions.GetLength(1);
        // X1側バンクの30番目のMLC位置の取り出し
        var x1_30 = leafPositions[0, 29];
        // X2側バンクの10番目のMLC位置の取り出し
        var x2_10 = leafPositions[1, 9];
    }
}

MLC位置情報のプロパティLeafPositionsは2次元のfloat型配列[,]となっており、最初のインデクスがバンク(0がX1、1がX2)を表し、2番目のインデクスが各Leafの座標となります。
図にすると、以下のようになっています。

5_4_0

ちなみに、MLCがない場合はLeafPositionsは長さ(0,0)の2次元配列を返す仕様となっています。

必要な情報の表示

まずはExercise_PlanCheck_Ex1.2.csを開き、関数 CheckFieldFunc 内にコードを記述してみましょう。

最初のFieldのX1 Jawの位置、およびX1側30番目/X2側10番目のMLCの位置を以下のようにして表示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 最初のBeamを取得
var beam = plan.Beams.ElementAt(0);
var id = beam.Id;

var jawPositions = beam.ControlPoints.ElementAt(0).JawPositions;
var x1 = jawPositions.X1;

var leafPositions = beam.ControlPoints.ElementAt(0).LeafPositions;

var x1_30 = leafPositions[0, 29];
var x2_10 = leafPositions[1, 9];

MessageBox.Show(string.Format("Field ID: {0}\nX1 Jaw: {1}\nX1_30MLC: {2}\n X2_10MLC: {3}", id, x1, x1_30, x2_10));

実際にEclipseで確認したときの位置情報と一致しているか、別のJaw位置やMLC位置でも実行して確認してみてください。

実装

上記の情報を用いてJawの位置とMLC位置の確認をしていきましょう。

まずはExercise_PlanCheck_Ex1.2.csを開き、関数 CheckFieldFunc 内の以下の部分のコメントを外し、その下にコードを記述します。 さきほどの確認用コードは消していただいて構いません。

1
//checkName = "Check Jaw/MLC position"

はじめに、Jaw位置とMLCの最大開度の規定距離と許容範囲を定義しておきましょう。今回の例では 0mm/2mm としておきます。
また、警告テキストも定義しておき、エラーが出た場合はここに文字列を足していくようにしましょう。

1
2
3
4
5
double distJawMLCX = 0.0;
double tolerance = 2.0;

// 警告テキスト
string checkJawMLCText = "\n";

Jaw位置を最大開度のリーフ位置と比較し、その距離が規定位置±2mm以内であればOKと判定します。

判定は各フィールドごとに行う必要があるため、foreach文を使用して各フィールドごとに判定フラグを設定しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
foreach(var beam in plan.Beams)
{
    // 各ビームごとに判定フラグを初期化します
    bool checkJawMLC = true;

    // MLCあり、かつStaticの場合のみ評価
    // beam.MLCPlanType => Help参照!
    if (beam.MLC != null && beam.MLCPlanType == 0)
    {
        var jawPositions = beam.ControlPoints.ElementAt(0).JawPositions;
        var leafPositions = beam.ControlPoints.ElementAt(0).LeafPositions;
        var leafPairs = leafPositions.GetLength(1); // Leaf対の数

        // ~~判定内容を記述~~

    }

    // ~~結果を出力する~~

}

このチェックを実施するためには、まずはX1側バンクのMLCの最大開度(=最小値)とX2側バンクのMLCの最大開度(=最大値)を求める必要があります。ただし、閉じているLeaf対はカウントしないようにしましょう。 for文を使用して、素直に実装してみましょう。
~~判定内容を記述~~ の下に以下を記述していきます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 初期値の設定
// これより 小さ/大き ければ値を更新
float minX = 200;
float maxX = -200;

// Leaf対を1つずつ確認していく
for (int i = 0; i < leafPairs; i++)
{
    // 開いているLeaf対のみ調べる
    if (leafPositions[0, i] != leafPositions[1, i])
    {
        minX = (minX > leafPositions[0, i]) ? leafPositions[0, i] : minX;
        maxX = (maxX < leafPositions[1, i]) ? leafPositions[1, i] : maxX;
    }
}

ここで、三項演算子 を用いました。

三項演算子

1
value = condition ? x : y;
これは、conditiontrueならばxを、falseならばyvalueに代入するという演算になります。 if-elseの内部が簡単な場合、三項演算子?:を使うことでプログラムが簡潔になります。
ちなみに、この演算子は エルビス演算子 とも呼ばれます。?:をよく見てみてください。 5_4_1

MLCの最大開度位置が決まれば、あとはJaw位置と比較すればOKです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// X Jawの規定位置を求める
// X1はMLCのマイナス側、X2はMLCのプラス側
var jawIdealX1 = minX - distJawMLCX;
var jawIdealX2 = maxX + distJawMLCX;

// 規定位置と許容値以上ずれている場合にエラーを出す
if (Math.Abs(jawIdealX1 - jawPositions.X1) > tolerance)
{
    checkJawMLCText += string.Format("{0} : X1 jaw should be {1:f1} cm", beam.Id, jawIdealX1/10);
    checkJawMLC = false;
}

if (Math.Abs(jawIdealX2 - jawPositions.X2) > tolerance)
{
    checkJawMLCText += string.Format("{0} : X2 jaw should be {1:f1} cm", beam.Id, jawIdealX2/10);
    checkJawMLC = false;
}

最後に、~~結果を出力する~~ の下にテキスト出力すれば完成です。

1
2
3
4
5
6
7
8
if (checkJawMLC)
{
    oText += MakeFormatText(true, checkName + "(" + beam.Id + ")", "");
}
else
{
    oText += MakeFormatText(false, checkName, checkJawMLCText);
}