4 years using ESAPI, how to improve your apps

João Castelo
6 min readJul 6, 2023

Hey guys! I’ve been hiding for a year because I was finishing my master thesis but I’m all good now!

Photo by Gabriel Heinzer on Unsplash

Since I’ve started my career in radiation oncology back in 2019, I’ve been a massive ESAPI user and supporter. After developing several apps I took some notes on my dev routine. Hope it helps others and probably myself in the future.

Python developers (give up) you have to master C# unfortunately

1 -> Do not make your application overwhelming.

Every plugin or executable should have an easy user interface, else people won’t use it.

When designing your User interface, the landing page and the default options should yield a good result in one or two clicks. If the user has to think to use your app, they’ll probably use it the wrong way use it wrong.

As physicists we like to be able to change stuff, but having so much control over the app is bad for the new user. Think of Android and iOS, they may be able to perform the same tasks, but iOS experience seems better, just because its default options are better.

Simple User interface

In our auxiliary structure generator script, the default template can’t mess up plans. But the other templates can. SBRT and SRS templates are dangerous if not used correctly. Default template: As simple aux structures as they can be to deliver a good VMAT plan.

The SBRT model would crop the optimization PTV (brown) inside the all organs at risk (OAR), but for this one might not be the best option. We had to choose in the Select OARs only heart and esophagus. Great vessels and Bronchi are not meant to be spared.

1.1 -> Create some kind of documentation

For coding, think of your code going online with GitHub, use good git messages for you commits. Create Branchs for each major change in your apps.

For the app, try to summarize possible errors and create examples for expert users.

2 -> Don’t expect user input to be correct.

Know that when people use your script they’ll click the run button 4 times in a row, they’ll select the wrong PTV, wrong CTV. Test your application for multiple repetitive actions in a row, if it yields a bad result, block it with pop ups or disabling buttons.

Create progress bars and warnings!

Eric and Carlos did an awesome job with this piece of code (almost a plug and play):

Use pop ups, with a message for OK and Cancel. I do not enjoy more complex decision popups (those you make with windows forms).

Our Isodose level converter allows users to add their Isodoses for any PTV they want and the value as they want. The script also reads dose from the PTV depending on the ID matching, and when it does not, it sets the default to 0 cGy. I thought no one was going to use an 0 cGy isodose, but it turns out that I’m seeing a lot of 0 cGy structures! (The power of the default). It can’t do harm, but it is slowing down the loading of structures in the LINAC.

UI for the isodoselevel converter

3 -> Start small but think about expansion and reusability

Whenever I get an Idea from colleagues or from myself, the first thinking is: Do I have to create a new app or can I insert the new idea in an existing app?

There are two paths, if the latter is true. The first is to hardcode the new idea as a Frankenstein or insert it as an extension from the code you wrote before. I always write code to be easy to update and extend in the future.

When I create a new app, I always start by thinking about the MVP:

Minimum Viable Product (MVP) Definition

“The minimum viable product is that version of a new product which allows a team to collect the maximum amount of validated learning about customers with the least effort.” Google’s first page (productboard)

I mostly reuse an old UI, get it working with the simplest code behind, and present it to other users. If they find it helpful/useful I then begin to build the code as if I had to present it to a Computer Science nerd.

I always create classes, objects, avoid nesting and avoid methods to hold too much info!

Think of the methods and classes as your intern, you won’t let him do tasks that he can’t handle or have to think a lot.

Examples:

Think of a pseudocode to create a VMAT plan:

Decide whether it’s a plugin or an executable

Plugin (.esapi.dll)

First think of the UI (mostly the landing default page), how it should be.

The user can’t mess up it with wrong typing or multiple actions in a row?

If you’re a geek you can use Figma to build the UI prototype! (I prefer a malfunctioning WPF prototype)

Design the code that runs behind (MVVM):

I like to do it simple:

// UI Code 
Window(course, structureSet)
vm = new ViewModel(course, structureSet)
TextBox("Selected Course", vm.CourseId)
TextBox("Selected Rx Prescription")
DropDown(vm.AvailablePrescriptions(), changed(e) => vm.ChangePrescription(e))
TextBox("Selected Target")
DropDown(vm.AvailableTargets(), changed(e) => vm.ChangeTarget(e))
TextBox("Selected RP Enhanced")
DropDown(vm.AvailablePrescriptions(), changed(e) => vm.ChangePrescription(e))
Button("CreateAutoPlan", pressed()=>vm.Automate())
// View Model Creates a information center between UI and Model
CourseId
Course
AvailablePrescriptions()=> SomeMethod()
SelectedPrescription
AvailableBeamTemplates
SelectedBeamTemplate
AvailableRPETemplates () => SomeMethod() // RapidPlan Enhanced
SelectedRPETemplate
constructor(course, structureSet){
Model = new ModelAll(this) // passing the VM to the model
}
Automate() => Model.AutomateAll()
// Model file
class ModelAll
vm = ViewModel
AutomateAll(){
course = GetCourse()
plan = CreatePlan(course)
target = vm.GetTarget()
prescription = GetPrescription(course)
AssignPrescription(plan, prescription)
templateBeams = vm.SelectedBeamTemplate
beams = CreateBeams(plan, templateBeams.BeamArrangement)
AdjustCollimators(beams, target, templateBeams.CollimatorInfo)
AdjustIsocenter(beams, target, templateBeams.IsocenterAdjustment)

}

In the future, I might want to add a new Template for Isocenter positioning, so I would have an easy plug and play way to code it. Using the same logic:

TextBox("Selected IsoPosition Template")
DropDown(vm.AvailableIsoTemplates(), changed(e) => vm.ChangeIsocenterTemplate(e))

Create a Visual Studio Project on TBOX:

I always use the Executable path, and then mimic the plugin behavior by getting a hardcoded patient. You can always use Carlos Anderson’s plugin runner!

Executable.cs

Do not let ESAPI code to run in the UI xaml.cs, try to avoid it at your ViewModel, you should keep it under you Model code (backend).

Although it goes in opposition to what I’ve said, I send the Patient or PlanSetup to a new Window, to start the UI and run the code behind it. Yes it would be better to do a separate class or a Facade, but I’m lazy 😶.

Script.cs

Data binding and other stuff are not really in question, but I always do the bindings myself. I’m dumb when trying to figure out the multiple ways to use the AutoFacs and AutoBinders (INotifyPropertyPlease NOOOO).

Take home points:

Always create an UI, please xaml

Get a good default option

Do not expect users to be smart when using your design

Avoid using too much 3rd party nuGet packages.

Thanks Jonas for the review!

--

--