Slowing Speed with each for loop iteration in matlab - matlab

I have written a while loop in Matlab that is supposed to send each value in an array from Matlab to arduino at a specified time interval using a tic toc delay in Matlab and then read values and store them in a variable and graph them.
The output of the while-loop slows down with each successive iteration.
I increased the buffersize which helped it a lot, but it still slows down too much. Is there another way to increase the speed to print the values on time. I have included another tic toc and graph to show the execution speed here is the code:
max = 80;
min = 40;
amp = (max-min)/2;
offset = amp + min;
btime = 5;
bpm = 12;
spb = 60/bpm;
sapb = spb/.05;
tosd = sapb*bpm*btime;
time1 = btime*60;
x = linspace(0,time1,tosd)';
x1 = amp*sin(x*(2*pi/20)) + offset;
pause(1);
fprintf(handles.UltraM,(['<P' num2str(offset) '>']))
pause(5);
y = [];
i = 1;
figure(1);
hold on;
title('Pressure Data');
xlabel('Data Number');
ylabel('Analog Voltage (0-1023)');
t1 = [];
figure(2);
hold on;
title('Time to execute task');
xlabel('iteration number');
ylabel('time taken');
while (i<=length(x))
t2 = tic;
t = tic;
fprintf(handles.UltraM,(['<P' num2str(x1(i)) '>']));
%disp((['<P' num2str(x1(i)) '>']));
y(i) = fscanf(handles.UltraM,'%d');
figure(1);
hold on;
plot(i, y(i), 'b*');
drawnow;
hold off;
while toc(t) < 0.05
continue
end
t1(i) = toc(t2);
figure(2);
hold on;
plot(i,t1(i),'b*');
drawnow;
hold off;
i = i + 1;
end

After a bit of back and forth I think I know what you're trying to achieve and what stands in your way.
I have edited your code to make it slightly more fast and readable. Most of the time the operations take just slightly above 0.05 seconds, and at several time points it can take about 5 milliseconds longer than expected. Your millage may vary of course. Since I do not have an arduino, I cannot know if there is a bottle neck there. You should also try profiling your code using the builtin Matlab profiler (it's very useful) to see what exactly slows your code down.
The main thing I found to slow your code is that you used the plot function to add one point at a time to your figure. Each time you call this function, it creates a new graphics object. After a few hundred of those, things get sluggish. Instead you should simply update the already plotted data and redraw it using drawnow.
In short, the solution is this:
1) Initialize you plot with a single point and save the graphics handle for later use:
p1 = plot(0,0,'b*');
2) Then, inside the loop, once your data arrays have been updated, replace the data in your existing plot with the new arrays.
set(p1, 'XData', 1:i, 'YData', y(1:i));
3) Redraw the plots to reflect the latest update.
drawnow;
drawnow will eventually slow down your code also since it has to redraw increasingly large plots at every iteration. To make things work faster, you might want to refresh your plot after a longer interval. For example, the following will refresh every 10 iterations:
if rem(i,10) == 0
drawnow;
end
Full code below. Let me know if you have any more issues.
max = 80;
min = 40;
amp = (max-min)/2;
offset = amp + min;
btime = 5;
bpm = 12;
spb = 60/bpm;
sapb = spb/.05;
tosd = sapb*bpm*btime;
time1 = btime*60;
x = linspace(0,time1,tosd)';
x1 = amp*sin(x*(2*pi/20)) + offset;
pause(1);
%fprintf(handles.UltraM,(['<P' num2str(offset) '>']))
disp(['<P' num2str(offset) '>']); % replacing with disp (I don't have an arduino)
pause(5);
%y = []; % unnecessary here, preallocated before loop
figure(1);
p1 = plot(0,0,'b*'); % plotting one dot to create an object, data will be overwritten
hold on;
title('Pressure Data');
xlabel('Data Number');
ylabel('Analog Voltage (0-1023)');
%t1 = []; % unnecessary here, preallocated before loop
figure(2);
p2 = plot(0,0,'b*'); % plotting one dot to create an object, data will be overwritten
hold on;
title('Time to execute task');
xlabel('iteration number');
ylabel('time taken');
% preallocate t1 and y arrays for faster operation
t1 = zeros(size(x));
y = zeros(size(x));
i = 1; % moved closer to loop beginning for better readability
while i <= length(x) % parentheses unnecessary in Matlab
t2 = tic;
t = tic;
%fprintf(handles.UltraM,(['<P' num2str(x1(i)) '>']));
disp((['<P' num2str(x1(i)) '>'])); % replacing with disp (I don't have an arduino)
%y(i) = fscanf(handles.UltraM,'%d');
y(i) = randn; % replacing with random number (I don't have an arduino)
%figure(1); % unnecessary
%hold on; % unnecessary
%plot(i, y(i), 'b*');
% replacing the above with a slightly faster version
set(p1, 'XData', 1:i, 'YData', y(1:i));
%drawnow; % first one is annecessary
%hold off; % unnecessary
while toc(t) < 0.05
continue
end
t1(i) = toc(t2);
%figure(2); % unnecessary
%hold on; % unnecessary
%plot(i,t1(i),'b*');
% replacing the above with a slightly faster version
set(p2, 'XData', 1:i, 'YData', t1(1:i));
if rem(i,10) == 0 % refreshing every 10 iterations
drawnow;
end
%hold off; % unnecessary
i = i + 1;
end
ANSWER TO PREVIOUS VERSION OF QUESTION
You can vectorize your loop by replacing it entirely with the following two statements:
% vectorizing num-to-string conversion
y4 = cellstr(strcat('<P',num2str(x1), '>'));
% deleting all spaces
y4 = cellfun(#(u) u(~isspace(u)), y4, 'UniformOutput', false)
This small adjustment makes your program run x4 faster on my PC.
Displaying/printing the results could also be done using the cellfun iterator: cellfun(#disp, y4)

Related

Real time NI-DAQ data plot in Matlab

I'm trying to get a real time plot of data I'm acquiring with a NI USB-6008. I tried doing the same with arduino and got a plot exactly as I wanted (see here https://i.stack.imgur.com/08kzU.jpg), and the x-axis would move in real-time, but I couldn't define the sampling rate. With NI I was able to define the sampling rate I wanted but I can't display the data in a continuous, real-time plot, I can only see 1 sec at a time and I need to be able to have access to all the data acquired since I want to measure a real-time EEG. I'm a new matlab user, so please consider no previous knowledge.
This is the code I've got so far:
clear
close all
dq = daq("ni");
ch1 = addinput(dq, "Dev1", "ai0", "Voltage");
dq.Rate = 1000;
dq.ScansAvailableFcn = #(src,evt) plotDataAvailable(src, evt);
dq.ScansAvailableFcnCount = 100;
start(dq, "Duration", seconds(5))
while dq.Running
pause(0.5);
end
time = 0;
data = 0;
n = ceil(dq.Rate/10);
%Set up Plot
figure(1)
plotGraph = plot(time,data);
title('DAQ data log','FontSize',15);
xlabel ('Elapsed Time (s)','FontSize',10); ylabel('Voltage (V)','FontSize',10);
h = animatedline;
ax = gca;
ax.YGrid = 'on';
ax.XGrid = 'on';
h = animatedline;
function plotDataAvailable(src, ~)
while ishandle(plotGraph) % Loop when Plot is Active will run until plot is closed
data = read(dq,n);
t = datetime('now');
% Add points to animation
addpoints(h,datenum(t),data)
% Update axes
ax.XLim = datenum([t-seconds(15) t]);
datetick('x','keeplimits')
drawnow
end
end
This was my previous arduino code that showed me the plot I needed (with the wrong sampling rate):
clear
clc
%User Defined Properties
a = arduino('com4','uno'); % Define the Arduino Communication port
plotTitle = 'Arduino Data Log'; % Plot title
%Define Function Variables
time = 0;
data = 0;
%Set up Plot
figure(1)
plotGraph = plot(time,data,'-r' );
title(plotTitle,'FontSize',15);
xlabel ('Elapsed Time (s)','FontSize',10); ylabel('Voltage (V)','FontSize',10);
h = animatedline;
ax = gca;
ax.YGrid = 'on';
ax.XGrid = 'on';
ax.YLim = [0 5]; % Sets y-min and y-max
while ishandle(plotGraph) % Loop when Plot is Active will run until plot is closed
startIteration = tic;
voltagem = readVoltage(a,'A0')
t = datetime('now');
% Add points to animation
addpoints(h,datenum(t),voltagem)
% Update axes
ax.XLim = datenum([t-seconds(15) t]);
datetick('x','keeplimits')
drawnow
end
I've tried using this while loop on my NI data but it doesn't work.
I would really appreciate your help.
You've got the order of the operations wrong. What your code is doing at the moment is:
initialize the data acquisition
read the data from the hardware while trying to add data to lines that don't exist
initialize the animated lines.
Furthermore, in your addpoints call you're trying to plot t, which is a scalar, and data, which is an array.
What you should do instead is:
clear
close all
% Set up the plot
figure(1)
title('DAQ data log','FontSize',15);
xlabel ('Elapsed Time (s)','FontSize',10)
ylabel('Voltage (V)','FontSize',10);
h = animatedline;
ax = gca;
ax.YGrid = 'on';
ax.XGrid = 'on';
% Set up the data acquisition
dq = daq("ni");
ch1 = addinput(dq, "Dev1", "ai0", "Voltage");
dq.Rate = 1000;
dq.ScansAvailableFcn = {#plotDataAvailable, h, ax}; % pass also the handle to the line and to the axes
dq.ScansAvailableFcnCount = 100;
% Now you start the data acquisition
start(dq, "Duration", seconds(5))
function plotDataAvailable(src, evt, h, ax)
data = read(dq, n);
% maybe your time array should look something like this?
t = datetime(now) - seconds((length(data)-1:-1:0)./src.Rate);
% Add points to animated line
addpoints(h, datenum(t), data)
% Update axes
% ax.XLim = datenum([t-seconds(15) t]); % this line is useless, why would you have a 15s window when your data acquisition is only 5s long?
datetick('x','keeplimits')
drawnow
end
I can't try this at the moment, so it might not work exactly.
So I think I finally got it, because I got the plot like I wanted, with the x-axis moving with real-time. However I still get an error saying "Unrecognized table variable name 'Dev1_ai0'" event tho I checked that's the name of the row that I want. Anyways, this is what my code looks now.
clear
close all
time = 0;
data = 0;
% Set up the plot
figure(1)
plotGraph = plot(time,data,'-r' );
title('DAQ data log','FontSize',15);
xlabel ('Elapsed Time (s)','FontSize',10)
ylabel('Voltage (V)','FontSize',10);
h = animatedline;
ax = gca;
ax.YGrid = 'on';
ax.XGrid = 'on';
% Set up the data acquisition
dq = daq("ni");
ch1 = addinput(dq, "Dev1", "ai0", "Voltage");
dq.Rate = 1000;
% Start the data acquisition
start(dq, "Duration", seconds(10))
n = ceil(dq.Rate/10);
while ishandle(plotGraph)
data = read(dq, n);
voltage = data.Dev1_ai0;
t = datetime('now');
for i = 1:100
% Add points to animated line
if isvalid(h)
addpoints(h, datenum(t), voltage(i))
end
end
% Update axes
ax.XLim = datenum([t-seconds(15) t]);
datetick('x','keeplimits')
drawnow
end
disp('Plot Closed')

How to make video in MATLAB from continous plots

This is part of the code the makes the video:
k = 10000;
j = 1;
v = VideoWriter('myVideo.avi');
open(v)
while j < k
axis([0 5 0 1000]);
plot(0:dr:R, u_sol_matrix(:,j))
frame = getframe(gcf);
writeVideo(v,frame);
j = j + 50;
% pause(0.01)
end
close(v)
Now, given a matrix u_sol_matrix, where every column represents a solution for a PDE at a certain time point, I plot the solutions and the by the getframe command capture these plots and make a movie out of it.
The problem is that the axis keeps changing as the plot keeps adjusting to the solution. I want the axis to be constant. How do I get this to work? I have tried adding axis but this does not work apparently.
k = 10000;
j = 1;
v = VideoWriter('myVideo.avi');
open(v)
while j < k
fig = figure(); % Explicitly create figure
plot(0:dr:R, u_sol_matrix(:,j))
axis([0 5 0 1000]); % first plot, then change axis
frame = getframe(gcf);
writeVideo(v,frame);
close(fig) % close figure explicitly.
j = j + 50;
% pause(0.01)
end
close(v)
Flipping the figure creation and setting axis limits should do the trick. When you call axis without an open figure, MATLAB creates one, only to overwrite it if you don't call hold on, thus changing the limits to whatever the plot "needs" to fit.
Like Adriaan answered, you just have to flip the order of plotting and setting axis limits to make this work.
However, when creating animations, it is faster to first initialize a figure and the graphics objects (i.e. lines, scatter points, etc.), and later update the data in a loop.
k = 10000;
j = 1;
v = VideoWriter('myVideo.avi');
open(v)
% some test data
x = 10;
y = sin(1:k);
% init a figure and plot handles
fig = figure(1);
p = plot(x, y(1), 'o'); % create line object, and store the handle
axis([9 11 -1 1]) % axis limits for test data
% update data during animation
while j < k
p.XData = x; % update X and Y data properties of line object
p.YData = y(j);
frame = getframe(gcf);
writeVideo(v,frame);
j = j + 50;
pause(0.01)
end
close(v)
Since you don't have to create a new line primitive each loop iteration, this will save a lot of time. You only have to adjust the X and Y data properties of the already existing line, which has considerable less overhead.

How to efficiently animate plots in MATLAB? [duplicate]

This question already has answers here:
How to do an animated plot in matlab
(3 answers)
Closed 4 years ago.
I wrote a simple code below for animating plots, but they tend to be rather computationally-intensive, taking entire seconds longer than intended:
function animplot(t,f,ymin,ymax,dt,num_iters)
h = plot(0,0); % set initial handle for first iteration
tic % start timer
for i=2:num_iters
delete(h);
h = plot(t,f(t-dt*i),'LineWidth',2,'color','b');
axis([min(t) max(t) ymin ymax]); pause(1/num_iters)
end
toc % end timer, return time elapsed since 'tic'
end
Replacing 1/num_iters with dT = T / num_iters, and setting T = 1, computation time for 1000 iterations is 6+ secs (rather than 1). Sample animation for t = 0:.01:2*pi; f = #(t)sin(t); dt = .05; num_iters = 1000
Any more efficient methods of animating in this manner?
A significantly more efficient code, adapted per solution in related inquiry:
function animplot(t,f,ymin,ymax,dt,num_iters,T)
% ANIMPLOT(t,f,ymin,ymax,dt,num_iters) -- f must be defined w/ handle,
% e.g. f = #(t)sin(t); default T = 5, num_iters = 500, dt = .05,
% (ymax - ymin) = 1.4*range.
switch nargin
case 6; T = 5;
case 5; T = 5; num_iters = 500;
case 4; T = 5; num_iters = 500; dt = .05;
case 2; T = 5; num_iters = 500; dt = .05;
ymin = 1.2*min(f(t)) - max(f(t))/5;
ymax = 1.2*max(f(t)) - min(f(t))/5;
end
dT = T/num_iters; % set pause interval
h = plot(0,0,'LineWidth',2,'color','b'); % set initial handle
set(h, 'Xdata',t, 'Ydata', f(t)); % initialize curve plot
axis([min(t) max(t) ymin ymax]);
tic % start timer
for i=2:num_iters
pause(dT)
set(h, 'Ydata', f(t-dt*i))
end
toc % end timer, return time elapsed since 'tic'
end
Note: num_iters serves primarily as animation 'resolution'; higher comes at expense of greater deviation from set T.
Using the command: "set(h, 'Xdata',t, 'Ydata', f(t))" will be way more efficient than reusing the plot function on every loop iteration, and may be sufficient by itself to achieve the results you are looking for. For further improvement you can enable a variable frame rate. To do this, eliminate any "pause(dt)", get the time of the current loop iteration "t", evaluate your function for that time "f(t)" and set the plot x and y data to that. This will ensure that the animation is smooth does not take any longer than specified. You can also use this technique on data sets, not just functions, by using the "interp1(x,v,xq)" function to get a linear interpolation between data points.

How to speed up a very slow animated plot in MATLAB

I'm trying to create an animated plot but my code is very slow, perhaps the method I'm using is too naive. In the below example, I have 4 subplots each with 3 lines, which I update in a 'time' loop.
clc;clear;close all;
state = {'$x-Position$','$x-Velocity$','$y-Position$','$y-Velocity$'};
ylabels = {'$x$','$\dot{x}$','$y$','$\dot{y}$'};
options1 = {'interpreter','latex'};
options2 = {'interpreter','latex','fontsize',20};
maxT = 300;
for pp = 1:4
hh1(pp)=subplot(2,2,pp);
xlabel('$t$',options2{:});
ylabel(ylabels{pp},options2{:});
title(state{pp},options1{:})
xlim([0 maxT])
hold on
end
x = randn(4,300);
z = randn(4,300);
x_est = randn(4,300);
for k = 2:maxT
for p = 1:4
plot(hh1(p),k-1:k,x(p,k-1:k),'b','linewidth',2)
plot(hh1(p),k-1:k,z(p,k-1:k),'m')
plot(hh1(p),k-1:k,x_est(p,k-1:k),':k','linewidth',2)
end
drawnow;
end
As can be seen from the profiler output, the drawnow is killing the time. Is there any way I can be more efficient in creating this animation?
Because you want an animation, there is no alternative to using drawnow to update the frame. However, it's not drawnow in particular which is slowing you down - the profiler can be misleading... drawnow simply updates all of the graphics changes since the last re-draw, which in your case is a dozen new plots!
You'll find that hold is pretty slowing. For instance if you're wiser about your holding, remove the existing hold on and only hold when actually plotting
% ... above code the same but without 'hold on'
for p = 1:4
hold(hh1(p), 'on');
% plots
hold(hh1(p), 'off');
end
This saves ~10% time on my PC (12.3sec down to 11.3sec).
The real speed up comes from removing hold entirely, along with all of the individual plot calls! This method also doesn't touch the line formatting which will help with speed. See a previous question about updating plot data here.
Simply update the plot data instead of adding plots. This gives me a speedup of ~68% (12.3sec down to 4.0sec).
% ... your same setup
% Initialise plot data
x = randn(4,300);
z = randn(4,300);
x_est = randn(4,300);
plts = cell(4,3);
hh1 = cell(4,1);
% Loop over subplots and initialise plot lines
for p = 1:4
hh1{p}=subplot(2,2,p);
xlabel('$t$',options2{:});
ylabel(ylabels{p},options2{:});
title(state{p},options1{:})
xlim([0 maxT])
% Hold on to make 3 plots. Create initial points and set line styles.
% Store the plots in a cell array for later reference.
hold on
plts{p,1} = plot(hh1{p},1:2,x(p,1:2),'b','linewidth',2);
plts{p,2} = plot(hh1{p},1:2,z(p,1:2),'m');
plts{p,3} = plot(hh1{p},1:2,x_est(p,1:2),':k','linewidth',2);
hold off
end
% March through time. No replotting required, just update XData and YData
for k = 2:maxT
for p = 1:4
set(plts{p,1}, 'XData', 1:k, 'YData', x(p,1:k) );
set(plts{p,2}, 'XData', 1:k, 'YData', z(p,1:k) );
set(plts{p,3}, 'XData', 1:k, 'YData', x_est(p,1:k) );
end
drawnow;
end
Now the plotting is pretty optimised. If you want the animation to be even quicker then just plot every 2nd, 3rd, ..., nth timestep instead of every timestep by using for k = 2:n:maxT.

Visualizing matrix values in real time

Suppose I have a 5x5 matrix.
The elements of the matrix change (are refreshed) every second.
I would like to be able to display the matrix (not as a colormap but with the actual values in a grid) in realtime and watch the values in it change as time progresses.
How would I go about doing so in MATLAB?
A combination of clc and disp is the easiest approach (as answered by Tim), here's a "prettier" approach you might fancy, depending on your needs. This is not going to be as quick, but you might find some benefits, such as not having to clear the command window or being able to colour-code and save the figs.
Using dispMatrixInFig (code at the bottom of this answer) you can view the matrix in a figure window (or unique figure windows) at each stage.
Example test code:
fig = figure;
% Loop 10 times, pausing for 1sec each loop, display matrix
for i=1:10
A = rand(5, 5);
dispMatrixInFig(A,fig)
pause(1)
end
Output for one iteration:
Commented function code:
function dispMatrixInFig(A, fig, strstyle, figname)
%% Given a figure "fig" and a matrix "A", the matrix is displayed in the
% figure. If no figure is supplied then a new one is created.
%
% strstyle is optional to specify the string display of each value, for
% details see SPRINTF. Default is 4d.p. Can set to default by passing '' or
% no argument.
%
% figname will appear in the title bar of the figure.
if nargin < 2
fig = figure;
else
clf(fig);
end
if nargin < 3 || strcmp(strstyle, '')
strstyle = '%3.4f';
end
if nargin < 4
figname = '';
end
% Get size of matrix
[m,n] = size(A);
% Turn axes off, set origin to top left
axis off;
axis ij;
set(fig,'DefaultTextFontName','courier', ...
'DefaultTextHorizontalAlignment','left', ...
'DefaultTextVerticalAlignment','bottom', ...
'DefaultTextClipping','on');
fig.Name = figname;
axis([1, m-1, 1, n]);
drawnow
tmp = text(.5,.5,'t');
% height and width of character
ext = get(tmp, 'Extent');
dy = ext(4);
wch = ext(3);
dwc = 2*wch;
dx = 8*wch + dwc;
% set matrix values to fig positions
x = 1;
for i = 1:n
y = 0.5 + dy/2;
for j = 1:m
y = y + 1;
text(x,y,sprintf(strstyle,A(j,i)));
end
x = x + dx;
end
% Tidy up display
axis([1-dwc/2 1+n*dx-dwc/2 1 m+1]);
set(gca, 'YTick', [], 'XTickLabel',[],'Visible','on');
set(gca,'XTick',(1-dwc/2):dx:x);
set(gca,'XGrid','on','GridLineStyle','-');
end
I would have thought you could achieve this with disp:
for i=1:10
A = rand(5, 5);
disp(A);
end
If you mean that you don't want repeated outputs on top of each other in the console, you could include a clc to clear the console before each disp call:
for i=1:10
A = rand(5, 5);
clc;
disp(A);
end
If you want to display your matrix on a figure it is quite easy. Just make a dump matrix and display it. Then use text function to display your matrix on the figure. For example
randMatrix=rand(5);
figure,imagesc(ones(20));axis image;
hold on;text(2,10,num2str(randMatrix))
If you want to do it in a for loop and see the numbers change, try this:
for i=1:100;
randMatrix=rand(5);
figure(1),clf
imagesc(ones(20));axis image;
hold on;text(2,10,num2str(randMatrix));
drawnow;
end