Portal Dosimetry Scripting: Export Images as CSV

João Castelo
7 min readOct 2, 2020

Hey guys, hope you’re doing great!

Photo by Kevin Ku on Unsplash

In this post I want to share with you a simple UI that I’ve started to develop a few days ago.

This script is just for testing, it is not supposed to be used in clinic.

It is a simple WPF app to ease the exporting process of Portal Dosimetry images.

Before all that, I’ll share 4 main aspects of Portal Dosimetry Scripting API.

Where to find Portal Dosimetry dlls?

They are located in the computer in which Eclipse is installed:

C:\Program Files (x86)\Varian\ProductLine\Workspaces\VMS.PortalDosimetry.Workspace

The Workspaces folder also has the Image Registration API libraries. (Which I won’t cover in this post).

You’ll import in Visual Studio the two dlls:

Where to run the plugins:

You have to access the Portal Dosimetry UI in Eclipse. And then click in Tools/Scripts:

On this interface you can run single file and binary plugins. One interesting thing is that in Portal Dosimetry API you don’t need to declare your assembly name as Something.esapi.dll for binary plugins.

All helper libraries can be executed, but only the VMS.DV.PD.Scripting.Execute will be targeted.

Testing Binary Plugins:

All versions of the assembly are kept loaded inside Eclipse after running just like in ESAPI, so you need to compile multiple times with different assembly names. Matt figured out a similar solution as Carlos Anderson’s plugin runner for TBOX.

Testing Executables:

Executables need to be executed via the computer that Eclipse is installed. So be aware CITRIX Users!

Please I need a template!

You can use the Script Wizard, but I do not like it because the separation between Project and Plugin makes the Git repository creation and commit difficult.

Remember to change the project’s CPU settings to x64.

So here are the templates for binary and single file plugins (Class Library Project):

And for executables (Console Application):

Is Portal Dosimetry Scripting writable?

You can’t really save anything, but you can do Portal Dosimetry analysis using the method CreateTransientAnalysis.

Can I trigger the Portal Dosimetry API from an ESAPI plugin?

Yes, you can use the methodology described in this earlier post on the blog. Instead of creating the executable using the VMS.TPS libraries, you should use the VMS.CA.Scripting and VMS.DV.PD.Scripting.

Portal Dosimetry Script to Export the acquired images:

The project is in the following repo in my GitHub:

Clone, download the references needed in the NuGet Package Manager and compile it on visual studio.

Code Highlights:

Passing the window as argument in the Execute method:

public void Execute(ScriptContext scriptContext, Window window)        { .....   // create an userControl    window.Content = showImg; // Add the userControl to the Window

This allow you to Add User Controls to the script Window from PD API and still use the ScriptContext.

There is no need to call window.Show() in PD API, but it’s a must in ESAPI.

Creating a method to close the window if you Close the UserControl:

showImg.Closed += new EventHandler((sender, e) => window.Close());

Using the images to get the course (Reversing the query order):

var planPortals = scriptContext.Patient.PDPlanSetups.SelectMany(e => e.Beams);        
var courses = planPortals.Select(e => e.PDPlanSetup.PlanSetup.Course.Id).Distinct();

In those lines I use SelectMany to get every beam available, then I use the beams to get their respective courses. When I use this query order I’ll be certain to only select courses with PD Beams.

Create a MV Image recipient:

I created a class to handle the MV Image, all the MV Images come from a PD Frame, that exists inside the beam property of the PDPlanSetup.

The ShowImageModel’s constructor handles the MVImage creation process. For each frame, it creates a list of MVImages. One interesting code optimization trick was to use the property Array as a method, this allows to only perform the long operation to get the calibration unit matrix when creating the heatmap (Once the image is selected in the dropdown) or when the user needs to export the MVImages.

public double[,] Array { get => GetCUMatrix(Frame);  }

Notice when I use it more than once I call it a local variable, so only one operation is performed.

...
var array = Array; // Calling the GetCUMatrix method once
File.WriteAllText(exportpath, "");
for (int y = 0; y <= array.GetUpperBound(1); y++) { ....

Using a ViewModel as proxy for the content:

A WPF app needs the xaml and the xaml.cs files. The xaml contains the markdown that create the Text, buttons and etc.

<Button Background="DarkSlateGray" Foreground="WhiteSmoke" Click="ExportPD_Click" Content="Export Portal Images" Name="ExportPD" />

And the xaml.cs contains the logic to handle dynamic changes in the xaml.

Notice that Background and Foreground are static properties, however the Click action is a method, so we defined it in the xaml.cs file.

private void ExportPD_Click(object sender, RoutedEventArgs e)        {            string dirToSave = "";

The ExportPD_Click action depends on the course selected by the user by the following dropdown (ComboBox).

<ComboBox x:Name="Courses" ItemsSource="{Binding Courses}" SelectedItem="{Binding SelectedCourse}" SelectionChanged="Courses_SelectionChanged"                  Background="LightSlateGray"/>

It is absolutely possible to do every operation related to the matrices and scriptContext inside, but I preferred to keep only the xaml objects behavior in the xaml.cs file. All the other tasks regarding the scriptContext were kept in the ShowImageViewModel, which is responsible for the connection between the View (ShowImage) and the MVImages List (ShowImageModel).

Using this logic, I set the xaml DataContext to be the ShowImageViewModel instance, and now it is possible to use its properties directly in the xaml. Notice that Courses (List of the courses Ids) does not exists in the xaml nor the xaml.cs file and yet can be called as the ComboBox ItemSource.

ItemsSource="{Binding Courses}"

Every time the selection of the course ComboBox changes, the Courses_SelectionChanged action repopulates the MVImages List (ShowImageViewModel.RefreshImages()) and shows in the OxyPlot Heatmap the first element of the former.

PortalImages.SelectedItem = ShowImageViewModel.SelectedModel;            PortalImages.ItemsSource = ShowImageViewModel.ModelsSource;

OxyPlot:

The usage of Oxyplot is very straightforward, you create a PlotModel (ImageModel) in the ShowImageViewModel based on the present MVImage (SelectedModel) and bind it to the oxyView in the xaml file.

<oxy:PlotView MinHeight="600" MinWidth="600" MaxWidth="600" MaxHeight="600"  Background="Black" x:Name="OxyView"  Model="{Binding ImageModel}"/>

To hide the axes, I get a black Background.

🤔 Well … WPF is not easy.

Script usage:

You choose the course that has at least one portal acquired and then you can inspect images in a fancy Oxyplot heatmap (that matches the Eclipse UI, except for windowing).

WPF UI to export portal images, textbox that checks all the images acquired, Oxyplot heatmap canvas, button to export the images, dropdown to choose course, dropdown to choose the image.
Choice of course

After checking the images, press the green button (Export Portal Images) and choose the folder to Export via CommonOpenFileDialog.

This dialog is not the FolderBrowserDialog, it is actually an abstraction of the OpenFileDialog, using the following line of code:

using (var fbd = new CommonOpenFileDialog()){fbd.IsFolderPicker = true;
....

After a few seconds you should be able to see the csv (comma separated values) files popping inside the folder you’ve selected:

Those files are the exported images for all frames found in the selected course.

To test if the image matrices are indeed exported, I’ll plot them using a python script (Here I’m using a jupyter notebook at Google’s Colab):

Python notebook

Asynchronous:

This project freezes the UI in the long operation to convert voxels values to calibration units (CU) and export it as csv. The loading cursor will pop, but the script is not crashing, it is just waiting.

One thing to add to this project, is moving the export process to an asynchronous task, this would solve the UI freeze problem. I’ll leave this task for a later post!

Is information about PD API easy to get?

They’re harder than ESAPI…
I’ve searched in github for projects:

https://github.com/UNLV-MedicalPhysics/PD_AdvancedAnalysis

https://github.com/Kiragroh/ESAPI_DataMiner_PortalDosimetry

And looked the Example projects of Eclipse Script Wizard (They’re located in the same folder as the dlls).

Thanks to Jonas for reviewing the post.

--

--

João Castelo

Radiation Therapy Medical Physicist and Programmer