MATLAB: Colors displayed incorrectly? - matlab

I have noticed that MATLAB sometimes displays my colors incorrectly. I'm not sure if this is a programming error on my side, or if it is an actual bug in MATLAB. I noticed this behavior with some regularity over the last year or so.
This time, I decided to take a snapshot of a figure with the error in question (taken on MATLAB 2011b on Windows 7, 64 bit):
The code that displays the image in question is the following:
figure;
clf;
cla;
imshow(matrix, []);
colormap(cmap);
set(gca, 'Clim', [0 highest_index]);
where:
matrix is of type uint32 (although I have also tried explitly casting matrix as double prior to calling imshow)
The values in matrix range between 0 and 900
cmap has 901 entries
highest_index is 900
The RGB entry for the value 259 in matrix is [1, 0, 0.1] both in the image above and in the colormap array cmap, i.e. cmap(300, :) = [1, 0, 0.1] (notice that the matrix value 259 gets the index 300 in the colormap, since the first entry of the colormap is for the matrix value 0).
Questions:
Why does this happen? Is it an error? Is there anything I am doing wrong?
Update 1:
I tried switching CDataMapping to direct or scaled, but it didn't make a difference.
I also tried using imagesc instead of imshow, but it didn't make a difference.
If I convert the image to RGB first (i.e. transform the indexed image to a true color image; see here for more info on this), i.e. with i_rgb = ind2rgb(i_indexed, cmap), the error goes away and the image is displayed correctly.
Unfortunately, if I display a true color image the data tip does not reveal the index in the original matrix for each color anymore and instead it just displays the RGB vector (i.e. this is logical, since MATLAB is not aware of the original index anymore).
Update 2:
Here's some sample code:
h_f = figure(1);
clf;
i_spiral = spiral(40);
h_i = image(i_spiral);
% Synthesize a colormap first in HSV and then transform it to RGB:
max_i_spiral = max(i_spiral(:));
m = max_i_spiral;
h = (0:m-1)'/max(m,1);
cmap_spiral = hsv2rgb([h ones(m,2)]);
colormap(cmap_spiral);
% If I comment out the following two lines or use imshow instead of image,
% it makes no difference (I still get the same error):
set(gca, 'Clim', [1 max_i_spiral]);
set(h_i, 'CDataMapping', 'direct');
The code above results in:

[Since this answer is totally unrelated to my former answer, I'm not editing the first]
The link you mention (http://www.mathworks.com/help/matlab/creating_plots/image-types.html)
says:
Note When using the painters renderer on the Windows platform, you should only use 256 colors when attempting to display an indexed
image. Larger colormaps can lead to unexpected colors because the
painters algorithm uses the Windows 256 color palette, which graphics
drivers and graphics hardware are known to handle differently. To work
around this issue, use the Zbuffer or OpenGL renderer, as appropriate.
For more information regarding graphics renderers in MATLAB, see
Technical Note 1201: The Technical Support Guide to Graphics Rendering
and Troubleshooting.
So it seems the problem is that your colormap has more then 256 values. It also explains why the problem goes away if you don't use an indexed image. Try using a different renderer, as suggested in the technical support link from the note:
set(gcf, 'Renderer', 'opengl')
or
set(gcf, 'Renderer', 'Zbuffer')

A better way to use IMSHOW is:
imshow(img,map)
Here is your example slightly rewritten:
%# indexed image
I = spiral(40);
%# Synthesize a colormap first in HSV and then transform it to RGB
mx = max(I(:));
cmap = hsv2rgb([(0:mx-1)'./max(mx,1) ones(mx,2)]); %'
%# show image
imshow(I,cmap)
colorbar
datacursormode on
EDIT:
Thanks to #ItamarKatz, we now know that on Windows, if you are displaying an indexed image with more than 256 colors, one must not use the 'painters' algorithm as renderer.
IMSHOW (which underneath calls the lower level IMAGE function), detects such a case and correctly handles it.
If you still want to use IMAGE/IMAGESC, you must be aware of the indexed image data type:
double:
Image contains integers in the range [1 length(cmap)] as indices in the current colormap
uint8/uint16:
Image contains integers in the range [0 255] for uint8 or [0 65535] for uint16, interpreted as indices in the current colormap.
thus there is an offset (range starts at 0 or 1) which you should be careful about.
Here is the same example as above using IMAGE function directly (once with double data type, the other with uint16):
%# indexed image and colormap
I = spiral(40);
cmap = hsv( max(I(:)) );
%# show indexed image (double)
hFig = figure(2);
hImg = image(I); %# one-based index into colormap
colormap(cmap), colorbar
axis off image
%# fix bug on Windows with indexed image of more than 256 colors
if ispc && strcmpi(get(hImg,'CDataMapping'),'direct') && size(cmap,1) > 256
set(hFig, 'Renderer','zbuffer') %# opengl renderer also works
end
%# show indexed image (uint16)
hFig = figure(3);
hImg = image( uint16(I-1) ); %# zero-based index into colormap
colormap(cmap), colorbar
axis off image
%# fix bug on Windows with indexed image of more than 256 colors
if ispc && strcmpi(get(hImg,'CDataMapping'),'direct') && size(cmap,1) > 256
set(hFig, 'Renderer','zbuffer')
end

I am not 100% sure (couldn't verify without your data), but I think the reason is wrong mapping/rounding done by the data-tip display function callback. You can create your own callback, by right-clicking the data tip, selecting Edit Text Update Function..., and entering something like that:
function output_txt = dataCursorCallback(obj,event_obj)
% Display the position of the data cursor, and the RGB data to 6 decimal places.
pos = get(event_obj,'Position');
output_txt = {['X: ',num2str(pos(1),4)],...
['Y: ',num2str(pos(2),4)]};
h = get(event_obj,'target');
cdata = get (h, 'CData');
cmap = colormap;
rgb = cmap(cdata(pos(2),pos(1)),:);
output_txt{end+1} = ['RGB: ' num2str(rgb,'%.6f')];
Note that the above code assumes the colormap length and and data range of the matrix plotted are the same - like in your example.
To save the callback, click save and close, and you can re-select it on next occasions by right-clicking the data tip and selecting Select Text Update Function...

Related

Separate colormap for each subplot in Octave

Matlab provides an ability to set individual colormaps for each subplot. The same functionality described by Octave docs
Namely following phrases:
Function File: cmap = colormap (hax, …)
If the first argument hax is an axes handle, then the colormap for the parent figure of hax is queried or set.
But when I try to run following code, I end up having single colormap for all subplots for some reason.
clear -all;
clc;
img = zeros(128, 128);
img(64,64) = 0.2;
rad = radon(img);
x = 1;
y = 4;
s1 = subplot(y,x,1); imagesc(img);
colormap(s1, gray);
s2 = subplot(y,x,2); imagesc(rad);
colormap(s2, hot);
colorbar;
line_img = zeros(128, 128);
line_img(64, 32:64) = 0.5;
line_img(63, 32:64) = 0.4;
line_img(63, 32:64) = 0.2;
line_rad = radon(line_img);
s3 = subplot(y,x,3); imshow(line_img);
colormap(s3, gray);
s4 = subplot(y,x,4); imagesc(line_rad);
colormap(s4, hot);
colorbar;
Any help is appreciated. I expect to have source images in grayscale and radon transform images in "hot". For some reason I'm getting first subplot somewhat like grayscale (it's actually not, since I initialize point with value 0.2 and octave gives me pure white color for it, while I'm expecting reasonably dark gray), and thee remaining images seem to have "hot" colormap being set.
If you read that line from the documentation a little close you will see that is that that if you pass an axes handle that the parent figure's colormap is changed. This is not the same as each axes having a different colormap since they're all in the same figure.
Function File: cmap = colormap (hax, …) If the first argument hax is an axes handle, then the colormap for the parent figure of hax is queried or set.
Currently, Octave does not support this functionality which was only just recently introduced in MATLAB.
The way around this is to convert your images to RGB images prior to displaying with imshow and then the figure's colormap is irrelevant. You can do this by first converting it to an indexed image (using gray2ind) and then to RGB with ind2rgb.
% To display the grayscale image
rgb = ind2rgb(gray2ind(img), gray);
imshow(rgb);
As a side note, the reason that your first grayscale image is showing up as all white is that if the input to imshow is of type double, then all values are expected to be between 0 and 1. If you want to change this behavior you can use the second input of imshow to specify that you'd like to scale the color limits to match your data
imshow(img, [])

Plot digitization in MATLAB using ginput

I'm trying to digitize this image using MATLAB:
I have the following script:
%// Get data from plot
clear all; close all;
%// Input
fname = 'Fig15a.PNG';
xvec = [1e3:1:1e8];
yvec = [1e-4:1:1e-1];
xt = [1e3 1e4 1e5 1e6 1e7 1e8];
yt = [1e-4 1e-3 1e-2 1e-1];
%// Read and plot the image
im = imread(fname);
figure(1), clf
im = im(end:-1:1,:,:);
image(xvec,yvec,im)
axis xy;
grid on;
%// Set ticks
set(gca,'xtick',xt,'ytick',yt); %// Match tick marks
%// Collect data
[x,y] = ginput; %// Click on points, and then hit ENTER to finish
%// Plot collected data
hold on; plot(x,y,'r-o'); hold off;
%// Then save data as:
save Fig15a.mat x y
The script works fine
Is there a way I can change the x and y axes to a log scale ?
I have tried adding the following code in different places without luck:
%// Set Log scale on x and y axes
set(gca,'XScale','log','YScale','log');
Below's a proof of concept that should get you on the right track. I have replaced things in your original code with what I consider "good practices".
function q36470836
%% // Definitions:
FIG_NUM = 36470836;
%% // Inputs:
fname = 'http://i.stack.imgur.com/2as4t.png';
xt = logspace(3,8,6);
yt = logspace(-4,-1,4);
%% // Init
figure(FIG_NUM); clf
% Read and plot the image
im = imread(fname);
hIMG = imshow(im); axis image;
%// Set ticks
hDigitizer = axes('Color','none',...
'XLim',[xt(1) xt(end)],'YLim',[yt(1) yt(end)],...
'XScale','log','YScale','log',...
'Position',hIMG.Parent.Position .* [1 1 696/785 (609-64+1)/609]);
uistack(hDigitizer,'top'); %// May be required in some cases
grid on; hold on; grid minor;
%// Collect data:
[x,y] = ginput; %// Click on points, and then hit ENTER to finish
%// Plot collected data:
scatter(x,y,'o','MarkerEdgeColor','r');
%// Save data:
save Fig15a.mat x y
Here's an example of what it looks like:
Few notes:
xt, yt may be created in a cleaner fashion using logspace.
It is difficult (possibly impossible) to align the digitization grid with the image correctly, which would inevitably result in errors in your data. Though this can be helped in the following scenarios (for which you will require a vector graphics editor, such as the freeware InkScape):
If, by any chance, you got this image from a PDF file, where it appears as a vector image (you can test this by zooming in as much as you like without the chart becoming pixelated; this seems to be your case from the way the .png looks), you would be better off saving it as a vector image and then you have two options:
Exporting the image to a bitmap with a greatly increased resolution and then attempting the digitization procedure again.
Saving the vector image as .svg then opening the file using your favorite text editor and getting the exact coordinates of the points.
If the source image is a bitmap (as opposed to vector graphic), you can "trace the bitmap", thus converting it to vectoric, then #GOTO step 1.
This solution doesn't (currently) support resizing of the figure.
The magic numbers appearing in the Position setting are scaling factors explained in the image below (and also size(im) is [609 785 3]). These can technically be found using "primitive image processing" but in this case I just hard-coded them explicitly.
You can plot in double logarithmic scale with
loglog(x,y);
help loglog or the documentation give additional information.
For a single logarithmic scale use
semilogx(x,y);
semilogy(x,y);

How to get image matrix from axes content

Is there a way to get the content of a contourf plot as an image matrix? I want rasterize only the content, not the axes, labels and the empty space of the entire figure.
My goal is to overlay a transparent, colored contour plot over a grayscale image and I don't see another way, since MATLAB has only one colormap per figure.
Try getframe and frame2im
Example from the frame2im documentation:
Create and capture an image using getframe and frame2im:
peaks %Make figure
f = getframe; %Capture screen shot
[im,map] = frame2im(f); %Return associated image data
if isempty(map) %Truecolor system
rgb = im;
else %Indexed system
rgb = ind2rgb(im,map); %Convert image data
end
Not a direct answer to the question, but this is how I think you could achieve your goal:
%# load in grayscale image
gray_im = rgb2gray(imread('peppers.png'));
%# converting n x m grey image to n x m x 3 rgb gray image
rgb_gray_im = cat( 3, gray_im, gray_im, gray_im );
%# displaying this image
imshow( rgb_gray_im );
%# plotting contourf on top with arbitrary colourmap
hold on
h = axes('position', [0.5, 0.5, 0.2, 0.2]);
z = peaks;
contourf(h, z, [min(z(:)), -6 : 8]);
Which gives the result:
The figure's colourmap is being used for the contourf plot. The background image is not relying on a colourmap, and is instead being displayed in truecolour - i.e. each pixel is being displayed as an RGB value defined in rgb_gray_im.
There are also other ways of getting around the MATLAB colourmap restrictions: see for example this blog post or these answers.

matlab: how to plot multidimensional array

Let's say I have 9 MxN black and white images that are in some way related to one another (i.e. time lapse of some event). What is a way that I can display all of these images on one surface plot?
Assume the MxN matrices only contain 0's and 1's. Assume the images simply contain white lines on a black background (i.e. pixel value == 1 if that pixel is part of a line, 0 otherwise). Assume images are ordered in such a way as to suggest movement progression of line(s) in subsequent images. I want to be able to see a "side-view" (or volumetric representation) of these images which will show the surface that a particular line "carves out" in its movement across the images.
Coding is done in MATLAB. I have looked at plot (but it only does 2D plots) and surf, which does 3D plots but doesn't work for my MxNx9 matrix of images. I have also tried to experiment with contourslice, but not sure what parameters to pass it.
Thanks!
Mariya
Are these images black and white with simple features on a "blank" field, or greyscale, with more dense information?
I can see a couple of approaches.
You can use movie() to display a sequence of images as an animation.
For a static view of sparse, simple data, you could plot each image as a separate layer in a single figure, giving each layer a different color for the foreground, and using AlphaData to make the background transparent so all the steps in the sequenc show through. The gradient of colors corresponds to position in the image sequence. Here's an example.
function plotImageSequence
% Made-up test data
nLayers = 9;
x = zeros(100,100,nLayers);
for i = 1:nLayers
x(20+(3*i),:,i) = 1;
end
% Plot each image as a "layer", indicated by color
figure;
hold on;
for i = 1:nLayers
layerData = x(:,:,i);
alphaMask = layerData == 1;
layerData(logical(layerData)) = i; % So each layer gets its own color
image('CData',layerData,...
'AlphaData',alphaMask,...
'CDataMapping','scaled');
end
hold off
Directly showing the path of movement a "line" carves out is hard with raster data, because Matlab won't know which "moved" pixels in two subsequent images are associated with each other. Don't suppose you have underlying vector data for the geometric features in the images? Plot3() might allow you to show their movement, with time as the z axis. Or you could use the regular plot() and some manual fiddling to plot the paths of all the control points or vertexes in the geometric features.
EDIT: Here's a variation that uses patch() to draw each pixel as a little polygon floating in space at the Z level of its index in the image sequence. I think this will look more like the "surface" style plots you are asking for. You could fiddle with the FaceAlpha property to make dense plots more legible.
function plotImageSequencePatch
% Made-up test data
nLayers = 6;
sz = [50 50];
img = zeros(sz(1),sz(2),nLayers);
for i = 1:nLayers
img(20+(3*i),:,i) = 1;
end
% Plot each image as a "layer", indicated by color
% With each "pixel" as a separate patch
figure;
set(gca, 'XLim', [0 sz(1)]);
set(gca, 'YLim', [0 sz(2)]);
hold on;
for i = 1:nLayers
layerData = img(:,:,i);
[x,y] = find(layerData); % X,Y of all pixels
% Reshape in to patch outline
x = x';
y = y';
patch_x = [x; x+1; x+1; x];
patch_y = [y; y; y+1; y+1];
patch_z = repmat(i, size(patch_x));
patch(patch_x, patch_y, patch_z, i);
end
hold off

How can I save an altered image in MATLAB?

I want to read an image into MATLAB, draw a rectangle on it, and then save the image.
Also, I'm just learning MATLAB--please be gentle. It seems like it should be simple, but I can't seem to do it.
im = imread('image.tif');
imshow(im);
rectangle('Position', [100, 100, 10, 10]);
imwrite(im, 'image2.tif');
Even though I can see the rectangle on the image, the saved image does not display the rectangle. How can I save the image and have the rectangle show up?
FWIW, I've already tried saveas(), but that gives me a HUGE image. Is there a way to use saveas() and make the saved image the correct size?
The reason the rectangle doesn't show up in the saved image is because you are not modifying the variable im, which stores the image data. The rectangle is simply a plot object displayed over the plotted image. You have to modify the image data itself.
Typically, images read into MATLAB are loaded as an N-by-M-by-3 matrix (i.e. an N-by-M pixel image with RGB (red-green-blue) values for each pixel). Usually, the image data is a uint8 data type, so the RGB values range from 0 to 255. If you wanted to change the RGB value for a given pixel, you would do the following:
im = imread('test.jpg'); % Load a jpeg image
im(1,1,1) = 255; % Change the red value for the first pixel
im(1,1,2) = 0; % Change the green value for the first pixel
im(1,1,3) = 0; % Change the blue value for the first pixel
imwrite(im,'new.jpeg'); % Save modified image
There are different ways you can modify more than one pixel at a time (i.e. a rectangular area), which will require that you look into how to index into multidimensional arrays. For more detail about how different types of images are read into MATLAB (i.e. truecolor vs. indexed), I would check the documentation for imread.
to the question in the top, there is quite a simple solution provided by matlab:
% you so far
im = imread('image.tif');
imshow(im);
rectangle('Position', [100, 100, 10, 10]);
% now you use "getframe" and "frame2im"
f = getframe(gca);
im = frame2im(f);
imwrite(im,'image2.tif');
that worked great for me when i also drew a rectangle on an image and tried to save it. If you want to keep on working with it, just add
imread('image2.tif');
and keep on working with it :)
Regards, Laura
There's actually a bug at The MathWorks site about this issue. Too bad they don't spell out a real answer (as, IMHO, holding up a ruler to your monitor is not a real solution).
Using the print command, you must manually change the -r parameter until the size of the saved image matches the size of the input image. The -r parameter specifies the DPI of the saved image. Since most screens have different DPIs, there's no one-size-fits-all solution.
im = imread('image.tif');
f = figure, imshow(im, 'Border', 'tight');
rectangle('Position', [100, 100, 10, 10]);
print(f, '-r80', '-dtiff', 'image2.tif');
Use the code above, tweak the -r parameter until it looks right, and voilà!
following up to jacobko answer. Setting the figures paperposition and paperunits properties and the axis units and position properties usually gives me the desired results without having to tweak the resolution. So,
>> im = imread('image.tif');
>> f = figure, imshow(im);
>> r=rectangle('Position',[100, 100,10,10]);
>> set(r,'edgecolor','b') % change the color of the rectangle to blue
>> set(f,'units','centimeters','position',[1 1 2.5 2.5]) % set the screen size and position
>> set(f,'paperunits','centimeters','paperposition',[1 1 2.5 2.5]) % set size and position for printing
>> set(gca,'units','normalized','position',[0 0 1 1]) % make sure axis fills entire figure
>> print(f, '-r80','-dtiff','image2.tif')
The output image, image2.tif, will now be 2.5cm by 2.5cm at a resoultion of 80dpi without the border around the axis.
If you want to save im, you must first modify its value.
I am not familiar with the rectangle function,
but you can do the following (brute force):
im = imread('image.tif');
im(100:110,100)=0;
im(100:110,110)=0;
im(100,100:110)=0;
im(110,100:110)=0;
imshow(im);
imwrite(im, 'image2.tif');
Note, the code above is for gray scale image, if your image is an RGB image, you will need to do the following:
im(100:110,100,:)=0;
....
You might be able to use getframe to grab the modified image from the figure window. I think you could pass the cdata and colormap fields of the structure returned by getframe to imwrite as the image and its colormap, respectively.
[f,p] = uigetfile('*.*');
I = imread([p,f]);
imwrite(I,'img12.tif');%
Any name we can give for saving the image
Automatically it will save in your folder and you can browse any image.
close all; clear; clc;
r = 240 ; c = 320;
fig = figure('Visible', 'off');
imshow( zeros(r,c) );
hold on;
plot([c-fix(c/2),c-fix(c/2)],[r-fix(r/2),r-fix(r/2)],'r*', 'MarkerSize', 10 );
% Sets position and size of figure on the screen
set(fig, 'Units', 'pixels', 'position', [100 100 c r] );
% Sets axes to fill the figure space
set(gca, 'Units', 'pixels', 'position', [0 0 c+1 r+1 ]);
% Sets print properties; Looks like 1 pixel = (3/4)th of a point
set(fig, 'paperunits', 'points', 'papersize', [fix((c-1)*(3/4))+1 fix((r-1)*(3/4))+1]);
set(fig, 'paperunits', 'normalized', 'paperposition', [0 0 1 1]);
print( fig, sprintf('-r%d', ceil(72*(4/3))), '-dpng', 'image.png' );
im = imread( 'image.png');
figure; imshow(im);