Webinars Revisited: Automated Treatment Planning from Templates
- mschmidt33
- Jul 10
- 10 min read
Introduction to Clinical Protocols
Developing automation for treatment planning can be a challenging task given the variables that go into plan creation (e.g. beam geometry setup, optimization objectives, algorithm selections and parameters). In a myVarian webinar titled Planning Automation #5: Clinical Template Driven Plan Automation with the Eclipse Scripting API delivered August 2021, the Clinical Template Reader was introduced to share how automated treatment planning can be accomplished if the Clinical Protocols are set up with care.

Clinical Protocols are the internal template for structure creation, objective template, and plan parameter template in Varian's Eclipse Treatment Planning System. The Clinical Protocol is a culmination of 3 separate types of templates: Structure Templates, Objective Templates and Plan Templates. The Clinical Protocol also contains dose metric evaluations -- a custom template in version 15.6 and older, but updated to the Clinical Goals feature syntax in Eclipse version 16.1.
To generate a clinical protocol in Eclipse, the user would first generate a plan that is representative of the treatment plan that should be templated for a given treatment site. The structures on the structure set in the plan, the optimization objectives, the field parameters and the clinical goals will all be templated. Though they can be modified afterward in the Clinical Protocol Manager, its best to have as many plan parameters correct as possible. Once the plan has been generated, a Clinical Protocol can be created by going to the Templates and Clinical Protocols menu from the Planning dropdown in Eclipse and selecting Create Clinical Protocol From Plan.

Notes on Clinical Protocols
Clinical Protocols will mirror the Structure Set, Optimization Objectives, and Plan and Beam Properties of the loaded plan when new Clinical Protocols are created, but there are some considerations to review once a new plan has been created. The creation of new Clinical Protocols may make some assumptions in how the plan was set up, and those assumptions may need to be changed manually.
Isocenter Positioning: Upon creation of a new clinical protocol, the isocenter position for each field is set to Relative to Image Center. Isocenter positioning for treatment beams and setup beams can be as follows:
Relative to Image Center (RIC)
At Image Center (AIC)
Relative to Image Origin (RIO)
At Image Origin (AIO)
Relative to Field Target Center (RFTS)
At Field Target Center (AFTS)
Most of the time, the beam isocenter position is not related to the Image Center -- this would be the DICOM image center-- as it changes from patient to patient based on their positioning. When beams are created in Eclipse, the default isocenter position is at the Field Target Center. To mirror this behavior the template should state AFTS. In the example below, the isocenter position is changed to At Image Origin (AIO) which means the isocenter should be placed at the Eclipse User Origin -- noted by the green cross in the image display.
While normally, any parameters should be modified within the UI provided by Eclipse, this parameter is not modifiable within the UI and must be modified directly from the template. To navigate to the template, one easy way to find the appropriate folder is to open the Published Scripts folder from Eclipse. Navigate to the tools menu within Eclipse and select Scripts and with the System Scripts radio button selected click Open Folder.... Go back one folder to the Vision folder and then find the Templates --> Protocols folder and sort by date modified. This should make the most recent protocol easily detectable. Open this file in your favorite text editor (e.g. notepad++, sublime).
Note: Due to the length and complexity of Clinical Protocol Templates consider editing this file in an intelligent editor that can support formatting XML files.
Utilize CTRL+F to search for the XML element Isocenter. Notice that this attribute has 4 attributes: Placement, X, Y, and Z.
<Isocenter Placement="RIC" X="2.24087" Y="-17.5222" Z="-78"/>In this example we will change the following attributes. Perform these changes for all instances of this Isocenter tag found in the file.
Placement => AIO
X => 0.0
Y => 0.0
Z => 0.0
<Isocenter Placement="AIO" X="0.0" Y="0.0" Z="0.0"/>Field Fitting: A newly created clinical protocol will store the jaw positions of the current plan without understanding how those field sizes were derived (i.e. a 1cm fit around the target structure. Therefore, default clinical protocol templates will have the following attributes in the FieldMargin element.
<FieldMargin BEVMarginFlag="false" EllipticalMarginFlag="false" OptimizeCollRtnFlag="false">
<Left xsi:nil="true"/>
<Right xsi:nil="true"/>
<Top xsi:nil="true"/>
<Bottom xsi:nil="true"/>
</FieldMargin>The field fitting margin can have 2 behaviors.
Symmetric margins: Only set the Left attribute margin.
Asymmetric margins: Set each of the margins (Left, Right, Top, and Bottom) individually.
In the following example, asymmetric margins will be used by changing the Clinical Protocol Template file to only modify the Left attribute.
<FieldMargin BEVMarginFlag="false" EllipticalMarginFlag="false" OptimizeCollRtnFlag="false">
<Left>1.0</Left>
<Right xsi:nil="true"/>
<Top xsi:nil="true"/>
<Bottom xsi:nil="true"/>
</FieldMargin>Field Target: Finally, if a field margin is set, the Clinical Protocol must know the target with which each field is attempting to fit. In the creation of a Clinical Protocol, the default Target attribute is empty.
<Target>
<VolumeID/>
<VolumeCode/>
<VolumeType/>
<VolumeCodeTable/>
</Target>In the following example, the Target is called "PTV" and the Clinical Protocol XML will be directly modified again. If it is challenging to standardize the target names, the Clinical Template Reader will search the VolumeCode attribute as well. In the example below, both will be modified.
Be sure to save your changes to the clinical protocol template!
The Clinical Template Reader
The Clinical Template Reader API interprets Clinical Protocols, Plan Templates, and RapidPlan Models for assistance with automated planning. The API supports some methods including:
GetPlanTemplateApprovals & GetObjectiveTemplateApprovals: Collects plan templates (and clinical protocols) and objective templates and displays the approval status.
GetPlanTemplateSiteStatistics & GetObjectiveTemplateSiteStatistics: Collect aggregate data on how many templates exist within the TPS with various approval status.
GeneratePlanFromTemplate: Generates a plan from a Plan Template. Attempts to set isocenter and field fitting automatically from Clinical Protocol Template.
OptimizeFromObjectiveTemplate: Performs optimization from a given optimization objective template.
SetRx: Utilizes Prescription information in Plan Template (or Clinical Protocol) for setting the Plan prescription.
CompareProtocolDoseMetrics: Get Plan quality metrics reported from Clinical protocol.
OptimizeFromRapidPlanModel: (V16+ only) Performs DVH estimation and optimization with a given RapidPlan Model.
A more detailed description of the features of the Clinical Template reader and how it works has been published as a Technical Report: Development and Implementation of an Open Source Template Interpretation Class Library for Automated Treatment Planning.
Building an Automated Planning UI
Please note: the application in this example can be found on Github under ProtocolPlanner_BlogExample.
In this blog post, we’ll walk through how to build a simple automated planning UI that leverages Clinical Template Reader to select a protocol and generate a treatment plan automatically—no manual beam setup required. In the following example, a binary plugin script with a user interface can be developed to build a treatment plan using the Clinical Template Reader. This application will follow MVVM pattern development and AI-assisted software development. To start, clone the Clinical Template Reader from Github to a known location on the development system. Using Visual Studio, compile this code for use in our upcoming script. Next, the Eclipse Script Wizard can be used to generate a binary plugin script, ProtocolPlanner.

First, build in the MVVM capabilities from a prior blog post, AI-Assisted Application Development with ESAPI and ChatGPT. In the example script, a ViewModelBase class has been created inside a Services folder in the project. All three classes have been added to this ViewModelBase class file. After creating another folder called Themes a file of type ResourceDictionary, called AppTheme.xaml is created. This file mirrors the downloadable file in a prior blog post, AI-Assisted Application Development II: Custom UI Controls and Styles. Two more folders, Views and ViewModels will be created prior to generating the view.
In this example, a simple UI will be created in a MainView.xaml file. This can be split up later into multiple view components. First, when the MainView.xaml file is added, reference the AppTheme.xaml to apply the UI theme to the Main View.
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="../Themes/AppTheme.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>The following prompt is fed into Copilot to generate the UI.
Hello. I would like to build the following UI in my MainView. First, I would like to build a section in the upper left of the UI where the user can select a Clinical Protocol from the Clinical Template Readers clinical protocols. Underneath I would like to have a small table of values using simply grid placement. The values I would like to display are the Plan name to be created by the clinical protocol, the dose per fraction, the number of fractions, the machine, and the target ID. In this same section, I would like a button that has the option to generate the plan. Then in the top left, I would like another combobox that I can select a Rapidplan model. Underneath I may need a dynamic list (ItemsControl) that will fill with the structure matches as detected by the clinical template reader. In this same section, I would liek a button to perform the DVH Estimation based on the selected RapidPlan model. Finally, at the bottom I would like to have a button that says Optimize, and when the optimization is successful a checkmark comes up beside it. Then another button that says Calculate Dose and when that calculation is successful a checkmark comes up beside that button but also the dose metrics from the clinical protocol are displayed in this section in another datagrid. This is a dynamic list that is read from the ClinicalTemplateReader as well and will include items like the STructure Id, Metric, Tolerance, Value and Evaluation.After prompting copilot again to not include names for the user interface elements, and to instead use binding expressions on the combobox, the following code is produced.
<Grid>
 <!-- Define grid layout -->
 <Grid.RowDefinitions>
     <RowDefinition Height="Auto"/>
     <RowDefinition Height="*"/>
 </Grid.RowDefinitions>
 <Grid.ColumnDefinitions>
     <ColumnDefinition Width="2*"/>
     <ColumnDefinition Width="3*"/>
 </Grid.ColumnDefinitions>
 <!-- Clinical Protocol Section -->
 <StackPanel Grid.Row="0" Grid.Column="0" Margin="10">
     <TextBlock Text="Select Clinical Protocol:" FontWeight="Bold"/>
     <ComboBox ItemsSource="{Binding ClinicalProtocols}" SelectedItem="{Binding SelectedClinicalProtocol}" Width="200" Margin="0,5,0,10"/>
    <Grid Margin="0,5,0,10"> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Plan Name -->
<TextBlock Text="Plan Name:" Grid.Row="0" Grid.Column="0" FontWeight="Bold" Margin="5"/>
<TextBlock Text="{Binding ClinicalProtocolDetails.PlanName}" Grid.Row="0" Grid.Column="1" Margin="5"/>
<!-- Dose Per Fraction -->
<TextBlock Text="Dose/Fraction:" Grid.Row="1" Grid.Column="0" FontWeight="Bold" Margin="5"/>
<TextBlock Text="{Binding ClinicalProtocolDetails.DosePerFraction}" Grid.Row="1" Grid.Column="1" Margin="5"/>
<!-- Number of Fractions -->
<TextBlock Text="Fractions:" Grid.Row="2" Grid.Column="0" FontWeight="Bold" Margin="5"/>
<TextBlock Text="{Binding ClinicalProtocolDetails.NumberOfFractions}" Grid.Row="2" Grid.Column="1" Margin="5"/>
<!-- Machine -->
<TextBlock Text="Machine:" Grid.Row="3" Grid.Column="0" FontWeight="Bold" Margin="5"/>
<TextBlock Text="{Binding ClinicalProtocolDetails.Machine}" Grid.Row="3" Grid.Column="1" Margin="5"/>
<!-- Target ID -->
<TextBlock Text="Target ID:" Grid.Row="4" Grid.Column="0" FontWeight="Bold" Margin="5"/>
<TextBlock Text="{Binding ClinicalProtocolDetails.TargetID}" Grid.Row="4" Grid.Column="1" Margin="5"/>
</Grid>
     <Button Content="Generate Plan" Command="{Binding GeneratePlanCommand}" Width="150" Margin="0,5,0,10"/>
 </StackPanel>
 <!-- RapidPlan Section -->
 <StackPanel Grid.Row="0" Grid.Column="1" Margin="10">
     <TextBlock Text="Select RapidPlan Model:" FontWeight="Bold"/>
     <ComboBox ItemsSource="{Binding RapidPlanModels}" SelectedItem="{Binding SelectedRapidPlanModel}" Width="200" Margin="0,5,0,10"/>
     <TextBlock Text="Structure Matches:" FontWeight="Bold"/>
     <ItemsControl ItemsSource="{Binding StructureMatches}" Margin="0,5,0,10">
         <ItemsControl.ItemTemplate>
             <DataTemplate>
                 <TextBlock Text="{Binding StructureName}"/>
             </DataTemplate>
         </ItemsControl.ItemTemplate>
     </ItemsControl>
     <Button Content="Perform DVH Estimation" Command="{Binding PerformDVHEstimationCommand}" Width="200" Margin="0,5,0,10"/>
 </StackPanel>
 <!-- Optimization and Dose Calculation Section -->
 <StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Vertical" Margin="10">
     <StackPanel Orientation="Horizontal" Margin="0,5,0,10">
         <Button Content="Optimize" Command="{Binding OptimizeCommand}" Width="150"/>
         <TextBlock Text="✔" Visibility="{Binding IsOptimizationSuccessful, Converter={StaticResource BooleanToVisibilityConverter}}" Foreground="Green" Margin="10,0,0,0"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Margin="0,5,0,10">
         <Button Content="Calculate Dose" Command="{Binding CalculateDoseCommand}" Width="150"/>
         <TextBlock Text="✔" Visibility="{Binding IsDoseCalculationSuccessful, Converter={StaticResource BooleanToVisibilityConverter}}" Foreground="Green" Margin="10,0,0,0"/>
     </StackPanel>
     <DataGrid ItemsSource="{Binding DoseMetrics}" AutoGenerateColumns="False" Height="200" Margin="0,5,0,10">         <DataGrid.Columns>
             <DataGridTextColumn Header="Structure ID" Binding="{Binding StructureID}"/>
             <DataGridTextColumn Header="Metric" Binding="{Binding Metric}"/>
             <DataGridTextColumn Header="Tolerance" Binding="{Binding Tolerance}"/>
             <DataGridTextColumn Header="Value" Binding="{Binding Value}"/>
             <DataGridTextColumn Header="Evaluation" Binding="{Binding Evaluation}"/>
         </DataGrid.Columns>
     </DataGrid>
 </StackPanel>
</Grid>Let's focus on the implementation of the plan creation from the Clinical Template Reader. As seen by the view created by Copilot, we will need a class object with the name ClinicalTemplateDetails. A Models folder with a new model ClinicalTemplateModel can implement these details. The new model now looks as follows:
public class ClinicalProtocolModel
{
    public string PlanName { get; set; }
    public double DosePerFraction { get; set; }
    public int NumberOfFractions { get; set; }
    public string Machine { get; set; }
    public string TargetID { get; set; }
}Finally, before the initial test, a ViewModel for our parameters. At the top of the ViewModel namespace enter the following assembly attribute.
[assembly: EsapiScript(IsWriteable=true)]In this first example, we are looking to implement properties for the ClinicalProtocols, SelectedClinicalProtocol, ClinicalProtocolDetails, and GeneratePlanCommand. The initialization of the ViewModel will look as follows.
//private fields
private ScriptContext _context;
private ClinicalTemplate _clinicalProtocols;
//properties
public List<Protocol> ClinicalProtocols { get; set; }
private Protocol _selectedClinicalProtocol;
 Â
public Protocol SelectedClinicalProtocol
{
        get { return _selectedClinicalProtocol; }
        setÂ
{Â
SetProperty(ref _selectedClinicalProtocol,value);
if (SelectedClinicalProtocol != null)
{
ClinicalProtocolDetails = new ClinicalProtocolModel()
{
PlanName = SelectedClinicalProtocol.Phases.FirstOrDefault().ID,
DosePerFraction = SelectedClinicalProtocol.Phases.FirstOrDefault().PlanTemplate.DosePerFraction.Value,
NumberOfFractions = SelectedClinicalProtocol.Phases.FirstOrDefault().PlanTemplate.FractionCount.Value,
Machine = SelectedClinicalProtocol.Phases.FirstOrDefault().PlanTemplate.Fields.First().TreatmentUnit,
TargetID = SelectedClinicalProtocol.Phases.FirstOrDefault().PlanTemplate.PrescriptionSite.VolumeID
}; }
}
}
private ClinicalProtocolModel _clinicalProtocolDetails;
public ClinicalProtocolModel ClinicalProtocolDetails
{
        get { return _clinicalProtocolDetails; }
        set { SetProperty(ref _clinicalProtocolDetails, value); }
}
public RelayCommand GeneratePlanCommand { get; private set; }
Generating methods for the RelayCommand and the collection of the Clinical Protocols. Considering the additional call to the methods in the constructor and the method implementations, see the code below.
//constructor
public MainViewModel(ScriptContext context, ClinicalTemplate clinicalTemplate)
{
    _context = context;
_clinicalProtocols = clinicalTemplate;
ClinicalProtocols = new List<Protocol>();
    GetClinicalProtocols();
    GeneratePlanCommand = new RelayCommand(GeneratePlan, CanGeneratePlan);
}
///Â <summary>
///Â Read clinical protocols from server.
///Â </summary>
private void GetClinicalProtocols()
{
    foreach(var protocol in _clinicalProtocols.ClinicalProtocols)
    {
        ClinicalProtocols.Add(protocol);
    }
}
///Â <summary>
///Â Generate plan based on selected clinical protocol
///Â </summary>
/// <param name="obj">NA</param>
private void GeneratePlan(object obj)
{
    if(_context.Patient!=null && _context.StructureSet != null)
    {
        _context.Patient.BeginModifications();
        var planTemplate = SelectedClinicalProtocol.Phases.First().PlanTemplate;
        var autoPlan = _clinicalProtocols.GeneratePlanFromTemplate(
null, //Course
            _context.StructureSet, //Structure Set
           planTemplate, //Plan Template
            null, //target ID
            null);//Machine Id
        _clinicalProtocols.SetRx(planTemplate, autoPlan, "cGy");
MessageBox.Show("Plan Generated!");
    }
}
///Â <summary>
///Â Check to determine if plan can be generated.
///Â </summary>
/// <param name="arg"></param>
///Â <returns></returns>
private bool CanGeneratePlan(object arg)
{
    return SelectedClinicalProtocol != null;
}Finally, remember to return to the initial script, in this case ProtocolPlanner.cs and set the window content to the MainView.
        public void Execute(ScriptContext context, System.Windows.Window window, ScriptEnvironment environment)
        {
            // TODO : Add here the code that is called when the script is launched from Eclipse.
            var view = new MainView();
            var clinicalTemplate = new ClinicalTemplate("Master-AE.vic.com");
var viewModel = new MainViewModel(context,clinicalTemplate);
            view.DataContext = viewModel;
            window.Content = view;
        }Build the application and open a sample patient to test. The only Eclipse object that needs to be open in context is the patient structure set as that is utilized within the GeneratePlan method. Once the app is loaded and the clinical protocol selected, the plan can be calculated.

Please note: In order to get the application working, a converter was created so that the Binding expression on the Visibility attribute of the Textblocks containing the checkmarks could be validated. As we cover more components of this application in future blog posts, we will explore the Converter classes required. Once the application has concluded, the new plan can be seen in Eclipse.

Conclusion
Clinical templates are powerful tools available in the Eclipse Treatment Planning System for driving consistency in treatment planning. With careful setup, they not only define templates for structures, optimization objectives, and plan parameters but also form the foundation for robust planning automation. By combining these templates with the Clinical Template Reader API and the Eclipse Scripting API, this post demonstrates how to build a streamlined application capable of reading clinical protocols and generating plans automatically—reducing manual steps and enabling reproducible planning workflows.
This blog post covered the first step in this automation journey: generating a treatment plan from a Clinical Protocol. We explored how to extract protocol details, set up the UI, and implement the necessary ViewModel logic to create a plan in Eclipse using the ESAPI scripting environment.
But our work doesn’t stop here. In next month’s blog post, we’ll take this project one step further by implementing automated optimization and dose calculation, bringing us closer to a fully automated planning pipeline. Stay tuned as we expand this application to optimize plans based on RapidPlan models, calculate dose, and evaluate clinical goals—all with minimal manual intervention.



