How to update a scatter3 plot (in a loop) in Matlab - 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

Related

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

How to set x and y values when using bar3 in Matlab?

Quick version
How can I control the x- and y-values for a 3-d bar plot in Matlab?
Details
Say we have an 10 x 20 data matrix and we plot it using bar3, and we want to set the x- and y-values. For instance:
foodat = rand(10,20);
xVals = [5:14];
yVals = [-3:16];
bar3(xVals, foodat);
xlabel('x'); ylabel('y');
Is there a way to feed it the yVals as well? Otherwise the y axes always defaults to [1:N].
Note I don't just want to change the labels using XTickLabel and YTickLabel. I need to change the actual values on the axes, because I am plotting multiple things in the same figure. It isn't enough to just change how the (wrong) axis ticks are labeled. So this is different from issues like this:
How can I adjust 3-D bar grouping and y-axis labeling in MATLAB?
Other things I have tried
When I try changing the xvals with:
set(gca,'XTick', xVals)
set(gca,'YTick', yVals)
The values are taken in, but actually show up on the wrong axes, so it seems x and y axes are switched using bar3. Plus, it is too late anyway as the bar graph was already plotted with the wrong x- and y-values, so we would end up giving ticks to empty values.
Note added
Matlab tech support just emailed me to let me know about the user contributed function scatterbar3, which does what I want, in a different way than the accepted answer:
http://www.mathworks.com/matlabcentral/fileexchange/1420-scatterbar3
I found a way of doing it. Ill give you a piece of code, then you'll need to "tidy up" , mainly the axis limits and the Xticks, as bar3 does set up the Xticks inside, so if you want others you'll need to set them manually yourself.
So the trick here is to get the Xdata from the bar3 handle. The thing here is that it seems that there is a handle for each row of the data, so you need to iterate for each of them. Here is the code with the current output:
foodat = rand(20,10);
xVals = [5:14];
yVals = [-3:16];
% The values of Y are OK if called like this.
subplot(121)
bar3(yVals, foodat);
subplot(122)
h=bar3(yVals, foodat);
Xdat=get(h,'XData');
axis tight
% Widdth of barplots is 0.8
for ii=1:length(Xdat)
Xdat{ii}=Xdat{ii}+(min(xVals(:))-1)*ones(size(Xdat{ii}));
set(h(ii),'XData',Xdat{ii});
end
axis([(min(xVals(:))-0.5) (max(xVals(:))+0.5) min(yVals(:))-0.5, max(yVals(:))+0.5])
Note: Y looks different but is not.
As you can see now the X values are the ones you wanted. If you'd want other size than 1 for the intervals between them you'd need to change the code, but you can guess how probably!

How to generate a video file using a series of plots on 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).

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!)

MATLAB - How to zoom subplots together?

I have multiple subplots in one figure. The X axis of each plot is the same variable (time). The Y axis on each plot is different (both in what it represents and the magnitude of the data).
I would like a way to zoom in on the time scale on all plots simultaneously. Ideally by using the rectangle zoom tool on one of the plots, and having the other plots change their X limits accordingly. The Y limits should remained unchanged for all of this. Auto fitting the data to fill the plot in the Y direction is acceptable.
(This question is almost identical to Stack Overflow question one Matplotlib/Pyplot: How to zoom subplots together? (except for MATLAB))
Use the built-in linkaxes function as follows:
linkaxes([hAxes1,hAxes2,hAxes3], 'x');
For more advanced linking (not just the x or y axes), use the built-in linkprop function
Use linkaxes as Yair and Amro already suggested. Following is a quick example for your case
ha(1) = subplot(2,1,1); % get the axes handle when you create the subplot
plot([1:10]); % Plot random stuff here as an example
ha(2) = subplot(2,1,2); % get the axes handle when you create the subplot
plot([1:10]+10); % Plot random stuff here as an example
linkaxes(ha, 'x'); % Link all axes in x
You should be able to zoom in all the subplots simultaneously
If there are many subplots, and collecting their axes handle one by one does not seem a clever way to do the job, you can find all the axes handle in the given figure handle by the following commands
figure_handle = figure;
subplot(2,1,1);
plot([1:10]);
subplot(2,1,2);
plot([1:10]+10);
% find all axes handle of type 'axes' and empty tag
all_ha = findobj( figure_handle, 'type', 'axes', 'tag', '' );
linkaxes( all_ha, 'x' );
The first line finds all the objects under figure_handle of type "axes" and empty tag (''). The condition of the empty tag is to exclude the axe handles of legends, whose tag will be legend.
There might be other axes objects in your figure if it's more than just a simple plot. In such case, you need to add more conditions to identify the axes handles of the plots you are interested in.
To link a pair of figures with linkaxes use:
figure;imagesc(data1);
f1h=findobj(gcf,,’type’,’axes’)
figure;imagesc(data2);
f2h=findobj(gcf,,’type’,’axes’)
linkaxes([f1h,f2h],’xy’)