A small .Net Framework 4.7.2 C# library for parsing and evaluating dose–volume constraints commonly used in radiotherapy treatment planning.
$ dotnet add package RTP.DoseVolumeConstraintsEvaluationRTP.DoseVolumeConstraintsEvaluation is a small library for parsing and evaluating dose–volume constraints commonly used in radiotherapy treatment planning.
D2.5ccm<=56Gy, D50%<20Gy, Dmean<=40Gy,V20Gy<=30ccm, V40Gy<=50%, V70Gy>5.5cmmDVHCurve objects (from RadiationTreatmentPlanner.Utils)Important: DVHs with relative volumes (volume axis in percent) are not supported for evaluation and will cause a NotSupportedException to be thrown. Use absolute-volume DVHs (ccm or cmm) when evaluating.
Dose at volume: D{volume}{unit}{op}{dose}{unit}
Examples: D2ccm<=56Gy, D50%<20Gy, Dmean<=40Gy
Dmax is internally mapped to D0%. Dmean uses DVHCurve.GetMeanDose().
Volume at dose: V{dose}{unit}{op}{volume}{unit}
Examples: V20Gy<=30ccm, V40Gy<=50%, V30cGy<=0.5ccm
Supported operators: <
Supported dose units: Gy, cGy, % (percent is allowed for constraints)
Supported volume units: ccm / / , / / ,
cm³cm3cmmmm³mm3%Create two DVH curves (PTV and Heart), parse constraints and evaluate them.
using System;
using System.Collections.Generic;
using RadiationTreatmentPlanner.Utils.DVH;
using RadiationTreatmentPlanner.Utils.Dose;
using RadiationTreatmentPlanner.Utils.Volume;
using RTP.DoseVolumeConstraintsEvaluation;
// Create PTV DVH (Gy vs ccm)
var ptvPoints = new List<Tuple<UDose, UVolume>>
{
Tuple.Create(new UDose(0, UDose.UDoseUnit.Gy), new UVolume(150, UVolume.VolumeUnit.ccm)),
Tuple.Create(new UDose(20, UDose.UDoseUnit.Gy), new UVolume(120, UVolume.VolumeUnit.ccm)),
Tuple.Create(new UDose(40, UDose.UDoseUnit.Gy), new UVolume(60, UVolume.VolumeUnit.ccm)),
Tuple.Create(new UDose(60, UDose.UDoseUnit.Gy), new UVolume(10, UVolume.VolumeUnit.ccm)),
Tuple.Create(new UDose(70, UDose.UDoseUnit.Gy), new UVolume(0, UVolume.VolumeUnit.ccm))
};
var ptvDvh = new DVHCurve(ptvPoints, DVHCurve.Type.CUMULATIVE, DVHCurve.DoseType.PHYSICAL);
// Create Heart DVH (Gy vs ccm)
var heartPoints = new List<Tuple<UDose, UVolume>>
{
Tuple.Create(new UDose(0, UDose.UDoseUnit.Gy), new UVolume(100, UVolume.VolumeUnit.ccm)),
Tuple.Create(new UDose(10, UDose.UDoseUnit.Gy), new UVolume(80, UVolume.VolumeUnit.ccm)),
Tuple.Create(new UDose(20, UDose.UDoseUnit.Gy), new UVolume(50, UVolume.VolumeUnit.ccm)),
Tuple.Create(new UDose(40, UDose.UDoseUnit.Gy), new UVolume(20, UVolume.VolumeUnit.ccm)),
Tuple.Create(new UDose(60, UDose.UDoseUnit.Gy), new UVolume(0, UVolume.VolumeUnit.ccm))
};
var heartDvh = new DVHCurve(heartPoints, DVHCurve.Type.CUMULATIVE, DVHCurve.DoseType.PHYSICAL);
// Parse constraints (one constraint per structure in this example)
var ptvConstraint = ConstraintParser.Parse("PTV", "D2.5ccm<=65Gy");
var heartConstraint = ConstraintParser.Parse("Heart", "V40Gy<=20ccm");
// Create evaluator with both constraints
var evaluator = new ConstraintEvaluator(new[] { ptvConstraint, heartConstraint });
// Evaluate PTV and Heart separately
var ptvReport = evaluator.Evaluate("PTV", ptvDvh);
var heartReport = evaluator.Evaluate("Heart", heartDvh);
// Print results
Console.WriteLine($"PTV: All satisfied: {ptvReport.AllSatisfied}");
foreach (var r in ptvReport.Results) Console.WriteLine($" {r.Message}");
Console.WriteLine($"Heart: All satisfied: {heartReport.AllSatisfied}");
foreach (var r in heartReport.Results) Console.WriteLine($" {r.Message}");