January 11, 2013

A .NET-based user interface for MATLAB applications

Filed under: Development — Tags: , , , — Arne Joris @ 6:58 pm

logo_matlab MATLAB is a numerical computing environment for engineering applications. It supports communicating with and controlling hardware and processing and visualizing data from that hardware, which makes it a very popular platform for writing the software that goes with custom hardware solutions.  It also supports creating user interfaces too, but they tend to look outdated and clunky. matlabGUI_example1a

For example, the control and display software for your custom pump controller system can easily be written in MATLAB. It would run on a PC that is connected to your hardware, lets the user turn the system on and off, set parameters such as pump cycle delays and display graphs showing overall efficiency or total energy input. You can compile all the MATLAB code and sell it, along with your hardware and the MATLAB compiler runtime, as a product.

But what are your options when you want a sleeker, more modern UI with a ribbon bar, interactive graphs and UI themes or skins?  The most economic solution with a relatively short time-to-market, is to create a .NET based UI which connects to your MATLAB functions. In this article, I’ll elaborate on how exactly this works.

The MATLAB programming language is also called ‘m-code’  and is saved in files with an .m extension. You can compile these m-files into a .NET dll using MATLAB builder NE.  In the examples below,  MATLAB has compiled all m-files into MatlabExample.dll (providing methods in the MatlabExample namespace), and the .NET C# project has a reference to this dll.

First, lets look at an m-file, loading an MyObject file from the local filesystem (or creating a default MyObject if the file does not exist yet):

function [MyObject,Error]=LoadFromFile(Id)
% Create a Structure with the Default Settings
Error=0;

loadpath=strcat(Setting.Path,Id,Setting.MyObjectExtension);

% Check if File exists
if exist(loadpath,'file')==2 %File exists
     load(loadpath,'-mat');
else
     MyObject=CreateDefaultObject(Id);
     Error=202;
end
end

The MyObject file is a file saved by MATLAB, using a binary format as in the example below:

function Create(MyObject)
% Creates and saves a MyObject

MyObject.Id = '123';
MyObject.Foo = 1.2;
MyObject.Bar = 9.99;
MyObject.LoremIpssum = 3.14;
MyObject.Name = 'sample';

savePath=strcat(Setting.Path,MyObject.Id, Setting.MyObjectExtension);
save(savePath,'MyObject','-mat');

end

The following C# uses the m-code and wraps its functionality into a MyObject class :

public class MyObject
{
    private static readonly Dictionary<String, MyObject> _instances = new Dictionary<string, MyObject>();
    private MWStructArray _settings;
    private MatlabExample.MyObject _myObjectFactory = new MatlabExample.MyObject();
    private string _id;

    public enum DoublePropertyType
    {
        [StringValue("Foo")] KEY_FOO,
        [StringValue("Bar")] KEY_BAR,
        [StringValue("LoremIpssum")] KEY_LOREMIPSUM
    }

    public enum StringPropertyType
    {
        [StringValue("Name")] NAME
    }

    public double this[DoublePropertyType name]
    {
        get { return ((MWNumericArray)_settings[StringEnum.GetStringValue(name)]).ToScalarDouble(); }
        set { _settings[StringEnum.GetStringValue(name)] = value; 
    }

    public String this[StringPropertyType name]
    {
        get { return ((MWCharArray)_settings[StringEnum.GetStringValue(name)]).ToString(); }
        set { _settings[StringEnum.GetStringValue(name)] = value; }
    }

    // Private constructor, calls LoadFromFile
    private MyObject(string myObjectId)
    {
        this._id = myObjectId;
        var ret = _myObjectFactory.LoadFromFile(2, myObjectId);
        this._settings = (MWStructArray) ret[0];
        int error = ((MWNumericArray) ret[1]).ToScalarInteger();
        if (error != 0) 
            throw new ApplicationException("Failed to load MyObject.");
    }

    // Retrieves a MyObject with a given id.
    public static MyObject GetInstance(String myObjectId)
    {
        MyObject instance;
        if (! _instances.TryGetValue(myObjectId, out instance))
        {
            lock(_instances)
            {
                if (! _instances.TryGetValue(myObjectId, out instance))
                {
                    instance = new MyObject(myObjectId); // call private constructor
                    _instances.Add(myObjectId, instance);
                }
            }
        }
        return instance;
    }
}
Now you can access properties of the MyObject with ID “123” like this:

MyObject obj = MyObject.GetInstance(“123”); // Identifier "123" is used for the filename (minus extension) by the m-code to load data
Double foo = obj[MyObject.DoublePropertyType.KEY_FOO]; // access a Double property

Matrices can be accessed in C# as follows:

public DataPoint[] GetAverage(int averageIndex)
{
    if (_xAverage == null)
        return new DataPoint[0];

    averageIndex++; // MATLAB index starts at 1
    int numberOfSamples = _xAverage.Dimensions[1];

    DataPoint[] points = new DataPoint[numberOfSamples];

    for (int j = 1; j <= numberOfSamples; j++)
    {
        points[j - 1] = new DataPoint((double)_xAverage[averageIndex, j], (double)_yAverage[averageIndex, j]);
    }

    return points;
}
Once you have wrapped all input and output functions in your m-code for C#, you can build a GUI using any .NET control libraries you want. Here is an example of a DevExpress-based GUI we built at ChasmX:

EZSLam_screenshot

 

For completeness’ sake, here are the enum helper classes used in the above code snippets.

public class StringValue: System.Attribute
{
   private string _value;
   public StringValue(string value)
   {
       _value = value;
   }

   public string Value
   {
       get { return _value; }
   }
}

public static class StringEnum
{
     public static string GetStringValue(Enum value)
     {
         string output = null;
         Type type = value.GetType();

         FieldInfo fi = type.GetField(value.ToString());
         StringValue[] attrs = fi.GetCustomAttributes(typeof (StringValue), false) as StringValue[];
         if (attrs.Length > 0)
         {
             output = attrs[0].Value;
         }
         return output;
     }

    public static object Parse(Type type, string stringValue, bool ignoreCase)
    {
        object output = null;
        string enumStringValue = null;

        if (! type.IsEnum)
             throw new ArgumentException(String.Format("Type must be an Enum, was {0}", type.ToString()));

         foreach (FieldInfo fi in type.GetFields())
         {
             StringValue[] attrs = fi.GetCustomAttributes(typeof (StringValue), false) as StringValue[];
             if (attrs.Length > 0)
                 enumStringValue = attrs[0].Value;
             if (string.Compare(enumStringValue, stringValue, ignoreCase) == 0)
             {
                 output = Enum.Parse(type, fi.Name);
                 break;
             }
         }

         return output;
     }
}

About

In my 15-year career, I have been involved in most aspects of developing, productizing and supporting software. I started out on the very technical side of I.T. development, writing C software for a machine vision company. I since have successfully crossed the technology chasm as a requirements engineer and later principal product manager for EMC.
Direct contact with customers and end users have made me keenly aware of the importance of balance between the business and the I.T. development side of things.

I grew up in Europe but now enjoy living close to the Rocky Mountains with my wife and three kids. I like to grew vegetables in my back yard and brew my own beer and wine; in winter you can find me in Jasper on the Marmot basin ski hill.

I sometimes try to help fellow programmers on StackExchange and blog about technical and practical issues around software development here and here.

6 Comments »

  1. Hello Arne,

    Great information, and I was looking for something like that a while ago..

    Thanks for sharing,
    Sami

    p.s.
    sorry, but not to be picky,
    there is a misspell in the “function CreateMyObject)” I think it should be as follows ” function Create(MyObject)”, where a “(” was missing.

    Comment by Sami Oweis — February 4, 2013 @ 12:46 pm

  2. Hey Sami,

    I’m glad you found it useful! Thanks for pointing out the syntax error, it should be fixed now.

    Arne

    Comment by Arne Joris — February 6, 2013 @ 2:52 pm

  3. Hi,

    I am facing a problem while communicating with matlab compiled project in c#. I have named the matlab project as “Sequence” and build it as .Net Assembly. After that when I am trying to access Sequence.abc() it gives me error ate Sequence and says that I am missing object Reference. I am also adding the dlls (generated by matlab compiler) into the reference of c’ project.

    Can you please help me pointing out whats wrong I am doing?

    Thanks

    Comment by SASIF — June 12, 2013 @ 6:42 am

  4. Hello,
    I’m searching my solution for problem.
    and than i find this site and you.
    I’coding C# with matlab compiler NE Tool. but My code has the error about mkl.dll.
    but my code is complete building and no error. i don’t know this situation.
    Details,
    when debugging and connecting to matlab functions in added class to making code, generate pop up windows about mkl.dll.
    and this pop up present “Can’t create the specifed module.”
    I’m so so sad… I’m crying soon.
    please help my problem.
    plz help me…ㅠㅠ

    Comment by Jungwoo — June 17, 2013 @ 7:01 am

  5. Hi,
    Great code thanks to share this.
    I would like to know if it is possible to use Axes with cursor data mode in a c# application.
    Thanks you

    Comment by sdecorme — October 3, 2013 @ 12:40 am

  6. Hey sdecorme, rather than trying to use the MATLAB data cursor and trying to interact with it through the MATLAB API, we have used native C# graph controls that offer the same functionality, ie. you can click on any point or line in the graph and a tooltip pops up with more details about the data point. See this devexpress graph control for example.

    Comment by Arne Joris — October 6, 2013 @ 11:59 am

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress

Switch to our mobile site