Render MATLAB figure in memory

12,984

Solution 1

If you create an avi file with avifile, and then add frames to it with addframe, MATLAB doesn't open up extra visible figures like it does with getframe.

avi = avifile('/path/to/output');
figure_handle = figure('visible', 'off');

% ...
for something = 1:1000
    cla
    % (draw stuff...)
    avi = addframe(avi, figure_handle);
end

Solution 2

I realize this is an old thread, but I ran into this problem again lately, so I wanted to summarize my findings. My main source is this page (cached). According to it, there are three alternatives:

  1. using ADDFRAME directly with the figure handle (without using GETFRAME). This is exactly what @rescdsk has shown in his answer.

    hFig = figure('Visible','off');
    
    aviobj = avifile('file.avi');
    for k=1:N
        %#plot(...)
        aviobj = addframe(aviobj, hFig);
    end
    aviobj = close(aviobj);
    
  2. using PRINT/SAVEAS/HGEXPORT to export the figure to an image file, then reading the image back from disk. This is approach#2 that you listed yourself in the question above.

    hFig = figure('Visible','off');
    set(hFig, 'PaperPositionMode','auto', 'InvertHardCopy','off')
    
    aviobj = avifile('file.avi');
    for k=1:N
        %#plot(...)
        print(['-f' num2str(hFig)], '-zbuffer', '-r0', '-dpng', 'file.png')
        img = imread('file.png');
        aviobj = addframe(aviobj, im2frame(img));
    end
    aviobj = close(aviobj);
    
  3. using the undocumented HARDCOPY function to capture the figure in-memory.

    hFig = figure('Visible','off');
    set(hFig, 'PaperPositionMode','auto')
    
    aviobj = avifile('file.avi');
    for k=1:N
        %#plot(...)
        img = hardcopy(hFig, '-dzbuffer', '-r0');
        aviobj = addframe(aviobj, im2frame(img));
    end
    aviobj = close(aviobj);
    

    In fact, this is the underlying function that other functions use either directly or indirectly. By inspecting the source codes where possible, here is an illustration of the dependencies of the related functions where A --> B denotes A calls B:

    saveas [M-file] --> print [M-file] --> render [private M-file] --> hardcopy [P-file]
    hgexport [P-file] --> print [M-file] --> ...
    @avifile/addframe [M-file] --> hardcopy [P-file]
    

    On the other hand, GETFRAME does not call HARDCOPY but an undocumented built-in function named CAPTURESCREEN (although it seems that it will be using PRINT for the upcoming HG2 system where there is a new -RGBImage print flag):

    getframe [M-file] --> capturescreen [builtin]
    

Note: Since AVIFILE is now deprecated, you can replace it with the newer VIDEOWRITER in (2) and (3), but not in (1) since it does not support passing figure handle directly.

Solution 3

Start MATLAB in headless mode: matlab -noFigureWindows

MATLAB is running in headless mode. Figure windows will not be displayed.

then simply plot and save the figures as usual (you won't see any graphical output of course). Example:

surf(peaks);
print output.eps     %# SAVEAS works as well
close

I tested the above on a Windows machine running R2010a. I don't have access to a Unix machine right now, but I answered a similar question in the past, and it worked just fine at the time (you will need to unset the $DISPLAY variable before starting MATLAB)


EDIT

Another option, in case you want to keep your normal workspace, is to start a new MATLAB instance in the background which will generate and save the plots (source).

Run this from the command prompt of your current MATLAB session (all on the same line):

!start /B /MIN matlab -noFigureWindows 
                      -automation 
                      -r "cd('c:\yourpath'); myscript; quit"

This will start a new MATLAB session in the background (using COM Automation), and execute a script called myscript (a simple M-file) that contains all your plotting code:

c:\yourpath\myscript.m

surf(peaks);
saveas(gcf, 'output.eps');

Solution 4

With avifile being deprecated, this is how you do it with VideoWriter:

hFig = figure('Visible','off');
set(hFig, 'PaperPositionMode','auto')

aviobj = VideoWriter('file','Archival');
for k=1:N
    %#plot(...)
    img = hardcopy(hFig, '-dzbuffer', '-r0');
    writeVideo(aviobj, im2frame(img));
end
close(aviobj);
Share:
12,984

Related videos on Youtube

André Caron
Author by

André Caron

You can find some of my M.Sc. projects and more of my personal projects online. Some of my better posts on StackOverflow: In C++, why should new be used as little as possible? Not using getters and setters Why and when shouldn't I kill a thread? How to properly use namespaces to avoid name collision? Using a Class Variable vs Sending Local Variable to Functions/Methods

Updated on May 08, 2022

Comments

  • André Caron
    André Caron about 2 years

    Are there any alternatives to using getframe and saveas for saving the contents of a figure to a raster image for further processing?

    Approach 1: getframe

    h = figure('visible', 'off');
    a = axes('parent', h);
    
    % render using `scatter3()` or other plot function.
    
    content = frame2im(getframe(h));
    

    This has the serious drawback of showing the figure to perform a screen capture in the call to getframe() and it is problematic when performing such a render in a loop (i.e. saving content at each iteration as a video frame).

    Approach 2: saveas

    h = figure('visible', 'off');
    a = axes('parent', h);
    
    % render using `scatter3()` or other plot function.
    
    saveas(h, '/path/to/file.png');
    content = imread(/path/to/file.png');
    

    This approach has the serious drawback of writing to disk, which is problematic in multithreaded applications, as well as being slower than rendering directly to memory. Since saveas() will obviously render to memory before invoking the PNG encoder, what I want is possible, but I can't find any function it in the MATLAB documentation that only performs the rendering part.

    Question:

    Does you know of an alternate way of rendering an arbitrary axes content to a raster image?

    • devin
      devin almost 13 years
      I'm having a similar problem, I'm also very curious why the MathWorks people implemented getframe() this way. I think it's completely insane. Also, why do you care about threads (unless you're running multiple matlab processes)? Can you even spawn threads with matlab?
    • André Caron
      André Caron almost 13 years
      I run the parallel processing toolbox. I've also had problems with global state changing because computation runs in a seperate thread from the UI. For example. opening the file browser in MATLAB and navigating to another directory changes the process' current directory.
    • André Caron
      André Caron almost 13 years
      The MathWorks people operate behind the idea that people want simple interfaces, not full control. Sometimes, this makes operations "easy for the simple case and impossible for the hard case".
  • rescdsk
    rescdsk over 13 years
    Sorry about the earlier answer, I didn't understand where the extra figure was coming from. Got it now!
  • André Caron
    André Caron over 13 years
    This is cool, I didn't know about the headless mode. However, this is not what I'm looking for as I usually inspect my data using other figures before running the output job.
  • André Caron
    André Caron over 13 years
    Wow, I didn't know addframe accepted figure handles! However, this is does not answer my question, because it does not render a figure to a raster image: I need to post-process the image before outputting to video.
  • André Caron
    André Caron over 13 years
    I thought this would be useful for using getframe() but I'm not sure it will work. The documentation says "make sure that the window to be captured by getframe exists on the currently active desktop". I'll run a test when I have a few more minutes.
  • Amro
    Amro over 13 years
    you're right about GETFRAME, it complains that Figure window not valid during getframe if you run it under the above conditions. Still, PRINT/SAVEAS work just fine.
  • Jonas
    Jonas over 13 years
    +1. I never thought of starting Matlab from within Matlab. You could probably use this as a poor man's parallel processing function.
  • André Caron
    André Caron over 13 years
    @Amro: you whole solution was based on trying to get getframe() to work without popping the figure, so if it can't do that, then it doesn't really solve my problem. So now, instead of an expensive render passing by my hard drive, I have an expensive render passing by my hard drive and I have to launch an extra process.
  • André Caron
    André Caron over 13 years
    Thanks for looking into this, though. I like the poor man's multi-processing method :-)
  • yuk
    yuk over 13 years
    Under unix you don't have to unset $DISPLAY. Just add to command line -nodisplay (mathworks.com/help/techdoc/ref/matlabunix.html)
  • rescdsk
    rescdsk over 13 years
    Ah, too bad. I'm looking at the source for addframe, and it doesn't seem that the intermediate data is stored in a useful way. In the getFrameForFigure subfunction of addframe, it looks like there's an undocumented function called hardcopy that can retrieve the contents of a figure, but of course it's undocumented + might change...
  • André Caron
    André Caron over 13 years
    @rescdsk: Although this doesn't quite answer my original question, it's the most viable alternative. I've re-written my code to use subplot and re-arrange my content so that I can use your proposed solution.
  • André Caron
    André Caron over 13 years
    @rescdsk: I've taken a look at addframe()'s getFrameForFigure(), and that does exactly what I want, so I'm going with that approach. Also, there is a reference to hardcopy on the MathWorks' website: mathworks.com/support/solutions/archived/1-15KWU.html . That's good enough for me.
  • André Caron
    André Caron over 11 years
    Thanks for you summary. Note that approach #3 was already suggested in the comments to @rescdsk's answer and that's what I ended up using.
  • CitizenInsane
    CitizenInsane over 11 years
    Truly interesting discussion as I fell in same issue and was diving in print and getframe codes to see how to get real raster data without going to file of clipboard. Thanks a lot!
  • Stretch
    Stretch over 10 years
    Very nice, Amro. I am creating a larger than 2Gb avifile, so VideoWriter was a must for me, and getframe was driving me nuts since my figure window extended beyond my laptop screen.
  • Stretch
    Stretch over 10 years
    Instead of calling hardcopy directly, it may be better to call getFrameForFigure( figHandle ). getFrameForFigure( figHandle ) is a sub-function inside addframe and it seems to set things up for feeding into hardcopy. getFrameForFigure( figHandle ) is also undocumented. If you want to use it, you'll have to open the source code to addframe, copy it, paste it into a new m-file, and stick the m-file on your path.
  • Tin
    Tin over 10 years
    @Amro what about if we need to insert a legend or a title to a given frame? In the context of the videoWriter class