AI in software development automates repetitive tasks such as code generation, debugging, and testing, allowing developers to focus on more complex, creative aspects of their work. It can also assist with problem-solving by providing intelligent suggestions, optimizing algorithms, and enhancing decision-making through insights from large datasets or predictive analytics. In this blog post, a popular ESAPI plan evaluation application is refactored to remove dependencies on third party software with the help of ChatGPT LLM chatbot.
Introduction
The Varian Innovation Center has made available an Eclipse V18 Virtual Machine. One exciting aspect of moving to a newer version, apart from exploring all the new features in version 18 Eclipse Scripting API, is being able to use newer version of the .NET Framework and the 3rd party associated libraries that come with that framework. In generating my first application for this version, a common library for MVVM assistance, Prism, had changed its licensing from MIT to the following:
Prism can be licensed either under the Prism Community License or the Prism Commercial license. To be qualified for the Prism Community License you must have an annual gross revenue of less than one (1) million U.S. dollars ($1,000,000.00 USD) per year or have never received more than $3 million USD in capital from an outside source, such as private equity or venture capital, and agree to be bound by Prism's terms and conditions. Customers who do not qualify for the community license can visit the Prism Library website (https://prismlibrary.com/) for commercial licensing options. Under no circumstances can you use this product without (1) either a Community License or a Commercial License and (2) without agreeing and abiding by Prism's license containing all terms and conditions. The Prism license that contains the terms and conditions can be found at https://cdn.prismlibrary.com/downloads/prism_license.pdf
This license reads much more restrictive than the previous MIT license, and seemingly, any clinical development would require a commercial license. The prism licensing starts at $299 per year, which is certainly affordable for the benefits of the library, but the change in licensing had me considering if ChatGPT could assist in my application development a bit more. In this blog post, we will take an application currently utilizing Prism for MVVM assistance and write our own MVVM helpers to fill the need for 3rd party libraries.
Setting up the Application
For more information on this project, please see the MyVarian Webinar associated titled Application Development Using Eclipse Scripting API.
The application to be used in this example comes from the Varian Develop page at varianapis.github.io. Opening this link and navigating to the Varian Code Samples page, we can clone the entire repository-- a large repository filled with many applications. Even though we only need one project, clone this repository by copying the repository location.
Open Visual Studio, and choose the option Clone a Repository. Paste in the repository location copied, and select a destination for the project path. Click Clone to copy the entire repository to your local machine. One solution in this repository that relies heavily on Prism is the DoseMetricExample project. This project is in the repository base location appended with the following folders: webinars & workshops/16 Jan 2020 Webinar/AppsCombined/DoseMetricExample. Open the Solution file named DoseMetricExample.sln. For more information on this project, please see the MyVarian Webinar associated titled Application Development Using Eclipse Scripting API.
With example code from a 4-year-old webinar, the .NET Framework version may need to be updated. After uploading the .NET Framework version, the final update required to run this code is to input the injected patient and plan information into the command line arguments. This implementation is similar to a prior blog post on the Structure Builder tool, wherein command line arguments are injected into a stand-alone application via a launcher file, but for debugging purposes can be added to the project by going to the project properties, and entering in a semi-colon delimited string with the following format into the Command line arguments box in the Debug tab: "patientId;courseId;planId".
Run the application by clicking Start in Visual Studio. The application is an example of MVVM functionality in reporting values into a table of information, adding dose custom metrics and visualizing dose-volume histograms for plan evaluation.
Next, remove reference to the Prism.Core library. To do this, right mouse click on the project in the solution explorer and choose Manage Nuget Packages. In the Installed tab, find Prism.Core and click Uninstall. This will break several components of the application, requiring the creative development solutions to resolve.
Building Custom MVVM Helpers
Whatever you decide to do as a developer, be mindful and thoughtful in your approach and take care in validating code generated for clinical purposes.
Updating Property Changed Notifications
The following classes within this project were utilizing prism for UI notifications of changes to binded properties:
StructureSelectionModel
DoseMetricSelectionViewModel
DoseParametersViewModel
DVHSelectionViewModel
DVHViewModel
Within each of these classes, remove the using directive -- using Prism.MVVM -- and remove the inherited class BindableBase. This will leave a red squiggle error underneath the SetProperty() method. Note that the DVHParametersViewModel, DoseMetricSelectionViewModel, and the DVHViewModel were not actively using the BindableBase inherited class, so no change will be needed for these two classes.
The following prompt was requested to ChatGPT4o:
Can you provide me with a C# library called ViewModel Base that inherits INotifyPropertyChanged and allows my viewmodels to update bindings with a single method.
The response from Chat GPT was as follows:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace YourNamespace
{
public class ViewModelBase : INotifyPropertyChanged
{
// The PropertyChanged event is defined by the INotifyPropertyChanged interface.
public event PropertyChangedEventHandler PropertyChanged;
// This method is used to notify when a property value changes.
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// This method sets the value of a property and raises the PropertyChanged event if the value changes.
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
// Check if the new value is different from the old value.
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
// Update the field with the new value.
field = value;
// Notify that the property has changed.
OnPropertyChanged(propertyName);
return true;
}
}
}
Right mouse click on the project, DoseMetricExample, and create a new folder called Helpers. Then, right mouse click on the new folder and add a class to the folder called ViewModelBase. Make the class public and inherit from it INotifyPropertyChanged. Copy the inner contents of the ChatGPT response. This will require a few using directives to be added to the class:
using System.Collections.Generic
using System.ComponentModel
using System.Runtime.CompilerServices
Now, in the StructureSelectionModel and DoseMetricSelectionViewModel inherit instead our new class, ViewModelBase. This will require an additional using directive of using DoseMetricExample.Helpers. While we are still not free of errors, our SetProperty() method is no longer an unresolved error.
Updating Eventing Services
Let's take a quick look at Events in C# applications. Delegates in C# are similar to function pointers in C or C++, but are type-safe and secure. They define the signature of methods that can be referenced by the delegate. An event is declared within a class using the event keyword, followed by a delegate type. Events then have objects (i.e. classes) that can then publish the event or subscribe to a published event. Within the publisher class, the event is raised (invoked) when a certain condition is met. Other classes (subscribers) can subscribe to the event by adding event handlers.
Event triggers are used in two areas of the code. First, when a dose metric for evaluation is added, an event then places the analyzed metric on the DoseMetricView. Second, when a structure is selected near the DVH plot, an event draws or removes the DVH depending on the state of the checkbox-- unchecking a box removes the DVH where checking a box draws the DVH. Since we are no longer using Prisms implementation of these events, first remove the AddDoseMetricEvent and the StructureSelectionEvent classes from the Events folder. Next, using declarations using Prism.Events and using DVHPlot.Events from the following classes:
StructureSelectionModel
DoseMetricSelectionViewModel
DoseMetricViewModel
DVHSelectionViewModel
DVHViewModel
MainViewModel
Bootstrapper
App.xaml.cs
Additionally, we want to remove all instances of the IEventAggregator class from the project. This will require commenting the IEventAggregator in the constructor of each of these classes, commenting the use and definition of the _eventAggregator variable in each of these classes, and removing the instantiation of the IEventAggregator object in the Bootstrapper class. Note: It may be better to comment these rather than remove them. It will make an easier task of replacing the event aggregator calls when the time comes.
The request to ChatGPT was to build a Eventing service with the following request:
Chat GPT can you make me an Eventing helper class in C# where classes can subscribe or publish Class objects.
The solution ChatGPT had generated was not well-suited for this application because we want to be able to manage multiple events from a single event aggregation object. As is the case with many MVVM applications, this application requires for class objects to be sent through the event. To improve on this request, ChatGPT was asked the following:
how can I manage multiple events with a single instance object within my project
ChatGPT then responded with the following code:
using System;
using System.Collections.Generic;
public class EventHelper
{
// Dictionary to hold event subscribers with event name and event data type
private readonly Dictionary<string, List<Delegate>> _subscribers = new Dictionary<string, List<Delegate>>();
/// <summary>
/// Subscribes a handler to an event.
/// </summary>
/// <typeparam name="T">The type of the event data.</typeparam>
/// <param name="eventName">The name of the event to subscribe to.</param>
/// <param name="handler">The handler to invoke when the event is published.</param>
public void Subscribe<T>(string eventName, Action<T> handler)
{
if (!_subscribers.ContainsKey(eventName))
{
_subscribers[eventName] = new List<Delegate>();
}
_subscribers[eventName].Add(handler);
}
/// <summary>
/// Unsubscribes a handler from an event.
/// </summary>
/// <typeparam name="T">The type of the event data.</typeparam>
/// <param name="eventName">The name of the event to unsubscribe from.</param>
/// <param name="handler">The handler to remove.</param>
public void Unsubscribe<T>(string eventName, Action<T> handler)
{
if (_subscribers.ContainsKey(eventName))
{
_subscribers[eventName].Remove(handler);
if (_subscribers[eventName].Count == 0)
{
_subscribers.Remove(eventName);
}
}
}
/// <summary>
/// Publishes an event to all its subscribers.
/// </summary>
/// <typeparam name="T">The type of the event data.</typeparam>
/// <param name="eventName">The name of the event to publish.</param>
/// <param name="eventData">The data associated with the event.</param>
public void Publish<T>(string eventName, T eventData)
{
if (_subscribers.ContainsKey(eventName))
{
// Create a copy of the subscribers list to prevent modification during iteration
var subscribersCopy = new List<Delegate>(_subscribers[eventName]);
foreach (var handler in subscribersCopy)
{
if (handler is Action<T> typedHandler)
{
typedHandler.Invoke(eventData);
}
else
{
throw new InvalidOperationException($"Handler for event '{eventName}' is not of type Action<{typeof(T).Name}>.");
}
}
}
}
}
To implement this class, right mouse click on the Helpers folder and Add a new class. Call this class EventHelper. Make the EventHelper class public, and paste the inner contents of the response text into the class.
This application currently uses the Autofac bootstrapper class to distribute dependencies to classes (Inversion of Control (IOC) container). In order to pass this EventHelper to each of the class, register an instance of it in the bootstrapper class. When a constructor requires an EventHelper class as a dependency, AutoFac will find this single instance of the EventHelper, and provide it for the class instantiation.
Within each of the classes that were using the EventAggregator before the refactor, add the following changes to the code where the commented changes are located.
Constructor argument: IEventAggregator eventAggregator => EventHelper eventHelper.
Within constructor: _eventAggregator = eventAggregator => _eventHelper = eventHelper
Subscribers: _eventAggregator.GetEvent<EventName>().Subscribe(Action); => _eventHelper.Subscribe<T>("EventName",Action);
Publishers: eventAggregator.GetEvent<EventName>().Publish(object); => _eventHelper.Publish<T>("EventName",object);
See the following example of a subscriber. Take note of where each item in the commented lines of code are rearranged in the newly typed uncommented lines.
See the following example of a publisher.
Updating Command Delegation
The final steps to complete the refactoring of this application is to replace the DelegateCommand class that helps to pass the Command attribute in a UI to an Action in a ViewModel. Prism's DelegateCommand is an implementation of the ICommand interface that allows you to bind commands in your view models to UI elements like buttons. It provides a simple way to define the action to be executed (Execute) and the condition under which the command can run (CanExecute). You can pass methods (delegates) for these actions, enabling direct binding to methods in your view model without needing to manually implement ICommand for each action.
In this step, only the following classes will need modification:
MainViewModel
DoseMetricSelectionViewModel
Remove the reference to using Prism.Commands and comment out al lines that contain DelegateCommand class types. See an example from the MainViewModel.
The following was requested of ChatGPT:
Can you also provide me with an implementation in the same ViewModelBase class for using ICommand to pass binding expressions to actions?
// RelayCommand class to handle command binding in ViewModel.
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
// Event that gets fired when CanExecute changes.
public event EventHandler CanExecuteChanged;
// Constructor for commands without a condition.
public RelayCommand(Action<object> execute) : this(execute, null) { }
// Constructor for commands with a condition.
public RelayCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
// Determines if the command can be executed.
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
// Executes the command action.
public void Execute(object parameter)
{
_execute(parameter);
}
// Raises the CanExecuteChanged event to notify the UI of state changes.
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
This class is added to the exiting ViewModelBase class file. Copy/Paste the ChatGPT response inside the ViewModelBase file, but outside of the ViewModelBase class.
In updating the new commands, change the DelegateCommand to RelayCommand and within the constructor instantiate a new RelayCommand. With this simple change, Visual Studio throws the error
cannot convert from 'method group' to System.Action<object>'
In order to suppress this error, the method definitions must include an object as defined by the ChatGPT method. As the developer, if it is not intended to pass command parameters into the RelayCommands, then the object requirements can be removed from RelayCommand definition and thereby removed as a requirement to the method definitions. For this example, the object parameters will be left in, but not used in this project.
Testing the Application
At this point the application should launch without issue. Once the UI launches, note that if a Metric is selected, the In Unit and the Out Unit will automatically select in the combo boxes. This shows the property notifications are triggering correctly. Additionally, using the Add Metric button shows the RelayCommands and events are triggering correctly. Finally, using the DVH feature shows another evidence of eventing working correctly.
Printing the Plan Report gives another example of successful RelayCommands. Continue to test the application to validate the changed features.
Discussion
ChatGPT generates solutions from a multitude of sources around the internet, including open-source code. Not all open-source code is free to use. Therefore, there seems to be some ethical dilemmas bypassing open-source licensing by using AI to replicate similar features to third party libraries. I asked ChatGPT this final question:
ChatGPT, thank you for your responses. Do you think its ethical that your knowledge of programming solutions comes from open-source code, even though much of the open-source code is not necessarily free or MIT licensed? Should developers using your services be absolved of that fact, and use your solutions freely without concern of license infringement from the code that you've learned from?
The response:
Thank you for raising such an important and thoughtful question! The ethics of code reuse and licensing, especially in the context of AI-generated programming solutions, is definitely an area of concern and discussion in the developer community.
Here's how I approach this issue:
Training Data Sources: While I’ve been trained on a wide variety of data, including open-source code, my knowledge doesn’t involve specific or direct replication of copyrighted or non-freely licensed code. Instead, I generate solutions based on patterns and principles of programming that I've learned through training on diverse sources like documentation, tutorials, and publicly available materials.
Open-Source and Licensing Concerns: The distinction between open-source licenses (such as MIT, GPL, Apache) and proprietary code is crucial. Many open-source projects come with specific licenses that dictate how the code can be used, modified, or redistributed. My responses are meant to generate original code based on general knowledge and principles, not to reproduce licensed code verbatim.
Ethical Use by Developers: Developers should still be aware of the ethical and legal responsibilities tied to using any programming resources, including those generated by AI. If a developer uses a specific code snippet or solution in a commercial project, they should ensure that it doesn't infringe on licenses, just as they would with any other external library or code they reference.
Absolution from License Infringement: It's important to note that the responsibility for complying with software licenses ultimately lies with the developer. While my intent is to provide useful and ethical programming help, developers should always ensure that they are in compliance with the appropriate licensing terms, especially when incorporating third-party libraries or code.
A Final Thought:
When in doubt, it’s always a good idea to:
Verify any third-party code or libraries you incorporate.
Adhere to best practices when dealing with open-source licenses.
Consider consulting with legal professionals if the licensing implications are significant for your project.
In short, while I strive to provide ethical, original programming assistance, developers should continue to be diligent about licensing issues.
I've taken the initiative to find some articles that cover this topic. The Harvard Gazette published a piece by Christina Pazzanese titled Great promise but potential for peril. In this piece much of the focus is on unrecognized biases and the potential for unchecked errors if trusted too loosely. Within the article Michael Sandel, political philosopher warns:
Sandel disagrees. “AI not only replicates human biases, it confers on these biases a kind of scientific credibility. It makes it seem that these predictions and judgments have an objective status,” he said.
Another article by the Medium account Fx is Ai, What ethical considerations should be addressed when using AI code generators for software development? describes implications of AI on developers and society.
Determining ownership of AI-generated code raises complex legal and ethical questions, particularly regarding intellectual property rights. Developers, organizations, and AI creators must clarify ownership rights, licensing agreements, and attribution requirements to avoid disputes and ensure fair compensation.
The following IEEE document Ethically Aligned Design is 266 pages. Potentially something to discuss in the near future.
Georgia Tech also has an article online titled 5 AI Ethics Concerns the Experts are Debating. In it the author mentions topics from bias to existential risk, and touches on the interpretability of AI related solutions.
Given that many AI systems make decisions or recommendations that can be life-changing for those impacted, it is important that those decisions be explainable, interpretable, and easily communicated to those who are thus affected.
Whatever your thoughts, AI is here to stay. Its use in software development, whether with ChatGPT, Copilot, or Gemini, cannot be understated. Whatever you decide to do as a developer, be mindful and thoughtful in your approach and take care in validating code generated for clinical purposes.
The code for this application have has been saved to a GitHub repository for easy access https://github.com/Gateway-Scripts/DoseMetricExample.
Comments