top of page

Call a MATLAB Function from a C# Application

You may have code in MATLAB that you want to use in a C# application. Instead of re-writing your MATLAB code in C#, you can package it as a .NET class library and call it from the C# application. In this post, I'll show you how to do that. For more detailed information, see the official documentation .NET Assembly Integration.


The sample application you'll build will get the physical dose of a plan using the Eclipse Scripting API (ESAPI), call a MATLAB function to convert it to the biologically equivalent dose (EQD2), and export it as a CSV file. You'll actually work with each dose plane separately, so you'll end up with one CSV file per dose plane. I've put all the source files on GitHub: MatlabInterop.


The MATLAB Function


Start by writing the MATLAB function to convert the physical dose to the biologically equivalent dose (EQD2). The function takes three parameters: the dose plane, the number of fractions, and the α/β value.


function y = biodose(dose, n, alphaBeta)
    y = arrayfun(@eqd2, dose);
    
    function y = eqd2(d)
        y = d * (d / n + alphaBeta) / (2 + alphaBeta);
    end
end

The arrayfun function applies the eqd2 function to each element of the dose matrix. The eqd2 function is nested inside the biodose function so that it can use the n and alphaBeta parameters.


Save this function as biodose.m.


Packaging the Function as a .NET Library


Before you can package this function as a .NET library, you need to install the MATLAB Compiler and Compiler SDK. You can do this directly from MATLAB: go to the APPS tab, click on Get More Apps, search for "compiler," and install them.


Click on the APPS tab and choose the Library Compiler—a new window opens. On the toolbar, there's a list that says C Shared Library, C++ Shared Library, etc. Scroll down the list until you find .NET Assembly and click on it. Then click on the plus button next to Add exported functions and choose the MATLAB file you created (biodose.m).


Under Library information, name the library as "BioDose," fill out the author and summary, and rename the Class Name to "BioDoseMatlab". You don't need to worry about the remaining fields.


Click on the Package button on the toolbar. It'll ask you to save this library project somewhere in case you need to change the information in the future (e.g., the version).


When the packaging is done, it'll open the folder with the output files. The ones you'll use in this example are in the for_testing folder. The other folders are used when you want to distribute your libraries for public consumption. For now, just note the location of the output files.


Calling the .NET Library from C#


Create a new .NET Console App. Add a reference to your packaged .NET library (BioDose.dll in the for_testing folder). Add another reference to MWArray.dll, which on my computer is located at C:\Program Files\MATLAB\R2019a\toolbox\dotnetbuilder\bin\win64\v4.0.


Create a new class called BioDoseService. This class will be a wrapper for the MATLAB function in the your .NET library. Here's the code:


public class BioDoseService
{
    private static readonly BioDoseMatlab BioDoseMatlab = new BioDoseMatlab();

    public double[,] Calculate(double[,] dose, int fractions, double alphaBeta)
    {
        var doseArray = new MWNumericArray(dose);
        var nArray = new MWNumericArray(fractions);
        var alphaBetaArray = new MWNumericArray(alphaBeta);

        var result = BioDoseMatlab.biodose(doseArray, nArray, alphaBetaArray);
        return (double[,])result.ToArray();
    }
}

The BioDoseMatlab class is declared static so that it's initialized only once, even if there are multiples instances of BioDoseService. The reason for this is that it's expensive to initialize BioDoseMatlab, given that it loads the MATLAB runtime. On my computer, it took about 10 seconds to initialize and used around 450 MB of memory.


The Calculate method takes the same parameters as the biodose function in MATLAB. The dose parameter, however, is a two-dimensional double array (a standard C# type). Before you can pass these parameters to the MATLAB function, you need to wrap each of them in a MWNumericArray. The biodose function is then called and the result, which is of type MWArray, is converted back to a two-dimensional double array.


In general, you need to wrap numeric types (e.g., int, double) in MWNumericArray, strings in MWCharArray, and booleans in MWLogicalArray. For more information, see Rules for Data Conversion Between .NET and MATLAB.


The following code implements the entire program, calling the Calculate method in the BioDoseService to convert the physical dose to the biologically equivalent dose.


public class Program
{
    [STAThread]
    public static void Main(string[] args)
    {
        var bioDoseService = new BioDoseService();

        // Initialize the ESAPI application object
        using (var app = Application.CreateApplication(null, null))
        {
            // Open a patient and get a plan
            var patient = app.OpenPatientById("123456789");
            var course = patient.Courses.First(c => c.Id == "LIVER");
            var plan = course.PlanSetups.First(p => p.Id == "LIVER_5Fx");

            // Configure to get doses in absolute units
            plan.DoseValuePresentation = DoseValuePresentation.Absolute;

            var dose = plan.Dose;
            for (int z = 0; z < dose.ZSize; z++)
            {
                Console.WriteLine($"Exporting plane {z}");

                var dosePlane = GetDosePlane(z, dose);
                var bioDosePlane = bioDoseService.Calculate(dosePlane, 5, 2.5);
                ExportToCsv(z, bioDosePlane);
            }
        }
    }

    public static double[,] GetDosePlane(int z, Dose dose)
    {
        var voxels = new int[dose.XSize, dose.YSize];
        dose.GetVoxels(z, voxels);

        var dosePlane = new double[dose.XSize, dose.YSize];
        for (int x = 0; x < dose.XSize; x++)
            for (int y = 0; y < dose.YSize; y++)
                dosePlane[x, y] = dose.VoxelToDoseValue(voxels[x, y]).Dose;

        return dosePlane;
    }

    private static void ExportToCsv(int z, double[,] dose)
    {
        var output = new StringBuilder();
        for (int y = 0; y < dose.GetLength(1); y++)
        {
            // Create the output line (doses separated by commas)
            var doseValues = new List();
            for (int x = 0; x < dose.GetLength(0); x++)
                doseValues.Add(dose[x, y]);
            output.AppendLine(string.Join(",", doseValues));
        }

        // Output the dose plane to a CSV file
        File.WriteAllText($@"C:\Temp\Dose\{z}.csv", output.ToString());
    }
}

Obviously, you'll need to change the IDs of the patient, course, and plan to something that exists in your system. Also, the program assumes you have the folder C:\Temp\Dose, where the CSV files will be written to.


There's just one last thing you need to do before you can run this program. And that's to add the MATLAB runtime path to your PATH environment variable. To do this, open the Windows Control Panel, search for "environment," and click on the option to edit the environment variables. Click on the Environment Variables button. Under the user variables, find the PATH variable and click on Edit. At the end of the variable value, add a semicolon—which separates paths—and the path to the MATLAB runtime. On my computer, it is C:\Program Files\MATLAB\R2019a\runtime\win64, but yours may differ depending on the version of MATLAB you have installed.


The program should now run and successfully export the biologically equivalent doses to CSV files. You can visualize them using any program with a plotting function, including MATLAB.


Final Thoughts


The example function in MATLAB is very simple, so in this case it may be worth re-writing that code in C#. This would not only avoid having to create a separate .NET library but would also avoid the time in initializing the MATLAB runtime.


This post discussed calling MATLAB from C#, but you can also call C# from MATLAB. Unfortunately, this doesn't work with ESAPI—I tried it and I always get an exception while creating the Application object. If you're not working with ESAPI, however, see Getting Started with Microsoft .NET.

Related Posts

See All

ESAPI Essentials 1.1 and 2.0

A few months ago, I introduced ESAPI Essentials—a toolkit for working with ESAPI (available via NuGet). I've recently added one major feature: asynchronous access to ESAPI for binary plugin scripts. Y

Announcement: ESAPI Subreddit

A few months ago, Matt Schmidt started the ESAPI subreddit. It's another useful resource for finding and discussing ESAPI-related topics. According to the description, This community is to post and di

Dump All Patient Data from ESAPI

If, for whatever reason, you need to dump out all of the data for a patient from ESAPI, there's a quick and dirty way to do it. Well, there are probably several ways to do it, but here's one I've foun

Comments


bottom of page