This question is following on from a previous question about HSV color space.
Let's say I have two arrays A and B, where A are my data points (2D) of interest to be shown in the colorbar and B is an RGB image transformed from the HSV color space where: Hue is in the interval [0.25-1] (corresponding to normalized A values 0.25-1), Saturation = 1, Value in interval [0-1] (corresponding to some other values).
When displaying B with imshow, I want to create a matching colorbar with ticks that correspond to the value range from A.
First difficulty that I'm facing is that I want my Hue to be in the interval [0.25-1] and hence I only need a certain part of the hsv colorbar to be displayed.
Second difficulty is that I need to match the value range from A to the colorbar.
Example code:
A = rand(30,30)*0.4; % Values range from 0 - 0.4
X = rand(30,30)*100+100; % Values range from 100 - 200
A_n = A / (max(A(:))/0.75) + 0.25; % "Normalize", with range 0.25 - 1
X_n = X / max(X(:)); % Normalize, range 0 - 1
colorRGB = NaN([size(A),3]); % preallocate
for ii = 1:size(A,1)
for jj = 1:size(A,2)
colorRGB(ii,jj,:) = hsv2rgb([A_n(ii,jj),1,X_n(ii,jj)]); % turn into RGB
end
end
imshow(colorRGB), % display image
colormap hsv; cb = colorbar();
In the example you can see that the colourbar covers the whole hsv range and has ticks from 0 - 1.
What I want it to be is showing only the upper 75% of the hsv range with ticks from 0 to max(A(:))
The correct colorbar assuming that max(A(:)) = 0.35 should look like this:
(you can see that I just cropped it, but that should not be necessary either)
In order to do that you need 2 things. First crop the colorbar, bu setting its limits. Secondly, change the text in the labels of the colobar, but to make sure they are in the rigth places, you also need to set the positions of them manually. Hopefully the code makes sense:
cb = colorbar();
set(cb, 'ylim', [25 100])
set(cb, 'XTick', [25:15:100]) % modify values if you preffer
set(cb,'XTickLabel',strsplit(num2str([0.25:0.15:1])));
Related
z is 200*200 array and I have a surf plot using Matlab surf(x,y,z). I am trying to plot the surf plot so that when z<10 it will be blue and when z>10 it will be red. At the moment the surf plot is like this. Can someone please suggest a way to do this in Matlab?
One way to achieve that (there are several ways) is to use a custom colormap:
Build a colormap with only the color you want to appear in your graph, then just adjust the levels so the midpoint is for Z=10.
The first part of the code shows how to create your custom colormap and apply it:
Zt = 10 ; % threshold level for Z
z = peaks+Zt ; % dummy test data
% build a colormap with your 2 custom color
% [%Red %green %blue]
cmap = [0.79 0.22 0.81 ; % some kind of purple
0 0 1 ] ; % pure blue
surf(z) ; % plot the surface
colormap(cmap) ; % apply the custom colormap
hcb = colorbar ;
This produces a surface with your two chosen colors:
But wait! The separation is not exactly at the Z=10 level. No problem, if we adjust the boundaries of the colormap so your threshold level is bang in the middle, Matlab will take care of adjusting the coloring for us:
%% Now center the colormap boundaries around your threshold level
% get the minimum and maximum
zmax = ceil( max(max(z)) ) ;
zmin = floor( min(min(z)) ) ;
span = max( [zmax-Zt, Zt-zmin] ) ; % calculate the span each side of [Zt]
caxis([Zt-span , Zt+span]) ; % center the colormap around [Zt]
The last bit of code above allow to define an equal span around your chosen threshold level and take the content of the Z data into account. If you know in advance the limits of your data you don't need to do the calculations. On the example above I could also have simply use the last line with some hard coded values:
caxis([0 , 20]) ;
As long as the interval you specify for caxis is centered around your threshold level it will work.
Edit:
To control the labels of the colorbar, I usually set the Ticks and TickLabels after the colorbar (or axes) is created. For this you need the handle of the colorbar object.
Note that in the code above I modified the last line of the first code block. I changed colorbar, to hcb=colorbar;. This way we have the handle of the colorbar, which allow us to set any arbitrary tick and associated label.
The most straightforward way to get your result for this specific example is:
hcb.Ticks = [ 5 , 10 , 15 ] ;
hcb.TickLabels = {'<10','10','>10'} ;
However if you want a more generic solution which can work with any threshold Zt then you can use:
%% adjust colorbar labels
zl = caxis ; % get the limits of the color scale
Zstr = num2str(Zt) ; % get a string representing the threshold
hcb.Ticks = [ (Zt+zl(1))/2 , Zt , (zl(2)+Zt)/2 ] ;
hcb.TickLabels = { ['<' Zstr] , Zstr , ['>' Zstr] } ;
For your example, both options produce:
Adapted from MATLAB Answers
z = peaks+10; % sample data generated between 3.4 and 18.1
% Make colors be red above 10, and blue below 10.
redChannel = z > 10;
greenChannel = 0*z;
blueChannel = z < 10;
% Make the RGB image.
colors = double(cat(3, redChannel, greenChannel, blueChannel));
% Plot the surface with those colors.
surf(z, colors);
try this
surf(x,y,z)
map = [0.0 0.0 1.0
1.0 0.0 0.0];
colormap(map);
caxis([0 20]);
Gnoivce and Hartmut helped a lot with this code but it takes a while to run.
The CData property in the bar command doesn't seem to be implemented in the Octave 4.0-4.2.1 version which I'm using. The work around for this was to plot all the single bars individually and set an individual color for each individual bar. People helped me out and got me this far but it takes 5 minutes for the plot to show does anyone know a way of speeding this up?
The following code runs:
marbles.jpg image file used below:
clear all,clf reset,tic,clc
rgbImage = imread('/tmp/marbles.jpg');
hsvImage = rgb2hsv(rgbImage); % Convert the image to HSV space
hPlane = 360.*hsvImage(:, :, 1); % Get the hue plane scaled from 0 to 360
binEdges = 0:360; %# Edges of histogram bins
N = histc(hPlane(:),binEdges); %# Bin the pixel hues from above
C = colormap(hsv(360)); %# create an HSV color map with 360 points
stepsize = 1; % stepsize 1 runs for a while...
for n=binEdges(2:stepsize:end) %# Plot the histogram, one bar each
if (n==1), hold on, end
h=bar(n,N(n));
set(h,'FaceColor',C(n,:)); %# set the bar color individually
end
axis([0 360 0 max(N)]); %# Change the axes limits
set(gca,'Color','k'); %# Change the axes background color
set(gcf,'Pos',[50 400 560 200]); %# Change the figure size
xlabel('HSV hue (in degrees)'); %# Add an x label
ylabel('Bin counts'); %# Add a y label
fprintf('\nfinally Done-elapsed time -%4.4fsec- or -%4.4fmins- or -%4.4fhours-\n',toc,toc/60,toc/3600);
Plot created after 5 mins:
To see original question original question
I'm guessing the loop is the bottleneck in your code that is taking so long? You could remove the loop and create the plot with one call to bar, then call set to modify the hggroup object and its child patch object:
h = bar(binEdges(1:end-1), N(1:end-1), 'histc'); % hggroup object
set(h, 'FaceColor', 'flat', 'EdgeColor', 'none');
hPatch = get(h, 'Children'); % patch object
set(hPatch, 'CData', 1:360, 'CDataMapping', 'direct');
Repeating your code with this fix renders right away for me in Octave 4.0.3:
As I suggested in a comment, I would use image (takes 0.12s on my system for your image).
EDIT: more comments, fix little bug, allow to create bins with stepsize > 1
img_fn = "17S9PUK.jpg";
if (! exist (img_fn, "file"))
disp ("downloading image from imgur.com...");
fflush (stdout);
urlwrite ("http://i.imgur.com/17S9PUK.jpg", "17S9PUK.jpg");
endif
rgbImage = imread (img_fn);
## for debugging so the matrixes fit on screen
if (0)
pkg load image
rgbImage = imresize (rgbImage, [6 8]);
endif
hsvImage = rgb2hsv(rgbImage);
hPlane = 360 .* hsvImage(:, :, 1);
## create bins, I've choosen 2 step to "smooth" the result
binEdges = 1:2:360;
N = histc (hPlane(:), binEdges)';
cm = permute (hsv (numel (binEdges)), [3 1 2]);
## Create an image with x = hue
img = repmat (cm, max(N), 1);
## Create binary mask which is used to black "img" dependent on N
sp = sparse (N(N > 0), (1:360)(N > 0), true, max(N), numel (binEdges));
mask = full (cumsum (flipud (sp)));
## extend mask in depth to suppress RGB
mask = repmat (mask, [1 1 3]);
## use inverted mask to "black out" pixels < N
img(logical (1 - flipud (mask))) = 0;
## show image
image (binEdges, 1:max(N), img)
set (gca, "ydir", "normal");
xlabel('HSV hue (in degrees)');
ylabel('Bin counts');
## print it for stackoverflow
print ("out.png")
Same as above but with bin width 1 (Elapsed time is 0.167423 seconds.)
binEdges = 1:360;
I read in an image in MATLAB and display it using imagesc. I then set the colormap to grey.
On top of this image, I plot points with the jet colour scheme. How do I then display the jet colorbar, to correspond to the colours of the points plotted on top of the original image? I tried redefining the colorbar after all of the plots, but this changes the original grey scale image back to colours, which isn't desired.
Code:
%Create Figure with handle.
h5=figure('units','normalized','outerposition',[0 0 1 1]);
whitebg(h5,[0 0 0]);
subplot(2,5,1);
k=1;
for i=16:25
subplot(2,5,k);
imagesc(squeeze(ana(:,:,i)));
title(['Z=',num2str(i)]);
colormap gray
axis equal
k=k+1;
colorbar
end
%Adapt colour values so that they are between 0 and 1. We want to scale
%both data sets equally, so we find the smallest value across Ix and Iy. We
%also find what will be the new largest value across Ix and Iy, after we
%add the magnitude of the smallest value to make all numbers greater than
%or equal to 0.
absolutemin=min(min(Ix(:,1)),min(Iy(:,1)));
absolutemax=max(abs(absolutemin)+(max(Ix(:,1))),abs(absolutemin)+max(Iy(:,1)));
%Add the smallest value, and divide by the largest maximum value for both Ix
%and Iy.
ixcolours=uint8(((Ix(:,1)+abs(absolutemin))/absolutemax).*255)+1;
iycolours=uint8(((Iy(:,1)+abs(absolutemin))/absolutemax).*255)+1;
mycolours=jet(256);
o=1;
for k=16:25; %For all 3D slices
for i=1:471; %and for all x and y seed slices
if k==seed_locs(i,3);
subplot(2,5,o);
hold all%go to the corresponding z subplot
plot(seed_locs(i,1),seed_locs(i,2),'MarkerFaceColor',mycolours(ixcolours(i),:),'MarkerEdgeColor',mycolours(ixcolours(i),:),'MarkerSize',10,'Marker','s') %plot the x and y seedlocs
%hold on
end
end
for i=1:486;
if k==test_locs(i,3);
subplot(2,5,o);
hold all
plot(test_locs(i,1),test_locs(i,2),'MarkerFaceColor',mycolours(iycolours(i),:),'MarkerEdgeColor',mycolours(iycolours(i),:),'MarkerSize',10,'Marker','s') %plot the x and y seedlocs
% hold on
end
end
o=o+1; %go to the next z subplot
end
colormap jet
colorbar
I think the following example can help you to improve your code. You firstly need to define two colormaps:
colormap([cool(64);gray(64)]);
Then let's say we have two different sets of datapoints to plot:
[X,Y,Z] = peaks(25);
h(1) = surf(X,Y,Z);hold on
h(2) = pcolor(X,Y,Z);
So the data is defined with two different handles. Now we need to make the CData using the minimum and maximum values.
cmin = min(Z(:));
cmax = max(Z(:));
C1 = min(64,round((64-1)*(Z-cmin)/(cmax-cmin))+1); % CData for the first datapoints
C2 = 64+C1; % CData for the second datapoints
Now we update the CDatas for each object.
set(h(1),'CData',C1);
set(h(2),'CData',C2);
Now you can set the CLim property of axes:
caxis([min(C1(:)) max(C2(:))])
colorbar;
I am trying to make a plot with an intensity that varies over time:
[X,Y] = meshgrid(-30:.1:30);
figure;
colormap(bone);
for t = 0:0.1:2*pi
R = sqrt(X.^2 + Y.^2);
Z = cos(t)*abs(besselj(2,R));
surf(Z,'EdgeColor','None');
view(90,90);
axis([0 600 0 600 -0.5 0.5])
pause(0.1);
end
I want to look at this from the top, such that as the Z value changes, the color changes. The problem is that rather than having an absolute scale (black = -0.5, white = 0.5), the color scale is relative to the maximum and minimum values, such that the colors only change when the sign flips change. How can I set an absolute scale for the color map?
Thank you.
You have to use scaled colour mapping mode and set the limits of the scaling by using the caxis command.
Now the problem with your current code is that you call surf at each iteration of the loop, essentially destroying the current plot and generating a new plot each time. This will reset a lot of properties, including the caxis limits to auto. To overcome that, simply create your plot only once before the loop, then in the loop you only change the properties which are modified (the Z values in this case). This way everything else stays the same in the figure.
So you code becomes:
%% // Prepare and initialize the surface plot
[X,Y] = meshgrid(-30:.1:30);
R = sqrt(X.^2 + Y.^2) ; %// this doesn't need to be in the loop
Z = cos(0)*abs(besselj(2,R)) ; %// calculate initial value to create the surface
surfHandle = surf( Z , 'EdgeColor','None' ) ; %// create a first surface, and save the handle to the surface object
colormap(bone);
colorbar %// this is optional, just to make sure the colorbar does not vary
caxis([-5 5 ] ) ; %// this is what sets the color scaling to what you want
view(90,90);
axis([0 600 0 600 -0.5 0.5]) ; %// this doesn't need to be in the loop anymore
%% // Modify and update the surface plot
for t = 0:pi/100:2*pi
Z = cos(t)*abs(besselj(2,R));
set( surfHandle , 'ZData' , Z )
drawnow
pause(0.01);
end
Read coloring-mesh-and-surface-plots for more info on how surfaces can be colored.
If you just want white for values less than 0 and black for values greater than 0, you ca simply do:
surf(Z,sign(Z),'EdgeColor','None');
which uses the optional C argument to surf, telling Matlab to colour the plot depending on the values of C, not Z. sign(Z) is a matrix that has 1's where Z>0, 0's where Z=0, and -1's where Z<0.
I have to create a map to show how far o how close some values are from a range and give them colors in consequence. Meanwhile, values that are within that range should have another different color.
For example: only the results that are within [-2 2] can be considered valid. For the other values, colors must show how far are from these limits (-3 lighter than -5, darker)
I've tried with colorbar but I'm not able to set up a self-defined color scale.
Any idea??
Thanks in advance!
You need to define a colormap for the range of values you have.
The colormap is N*3 matrix, defining the RGB values of each color.
See the example below, for a range -10:10 and valid values v1,v2:
v1=-3;
v2=+3;
a = -10:10;
graylevels=[...
linspace(0,1,abs(-10-v1)+1) , ...
ones(1, v2-v1-1) , ...
linspace(1,0,abs(10-v2)+1)];
c=repmat(graylevels , [3 1])';
figure;
imagesc(a);
colormap(c);
Here is some code that I just put together to demonstrate a simple means of creating your own lookup table and assigning values from it to the image that you're working with. I'm assuming that your results are in a 2D array and I just used randomly assigned values, but the concept is the same.
I mention the potentila use of HSV as a coloring scheme. Just note that, that requires you to have a m by n by 3 matrix. The top layer is the H - hue, 2nd being the S - saturation and the 3rd being the V or value (light/dark). Simply set the H and S to whatever values you want for the color and vary the V in a similar manner as shown below and you can get the varied light and dark color you want.
% This is just assuming a -10:10 range and randomly generating the values.
randmat = randi(20, 100);
randmat = randmat - 10;
% This should just be a black and white image. Black is negative and white is positive.
figure, imshow(randmat)
% Create your lookup table. This will illustrate having a non-uniform
% acceptable range, for funsies.
vMin = -3;
vMax = 2;
% I'm adding 10 here to account for the negative values since matlab
% doesn't like the negative indicies...
map = zeros(1,20); % initialized
map(vMin+10:vMax+10) = 1; % This will give the light color you want.
%linspace just simply returns a linearly spaced vector between the values
%you give it. The third term is just telling it how many values to return.
map(1:vMin+10) = linspace(0,1,length(map(1:vMin+10)));
map(vMax+10:end) = linspace(1,0,length(map(vMax+10:end)));
% The idea here is to incriment through each position in your results and
% assign the appropriate colorvalue from the map for visualization. You
% can certainly save it to a new matrix if you want to preserve the
% results!
for X = 1:size(randmat,1)
for Y = 1:size(randmat,2)
randmat(X,Y) = map(randmat(X,Y)+10);
end
end
figure, imshow(randmat)