MATLAB: Plot raster map with custom colormap - matlab

In MATLAB, I have a matrix map_data associated with referencing object R (both in this MAT-file). I want to map it with a discrete colorbar given an irregular range of values to look something like this:
I would like to use geoshow() or something similar that allows me to reproject at will and to overlay shapefiles on top of the raster. But really anything that gets me on the right track would be much appreciated.
I'm using MATLAB r2014b. Here is the relevant information for the colormap:
R G B
0 <= map_data < 0.001 204 204 204
0.001 <= map_data < 0.005 153 153 153
0.005 <= map_data < 0.01 255 255 178
0.01 <= map_data < 0.05 254 204 92
0.05 <= map_data < 0.1 253 141 60
0.1 <= map_data < 0.25 240 59 32
0.25 <= map_data < 0.5 189 0 38
0.5 <= map_data < 1 0 0 0
Cross-posted at MATLAB answers.

MATLAB only has built-in support for linear colormaps. Therefore for nonlinear mappings like this one, you need to transform the values of map_data so that the changes in color are evenly spaced. For a discrete colormap like this one, integer indices are ideal, and you can get them easily using histc:
ranges = [0 0.001 0.005 0.01 0.05 0.1 0.25 0.5 1];
[~,ind] = histc(map_data,ranges);
Use the indices in ind in lieu of the values in map_data as your color data, and then all you need is to apply the specified colors as a colormap. Where you need to label the true map_data values on your colorbar, manually relabel the YTickLabel of the corresponding colorbar.
I don't have the mapping toolbox to demonstrate this with geoshow, but displaying as a straightforward image works as follows:
image(ind)
axis equal tight
set(gca,'YDir','normal')
colormap([204 204 204
153 153 153
255 255 178
254 204 92
253 141 60
240 59 32
189 0 38
0 0 0]/255);
h = colorbar;
h.YTickLabel = edges(h.YTick)*100;
Which results in the following:

Will had a great idea to use histc(), but I had to edit his code to make it work for me. Here's what I ended up with.
my_colormap = [204 204 204
153 153 153
255 255 178
254 204 92
253 141 60
240 59 32
189 0 38
0 0 0]/255 ;
binEdges = [0 0.001 0.005 0.01 0.05 0.1 0.25 0.5 1] ;
labels = textscan(num2str(binEdges*100),'%s') ;
labels = labels{1} ;
labels{length(labels)} = [labels{length(labels)} '%'] ;
[~,indices] = histc(map_data,binEdges);
indices(isnan(map_data)) = NaN ;
indices(isinf(map_data)) = NaN ;
figure ;
pcolor(indices-1) % Instead of image(), to display NaN values as white
shading flat
axis equal tight
colormap(gca,my_colormap); % gca as first argument prevents
% colormap from changing for all
% subsequent plots
h = colorbar;
caxis([0 length(binEdges)-1])
h.YTickLabel = labels ;

Related

How can I plot heatmap of Wi-Fi signal strength in a 2-D surface?

I've my dataset as follows (for instance):
strength = [-90 -90 -90 -90 -40 -20 -22.4 -45 -35 -41 -44 -55 -40 -75 -26]
X = [10 550 550 10 50 234 393 129 237 328 448 225 344 457 477]
Y = [10 10 410 410 293 210 202 132 130 142 141 272 268 274 200]
Here, strength is the received signal strength in dBm, X and Y are 2-D coordinates. I want to plot the heatmap of the signal strength at all the coordinate points.
My situation is similar to this question. However, I want to plot the received signal strength and in Matlab, as shown in the attached figure (because of picture quality). A solution in Matlab is found here, however, the code given in the link does not work. The Command Window of Matlab displays HeatMap object with 0 rows and 0 columns. The available code in Matlab is as follows:
strength = [-90 -90 -90 -90 -40 -20 -22.4 -45 -35 -41 -44 -55 -40 -75 -26]';
X = [10 550 550 10 50 234 393 129 237 328 448 225 344 457 477]';
Y = [10 10 410 410 293 210 202 132 130 142 141 272 268 274 200]';
strengthPercent = 2*(strength+100)/100;
picture = imread('https://www.mathworks.com/help/examples/thingspeak/win64/CreateHeatmapOverlayImageTSExample_02.png');
[height,width,depth] = size(picture);
OverlayImage=[];
F = scatteredInterpolant(Y, X, strengthPercent,'linear');
for i = 1:height-1
for j = 1:width-1
OverlayImage(i,j) = F(i,j);
end
end
alpha = (~isnan(OverlayImage))*0.6;
imshow(picture);
hold on
OverlayImage = imshow( OverlayImage );
caxis auto
colormap( OverlayImage.Parent, jet );
colorbar( OverlayImage.Parent );
set( OverlayImage, 'AlphaData', alpha );
Any suggestions, please!
Thank you.
You can use scatteredInterpolant as in the code you showed, just pair it with surf
F = scatteredInterpolant(X(:), Y(:), strength(:));
[XD, Yd] = meshgrid( 0:10:410, 0:10:550 );
Zd = F( XD, Yd );
surf( XD, Yd, Zd, 'EdgeColor', 'interp' );
view([0,90]); % view 3D plot from above
You can change the colormap to customise the appearance.
colormap( 'hot' );
colorbar();
You should be able to reduce the FaceAlpha of the surface to lay it over an image.

How do I calculate the area of a 3 dimensional projection?

For example, using the following code, I have a coordinate matrix with 3 cubical objects defined by 8 corners each, for a total of 24 coordinates. I apply a rotation to my coordinates, then delete the y coordinate to obtain a projection in the x-z plane. How do I calculate the area of these cubes in the x-z plane, ignoring gaps and accounting for overlap? I have tried using polyarea, but this doesn't seem to work.
clear all
clc
A=[-100 -40 50
-100 -40 0
-120 -40 50
-120 -40 0
-100 5 0
-100 5 50
-120 5 50
-120 5 0
-100 0 52
-100 0 52
20 0 5
20 0 5
-100 50 5
-100 50 5
20 50 52
20 50 52
-30 70 53
-30 70 0
5 70 0
5 70 53
-30 120 53
-30 120 0
5 120 53
5 120 0]; %3 Buildings Coordinate Matrix
theta=60; %Angle
rota = [cosd(theta) -sind(theta) 0; sind(theta) cosd(theta) 0; 0 0 1]; %Rotation matrix
R=A*rota; %rotates the matrix
R(:,2)=[];%deletes the y column
The first step will be to use convhull (as yar suggests) to get an outline of each projected polygonal region. It should be noted that a convex hull is appropriate to use here since you are dealing with cuboids, which are convex objects. I think you have an error in the coordinates for your second cuboid (located in A(9:16, :)), so I modified your code to the following:
A = [-100 -40 50
-100 -40 0
-120 -40 50
-120 -40 0
-100 5 0
-100 5 50
-120 5 50
-120 5 0
-100 0 52
-100 0 5
20 0 52
20 0 5
-100 50 5
-100 50 52
20 50 5
20 50 52
-30 70 53
-30 70 0
5 70 0
5 70 53
-30 120 53
-30 120 0
5 120 53
5 120 0];
theta = 60;
rota = [cosd(theta) -sind(theta) 0; sind(theta) cosd(theta) 0; 0 0 1];
R = A*rota;
And you can generate the polygonal outlines and visualize them like so:
nPerPoly = 8;
nPoly = size(R, 1)/nPerPoly;
xPoly = mat2cell(R(:, 1), nPerPoly.*ones(1, nPoly));
zPoly = mat2cell(R(:, 3), nPerPoly.*ones(1, nPoly));
C = cell(1, nPoly);
for iPoly = 1:nPoly
P = convhull(xPoly{iPoly}, zPoly{iPoly});
xPoly{iPoly} = xPoly{iPoly}(P);
zPoly{iPoly} = zPoly{iPoly}(P);
C{iPoly} = P([1:end-1; 2:end].')+nPerPoly.*(iPoly-1); % Constrained edges, needed later
end
figure();
colorOrder = get(gca, 'ColorOrder');
nColors = size(colorOrder, 1);
for iPoly = 1:nPoly
faceColor = colorOrder(rem(iPoly-1, nColors)+1, :);
patch(xPoly{iPoly}, zPoly{iPoly}, faceColor, 'EdgeColor', faceColor, 'FaceAlpha', 0.6);
hold on;
end
axis equal;
axis off;
And here's the plot:
If you wanted to calculate the area of each polygonal projection and add them up it would be very easy: just change the above loop to capture and sum the second output from the calls to convexhull:
totalArea = 0;
for iPoly = 1:nPoly
[~, cuboidArea] = convhull(xPoly{iPoly}, zPoly{iPoly});
totalArea = totalArea+cuboidArea;
end
However, if you want the area of the union of the polygons, you have to account for the overlap. You have a few alternatives. If you have the Mapping Toolbox then you could use the function polybool to get the outline, then use polyarea to compute its area. There are also utilities you can find on the MathWorks File Exchange (such as this and this). I'll show you another alternative here that uses delaunayTriangulation. First we can take the edge constraints C created above to use when creating a triangulation of the projected points:
oldState = warning('off', 'all');
DT = delaunayTriangulation(R(:, [1 3]), vertcat(C{:}));
warning(oldState);
This will automatically create new vertices where the constrained edges intersect. Unfortunately, it will also perform the triangulation on the convex hull of all the points, filling in spots that we don't want filled. Here's what the triangulation looks like:
figure();
triplot(DT, 'Color', 'k');
axis equal;
axis off;
We now have to identify the extra triangles we don't want and remove them. We can do this by finding the centroids of each triangle and using inpolygon to test if they are outside all 3 of our individual cuboid projections. We can then compute the areas of the remaining triangles and sum them up using polyarea, giving us the total area of the projection:
dtFaces = DT.ConnectivityList;
dtVertices = DT.Points;
meanX = mean(reshape(dtVertices(dtFaces, 1), size(dtFaces)), 2);
meanZ = mean(reshape(dtVertices(dtFaces, 2), size(dtFaces)), 2);
index = inpolygon(meanX, meanZ, xPoly{1}, zPoly{1});
for iPoly = 2:nPoly
index = index | inpolygon(meanX, meanZ, xPoly{iPoly}, zPoly{iPoly});
end
dtFaces = dtFaces(index, :);
xUnion = reshape(dtVertices(dtFaces, 1), size(dtFaces)).';
yUnion = reshape(dtVertices(dtFaces, 2), size(dtFaces)).';
totalArea = sum(polyarea(xUnion, yUnion));
And the total area for this example is:
totalArea =
9.970392341143055e+03
NOTE: The above code has been generalized for an arbitrary number of cuboids.
polyarea is the right way to go, but you need to call it on the convex hull of each projection. If not, you will have points in the centers of your projections and the result is not a "simple" polygon.

Find the difference between positive and negative peaks MATLAB

I need to find the difference between positive and negative peaks where the difference is greater than +-3.
I am using findpeaks function in MATLAB to find the positive and negative peaks of the data.
In an example of my code:
[Ypos, Yposloc] = findpeaks(YT0);
[Yneg, Ynegloc] = findpeaks(YT0*-1);
Yneg = Yneg*-1;
Yposloc and Ynegloc return the locations of the positive and negative peaks in the data.
I want to concatenate Ypos and Yneg based on the order of the peaks.
For example, my peaks are
Ypos = [11 6 -10 -10 6 6 6 6 6 -5]
Yneg = [-12 -14 -11 -11 -11 5 5 5 -6]
Locations in YT0
Yposloc = [24 63 79 84 93 95 97 100 156]
Ynegloc = [11 51 78 81 85 94 96 99 154]
In this case, where both Yposloc and Ynegloc are 9x1, I can do the following;
nColumns = size(Yposs,2);
YTT0 = [Yneg, Ypos]';
YTT0 = reshape(YTT0(:),nColumns,[])';
YTT0 = diff(YTT0)
YT0Change = numel(YTT0(YTT0(:)>=3 | YTT0(:)<=-3));
Total changes that I am interested is 6
However, I need to concatenate Yneg and Ypos automatically, based on their locations. So I think I need to to do an if statement to figure out if my positive or negative peaks come first? Then, I am not sure how to tackle the problem of when Ypos and Yneg are different sizes.
I am running this script multiple times where data changes and the negative/positive peak order are constantly changing. Is there a simple way I can compare the peak locations or am I on the right track here?
I would check each minimum with both the previous and the next maxima. In order to do that you can first combine positive and negative peaks according to their order:
Y = zeros(1, max([Yposloc, Ynegloc]));
Yloc = zeros(size(Y));
Yloc(Yposloc) = Yposloc;
Yloc(Ynegloc) = Ynegloc;
Y(Yposloc) = Ypos; % I think you inserted one extra '6' in your code!
Y(Ynegloc) = Yneg;
Y = Y(Yloc ~= 0) % this is the combined signal
Yloc = Yloc(Yloc ~= 0) % this is the combined locations
% Y =
%
% -12 11 -14 6 -11 -10 -11 -10 -11 6 5 6 5 6 5 6 -6 -5
%
% Yloc =
%
% 11 24 51 63 78 79 81 84 85 93 94 95 96 97 99 100 154 156
And then calculate the differences:
diff(Y)
% ans =
%
% 23 -25 20 -17 1 -1 1 -1 17 -1 1 -1 1 -1 1 -12 1
If you want changes of at least 6 units:
num = sum(abs(diff(Y)) > 6)
% num =
%
% 6
Ypos = [11 6 -10 -10 6 6 6 6 -5];
Yneg = [-12 -14 -11 -11 -11 5 5 5 -6];
Yposloc = [24 63 79 84 93 95 97 100 156];
Ynegloc = [11 51 78 81 85 94 96 99 154];
TOTAL=[Yposloc Ynegloc;Ypos Yneg];
%creatin a vector with positions in row 1 and values in row 2
[~,position]=sort(TOTAL(1,:));
%resort this matrix so the values are in the orginial order
TOTAL_sorted=TOTAL(:,position);
%look at the changes of the values
changes=diff(TOTAL_sorted(2,:));
if changes(1)>0
disp('First value was a Minimum')
else
disp('First value was a MAximum')
end
%same structure at the TOTAL matrix
%abs(changes)>3 produces a logical vector that shows where the absolute values was bigger
%than 3, in my opinon its rather intresting where the end is then were the start is
% thats why i add +1
Important_changes=TOTAL_sorted(:,find(abs(changes)>3)+1);
plot(TOTAL_sorted(1,:),TOTAL_sorted(2,:))
hold on
plot(Important_changes(1,:),Important_changes(2,:),...
'Marker','o','MarkerSize',10, 'LineStyle','none');
hold off

Fast way to find the neighboor of pixel

I am programming for task that finds the neighbor of a given pixel x in image Dthat can formula as:
The formula shown pixels y which satisfy the distance to pixel x is 1, then they are neighbor of pixel x. This is my matlab code. However, it still takes long time to find. Could you suggest a faster way to do it. Thank you so much
%-- Find the neighborhood of one pixel
% x is pixel coordinate
% nrow, ncol is size of image
function N = find_neighbor(x,nrow,ncol)
i = x(1);
j = x(2);
I1 = i+1;
if (I1 > nrow)
I1 = nrow;
end
I2 = i-1;
if (I2 < 1)
I2 = 1;
end
J1 = j+1;
if (J1 > ncol)
J1 = ncol;
end
J2 = j-1;
if (J2 < 1)
J2 = 1;
end
N = [I1, I2, i, i; j, j, J1, J2];
For example: ncol=128; nrow=128; x =[30;110] then output
N =31 29 30 30; 110 110 111 109]
For calling the function in loop
x=[30 31 32 33; 110 123 122 124]
for i=1:length(x)
N = find_neighbor(x(:,i),nrow,ncol);
end
Here's a vectorized approach using bsxfun:
% define four neighbors as coordinate differences
d = [-1 0 ; 1 0 ; 0 -1 ; 0 1]';
% add to pixel coordinates
N = bsxfun(#plus, x, permute(d, [1 3 2]));
% make one long list for the neighbors of all pixels together
N = reshape(N, 2, []);
% identify out-of-bounds coordinates
ind = (N(1, :) < 1) | (N(1, :) > nrow) | (N(2, :) < 1) | (N(2, :) > ncol);
% and remove those "neighbors"
N(:, ind) = [];
The permute is there to move the "dimension" of four different neighbors into the 3rd array index. This way, using bsxfun, we get the combination of every pair of original pixel coordinates with every pair of relative neighbor coordinates. The out-of-bounds check assumes that nrow belongs to the first coordinate and ncol to the second coordinate.
With
ncol=128;
nrow=128;
x = [30 31 32 33; 110 123 122 124];
the result is
N =
29 30 31 32 31 32 33 34 30 31 32 33 30 31 32 33
110 123 122 124 110 123 122 124 109 122 121 123 111 124 123 125
Different neighbors of different pixels can end up to be the same pixel, so there can be duplicates in the list. If you only want each resulting pixel once, use
% remove duplicates?
N = unique(N', 'rows')';
to get
N =
29 30 30 30 31 31 31 32 32 32 33 33 33 34
110 109 111 123 110 122 124 121 123 124 122 123 125 124
Matlab's performance is horrible when calling small functions many time. The Matlab approach is to do vectorize as much as possible. A vectorized version of your code:
function N = find_neighbor(x,nrow,ncol)
N = [min(x(1,:)+1,nrow), max(x(1,:)-1,1), x(1,:), x(1,:); x(2,:), x(2,:),min(x(2,:)+1,ncol), max(x(2,:)-1,1)];
end
and usage
x=[30 31 32 33; 110 123 122 124]
N = find_neighbor(x,nrow,ncol);
BTW, for pixels on the border , your solution always gives 4 neighbors. This is wrong. the neighbors of (1,1) for examples should be only (2,1) and (1,2), while you add two extra (1,1).
The solution to this is quite simple - delete all neighbors that are outside the image
function N = find_neighbor(x,nrow,ncol)
N = [x(1,:)+1, x(1,:)-1, x(1,:), x(1,:); x(2,:), x(2,:),x(2,:)+1, x(2,:)-1];
N(:,N(1,:)<1 | N(1,:)> nrow | N(2,:)<1 | N(2,:)>ncol)=[];
end

Matlab subtract without rounding off negative number to zero

I have two arrays,
X = uint8 ([ 255 0 75; 44 225 100]);
Y = uint8 ([ 50 50 50; 50 50 50]);
When I perform X-Y, I get the result as
205 0 25
0 175 50
What I expect is
205 -50 25
-6 175 50
How to achieve this. Kindly help.
uint8 is can only contain values between 0 and 255 - it can't contain negative values. Use a signed data type (one without a u as its first letter).
Incidentally, do you have a good reason to specify the data type at all?