How to generate a video file using a series of plots on MATLAB? - matlab

I'm trying to stitch together a bunch of plots I created within a loop into a single video file. I've been at this for several hours, but had no luck. Here is my minimum working example where I attempt to use the VideoWriter function to create a video. I always get an error saying my frame(s) can't be copied into the video objects. Grr.
Here is my minimum working example:
n=(1:50)*2*pi;
for t = 1:1000
Y = sin(n*50/t);
plot(Y); %plot shows a sine wave with decreasing frequency
F(t) = getframe; %I capture the plot here
end
writerObj = VideoWriter('test2.avi'); %Attempt to create an avi
open(writerObj);
for t= 1:time
writeVideo(writerObj,F(t))
end
close(writerObj);

Matheburg answer is correct and identified the part which was causing the error (at some point the scale of your axis was resized, which cause the frame size to change).
His solution works fine and if the usage of fplot works for you then follow his way.
In case you still want to use the traditional plot (2d lineserie object) method, then here's how I usually organize "animated" plots:
The plot function is high level. It means when it runs it plots the data (obviously) but also does a lot of other things. In any case it generate a completely new plot (erasing previous plot if hold on wasn't specified), but also readjust the axes limits and many other settings (color, style etc ...).
If in your animation you only want to update the plot data (the points/line position) but not change any other settings (axes limits, colors etc ...), it is better to define the plots and it's settings one time only, outside of the loop, then in the loop you only update the YData of the plot object (and/or the XData if relevant).
This is done by retrieving the plot object handle when you create it, then use the set method (which unlike plot will only modify the parameters you specify explicitly, and won't modify anything else).
In your case it looks like this:
n=(1:50)*2*pi ;
Y = sin(n*50) ;
hp = plot(Y) ; %// Generate the initial plot (and retrieve the handle of the graphic object)
ylim([-1,1]) ; %// Set the Y axes limits (once and for all)
writerObj = VideoWriter('test2.avi'); %// initialize the VideoWriter object
open(writerObj) ;
for t = 1:1000
Y = sin(n*50/t) ; %// calculate new Y values
set(hp,'YData',Y) ; %// update the plot data (this does not generate a "new" plot), nor resize the axes
F = getframe ; %// Capture the frame
writeVideo(writerObj,F) %// add the frame to the movie
end
close(writerObj);
Also, this method will usually runs faster, and save a significant amount of time if your loop has a great number of iterations.
Side note: As said above, Matheburg solution runs also fine. For such an application, the difference of speed will not be a major issue, but note that the plots (and movie) generated are slightly different (due to it's use of fplot instead of plot). So I encourage you to try both versions and choose which one suits you best.

You are missing a constant height of images. You can guarantee it by, e.g., ylim:
time = 100;
for t = 1:time
fplot(#(x) sin(x*50/t),[0,2*pi]); % plot
ylim([-1,1]); % guarantee consistent height
F(t) = getframe; % capture it
end
writerObj = VideoWriter('test2.avi');
open(writerObj);
writeVideo(writerObj, F)
close(writerObj);
I have further replaced your discrete plot by a "continuous" one (using fplot).

Related

How to update a scatter3 plot (in a loop) in Matlab

Quite a simple question but just couldn't find the answer online... I want to visualise a point cloud gathered from a lidar. I can plot the individual frames but wanted to loop them to create a "animation". I know how to do it for normal plots with drawnow but can't get it working with a scatter3. If I simply call scatter3 again like I have done in the commented code then the frame that I am viewing in the scatter plot jumps around with every update (Very uncomfortable). How do i get the scatter3 plot to update to the new points without changing the UI of the scatter ie. Still be able to pan and zoom around the visualised point cloud while it loops through.
EDIT: The file is a rosbag file, I cannot attach it because it is 170MB. The problem doesn't happen when using scatter3 in a loop with a normal array seems to be something with using scatter3 to call a PointCloud2 type file using frame = readMessages(rawBag, i).
EDIT: The problem does not seem to be with the axis limits but rather with the view of the axis within the figure window. When the scatter is initialised it is viewed with the positive x to the right side, positive y out of the screen and positive z upwards, as shown in view 1. Then after a short while it jumps to the second view, where the axis have changed, positive x is now out of the screen, positive y to the right and positive z upwards (both views shown in figures). This makes it not possible to view in a loop as it is constantly switching. So basically how to update the plot without calling scatter3(pointCloudData)?
rawBag = rosbag('jackwalking.bag');
frame = readMessages(rawBag, 1);
scatter3(frame{1});
hold on
for i = 1:length(readMessages(rawBag))
disp(i)
frame = readMessages(rawBag, i);
% UPDATE the 3D Scatter %
% drawnow does not work?
% Currently using:
scatter3(frame{1})
pause(.01)
end
The trick is to not use functions such as scatter or plot in an animation, but instead modify the data in the plot that is already there. These functions always reset axes properties, which is why you see the view reset. When modifying the existing plot, the axes are not affected.
The function scatter3 (as do all plotting functions) returns a handle to the graphics object that renders the plot. In the case of scatter3, this handle has three properties of interest here: XData, YData, and ZData. You can update these properties to change the location of the points:
N = 100;
data = randn(N,3) * 40;
h = scatter3(data(:,1),data(:,2),data(:,3));
for ii = 1:500
data = data + randn(N,3);
set(h,'XData',data(:,1),'YData',data(:,2),'ZData',data(:,3));
drawnow
pause(1/5)
end
The new data can be totally different too, it doesn't even need to contain the same number of points.
But when modifying these three properties, you will see the XLim, YLim and ZLim properties of the axes change. That is, the axes will rescale to accommodate all the data. If you need to prevent this, set the axes' XLimMode, YLimMode and ZLimMode to 'manual':
set(gca,'XLimMode','manual','YLimMode','manual','ZLimMode','manual')
When manually setting the limits, the limit mode is always set to manual.
As far as I understood what you describe as "plots jumpying around", the reason for this are the automatically adjusted x,y,z limits of the scatter3 plot. You can change the XLimMode, YLimMode, ZLimMode behaviour to manual to force the axis to stay fixed. You have to provide initial axes limits, though.
% Mock data, since you haven't provided a data sample
x = randn(200,50);
y = randn(200,50);
z = randn(200,50);
% Plot first frame before loop
HS = scatter3(x(:,1), y(:,1), z(:,1));
hold on
% Provide initial axes limits (adjust to your data)
xlim([-5,5])
ylim([-5,5])
zlim([-5,5])
% Set 'LimModes' to 'manual' to prevent auto resaling of the plot
set(gca, 'XLimMode', 'manual', 'YLimMode', 'manual', 'ZLimMode', 'manual')
for i=2:len(x,2)
scatter3(x(:,i), y(:,i), z(:,i))
pause(1)
end
This yields an "animation" of plots, where you can pan and zoom into the data while continuous points are added in the loop

Freeze the axes size in matlab

I have images captured in many frames. All the frames have different sizes so I set the global maximum and minimum of each axes and used axis command like below:
h = figure;
axis([-8.4188e+03 -7.9061e+03 -102.6261 518.2502 -25.4673 0.2780])
%axis tight manual % this ensures that getframe() returns a consistent size
filename = 'testanim.gif';
But the animation boxes still keep changing the sizes although I have found the maximum and minimum limits of Xs, Ys and Zs. Do I need something more in my code?
This is the code I use for plotting:
% read the data
for k=1:length(FileNames)
FName = plyfiles(k).name;
plys=pcread(['Files\',FName]);
pcshow(plys) % Capture the plot as an image
frame = getframe(h);
im = frame2im(frame);
[imind,cm] = rgb2ind(im,256); % Write to the GIF File
if k == 1
imwrite(imind,cm,filename,'gif', 'Loopcount',inf);
else
imwrite(imind,cm,filename,'gif','WriteMode','append');
end
end
pcshow will, just like imshow or plot, reset the axes it writes to. You can prevent this by setting
hold on
after creating the axes. However, now the point clouds will be shown on top of the previous ones, so you will also have to clear the axes before plotting a new one.
The easiest solution is, instead of holding the plot, to set the axes position every time after plotting. That is, do
set(gca, 'PropertyName', property_value)
after the pcshow call. The properties to set are, I believe, 'XLim', 'YLim', and 'ZLim'.

Increasing size of arrows in Nyquist plot?

Does anyone know how to increase the size of the arrows in the Nyquist plot generated by MATLAB Control System Toolbox (while keeping the line width and everything else in the figure the same)?
I noticed that you also asked the same question in MATLAB
Central. Please make sure to refer other visitors to this answer if
you found it useful.
The Bad News first
MATLAB Control System Toolbox provides the functions nyquist and nyquistplot to draw a Nyquist plot of the frequency response of a dynamic system model.
Even though these functions allow a certain level of graphical customization (units, grids, labels and basic LineSpec), they don't allow to alter the size of the arrows that appear by default in the Nyquist plot (you can read more about this here and here).
The Good News (time to hack!)
I started exploring the object hierarchy of the Nyquist plot figure and realized that the arrows that are drawn are patch objects.
How did I notice that?
First I generated a sample Nyquist plot:
sys = tf([2 5 1],[1 2 3]); % System transfer function.
nyquist(sys); % Nyquist plot.
Then I ran a brute force test!
The following test iterates through every graphics object of the Nyquist plot figure window and blinks one element in every iteration. This way I was able to visually identify the objects that were linked to the arrows, when they started blinking.
h = findall(gcf); % Find all graphics objects including hidden ones.
t = 0.5; % Time interval.
for i = 1:length(h) % Loop through every object handle.
disp(i); % Display handle array index.
pause(t); % Pause for t seconds.
status = h(i).Visible; % Copy Visible property.
if strcmp(status, 'on') % If Visible = 'on' --> Visible = 'off'
h(i).Visible = 'off';
else % If Visible = 'off' --> Visible = 'on'
h(i).Visible = 'on';
end
pause(t); % Pause for t seconds.
h(i).Visible = status; % Restore original Visible property.
pause(t); % Pause for t seconds.
end
disp('Finished!'); % Display Finished!
After running this test, I found out that h(196) and h(197) were the two arrows of the Nyquist plot, and they were patch objects.
Changing the 'LineWidth' property was the next logical step. The following piece of code is really all you need to do in order to change the size of the arrows:
sys = tf([2 5 1],[1 2 3]); % System transfer function.
nyquist(sys); % Nyquist plot.
h = findall(gcf, 'Type', 'Patch'); % Find all patch objects.
for i = 1:length(h) % Loop through every patch object handle.
h(i).LineWidth = 4; % Set the new LineWidth value.
end
This is the result:
I hope that you enjoyed the adventure! :D

Matlab Plot that changes with time

I am trying to plot a figure that changes with time (think of it as plotting the shape of a pole as the wind passes through it, so I want to plot the shape at every second).
To avoid the x axis limit changing frequently I want to fix it to the limits (max and min that I calculate before plotting). Here is a sample of my code:
for i=1:1:numberofrows
momentvaluesatinstant = momentvalues(i,:);
figure(1)
plot(momentvaluesatinstant,momentheader)
drawnow
title(sprintf('Moment profile along pile at time 0.2%f',time(i)'))
xlabel('Moment (kN.m)')
xlim([momentvalues(rowminmoment) momentvalues(rowmaxmoment)])
ylabel('Length of pile (m)')
delay(1);
end
Although I am specifying the limits of the x axis to be fixed to the values I specify, the plot keeps changing the limits depending on the data being plotted? Is there something I am missing?
Figured it, need to add xlim manual
I'm not sure why you needed xlim manual, but here is a more compact and correct way to animate your data:
% use 'figure', `plot` and all the constant parts of the figure only once, before the loop.
figure(1)
m = plot(momentvalues(1,:),momentheader); % plotting only step 1
xlim([momentvalues(rowminmoment) momentvalues(rowmaxmoment)])
xlabel('Moment (kN.m)')
ylabel('Length of pile (m)')
% loop from step 2 ahead
for k = 2:length(momentvalues)
pause(1); % use pause to set the delay between shots
% use 'set' to change the x values
set(m,'Xdata',momentvalues(k,:));
drawnow
title(sprintf('Moment profile along pile at time 0.2%f',k))
end

How to specify the axis size when plotting figures in Matlab?

Suppose that I have 2 figures in MATLAB both of which plot data of size (512x512), however one figure is being plotted by an external program which is sets the axis parameters. The other is being plotted by me (using imagesc). Currently the figures, or rather, the axes are different sizes and my question is, how do I make them equal?.
The reason for my question, is that I would like to export them to pdf format for inclusion in a latex document, and I would like to have them be the same size without further processing.
Thanks in Advance, N
Edit: link to figures
figure 1: (big)
link to smaller figure (i.e. the one whose properties I would like to copy and apply to figure 1)
For this purpose use linkaxes():
% Load some data included with MATLAB
load clown
% Plot a histogram in the first subplot
figure
ax(1) = subplot(211);
hist(X(:),100)
% Create second subplot
ax(2) = subplot(212);
Now link the axes of the two subplots:
linkaxes(ax)
By plotting on the second subplot, the first one will adapt
imagesc(X)
First, you have the following:
Then:
Extending the example to images only:
load clown
figure
imagesc(X)
h(1) = gca;
I = imread('eight.tif');
figure
imagesc(I)
h(2) = gca;
Note that the configurations of the the first handle prevail:
linkaxes(h)
1.Get the handle of your figure and the axes, like this:
%perhaps the easiest way, if you have just this one figure:
myFigHandle=gcf;
myAxHandle=gca;
%if not possible, you have to search for the handles:
myFigHandle=findobj('PropertyName',PropertyValue,...)
%you have to know some property to identify it of course...
%same for the axes!
2.Set the properties, like this:
%set units to pixels (or whatever you prefer to make it easier to compare to the other plot)
set(myFigHandle, 'Units','pixels')
set(myAxHandle, 'Units','pixels')
%set the size:
set(myFigHandle,'Position',[x_0 y_0 width height]) %coordinates on screen!
%set the size of the axes:
set(myAxHandle,'Position',[x_0 y_0 width height]) %coordinates within the figure!
Ok, based on the answer of #Lucius Domitius Ahenoba here is what I came up with:
hgload('fig1.fig'); % figure whose axis properties I would like to copy
hgload('fig2.fig');
figHandles = get(0,'Children');
figHandles = sort(figHandles,1);
ax(1) = findobj(figHandles(1),'type','axes','-not','Tag','legend','-not','Tag','Colorbar');
ax(2) = findobj(figHandles(2),'type','axes','-not','Tag','legend','-not','Tag','Colorbar');
screen_pos1 = get(figHandles(1),'Position');
axis_pos1 = get(ax(1),'Position');
set(figHandles(2),'Position',screen_pos1);
set(ax(2),'Position',axis_pos1);
This is the 'before' result:
and this is the 'after' result:
Almost correct, except that the aspect ratios are still off. Does anybody know how to equalize everything related to the axes? (I realize that I'm not supposed to ask questions when posting answers, however adding the above as a comment was proving a little unwieldy!)