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 his 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.

2 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

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress

Switch to our mobile site