【AI初心者でもできる!】Elekta Monaco治療計画チェックツールをC#で自作!自動化で業務効率UP!
はじめに
こんにちは!放射線治療の世界で日々奮闘されている皆様、治療計画の品質管理は大変ですよね。今回は、Elekta Monaco治療計画システムで使用するプランを、より簡単に、そして確実にチェックするためのツールを自作する方法をご紹介します。プログラミング初心者の方でも、この記事を読めば、AIを活用して自分だけのプランチェックツールを作成する第一歩を踏み出せるでしょう!
この記事は、Elekta Monaco Scripting APIを正しく利用し、治療計画の効率化を目指すための非営利目的のゆるーい技術解説です。
注意書き
本記事は2024年12月末時点の情報に基づいています。技術の進歩やバージョンの更新により内容が変更される可能性がありますので、最新情報をご確認ください。
記事内の内容は筆者の個人的な見解に基づいており、正確性や完全性を保証するものではありません。使用に際しては、公式のマニュアルや専門家のアドバイスをご参照ください。
本記事で紹介する手順やコードを実行する際は、必ずテスト環境で動作確認を行い、本番環境での実行は慎重に行ってください。
患者データや個人情報を取り扱う場合は、施設のセキュリティポリシーに従い、不必要なデータ保存や外部への送信を行わないでください。
記事内で使用されるコードや内容の再利用を希望する場合は、著作権に配慮し、出典を明記してください。
本記事で使用される「Elekta」「Monaco」は、Elekta社の登録商標です。本記事は非公式の技術解説であり、Elekta社とは一切関係がありません。
本記事の内容は、参考目的のみで提供されています。実際の使用に際しては、Elekta社が提供する公式ドキュメントおよびサポートを参照し、適切なライセンスの下でご利用ください。
記事内のコードや方法を実行した結果について、筆者および本サイトは一切の責任を負いません。自己責任でご利用ください。
なぜプランチェックツールが必要なのか?
放射線治療計画は、患者さん一人ひとりに合わせたオーダーメイドの治療を提供するための重要なものです。しかし、手動でのチェックには限界があり、設定ミスや見落としが起こる可能性もあります。そこで、自動化されたプランチェックツールがあれば、
設定ミスを早期に発見:手動では見逃しがちな設定ミスを、自動で検出できます。
品質の向上:チェックプロセスを標準化することで、治療計画の品質を向上させることができます。
時間の節約:チェック作業を自動化することで、より多くの時間を治療計画の改善に費やすことができます。
AIの活用?
今回のプランチェックツールは、AI(人工知能)を直接的に使うわけではありません。しかし、API (Application Programming Interface) を利用することで、Monacoシステムと連携し、自動的にプラン情報を取得・チェックする「賢い」ツールを作成することができます。APIは、まるでプログラミング言語でMonacoシステムと「会話」をするようなもので、データを取得したり、操作を指示したりすることができます。
プランチェックツール作成のステップ
今回作成するプランチェックツールは、C#というプログラミング言語を使用します。難しそうに聞こえるかもしれませんが、一つずつステップを踏んでいけば大丈夫です!
開発環境の準備
Visual Studio Community などの無料の開発環境をインストールします。
Elekta Monaco Scripting API (MonacoScripting.dll) をプロジェクトに追加します(これはMonacoシステムに付属しています)。
基本構成の作成
C# のコンソールアプリケーションプロジェクトを作成します。
必要なライブラリ(System.Windows.Forms, System.Threading, System.Linq, System.Text, System.IO, System.Drawing, System.Text.RegularExpressions)をインポートします。
Monacoに接続するためのコードを追加します。
チェックロジックの実装
プランの基本情報 (患者ID、プランID) をチェックするコードを追加します。
処方線量、分割線量、最大線量比などをチェックするコードを追加します。
ビームの角度、MU、コリメータ、カウチ、ボーラス、ウェッジなどの情報を取得し、チェックするコードを追加します。
DVH統計(Heterogeneity Index(HI)、Conformity Index (CI))に関するコードを実装します。
計算設定(線量計算アルゴリズム、最終計算アルゴリズム、グリッド間隔、最大粒子数設定)を取得するコードを追加します。
すべてのチェック結果をリストに格納します。
結果表示機能の実装
チェック結果を分かりやすく表示するために、DataGridView を使用した Windows Form を作成します。
エラー、警告、情報ごとに色分け表示します。
各チェック項目のサマリーを表示します。
結果保存機能の実装
チェック結果をテキストファイルに保存するコードを追加します。
ファイル名は日付と時間を含め、一意となるように設定します。
保存後、エクスプローラーで自動的にファイルを開きます。
ログ機能の実装
エラー、警告、情報メッセージをログファイルに出力します。
エラー発生時には、スタックトレースや内部例外情報も記録します。
実装のポイント
Monaco APIの活用:
MonacoApplication クラスの Instance プロパティを使用して、Monacoアプリケーションのインスタンスを取得します。
GetBeamsSpreadsheet、GetPrescription、GetDVHStatisticsSpreadsheet、GetCalculationPropertiesSettings, GetAssignCTToEDSettingsなどのメソッドを使用して、Monacoの情報を取得します。
DoseDeposition, FinalCalculationAlg, GridSpacing, MaxParticlesPerBeam, UncentaintyPerSpot, RxDose, FractionalDose, BeamIso, Gantry, Collimator, Couch, WedgeID, Bolus, ConformityIndex, HeterogeneityIndex, 等、APIを使用して様々な情報を取得します。
データの整理: チェック結果を PlanCheckResult クラスに格納することで、後々の処理を容易にします。
柔軟な表示: DataGridView を使うことで、チェック結果を分かりやすく表示できます。
可読性の向上: コード内にコメントを記述することで、コードの意図を明確にしました。
AIによるコードの説明
今回のツール作成にあたって、私はAIに次のような指示を出しました。なお、Helpファイルからテキストデータとしたもの(all_texts_combined.txt)は重要だと感じました。このテキストファイルも、AIを使い、pythonでプログラミング、実行してGetしました。
この指示に基づいて、AIはC#のコードを生成し、以下のような改善を提案してくれました。
Program.cs のコードを構造化し、各チェック機能がモジュール化されるように修正しました。
エラーメッセージを詳細化し、エラー発生時のデバッグが容易になるように修正しました。
CalcurationPropertiesSetting.GetCalcilation methodを使用して、計算設定のチェックを追加しました。
コリメータサイズのチェックに、指定された条件を追加しました。
3. 改良の提案と実装
以下の新しい機能を提案し、プログラムに反映しました。
提案1: コリメータサイズの判定追加
条件: "Width1 + Width2 > 3.0 cm, Length1 + Length2 > 3.0 cm"の場合に"合格"と判定。
実装例:
if (geom.Width1 + geom.Width2 > 3.0 && geom.Length1 + geom.Length2 > 3.0)
{
results.Add(new PlanCheckResult
{
Category = "コリメータ",
Item = "サイズ判定",
Pass = true,
ActualValue = $"Width1: {geom.Width1:F1} cm, Width2: {geom.Width2:F1} cm, Length1: {geom.Length1:F1} cm, Length2: {geom.Length2:F1} cm",
Severity = "情報"
});
}
提案2: 計算設定の詳細チェック
条件: DoseDeposition, FinalCalculationAlg, GridSpacing などのプロパティを取得して確認。
実装例:
results.Add(new PlanCheckResult
{
Category = "計算設定",
Item = "グリッド間隔",
Pass = calcProp.GridSpacing >= 0.1 && calcProp.GridSpacing <= 0.8,
ActualValue = calcProp.GridSpacing.ToString("F2"),
ExpectedValue = "0.1 ~ 0.8",
Severity = calcProp.GridSpacing > 0.8 ? "警告" : "情報"
});
提案3: 結果の柔軟な出力
GUIでの表示だけでなく、テキストファイルやJSON形式での出力も追加。(まだ私は使っていませんが)
プログラムの全体像
以下は、改良版プログラムの主要部分です。
using System;
using System.Collections.Generic;
using Elekta.MonacoScripting.API;
namespace EnhancedMonacoPlanCheck
{
class Program
{
static void Main()
{
MonacoApplication app = MonacoApplication.Instance;
List<PlanCheckResult> results = new List<PlanCheckResult>();
// チェックロジック
var calcSettings = app.GetCalculationPropertiesSettings();
results.Add(new PlanCheckResult
{
Category = "計算設定",
Item = "グリッド間隔",
Pass = calcSettings.GridSpacing >= 0.1 && calcSettings.GridSpacing <= 0.8,
ActualValue = calcSettings.GridSpacing.ToString("F2"),
ExpectedValue = "0.1 ~ 0.8",
Severity = calcSettings.GridSpacing > 0.8 ? "警告" : "情報"
});
// 結果を保存
SaveResults(results);
}
static void SaveResults(List<PlanCheckResult> results)
{
foreach (var result in results)
{
Console.WriteLine($"{result.Category}: {result.Item} - {result.Pass}");
}
}
}
class PlanCheckResult
{
public string Category { get; set; }
public string Item { get; set; }
public bool Pass { get; set; }
public string ActualValue { get; set; }
public string ExpectedValue { get; set; }
public string Severity { get; set; }
}
}
まとめ
この記事では、Elekta Monaco Scripting APIを活用した自動プランチェックツールの作成方法を解説しました。自動化は、放射線治療の現場での精度向上や時間の節約に大きく貢献します。
引き続き以下の課題に取り組むことで、さらに実用的なツールへと進化させることができます。
チェック項目の拡充
ユーザーフレンドリーなGUIの開発
エラーログ機能の強化
自作ツールを通じて、より効率的で安全な治療計画を実現しましょう!
January 11th, 2025
おまけ コード例(分割版)
1. 名前空間と基本クラスの定義
using Elekta.MonacoScripting.API;
using Elekta.MonacoScripting.API.General;
using System.Windows.Forms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
namespace EnhancedMonacoPlanCheck
{
// プランチェックの結果を格納するクラス
public class PlanCheckResult
{
public string Category { get; set; }
public string Item { get; set; }
public bool Pass { get; set; }
public string ActualValue { get; set; }
public string ExpectedValue { get; set; }
public string Severity { get; set; }
}
// Logger クラスの定義は後述
説明:
`using` ステートメントで必要な名前空間をインポートしています。
`EnhancedMonacoPlanCheck` 名前空間内に、`PlanCheckResult` クラスを定義しています。このクラスは、チェック結果を格納するために使用されます。
”using Newtonsoft.Json;”は、jsonファイルを使わなければいらないかもしれません。
2. 結果表示フォームの定義
// 結果を表示するフォーム
public class ResultForm : Form
{
private readonly DataGridView grid;
private readonly Label summaryLabel;
public ResultForm()
{
// フォームの設定
this.Text = "プランチェック結果";
this.Width = 1000; // フォームの幅を調整
this.Height = 600; // フォームの高さを調整
this.StartPosition = FormStartPosition.CenterScreen; // フォームを画面中央に表示
// DataGridViewコントロールの作成と設定
grid = new DataGridView();
grid.Dock = DockStyle.Fill;
grid.AllowUserToAddRows = false;
grid.AllowUserToDeleteRows = false;
grid.ReadOnly = true;
this.Controls.Add(grid);
// サマリーラベルの作成と設定
summaryLabel = new Label();
summaryLabel.Dock = DockStyle.Top;
summaryLabel.TextAlign = ContentAlignment.MiddleCenter;
summaryLabel.Font = new Font(summaryLabel.Font.FontFamily, 12, FontStyle.Bold);
this.Controls.Add(summaryLabel);
}
public void DisplayResults(List<PlanCheckResult> results)
{
// 結果をDataGridViewに表示
grid.DataSource = results;
//DataGridViewに列ヘッダーを設定
grid.Columns["Category"].HeaderText = "カテゴリ";
grid.Columns["Item"].HeaderText = "項目";
grid.Columns["Pass"].HeaderText = "合格";
grid.Columns["ActualValue"].HeaderText = "実際値";
grid.Columns["ExpectedValue"].HeaderText = "期待値";
grid.Columns["Severity"].HeaderText = "重要度";
// 各セルの内容に基づいて色分け
foreach (DataGridViewRow row in grid.Rows)
{
if (row.DataBoundItem is PlanCheckResult result)
{
if (!result.Pass)
{
row.DefaultCellStyle.BackColor = Color.LightCoral; // エラーの場合、背景色を赤
row.DefaultCellStyle.ForeColor = Color.Black; // 文字色を黒
}
else if (result.Severity == "警告")
{
row.DefaultCellStyle.BackColor = Color.LightYellow; // 警告の場合、背景色を黄色
}
else
{
row.DefaultCellStyle.BackColor = Color.LightGreen; // 情報の場合、背景色を緑
}
}
}
// サマリー表示
int totalCount = results.Count;
int passCount = results.Count(r => r.Pass);
int failCount = results.Count(r => !r.Pass);
int warningCount = results.Count(r => r.Severity == "警告");
summaryLabel.Text = $"合計項目: {totalCount} | 合格: {passCount} | 不合格: {failCount} | 警告: {warningCount}";
}
}
説明:
`ResultForm` クラスは、チェック結果をGUIで表示するためのWindows Formを定義しています。
`DataGridView` コントロールを使用して、結果をテーブル形式で表示します。
結果の重要度に応じて、行の背景色を変更します。
3. `Program` クラスの定義と`Main`メソッド
internal class Program
{
private const double MIN_MU = 10.0;
private const double MIN_DOSE = 1.0;
private const double MAX_DOSE_RATIO = 1.17;
[STAThread]
static void Main()
{
try
{
// Monacoアプリケーションのインスタンスを取得
MonacoApplication app = MonacoApplication.Instance ??
throw new Exception("Monacoアプリケーションの初期化に失敗しました。");
var results = new List<PlanCheckResult>();
var pat = app.GetCurrentPatient() ??
throw new Exception("現在の患者情報の取得に失敗しました。");
var plan = app.GetActivePlan() ??
throw new Exception("アクティブな治療計画の取得に失敗しました。");
//
//What kind of CHECK's ?
//
CheckBasicPlanInfo(pat, plan.PlanId, results);
CheckPrescriptionAndDose(app, results);
CheckBeamProperties(app.GetBeamsSpreadsheet(), results);
CheckDVHStatistics(app, results);
CheckCalculationSettings(app, results);
//
var form = new ResultForm();
form.DisplayResults(results);
Application.Run(form);
SaveResultsToFile(pat, plan.PlanId, results);
Logger.Instance.Info("プランチェックが正常に完了しました。");
}
catch (Exception ex)
{
// エラーが発生した場合、メッセージボックスを表示し、ログを出力
MessageBox.Show($"プランチェック中にエラーが発生しました: {ex.Message}", "エラー",
MessageBoxButtons.OK, MessageBoxIcon.Error);
Logger.Instance.Error($"プランチェック中にエラーが発生しました: {ex.Message}");
if (ex.InnerException != null)
{
Logger.Instance.Error($"内部例外: {ex.InnerException.Message}");
}
Logger.Instance.Error($"スタックトレース: {ex.StackTrace}");
}
}
説明:
`Program` クラスは、アプリケーションのエントリーポイントです。
`Main` メソッド内で、Monacoアプリケーションのインスタンスを取得し、各チェックメソッドを呼び出します。
エラーハンドリングを行い、エラー発生時にはログを出力します。
結果表示フォームを表示し、結果をファイルに保存します。
`[STAThread]` 属性は、Windows Formsアプリケーションで必要です。
4. 基本プラン情報のチェック
private static void CheckBasicPlanInfo(PatientDemographic pat, string planId, List<PlanCheckResult> results)
{
if (pat == null)
{
Logger.Instance.Error("患者情報の取得に失敗しました。");
return;
}
// 患者IDのチェック
if (string.IsNullOrEmpty(pat.PatientId))
{
Logger.Instance.Warn("患者IDが取得できませんでした。");
results.Add(new PlanCheckResult
{
Category = "患者",
Item = "患者ID",
Pass = false,
ActualValue = "未設定",
Severity = "警告"
});
}
else
{
results.Add(new PlanCheckResult
{
Category = "患者",
Item = "患者ID",
Pass = true,
ActualValue = pat.PatientId,
Severity = "情報"
});
}
//プランID形式のチェック
if (string.IsNullOrEmpty(planId))
{
Logger.Instance.Warn("プランIDが取得できませんでした。");
results.Add(new PlanCheckResult
{
Category = "プラン",
Item = "プランID",
Pass = false,
ActualValue = "未設定",
Severity = "警告"
});
}
else
{
results.Add(new PlanCheckResult
{
Category = "プラン",
Item = "プランID",
Pass = true,
ActualValue = planId,
Severity = "情報"
});
}
}
説明:
`CheckBasicPlanInfo` メソッドは、患者情報とプランIDの基本的なチェックを行います。
患者IDとプランIDが取得できているかどうかを確認し、結果を `PlanCheckResult` に追加します。
5. 処方線量と線量比のチェック
private static void CheckPrescriptionAndDose(MonacoApplication app, List<PlanCheckResult> results)
{
var pres = app.GetPrescription() ??
throw new Exception("処方情報の取得に失敗しました。");
var rxDose = pres.RxDose ?? 0;
var fractionalDose = pres.FractionalDose ?? 0;
var maxDoseRatio = GetMaxPlanDose(app) / rxDose;
// 処方線量のチェック
results.Add(new PlanCheckResult
{
Category = "処方",
Item = "処方線量",
Pass = rxDose >= MIN_DOSE,
ActualValue = $"{rxDose:F2} cGy",
ExpectedValue = $">= {MIN_DOSE} cGy",
Severity = rxDose >= MIN_DOSE ? "情報" : "警告"
});
//分割線量のチェック
results.Add(new PlanCheckResult
{
Category = "処方",
Item = "分割線量",
Pass = fractionalDose >= MIN_DOSE,
ActualValue = $"{fractionalDose:F2} cGy",
ExpectedValue = $">= {MIN_DOSE} cGy",
Severity = fractionalDose >= MIN_DOSE ? "情報" : "警告"
});
// 最大線量比のチェック
results.Add(new PlanCheckResult
{
Category = "処方",
Item = "最大線量比",
Pass = maxDoseRatio <= MAX_DOSE_RATIO,
ActualValue = $"{maxDoseRatio:F2}",
ExpectedValue = $"<= {MAX_DOSE_RATIO}",
Severity = maxDoseRatio <= MAX_DOSE_RATIO ? "情報" : "警告"
});
}
説明:
`CheckPrescriptionAndDose` メソッドは、処方線量、分割線量、最大線量比をチェックします。
`MIN_DOSE` および `MAX_DOSE_RATIO` 定数を使用して、チェック基準を設定します。
チェック結果を `PlanCheckResult` に追加します。
6. ビームプロパティのチェック
private static void CheckBeamProperties(BeamsSpreadsheet sheet, List<PlanCheckResult> results)
{
if (sheet == null)
{
Logger.Instance.Error("ビーム情報の取得に失敗しました。");
return;
}
List<BeamGeneralProperty> generalProps = sheet.GetBeamGeneralProperties();
CheckIsocenterConsistency(generalProps, results);
foreach (var prop in generalProps)
{
AddBeamGeneralChecks(prop, results);
}
}
説明:
`CheckBeamProperties` メソッドは、ビームスプレッドシートからビーム情報を取得し、各ビームに対してチェックを行います。
アイソセンターの一貫性をチェックし、`AddBeamGeneralChecks` メソッドを呼び出して各ビームの詳細なチェックを行います。
7. アイソセンターの一貫性チェック
private static void CheckIsocenterConsistency(List<BeamGeneralProperty> generalProps, List<PlanCheckResult> results)
{
if (generalProps == null || !generalProps.Any())
{
Logger.Instance.Warn("ビームプロパティが取得できませんでした。");
return;
}
// アイソセンターの一貫性をチェック
var firstIso = generalProps[0].BeamIso;
for (int i = 1; i < generalProps.Count; i++)
{
if (generalProps[i].BeamIso != firstIso)
{
results.Add(new PlanCheckResult
{
Category = "ビーム",
Item = "アイソセンターの一貫性",
Pass = false,
ActualValue = "不一致",
Severity = "エラー"
});
return; // 一つでも不一致があれば即return
}
}
// すべてのビームでアイソセンターが一致する場合
results.Add(new PlanCheckResult
{
Category = "ビーム",
Item = "アイソセンターの一貫性",
Pass = true,
ActualValue = "一致",
Severity = "情報"
});
}
説明:
`CheckIsocenterConsistency` メソッドは、すべてのビームのアイソセンターが一致しているかどうかをチェックします。
不一致が見つかった場合は、エラーとして結果を記録します。
8. ビームの一般プロパティのチェック
private static void AddBeamGeneralChecks(BeamGeneralProperty prop, List<PlanCheckResult> results)
{
// 各ビームの詳細チェック
if (prop == null)
{
Logger.Instance.Error("ビームプロパティが取得できませんでした。");
return;
}
// フィールドIDのチェック
results.Add(new PlanCheckResult
{
Category = "ビーム",
Item = "フィールドID (ビーム " + prop.FieldId + ")",
Pass = !string.IsNullOrEmpty(prop.FieldId),
ActualValue = prop.FieldId,
Severity = !string.IsNullOrEmpty(prop.FieldId) ? "情報" : "警告"
});
// 最小MUのチェック
results.Add(new PlanCheckResult
{
Category = "ビーム",
Item = $"最小MU (ビーム {prop.FieldId})",
Pass = prop.MUs >= MIN_MU,
ActualValue = $"{prop.MUs:F2}",
ExpectedValue = $">= {MIN_MU}",
Severity = prop.MUs >= MIN_MU ? "情報" : "警告"
});
//エネルギーのチェック
results.Add(new PlanCheckResult
{
Category = "ビーム",
Item = $"エネルギー (ビーム {prop.FieldId})",
Pass = true,
ActualValue = $"{prop.Energy}",
Severity = "情報"
});
// ジオメトリに関するチェック
AddGeometryChecks(prop.Geometry, results);
// 治療補助具に関するチェック
AddTreatmentAidsChecks(prop.TreatmentAids, results);
}
説明:
`AddBeamGeneralChecks` メソッドは、個々のビームの一般プロパティをチェックします。
フィールドID、最小MU、エネルギーをチェックし、必要に応じて他のチェックメソッド (`AddGeometryChecks`, `AddTreatmentAidsChecks`) を呼び出します。
9. ビームジオメトリのチェック
private static void AddGeometryChecks(BeamGeometryProperty geom, List<PlanCheckResult> results)
{
if (geom == null)
{
Logger.Instance.Error("ビームジオメトリプロパティが取得できませんでした。");
return;
}
// 各角度に関するチェック
results.Add(new PlanCheckResult
{
Category = "ジオメトリ",
Item = $"角度 (ビーム {geom.FieldId})",
Pass = true,
ActualValue = $"ガントリ: {geom.Gantry:F1}, コリメータ: {geom.Collimator:F1}, カウチ: {geom.Couch:F1}",
Severity = "情報"
});
// コリメータの各サイズを表示
string collimatorSize = $"Width1: {geom.Width1:F1} cm, Width2: {geom.Width2:F1} cm, Length1: {geom.Length1:F1} cm, Length2: {geom.Length2:F1} cm";
// コリメータのサイズをチェック
bool collimatorPass = (geom.Width1 + geom.Width2 > 3.0 && geom.Length1 + geom.Length2 > 3.0);
if (geom.Length1 == 0 && geom.Length2 ==0)
{
collimatorPass = true;
}
results.Add(new PlanCheckResult
{
Category = "コリメータ",
Item = $"コリメータ詳細 (ビーム {geom.FieldId})",
Pass = collimatorPass,
ActualValue = collimatorSize,
ExpectedValue = "Width1 + Width2 > 3.0 cm, Length1 + Length2 > 3.0 cm",
Severity = collimatorPass ? "情報" : "警告"
});
}
説明:
`AddGeometryChecks` メソッドは、ビームのジオメトリ情報をチェックします。
ガントリ、コリメータ、カウチの角度を表示します。
コリメータのサイズに関するチェックを行い、基準を満たさない場合は警告として結果を記録します。
10. 治療補助具のチェック
private static void AddTreatmentAidsChecks(BeamTreatmentAidsProperty aid, List<PlanCheckResult> results)
{
if (aid == null)
{
Logger.Instance.Warn("ビーム治療補助具プロパティが取得できませんでした。");
return;
}
// カウチに関するチェック
results.Add(new PlanCheckResult
{
Category = "治療補助具",
Item = $"カウチ (ビーム {aid.FieldId})",
Pass = true,
ActualValue = aid.Couch,
Severity = "情報"
});
// ボーラスに関するチェック
results.Add(new PlanCheckResult
{
Category = "治療補助具",
Item = $"ボーラス (ビーム {aid.FieldId})",
Pass = true,
ActualValue = aid.Bolus,
Severity = "情報"
});
//ウェッジに関するチェック
results.Add(new PlanCheckResult
{
Category = "治療補助具",
Item = $"ウェッジID (ビーム {aid.FieldId})",
Pass = true,
ActualValue = aid.WedgeID,
Severity = "情報"
});
}
説明:
`AddTreatmentAidsChecks` メソッドは、ビームの治療補助具(カウチ、ボーラス、ウェッジ)に関する情報をチェックします。
各補助具の情報を `PlanCheckResult` に追加します。
11. DVH統計情報のチェック
private static void CheckDVHStatistics(MonacoApplication app, List<PlanCheckResult> results)
{
var dvhSheet = app.GetDVHStatisticsSpreadsheet() ??
throw new Exception("DVH統計情報の取得に失敗しました。");
if (dvhSheet == null)
{
Logger.Instance.Warn("DVH統計情報が取得できませんでした。");
return;
}
List<StatisticsOfStructure> structureStats = dvhSheet.GetStatisticsOfStructures();
foreach (var statProp in structureStats)
{
// PTVの適合度指数と不均一性指数のチェック
if (statProp.Structure == null)
{
Logger.Instance.Warn($"構造名が取得できませんでした。スキップします。");
continue;
}
if(statProp.Structure.Name == "PTV")
{
results.Add(new PlanCheckResult
{
Category = "DVH統計",
Item = "PTV 適合度指数",
Pass = true,
ActualValue = statProp.ConformityIndex.ToString("F2"),
Severity = "情報"
});
results.Add(new PlanCheckResult
{
Category = "DVH統計",
Item = "PTV 不均一性指数",
Pass = true,
ActualValue = statProp.HeterogeneityIndex.ToString("F2"),
Severity = "情報"
});
}
}
}
説明:
`CheckDVHStatistics` メソッドは、DVH統計情報を取得し、PTV (計画標的体積) の適合度指数と不均一性指数をチェックします。
結果を `PlanCheckResult` に追加します。
12. 計算設定のチェック
private static void CheckCalculationSettings(MonacoApplication app, List<PlanCheckResult> results)
{
try
{
// 計算設定を取得
var calcSettings = app.GetCalculationPropertiesSettings();
if (calcSettings == null)
{
Logger.Instance.Warn("計算プロパティを取得できませんでした。");
return;
}
else
{
// 計算プロパティを取得
var calcProp = calcSettings.GetCalculationProperty();
if (calcProp != null)
{
// 線量計算アルゴリズムのチェック
results.Add(new PlanCheckResult
{
Category = "計算設定",
Item = "線量計算アルゴリズム",
Pass = true,
ActualValue = calcProp.DoseDeposition ?? "N/A",
Severity = "情報"
});
// 最終計算アルゴリズムのチェック
results.Add(new PlanCheckResult
{
Category = "計算設定",
Item = "最終計算アルゴリズム",
Pass = true,
ActualValue = calcProp.FinalCalculationAlg ?? "N/A",
Severity = "情報"
});
// グリッド間隔のチェック
results.Add(new PlanCheckResult
{
Category = "計算設定",
Item = "グリッド間隔",
Pass = true,
ActualValue = calcProp.GridSpacing.ToString("F2"),
ExpectedValue = "0.1 ~ 0.8",
Severity = "情報"
});
// ビームあたりの最大粒子数のチェック
results.Add(new PlanCheckResult
{
Category = "計算設定",
Item = "ビームあたりの最大粒子数",
Pass = true,
ActualValue = calcProp.MaxParticlesPerBeam.ToString("F0"),
ExpectedValue = "設定済み",
Severity = "情報"
});
}
}
}
catch (Exception ex)
{
// エラーが発生した場合、ログを出力
Logger.Instance.Error($"計算設定のチェック中にエラーが発生しました: {ex.Message}");
}
}
説明:
`CheckCalculationSettings` メソッドは、線量計算アルゴリズム、最終計算アルゴリズム、グリッド間隔、最大粒子数設定などの計算設定をチェックします。
各設定値を `PlanCheckResult` に追加します。
13. 最大線量を取得
private static double GetMaxPlanDose(MonacoApplication app)
{
var dose = app.GetDose() ??
throw new Exception("線量情報の取得に失敗しました。");
return dose.GetMaxDose();
}
説明:
`GetMaxPlanDose` メソッドは、Monacoアプリケーションから最大線量を取得します。
線量情報が取得できない場合は例外をスローします。
14. チェック結果のファイル保存
private static void SaveResultsToFile(PatientDemographic pat, string planId, List<PlanCheckResult> results)
{
try
{
// ファイル名を作成(日付と時間を追加)
string fileName = $"PlanCheckResults_{DateTime.Now:yyyyMMdd_HHmmss}.txt";
string filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), fileName);
using (StreamWriter writer = new StreamWriter(filePath, false, Encoding.UTF8))
{
// ヘッダーを書き込む
int[] columnWidths = GetColumnWidths(results);
writer.WriteLine(CreateSeparatorLine(columnWidths));
writer.WriteLine(CreateTableRow(columnWidths, "カテゴリ", "項目", "合格", "実際値", "期待値", "重要度"));
writer.WriteLine(CreateSeparatorLine(columnWidths));
// 各行を書き込む
foreach (var result in results)
{
writer.WriteLine(CreateTableRow(columnWidths,
result.Category,
result.Item,
result.Pass.ToString(),
result.ActualValue,
result.ExpectedValue,
result.Severity));
}
writer.WriteLine(CreateSeparatorLine(columnWidths));
}
// ファイルをエクスプローラーで開く
System.Diagnostics.Process.Start("explorer.exe", filePath);
Logger.Instance.Info($"チェック結果をファイルに保存しました: {filePath}");
}
catch (Exception ex)
{
Logger.Instance.Error($"チェック結果のファイル保存中にエラーが発生しました: {ex.Message}");
}
}
説明:
`SaveResultsToFile` メソッドは、チェック結果をテキストファイルに保存します。
ファイル名は日付と時間を含め、一意となるように設定します。
保存後、ファイルをエクスプローラーで開きます。
`GetColumnWidths`、`CreateTableRow`、`CreateSeparatorLine` メソッドを使用して、テーブル形式のテキストを生成します。
15. テーブル表示のためのヘルパーメソッド
// 各列の幅を計算するヘルパーメソッド
private static int[] GetColumnWidths(List<PlanCheckResult> results)
{
int[] columnWidths = new int[6]; // 列の幅を保持する配列
string[] headers = { "カテゴリ", "項目", "合格", "実際値", "期待値", "重要度" };// ヘッダー
// ヘッダーの幅を初期値として設定
for (int i = 0; i < headers.Length; i++)
{
columnWidths[i] = headers[i].Length;
}
// 各セルの最大幅を計算
foreach (var result in results)
{
columnWidths[0] = Math.Max(columnWidths[0], result.Category?.Length ?? 0); // カテゴリー列の最大幅を更新
columnWidths[1] = Math.Max(columnWidths[1], result.Item?.Length ?? 0); // 項目列の最大幅を更新
columnWidths[2] = Math.Max(columnWidths[2], result.Pass.ToString().Length); // 合否列の最大幅を更新
columnWidths[3] = Math.Max(columnWidths[3], result.ActualValue?.Length ?? 0); // 実際値列の最大幅を更新
columnWidths[4] = Math.Max(columnWidths[4], result.ExpectedValue?.Length ?? 0); // 期待値列の最大幅を更新
columnWidths[5] = Math.Max(columnWidths[5], result.Severity?.Length ?? 0); // 重要度列の最大幅を更新
}
// パディングの追加
for (int i = 0; i < columnWidths.Length; i++)
{
columnWidths[i] += 10; // 各列に10文字分のパディングを追加
}
return columnWidths; // 計算された列の幅を返す
}
// テーブルの行を作成するヘルパーメソッド
private static string CreateTableRow(int[] columnWidths, params string[] values)
{
StringBuilder row = new StringBuilder("|"); // 行の開始
for (int i = 0; i < values.Length; i++)
{
// 各セルの内容を右寄せして、指定された幅でパディングする
row.Append($" {values[i]?.PadRight(columnWidths[i] - 1) ?? string.Empty.PadRight(columnWidths[i] - 1)}|");
}
return row.ToString(); // テーブル行を表す文字列を返す
}
// テーブルの区切り線を作成するヘルパーメソッド
private static string CreateSeparatorLine(int[] columnWidths)
{
StringBuilder separator = new StringBuilder("+"); // 区切り線の開始
foreach (int width in columnWidths)
{
separator.Append(new string('-', width)); // 列幅分の'-'を追加
separator.Append("+"); // 列の区切り
}
return separator.ToString(); // 区切り線を表す文字列を返す
}
説明:
`GetColumnWidths` メソッドは、チェック結果に基づいて各列の幅を計算します。
`CreateTableRow` メソッドは、指定された幅と値に基づいてテーブルの行を作成します。
`CreateSeparatorLine` メソッドは、テーブルの区切り線を作成します。