Reputation: 1091
I am trying to improve the rate at which an animation is drawn and updated in a MATLAB figure. I was hoping that by using gpuarray
or parfor
loops, I would be able to update many variables on the figure but apparently that is not the case, handles for the figure lose connections when parsed by these functions. Is there potentially another way to improve and speed up an animation. I will attach my code and briefly explain. I will also attach 2 GIFs showing what I'm trying to do.
Here is the code. I am writing the animation as an object as I will be inserting in into a variety of programs. I define the class, build the objects using the 1st function and then run the stimulus using the 2nd function. In an effort to reduce speed, I copied all the transformation and XY data into separate containers so I can perform cellfun
or other computationally intense calculations on the position data and then overwrite the position data of the objects.
classdef StimBud<handle
properties
Window % holds figure
ObjectList % holds object handles
ObjectTransformation % holds object transformation
ObjectLocX
ObjectLocY
ObjectRotation
end
methods
function gh = StimBud()
end
function buildobjects(gh,numofObj)
gh.Window = figure('MenuBar','none', ... % Build Window for stimulus
'Color',[0,0,0]);
for aa = 1:numofObj
gh.ObjectTransformation{aa} = hgtransform; % Add object to end of transformation list
gh.ObjectList{aa} = patch(... % Add object to end of Object list, bind to transformation list
'Parent', gh.ObjectTransformation{aa}, ...
'XData',[0,0,1,1], 'YData',[0,1,1,0],...
'Facecolor', [1,1,0], 'EdgeColor', [1,1,0], ...
'visible','on');
end
% define transforamtion and position variables
parse = 360/numofObj;
rotationcalculation = 0;
XData = repmat(10,1,numofObj);
YData = zeros(1,numofObj);
TmpXdata = cell(numofObj,1); % container to hold all X location data
TmpRot = zeros(numofObj,1); % container to hold the Rotation data
TmpYdata = cell(numofObj,1); % container to hold all Y location data
% Adjust position & rotation of all available objects
for aa = 1:numofObj
% Rotate objects to change direction of movement
gh.ObjectTransformation{aa}.Matrix = makehgtform('zrotate',(deg2rad(aa*parse)+rotationcalculation));
% move object to proper position
gh.ObjectList{aa}.XData = gh.ObjectList{aa}.XData + XData(aa);
gh.ObjectList{aa}.YData = gh.ObjectList{aa}.YData + YData(aa);
TmpXdata{aa} = gh.ObjectList{aa}.XData;
TmpYdata{aa} = gh.ObjectList{aa}.YData;
TmpRot(aa) = (deg2rad(aa*parse)+rotationcalculation);
end
% store variable out of objects for threading
gh.ObjectLocX = TmpXdata;
gh.ObjectLocY = TmpYdata;
gh.ObjectRotation = TmpRot;
end
function RunStim(gh)
figure(gh.Window);
TrialLength = 10; % Length of trial to be run
Framerate = 60;
ObjSpeed = 10;
% pull variables into function
ObjList = gh.ObjectList;
ObjLocX = gh.ObjectLocX;
ObjLocY = gh.ObjectLocY;
ObjRotation = gh.ObjectRotation;
NumofObj = length(ObjList); % Number of Objects in stim system
timer = tic(); % Timer for the stimulus
moveforward = .03*.1*ObjSpeed; % Distance to move in figure
while toc(timer) < TrialLength % Run stimulus through length of project
NextStepX = cellfun(@(x) x+moveforward,ObjLocX);
NextStepY = cellfun(@(x) x+moveforward,ObjLocY);
NextRot = ObjRotation + moveforward;
for aa = 1:NumofObj %% parfor does not work here %%
ObjList{aa}.XData = NextStepX{aa};
end
ObjLocX = NextStepX; % Update X location matrix for next step
ObjLocY = NextStepY; % Update Y location matrix for next step
ObjRotation = NextRot; % Update Rotation matrix for next step
pause(1/Framerate) % Pause window briefly to allow for drawing
end
end
end
end
To create an animation:
s = StimBud;
s.buildobjects(5)
s.RunStim
This GIF shows an animation I have built using the code above which has 5 objects. It is quick in appearing and animating even though I'm using a for loop.
Here is what happens when I increase the number of objects (pretty obvious due to for loop). The animation slows down considerably, not nearly as smooth and doesn't travel the same distance as the animation with fewer objects.
I was hoping to correct this using multi threading but that doesn't appear to be a viable option (at least from what I have learned). How to improve animating in a MATLAB figure? Am I thinking about this wrongly and should not be using a figure
at all?
Upvotes: 3
Views: 283
Reputation: 24169
The main issue with your approach is that your code ignores the fact that updating the drawing takes time. For this reason, the framerate that you set is not the actual framerate, I'll explain this using the profiler:
We see here that the marked line, which takes care of the drawing as well as waiting, takes 8.57
sec and not 350*1/60 = 5.83
, an increase of almost 50%! Ok, might suggest that the framerate that we want might be too high. We can try hardcoding a smaller framerate - but this only postpones the problem and doesn't really solve it.
This is why I think that the best approach is an adaptive framerate, which increases or decreases based on the current workload. Here's a rough implementation to show what I mean:
% same as yours until line 72 including
tmp = [ObjList{:}];
skipNext = 0;
while true % Run stimulus through length of project
t1 = toc(timer);
if t1 >= TrialLength, break; end
NextStepX = cellfun(@(x) x+moveforward, ObjLocX, 'UniformOutput', false);
NextStepY = cellfun(@(x) x+moveforward, ObjLocY, 'UniformOutput', false);
NextRot = ObjRotation + moveforward;
if ~skipNext
[tmp.XData] = NextStepX{:};
end
ObjLocX = NextStepX; % Update X location matrix for next step
ObjLocY = NextStepY; % Update Y location matrix for next step
ObjRotation = NextRot; % Update Rotation matrix for next step
t2 = toc(timer);
if t2-t1 < max(1,skipNext)/Framerate
% if we have some time left, update the plot
drawnow;
skipNext = 0;
% if we still have time left, increase the framerate and wait
t = 1/Framerate - (toc(timer) - t1);
if t > 0
Framerate = Framerate * 1.1;
java.lang.Thread.sleep( 1E3 * t );
end
else
skipNext = skipNext + 1;
Framerate = Framerate * 0.75;
disp("Frame skipped!");
end
disp("Framerate is now: " + Framerate);
end
This also includes some logging to show what happens to the frame rate. On my system, for n=10
the framerate ends up being 227.85
, for n=50
it's 72.6
, for n=1000
it's 18.98
, etc. Although the animation is not smooth in the last case, at least it doesn't appear stuck.
Going forward, I think you should try replacing the patches with some other entities, perhaps a line
or a scatter
plot with some large square markers (which would imitate square patches, but be much faster). If these are not suitable for your uses, I'd just say that these are the MATLAB limitations, and you might want to look into some dedicated graphic library, although I am not an expert on these, so I cannot suggest one unfortunately.
Please also note how I do the coordinate update instead of your for
loop:
[tmp.XData] = NextStepX{:};
Upvotes: 3