Render MATLAB figure in memory
Asked Answered
J

4

25

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?

Juvenescence answered 9/11, 2010 at 19:29 Comment(3)
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?Gris
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.Dorri
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".Dorri
D
10

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
Damn answered 9/11, 2010 at 21:9 Comment(5)
Sorry about the earlier answer, I didn't understand where the extra figure was coming from. Got it now!Damn
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.Dorri
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...Damn
@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.Dorri
@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.Dorri
B
18

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.

Byrdie answered 25/10, 2012 at 22:51 Comment(5)
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.Dorri
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!Dedans
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.Kachine
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.Kachine
@Byrdie what about if we need to insert a legend or a title to a given frame? In the context of the videoWriter classTriviality
D
10

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
Damn answered 9/11, 2010 at 21:9 Comment(5)
Sorry about the earlier answer, I didn't understand where the extra figure was coming from. Got it now!Damn
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.Dorri
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...Damn
@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.Dorri
@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.Dorri
B
7

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');
Byrdie answered 9/11, 2010 at 21:5 Comment(7)
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.Dorri
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.Dorri
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.Byrdie
+1. I never thought of starting Matlab from within Matlab. You could probably use this as a poor man's parallel processing function.Khalkha
@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.Dorri
Thanks for looking into this, though. I like the poor man's multi-processing method :-)Dorri
Under unix you don't have to unset $DISPLAY. Just add to command line -nodisplay (mathworks.com/help/techdoc/ref/matlabunix.html)Vesicatory
S
2

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);
Sephira answered 6/5, 2015 at 7:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.