How to update app designer GUI in real time at high refresh rate without stutter/lag?

41 views (last 30 days)
I'm currently working on an application that simulates a medical monitor. I designed a GUI in matlab's app designer which 1) shows the 6 graphs/signals and 2) provides the controls to adjust those signals. A few simple functions have been implemented to simulate e.g. an ECG signal. I use a timer object within the app to call a function that 1) refreshes the current signal values and 2) plots the new signals in the GUI axes. It is currently working at a refresh rate of 20 Hz (way too low) and a physiological model to be used to replace the current 'simple functions' is yet to be implemented.
However, I'm already running into a problem that the application becomes too slow, shortly after starting up. The timer seems unable to maintain the desired refresh rate, resulting in stuttering or 'lag' in the displaying of the signals. The controls become slow to respond as well or even stop responding altogether.
The problem is so bad I'm currently debating rewriting everything in another language (maybe Java or C++), even though I have virtually no experience with those.
If anyone has an idea of how to solve this, I'd be very grateful to hear it.
To give an idea of what the (simplified) code for the primary function looks like:
function Timer = timed_dash(app)
% Function to create and start the main timer needed for real-time simulation
Timer = timer('ExecutionMode','fixedRate','Period',app.period);
Timer.TimerFcn = {@app.myfun};
% <snip>...lots of one time variable creation...<snip>
start(Timer);
end
function myfun(app, ~, ~)
% Main Function called by timer to run / plot everything
% Is ECG connected?
if app.ConnectECGButton.Value % Yes
app.ECGplot(app.shortcounter)=func_ECG(app.t(app.longcounter));
app.HRplot(app.longcounter)=app.HR+random('Normal', 0,.3);%+2*sin(app.t(app.longcounter)*pi);
app.RFplot(app.longcounter)=app.RF+2*random('Normal', 0,.1);
app.Respplot(app.medcounter)=.6*sin(2*pi*app.RF/60*app.t(app.longcounter)); % temporary demo func
% Adjust current value for numeric display of monitor data
app.RFText.Value = sprintf('%d',round(app.RFplot(app.longcounter)));
app.HRText.Value = sprintf('%d',round(app.HRplot(app.longcounter)));
else % No
app.ECGplot(app.shortcounter)=0;
app.Respplot(app.medcounter)=0;
app.HRplot(app.longcounter)=0;
app.RFplot(app.longcounter)=0;
% Adjust current value for numeric display of monitor data
app.RFText.Value = '0';
app.HRText.Value = '0';
end
if app.loopshortcounter
plot(app.ECGAxes,app.t(1:app.shortcounter),app.ECGplot(1:app.shortcounter),'-g','LineWidth',2)
plot(app.PlethAxes,app.t(1:app.shortcounter),app.Plethplot(1:app.shortcounter),'-c','LineWidth',2)
else
plot(app.ECGAxes,app.t(1:app.shortcounter),app.ECGplot(1:app.shortcounter), '-g',app.t(app.shortcounter+3:app.endshortcounter+3),app.ECGplot(app.shortcounter+3:end),'-g','LineWidth',2)
plot(app.PlethAxes,app.t(1:app.shortcounter),app.Plethplot(1:app.shortcounter),'-c',app.t(app.shortcounter+3:app.endshortcounter+3),app.Plethplot(app.shortcounter+3:end),'-c','LineWidth',2)
end
if app.loopmedcounter % Resp loop
plot(app.RespAxes,app.t(1:app.medcounter),app.Respplot(1:app.medcounter),'-y','LineWidth',2)
else
plot(app.RespAxes,app.t(1:app.medcounter),app.Respplot(1:app.medcounter),'-y',app.t(app.medcounter+3:app.endmedcounter+3),app.Respplot(app.medcounter+3:end),'-y','LineWidth',2)
end
if app.looplongcounter
plot(app.RFAxes,app.t(1:app.longcounter)./60-10,app.RFplot(1:app.longcounter),'-y','LineWidth', 2)
plot(app.SpO2Axes,app.t(1:app.longcounter)./60-10,app.SpO2plot(1:app.longcounter),'-c','LineWidth', 2)
plot(app.HRAxes,app.t(1:app.longcounter)./60-10,app.HRplot(1:app.longcounter),'-g','LineWidth', 2)
else
plot(app.RFAxes,app.t(1:app.longcounter)./60-10,app.RFplot(1:app.longcounter), '-y',app.t(app.longcounter+3:end),app.RFplot(app.longcounter+3:end),'-c', 'LineWidth',2)
plot(app.SpO2Axes,app.t(1:app.longcounter)./60-10,app.SpO2plot(1:app.longcounter), '-c',app.t(app.longcounter+3:end),app.SpO2plot(app.longcounter+3:end),'-y', 'LineWidth',2)
plot(app.HRAxes,app.t(1:app.longcounter)./60-10,app.HRplot(1:app.longcounter), '-g',app.t(app.longcounter+3:end),app.HRplot(app.longcounter+3:end),'-c', 'LineWidth',2)
end
% Update counters & reset when necessary.
if app.shortcounter==app.endshortcounter
app.shortcounter=1;
if app.loopshortcounter
app.loopshortcounter=false;
end
else
app.shortcounter=app.shortcounter+1;
end
if app.medcounter==app.endmedcounter
app.medcounter=1;
if app.loopmedcounter
app.loopmedcounter=false;
end
else
app.medcounter=app.medcounter+1;
end
if app.longcounter==app.endlongcounter
app.longcounter=1;
if app.looplongcounter
app.looplongcounter=false;
end
else
app.longcounter=app.longcounter+1;
end
end

Answers (1)

Jan
Jan on 31 Aug 2017
Edited: Jan on 31 Aug 2017
Do I see correctly that you create a new set of plot's in each timer callback? This is rather inefficient. Prefer to create the line object once and update the XData and YData afterwards.
Search for "XData" or "animation" in this forum or in the net to see many examples.
  3 Comments
Camile van der Heijden
Camile van der Heijden on 22 Feb 2018
Some other optimizations I'm using: - Instead of using 2 line objects per plot to create a gap, I'm instead using a (moving) NaN value to get the same effect. - I'm using interp1 tot resample to fewer points as much as possible. - I've also found standard figure/GUIDE GUI's to be much more efficient. Updating a figure is much faster that updating a uifigure due to the latter employing a HTML/Javascript layer (Matlab support confirmed this to me).
As for myself, I'm going to try implementing the 'animatedline' function to see if that will solve my problems. I thought I would be able to keep my 'old' data on screen until overwritten, but it seems this happens automatically using 'animatedline'.

Sign in to comment.

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!