Hojo.Timberwolf
Hojo.Timberwolf

Reputation: 1091

Speeding up an animation

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.

enter image description here

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.

enter image description here

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

Answers (1)

Dev-iL
Dev-iL

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:

Profiler output for n = 100

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

Related Questions