見出し画像

【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#というプログラミング言語を使用します。難しそうに聞こえるかもしれませんが、一つずつステップを踏んでいけば大丈夫です!

  1. 開発環境の準備

    • Visual Studio Community などの無料の開発環境をインストールします。

    • Elekta Monaco Scripting API (MonacoScripting.dll) をプロジェクトに追加します(これはMonacoシステムに付属しています)。

  2. 基本構成の作成

    • C# のコンソールアプリケーションプロジェクトを作成します。

    • 必要なライブラリ(System.Windows.Forms, System.Threading, System.Linq, System.Text, System.IO, System.Drawing, System.Text.RegularExpressions)をインポートします。

    • Monacoに接続するためのコードを追加します。

  3. チェックロジックの実装

    • プランの基本情報 (患者ID、プランID) をチェックするコードを追加します。

    • 処方線量、分割線量、最大線量比などをチェックするコードを追加します。

    • ビームの角度、MU、コリメータ、カウチ、ボーラス、ウェッジなどの情報を取得し、チェックするコードを追加します。

    • DVH統計(Heterogeneity Index(HI)、Conformity Index (CI))に関するコードを実装します。

    • 計算設定(線量計算アルゴリズム、最終計算アルゴリズム、グリッド間隔、最大粒子数設定)を取得するコードを追加します。

    • すべてのチェック結果をリストに格納します。

  4. 結果表示機能の実装

    • チェック結果を分かりやすく表示するために、DataGridView を使用した Windows Form を作成します。

    • エラー、警告、情報ごとに色分け表示します。

    • 各チェック項目のサマリーを表示します。

  5. 結果保存機能の実装

    • チェック結果をテキストファイルに保存するコードを追加します。

    • ファイル名は日付と時間を含め、一意となるように設定します。

    • 保存後、エクスプローラーで自動的にファイルを開きます。

  6. ログ機能の実装

    • エラー、警告、情報メッセージをログファイルに出力します。

    • エラー発生時には、スタックトレースや内部例外情報も記録します。

実装のポイント

  • 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しました。

"Program.cs はElekta Script を使ったC#プログラムです。all_texts_combined.txt(Helpファイルをテキストデータししたもの)を参考にして、プログラムの流れを説明してください。

Program.csをもっといいものにするような、アイデアを提案してください。

この提案をスクリプトに反映して、新しいプログラムをすべて表示させてください。

Monaco scriptによるPlanCHECKを作りました。CalcurationPropertiesSetting.GetCalcilation methodのDoseDeposition, FinalCalculationAlg, GridSpacing, MaxParticlesPerBeam, UncentaintyPerSpotを使って、チェック出来るよう修正してください。なお、all_texts_combined.txt(Helpファイルをテキストデータししたもの)はMonaco APIの説明書ですので、これに習ってスクリプトを修正してください。

Monaco scriptによるPlanCHECKを作っています。「"Width1: {geom.Width1:F1} cm, Width2: {geom.Width2:F1} cm, Length1: {geom.Length1:F1} cm, Length2: {geom.Length2:F1} cm",// コリメータの各サイズを表示」について、「Width1+Width2 > 3.0 cm, Length1+Length2 > 3.0 cm」は"合格"、の判定を追加してください。」

私が出したAIの指示

この指示に基づいて、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` メソッドは、テーブルの区切り線を作成します。


いいなと思ったら応援しよう!

この記事が参加している募集