In a for loop, I create a variable number of subplots that are displayed on a single figure. Can I also save each subplot as a separate, full size plot and image file (preferably JPG)?
Use copyobj to a new figure and then use saveas with the new figure handle:
Example code that YOU should have provided (see SSCCE):
figure
nLines = 2;
nColumns = 3;
handles = zeros(nLines,nColumns)
for line = 1:nLines
for column = 1:nColumns
handles(line,column)=subplot(nLines,nColumns,column+(line-1)*nColumns);
plot([line column]);
title(sprintf('Cool title (%d,%d)',line,column))
ylabel(sprintf('Ylabel yeah (%d,%d)',line,column))
xlabel(sprintf('Xlabel nah (%d,%d)',line,column))
end
end
Here I have the subplot handles saved, but supposing you don't have them saved:
axesH = findobj(gcf,'Type','axes','-not','Tag','legend'); % This will change the order from which the axes will be saved. If you need to save in the correct order, you will need access to the subplot handles
nAxes = numel(axesH)
newFig = figure;
for k=1:nAxes
newAxes=copyobj(axesH(k),newFig);
% Expand subplot to occupy the hole figure:
set(newAxes,'OuterPosition',[0 0 1 1]);
tightInset=get(newAxes,'TightInset');
set(newAxes,'Position',[tightInset(1:2) [1 1]-(tightInset(3:4)+tightInset(1:2))])
saveas(newFig,sprintf('axes_%d.jpg',k),'jpg');
delete(newAxes);
end
delete(newFig);
Example of one axes saved:
To remove the deadspace I used information available on this topic.
Say you have the handle to the subfigure's axis, ha, you can use getframe and frame2im as follows,
F = getframe(ha);
[im,map] = frame2im(F);
if isempty(map)
imwrite(im,'subframe.jpg');
else
imwrite(im,map,'subframe.jpg');
end
Note that this will save the axis exactly how it appears on your screen, so resize the figure to your liking before saving. To use as much figure real estate as possible, tryout the subfigure_tight function on MATLAB Central.
Related
I know there are a lot of answers regarding this issue but I didn’t found any one that help me..
I have a GUI in MATLAB with 2 axes and I want to save separately each axes as .jpeg or any other format.
Any way I have tried – I got either image that including all the GUI or cut figure.
Any idea how can I get 2 good images?
You could loop through all of the axes and call getframe to get just that axes. You can then save the cdata using imwrite.
% Get a list of all axes in the figure
allax = findall(gcf, 'type', 'axes');
for k = 1:numel(allax)
% Get the axes as an image
fr = getframe(allax(k));
% Save the image
imwrite(fr.cdata, sprintf('%d.png'));
end
If you already have axes handles you can just use those directly
fr = getframe(axes2);
imwrite(fr.cdata, 'axes2.png')
fr = getframe(axes1);
imwrite(fr.cdata, 'axes1.png')
If you want to include the X and Y axes labels, you could do something like
function axes2image(ax, filename)
hfig = ancestor(ax, 'figure');
rect = hgconvertunits(hfig, get(ax, 'OuterPosition'), ...
get(ax, 'Units'), 'pixels', get(ax, 'Parent'));
fr = getframe(hfig, rect);
imwrite(fr.cdata, filename);
end
axes2image(axes2, 'axes2.png')
axes2image(axes1, 'axes1.png')
I have written the following code to try and retrieve ONLY the axes and its plot from my MATLAB GUI.
F = getframe(gca);
figure();
image(F.cdata);
saveas(gcf,'PlotPic','png');
close(gcf);
I noticed, however, that this method does not include ANY of my axis labels or title. Is there any way which I can get the getframe function to include the axis labels and title?
I tried the following code but it did exactly the same
pl = plot(x,y);
xlabel('x')
ylabel('y')
ftmp = figure;
atmp = axes;
copyobj(pl,atmp);
saveas(ftmp,'PlotPic.png');
delete(ftmp);
I would do it using the rect option of the getframe function.
Basically you can provide a 2nd input argument to getframe, which then captures the content of the rectangle specified as argument. The nice thing is that you can use the handles to an axes, so it does not capture your whole GUI figure but rather a specific axes.
For example, using this line:
F = getframe(gca,RectanglePosition);
Concretely, you could set the coordinates of the rectangle such that they span both axis labels and the title as well. Here is a sample code. The pushbutton callback executes getframe and opens a new figure with the content of F.cdata:
function GUI_GetFrame
clc
clear
close all
%// Create GUI components
hFigure = figure('Position',[100 100 500 500],'Units','Pixels');
handles.axes1 = axes('Units','Pixels','Position',[60,90,400,300]);
handles.Button = uicontrol('Style','Push','Position',[200 470 60 20],'String','Get frame','Callback',#(s,e) GetFrameCallback);
%// Just create a dummy plot to illustrate
handles.Period = 2*pi;
handles.Frequency = 1/handles.Period;
handles.x = 0:pi/10:2*pi;
handles.y = rand(1)*sin(handles.Period.*handles.x);
plot(handles.x,handles.y,'Parent',handles.axes1)
title('This is a nice title','FontSize',18);
guidata(hFigure,handles); %// Save handles structure of GUI.
function GetFrameCallback(~,~)
handles = guidata(hFigure);
%// Get the position of the axes you are interested in. The 3rd and
%// 4th coordinates are useful (width and height).
AxesPos = get(handles.axes1,'Position');
%// Call getframe with a custom rectangle size.You might need to change this.
F = getframe(gca,[-30 -30 AxesPos(3)+50 AxesPos(4)+80]);
%// Just to display the result
figure()
imshow(F.cdata)
end
end
The GUI looks like this:
And once I press the pushbutton, this is the figure that pops up:
So the only trouble you have is to figure out the dimensions of the rectangle you need to select to capture the axis labels and the title.
Hope that solves your problem!
Imagine I have function myPlot which creates a simple plot and returns figure and axes handle:
function [ fig_handle, axes_handle ] = myPlot( myTitle )
x = linspace(-10,10,100);
y = x.^2;
fig_handle = figure;
plot(x,y,'Color','b')
ylim([0,42]); xlim([-42/10,42/10]);
ylabel('y'); xlabel('x');
title(myTitle);
grid on;
axes_handle = gca;
end
Now I want to call that function multiple times with different input parameters and concatenate them into an array of subplots. The solution I came up with is
[f1,a1] = myPlot('Plot #1');
[f2,a2] = myPlot('Plot #2');
figure(3)
s221 = subplot(211);
s222 = subplot(212);
copyobj(get(a1,'children'),s221)
copyobj(get(a2,'children'),s222)
it gives me
so the new 2-part plot does not keep any property of the two plots before. Of course I am aware that I can just do:
set(s221,'Ylabel',get(a1,'Ylabel'))
with all properties. But I try to avoid this. Is there something easier I am missing?
Another workaround is to copy the full axes object (so all the properties come along) into a new figure. The only property you have left to set yourself is the position according to the rules of subplot(xyz).
To start with your example :
[f1,a1] = myPlot('Plot #1');
[f2,a2] = myPlot('Plot #2');
Then copy the axis of each figure into the new figure
h4 = figure(4)
copyobj(get(f1,'children'),h4)
copyobj(get(f2,'children'),h4)
They are both here, but they are still superimposed. You just have to reposition them now.
For a quick workaround I use an intermediate figure to let subplot calculate the position for me but these positions (axes arrangement) could probably be calculated by yourself with a helper function (rip some code from subplot.m if you need).
%// Just to get some position calculated for me
figure(3)
s221 = subplot(211);
s222 = subplot(212);
Then I apply the positions to axes in the figure:
hl = flipud( get(h4,'Children') ) ;
set( hl(1),'Position', get(s221,'Position') )
set( hl(2),'Position', get(s222,'Position') )
Just be careful, the axes/children list is upside down (so the use of the flipud function), but if you put that in a loop, you could just run the loop backward.
Edit
If you are going to discard the original figure (f1 and f2), then you can also simply move the axes to the new figure (instead of copying them) by assigning the Parent property of the axes, then close the figure (to keep things tidy). Just use :
set(a1,'Parent',h4) ; close(f1)
set(a2,'Parent',h4) ; close(f2)
instead of the 2 lines with copyobj. The rest of the solution is identical. Not sure if there is a performance gain by moving an object instead of replicating it (Matlab may do a copy in the background anyway ... or not) but if it involve many figures with heavy data sets it may at least save some memory during the process.
One possible workaround could be the following, not really generic though. Would be interested in a solution solving the original problem.
If it is possible to modify the myPlot function one could pass the figure handle and subplot specifier and plot everything correctly from the beginning.
function [ fig_handle, axes_handle ] = myPlot(fig_handle, sub, myTitle )
figure(fig_handle); subplot(sub)
....
end
called by
f = figure(3);
[f,a1] = myPlot(f, 221,'Plot #1');
[f,a2] = myPlot(f, 222,'Plot #2');
I have a set of polygon vertices in in X, Y matrices and their colors are in RGB values in another matrix C.
I then use fill() function to generate patch objects that is displayed in a Matlab figure.
I want to create a bmp object from this figure. What I mean by a bmp object is the x,y coordinates of the pixels and there RGB values.
If I use the print() function, with '-dbmp' and a file name, matlab can write the bmp to a file. But then I have to read the file with imread() to create the bmp object.
Is there a way to create the bmp object without writing and reading to from a file?
Becauswe I have to perform this operation many times and writing and reading to file is time consuming and will reduce the life time of my disk too I guess.
Edit: code after editing according to answer
N = 5;
Tri = 100;
res = 200; %200 pixles per inch
G = zeros(Tri,9,N);
X = 2*rand(Tri,3,N);
Y = 2*rand(Tri,3,N);
R = randi([0 255],Tri,N)/255;
G = randi([0 255],Tri,N)/255;
B = randi([0 255],Tri,N)/255;
for c1=1:N
G(:,1:3,c1)= X(:,:,c1);
G(:,4:6,c1)= Y(:,:,c1);
G(:,7,c1)= R(:,c1);
G(:,8,c1)= G(:,c1);
G(:,9,c1)= B(:,c1);
end
for c2=1:N;
h = figure('Visible','off');
set(h, 'PaperUnits', 'inches', 'PaperPosition', [0 0 400 400]/res);
for c3 =1:Tri
h1 = fill(G(c3,1:3,c2), G(c3,4:6,c2), [G(c3,7,c2) G(c3,8,c2) G(c3,9,c2)]);
set(h1,'EdgeColor','None');
hold on;
end
%print(h,'-dbmp',['-r' num2str(res)],['file' num2str(c2)]);
F = getframe(h);
[a, b] = frame2im(F);
Tmp_v1 = a;
Tmp_v1 = Tmp_v1(:);
Norm_v1(c2) = norm(single(Tmp_v1));
end
Thank you.
If you have the current figure open, you can try the getframe idiom. Once you have access to this, you can access the frame's image data by looking at the cdata field in the structure. After you have this, you can use the imwrite command from MATLAB's image processing toolbox (let's hope you have it...) to write the image to file.
Here's an example:
x = 1 : 5;
y = 1 : 5;
plot(x,y); %// Plot a line
h = getframe;
img = h.cdata;
imwrite(img, 'testFrame.bmp');
This should be able to grab what is inside the current figure and save it to file. In this case, this will be a straight line from (x,y) = (1,1) to (x,y) = (5,5), with a slope of 1. Bear in mind that this won't save the title of the graph or the axes. This will only grab what is rendered inside the frame.
Edit from comments
Now that I know what you're really after, you want to generate a bunch of random polygons, then extract just the core image, without any axes or tick marks and so on. You'll have to modify your for loop where you're creating the images so that you want the figure to fill the entire window without any gray padding You'll also want to turn off the axis too. Also, when you're generating each image, you'll need to turn off the ticks. This is done with a combination of not writing any tick labels, as well as setting the tick length to 0. In other words, you'll need to modify your code so that it looks like this. You'll see where I inserted code by seeing the %// NEW statements in your code:
for c2=1:N;
h = figure('Visible','off');
subplot('position', [0 0 1 1]); %// NEW
axis off; %// NEW
set(h, 'PaperUnits', 'inches', 'PaperPosition', [0 0 400 400]/res);
for c3 =1:Tri
h1 = fill(G(c3,1:3,c2), G(c3,4:6,c2), [G(c3,7,c2) G(c3,8,c2) G(c3,9,c2)]);
set(h1,'EdgeColor','None');
set(gca,'xcolor','w','ycolor','w','xtick',[],'ytick',[]) %// NEW
set(gca,'Ticklength',[0 0]); %// NEW
hold on;
end
%print(h,'-dbmp',['-r' num2str(res)],['file' num2str(c2)]);
F = getframe(h);
close all; %// NEW
[a, b] = frame2im(F);
a = imresize(a, [400 400], 'nearest'); %// NEW
Tmp_v1 = a;
Tmp_v1 = Tmp_v1(:);
Norm_v1(c2) = norm(single(Tmp_v1));
end
This will still show you the frames being popped up for each image you're creating, but you should be able to get just pure image data at this point. Note that the images are still coming out as a bit larger than 400 x 400. This is due to the fact that once we have removed the borders and the tick marks and so on, the figure will stretch to fill the entire figure. To get around this, I use imresize and shrink the images back down to 400 x 400 as per your desired size.
Also, take note that every time you generate a new image, a new figure is spawned. Every time you call getframe, the figure with this randomly generated polygon image pops up, and getframe takes a snapshot of that current frame. There isn't a way to prevent this from happening, as you won't be able to take a snapshot of that figure. One way to get around this would be to close the figure after you grab the image. You can do a close all; after each call to getframe. That way, only one figure gets shown at any one time, but this still won't prevent the figure from showing up.
If I do find a solution to this, I'll let you know!
I'm trying to find a way to nicely plot my measurement data of digital signals.
So I have my data available as csv and mat file, exported from an Agilent Oscilloscope. The reason I'm not just taking a screen shot of the Oscilloscope screen is that I need to be more flexible (make several plots with one set of data, only showing some of the lines). Also I need to be able to change the plot in a month or two so my only option is creating a plot from the data with a computer.
What I'm trying to achieve is something similar to this picture:
The only thing missing on that pic is a yaxis with 0 and 1 lines.
My first try was to make a similar plot with Matlab. Here's what I got:
What's definitely missing is that the signal names are right next to the actual line and also 0 and 1 ticks on the y-axis.
I'm not even sure if Matlab is the right tool for this and I hope you guys can give me some hints/a solution on how to make my plots :-)
Here's my Matlab code:
clear;
close all;
clc;
MD.RAW = load('Daten/UVLOT1 debounced 0.mat'); % get MeasurementData
MD.N(1) = {'INIT\_DONE'};
MD.N(2) = {'CONF\_DONE'};
MD.N(3) = {'NSDN'};
MD.N(4) = {'NRST'};
MD.N(5) = {'1V2GD'};
MD.N(6) = {'2V5GD'};
MD.N(7) = {'3V3GD'};
MD.N(8) = {'5VGD'};
MD.N(9) = {'NERR'};
MD.N(10) = {'PGD'};
MD.N(11) = {'FGD'};
MD.N(12) = {'IGAGD'};
MD.N(13) = {'GT1'};
MD.N(14) = {'NERRA'};
MD.N(15) = {'GT1D'};
MD.N(16) = {'GB1D'};
% concat vectors into one matrix
MD.D = [MD.RAW.Trace_D0, MD.RAW.Trace_D1(:,2), MD.RAW.Trace_D2(:,2), MD.RAW.Trace_D3(:,2), ...
MD.RAW.Trace_D4(:,2), MD.RAW.Trace_D5(:,2), MD.RAW.Trace_D6(:,2), MD.RAW.Trace_D7(:,2), ...
MD.RAW.Trace_D8(:,2), MD.RAW.Trace_D9(:,2), MD.RAW.Trace_D10(:,2), MD.RAW.Trace_D11(:,2), ...
MD.RAW.Trace_D12(:,2), MD.RAW.Trace_D13(:,2), MD.RAW.Trace_D14(:,2), MD.RAW.Trace_D15(:,2)];
cm = hsv(size(MD.D,2)); % make colormap for plot
figure;
hold on;
% change timebase to ns
MD.D(:,1) = MD.D(:,1) * 1e9;
% plot lines
for i=2:1:size(MD.D,2)
plot(MD.D(:,1), MD.D(:,i)+(i-2)*1.5, 'color', cm(i-1,:));
end
hold off;
legend(MD.N, 'Location', 'EastOutside');
xlabel('Zeit [ns]'); % x axis label
title('Messwerte'); % title
set(gca, 'ytick', []); % hide y axis
Thank you guys for your help!
Dan
EDIT:
Here's a pic what I basically want. I added the signal names via text now the only thing that's missing are the 0, 1 ticks. They are correct for the init done signal. Now I just need them repeated instead of the other numbers on the y axis (sorry, kinda hard to explain :-)
So as written in my comment to the question. For appending Names to each signal I would recommend searching the documentation of how to append text to graph. There you get many different ways how to do it. You can change the position (above, below) and the exact point of data. As an example you could use:
text(x_data, y_data, Var_Name,'VerticalAlignment','top');
Here (x_data, y_data) is the data point where you want to append the text and Var_Name is the name you want to append.
For the second question of how to get a y-data which contains 0 and 1 values for each signal. I would do it by creating your signal the way, that your first signal has values of 0 and 1. The next signal is drawn about 2 higher. Thus it changes from 2 to 3 and so on. That way when you turn on y-axis (grid on) you get values at each integer (obviously you can change that to other values if you prefer less distance between 2 signals). Then you can relabel the y-axis using the documentation of axes (check the last part, because the documentation is quite long) and the set() function:
set(gca, 'YTick',0:1:last_entry, 'YTickLabel',new_y_label(0:1:last_entry))
Here last_entry is 2*No_Signals-1 and new_y_label is an array which is constructed of 0,1,0,1,0,....
For viewing y axis, you can turn the grid('on') option. However, you cannot chage the way the legends appear unless you resize it in the matlab figure. If you really want you can insert separate textboxes below each of the signal plots by using the insert ->Textbox option and then change the property (linestyle) of the textbox to none to get the exact same plot as above.
This is the end result and all my code, in case anybody else wants to use the good old ctrl-v ;-)
Code:
clear;
close all;
clc;
MD.RAW = load('Daten/UVLOT1 debounced 0.mat'); % get MeasurementData
MD.N(1) = {'INIT\_DONE'};
MD.N(2) = {'CONF\_DONE'};
MD.N(3) = {'NSDN'};
MD.N(4) = {'NRST'};
MD.N(5) = {'1V2GD'};
MD.N(6) = {'2V5GD'};
MD.N(7) = {'3V3GD'};
MD.N(8) = {'5VGD'};
MD.N(9) = {'NERR'};
MD.N(10) = {'PGD'};
MD.N(11) = {'FGD'};
MD.N(12) = {'IGAGD'};
MD.N(13) = {'GT1'};
MD.N(14) = {'NERRA'};
MD.N(15) = {'GT1D'};
MD.N(16) = {'GB1D'};
% concat vectors into one matrix
MD.D = [MD.RAW.Trace_D0, MD.RAW.Trace_D1(:,2), MD.RAW.Trace_D2(:,2), MD.RAW.Trace_D3(:,2), ...
MD.RAW.Trace_D4(:,2), MD.RAW.Trace_D5(:,2), MD.RAW.Trace_D6(:,2), MD.RAW.Trace_D7(:,2), ...
MD.RAW.Trace_D8(:,2), MD.RAW.Trace_D9(:,2), MD.RAW.Trace_D10(:,2), MD.RAW.Trace_D11(:,2), ...
MD.RAW.Trace_D12(:,2), MD.RAW.Trace_D13(:,2), MD.RAW.Trace_D14(:,2), MD.RAW.Trace_D15(:,2)];
cm = hsv(size(MD.D,2)); % make colormap for plot
figure;
hold on;
% change timebase to ns
MD.D(:,1) = MD.D(:,1) * 1e9;
% plot lines
for i=2:1:size(MD.D,2)
plot(MD.D(:,1), MD.D(:,i)+(i-2)*2, 'color', cm(i-1,:));
text(MD.D(2,1), (i-2)*2+.5, MD.N(i-1));
end
hold off;
%legend(MD.N, 'Location', 'EastOutside');
xlabel('Zeit [ns]'); % x axis label
title('Messwerte'); % title
% make y axis and grid the way I want it
set(gca, 'ytick', 0:size(MD.D,2)*2-3);
grid off;
set(gca,'ygrid','on');
set(gca, 'YTickLabel', {'0'; '1'});
ylim([-1,(size(MD.D,2)-1)*2]);