Jonathan Lam

Core Developer @ Hudson River Trading


Blog

Interfacing with MATLAB

On 4/19/2021, 1:02:16 PM

Return to blog


(This is in preparation for my upcoming MATLAB seminar lecture, because the next prepared lecture doesn't follow the schedule of the corequisite class.)

I've never enjoyed having to open the MATLAB IDE in order to be able to run A MATLAB script. Even a warm start on a SSD takes over a dozen seconds for the UI to open and become usable (there's an initial few-second delay between the UI becoming visible and when it becomes responsive to UI events and running MATLAB commands). At least a command-line interface to start off batch scripts would be useful, and ideally a language SDK. Luckily MATLAB has these tools, but it doesn't seem as though they're widely advertised. (I was previously unaware of most of the following topics until I had to search for topics to cover for the seminar.)

This post will cover a few ways to handle external data from MATLAB, and call MATLAB scripts externally.

File I/O (from MATLAB)

This topic doesn't entirely follow the theme here, but it'll be included for completeness of external MATLAB interfaces. This is a common task and should be covered within a general MATLAB tutorial.

From within MATLAB we often need to read files containing some data (whether a standard image/audio/video data format, or MATLAB's internal memory dump format), and write out the signal to a file.

Luckily, MATLAB makes it fairly easy to read and write a good range of common data formats without any client-side frustration dealing with compression or encoding methods. Commonly, you'll be dealing with audio and images, for which the following methods will be helpful1:

% audio represented by a 1-D string of samples
[Y, Fs] = audioread(filename);
audiowrite(filename, Y, Fs);

% images represented by a 3-D array (H x W x channels)
A = imread(filename);
imwrite(A, filename);

Of course, there are many other options to play around with that you can explore with help fnname. For example, you can use remote URIs in addition to local paths!

(In my experience, you're less prone to having any issues if you use "simpler" formats such as .wav for audio files and .tiff or .bmp (raw, uncompressed bitmaps) for image files, as I've sometimes had issues with color channels on other image types without fiddling with color options. YMMV.)

For arbitrary variables that you want to save to a file, you can use the save and load commands. For example:

%% saves workspace to dataFile.mat
clear;
A = 4;
B = 6;
save dataFile;

%% restores workspace from dataFile.mat
clear;
load dataFile;

The output *.mat file is an opaque binary data format much like Python's pickle or Go's gob output formats.

Running a MATLAB file as a script

A few months ago, I was delighted that I could run MATLAB from the command line2. This solution isn't perfect, but it allows you to run MATLAB work without opening the IDE.

# full MATLAB engine in terminal
matlab -nodisplay

# MATLAB with reduced feature set (e.g., no figures)
matlab -nodisplay -nojvm

# run a command
matlab -nodisplay -r "display(2+3);quit;"

This approach isn't perfect: (afaik) there's no way to turn off the MATLAB text greeting message nor the prompt, which makes it a pretty poor language to script with directly. And it takes almost as much time as booting the GUI without the -nojvm option. Note that without with -nodisplay set but -nojvm not set, you can still create figures and save them to files, but they will not show as windows; setting the -nojvm option disables the ability to work with figures.

If you want to run a MATLAB (*.m) file or call a shell command, the MATLAB commands are the same as what you'd do from the command window:

% run a MATLAB file
run('path/to/matlab/file.m')

% call a shell script
system('whoami');

% call a shell script, save stdout to variable
output = strip(evalc('system("whoami");'));

MATLAB from the terminal has been personally useful for me when I've been working on a MATLAB script in a group using VSCode live share, so that anyone can run the MATLAB script from the command line. But other than obscure use cases like this, anything more complex than launching MATLAB scripts from the shell or vice versa is pretty awkward, I/O-wise.

For more robust scripting, that's where the MATLAB Engine API come into play.

MATLAB Engine API

TODO: haven't learnt this yet

Building apps using appdesigner

TODO: transition

MATLAB has "Apps," which "typically consist of a graphical user interface, code that performs the underlying actions, associated data, and any other supporting files," according to the documentation. An example widely used by us EE majors is the Filter Designer app (filterDesigner) from the Signal Processing Toolkit. Apps are basically nicely-packaged GUI applications3.

With only a few lines of handwritten code, I was able to develop the following MATLAB app:

Screenshot of simple designed MATLAB app

I won't go into the details, because the tutorial covers it well, and because the app designer generates most of the code anyways. All I had to do was run appdesigner to start the App Designer wizard, choose the "2-Panel App with Auto-Reflow" layout option, add the two sliders and drawing axis, and add callbacks for each slider that update the plot.

The entire generated MATLAB source is as follows

classdef line_exported < matlab.apps.AppBase

    % Properties that correspond to app components
    properties (Access = public)
	UIFigure      matlab.ui.Figure
	GridLayout    matlab.ui.container.GridLayout
	LeftPanel     matlab.ui.container.Panel
	bSlider       matlab.ui.control.Slider
	bSliderLabel  matlab.ui.control.Label
	mSlider       matlab.ui.control.Slider
	mSliderLabel  matlab.ui.control.Label
	RightPanel    matlab.ui.container.Panel
	UIAxes        matlab.ui.control.UIAxes
    end

    % Properties that correspond to apps with auto-reflow
    properties (Access = private)
	onePanelWidth = 576;
    end


    properties (Access = private)
	m = 0;  % slope
	b = 0;  % y-intercept
    end

    methods (Access = private)

	function results = plotLine(app)
	    x = -10:10;
	    y = app.m*x + app.b;

	    plot(app.UIAxes, x, y);
	end
    end


    % Callbacks that handle component events
    methods (Access = private)

	% Code that executes after component creation
	function startupFcn(app)
	    app.plotLine();
	end

	% Value changing function: bSlider
	function bSliderValueChanging(app, event)
	    changingValue = event.Value;
	    app.b = changingValue;

	    app.plotLine();
	end

	% Value changing function: mSlider
	function mSliderValueChanging(app, event)
	    changingValue = event.Value;
	    app.m = changingValue;

	    app.plotLine();
	end

	% Changes arrangement of the app based on UIFigure width
	function updateAppLayout(app, event)
	    currentFigureWidth = app.UIFigure.Position(3);
	    if(currentFigureWidth <= app.onePanelWidth)
		% Change to a 2x1 grid
		app.GridLayout.RowHeight = {480, 480};
		app.GridLayout.ColumnWidth = {'1x'};
		app.RightPanel.Layout.Row = 2;
		app.RightPanel.Layout.Column = 1;
	    else
		% Change to a 1x2 grid
		app.GridLayout.RowHeight = {'1x'};
		app.GridLayout.ColumnWidth = {221, '1x'};
		app.RightPanel.Layout.Row = 1;
		app.RightPanel.Layout.Column = 2;
	    end
	end
    end

    % Component initialization
    methods (Access = private)

	% Create UIFigure and components
	function createComponents(app)

	    % Create UIFigure and hide until all components are created
	    app.UIFigure = uifigure('Visible', 'off');
	    app.UIFigure.AutoResizeChildren = 'off';
	    app.UIFigure.Position = [100 100 640 480];
	    app.UIFigure.Name = 'MATLAB App';
	    app.UIFigure.SizeChangedFcn = createCallbackFcn(app, @updateAppLayout, true);

	    % Create GridLayout
	    app.GridLayout = uigridlayout(app.UIFigure);
	    app.GridLayout.ColumnWidth = {221, '1x'};
	    app.GridLayout.RowHeight = {'1x'};
	    app.GridLayout.ColumnSpacing = 0;
	    app.GridLayout.RowSpacing = 0;
	    app.GridLayout.Padding = [0 0 0 0];
	    app.GridLayout.Scrollable = 'on';

	    % Create LeftPanel
	    app.LeftPanel = uipanel(app.GridLayout);
	    app.LeftPanel.Layout.Row = 1;
	    app.LeftPanel.Layout.Column = 1;

	    % Create mSliderLabel
	    app.mSliderLabel = uilabel(app.LeftPanel);
	    app.mSliderLabel.HorizontalAlignment = 'right';
	    app.mSliderLabel.Position = [11 457 25 22];
	    app.mSliderLabel.Text = 'm';

	    % Create mSlider
	    app.mSlider = uislider(app.LeftPanel);
	    app.mSlider.Limits = [-10 10];
	    app.mSlider.ValueChangingFcn = createCallbackFcn(app, @mSliderValueChanging, true);
	    app.mSlider.Position = [57 466 150 3];

	    % Create bSliderLabel
	    app.bSliderLabel = uilabel(app.LeftPanel);
	    app.bSliderLabel.HorizontalAlignment = 'right';
	    app.bSliderLabel.Position = [12 415 25 22];
	    app.bSliderLabel.Text = 'b';

	    % Create bSlider
	    app.bSlider = uislider(app.LeftPanel);
	    app.bSlider.Limits = [-10 10];
	    app.bSlider.ValueChangingFcn = createCallbackFcn(app, @bSliderValueChanging, true);
	    app.bSlider.Position = [58 424 150 3];

	    % Create RightPanel
	    app.RightPanel = uipanel(app.GridLayout);
	    app.RightPanel.Layout.Row = 1;
	    app.RightPanel.Layout.Column = 2;

	    % Create UIAxes
	    app.UIAxes = uiaxes(app.RightPanel);
	    title(app.UIAxes, 'y=mx+b')
	    xlabel(app.UIAxes, 'X')
	    ylabel(app.UIAxes, 'Y')
	    zlabel(app.UIAxes, 'Z')
	    app.UIAxes.XLim = [-10 10];
	    app.UIAxes.YLim = [-10 10];
	    app.UIAxes.XGrid = 'on';
	    app.UIAxes.YGrid = 'on';
	    app.UIAxes.Position = [3 4 413 473];

	    % Show the figure after all components are created
	    app.UIFigure.Visible = 'on';
	end
    end

    % App creation and deletion
    methods (Access = public)

	% Construct app
	function app = line_exported

	    % Create UIFigure and components
	    createComponents(app)

	    % Register the app with App Designer
	    registerApp(app, app.UIFigure)

	    % Execute the startup function
	    runStartupFcn(app, @startupFcn)

	    if nargout == 0
		clear app
	    end
	end

	% Code that executes before app deletion
	function delete(app)

	    % Delete UIFigure when app is deleted
	    delete(app.UIFigure)
	end
    end
end

Once you're done creating the app, there's a few things you can do. You can export it to a *.m file, so that you can launch it from MATLAB just like calling a function (in this case, line_exported()). In the App Designer menubar, there are three additional options under "Share":

The first option generates a *.mlappinstall file so that anyone with MATLAB can install the app using the Apps > Install App button. It runs just as if you had exported it to a .m file, and you also need to have the MATLAB IDE to run this option. (I'm not sure what is going on under the hood in this packaged version, or how it is any different than running the exported .m function.)

The other two options were much more intriguing to me, so I went and installed the required MATLAB Compiler toolbox. I tried compiling to a standalone desktop application. It turns out that this compiles to a standalone ELF binary, but it still requires special MATLAB dynamically-linked libraries called the MATLAB Compiler Runtime (MCR). The MCR can be downloaded for free from the MATLAB website (a hefty 3.7GB download). I suppose this means that you can run a MATLAB Compiler binary for free, even if developing it costs a MATLAB license.

Finally, now that we have the binary and the MCR, we just need to tell the linker where to find these MCR, because this will most likely not be in the standard location (e.g., /usr/lib), you will have to manually add the library to $LD_LIBRARY_PATH. In my case, this looks something like:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/MATLAB/MATLAB_Runtime/v910/runtime/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/v910/bin/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/v910/sys/os/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/v910/sys/opengl/lib/glnxa64

At this point, I could run the binary like any other Linux executable. I did not try out the web app option, but its name is self-explanatory.

Overall, I think it's a cool idea and I love the power of having the ease of MATLAB development and the niceties of a native app. Unfortunately, it seems like there's still some way to go with performance and bugginess -- the UI updates are extremely slow, sometimes lagging for up to half a minute on the simple tutorial app for unknown reasons. The boot time is also as atrocious as that of the regular MATLAB IDE, which makes sense but is aggravating. Sometimes the UIAxes glitch and only show a black interface, so that restarting the app is the only remedy.

I did have a talk with my manager at MathWorks and it seems like they're really pushing these apps, so I'm excited to see if I can help with some of these problems!


Footnotes

1. Using prismjs as a syntax highlighter. Historically, I've used highlight.js a lot, but it seems that this one is also pretty good.

2. Another step up is to use a separate editor for your MATLAB files as well! You can change the default text editor in MATLAB from Home > Preferences > Editor/Debugger to the editor of your choice, e.g., ViM. Or you can simply edit the MATLAB file in an editor of your choice, and MATLAB will auto-sync the file when you return to the MATLAB IDE.

3. Another cool thing I've noticed is that the apps and the App Designer work in the online version of MATLAB!


© Copyright 2023 Jonathan Lam