MATLAB - Pixelize a plot and make it into a heatmap - matlab

I have a matrix with x and y coordinates as well as the temperature values for each of my data points. When I plot this in a scatter plot, some of the data points will obscure others and therefore, the plot will not give a true representation of how the temperature varies in my data set.
To fix this, I would like to decrease the resolution of my graph and create pixels which represent the average temperature for all data points within the area of the pixel. Another way to think about the problem that I need to put a grid over the current plot and average the values within each segment of the grid.
I have found this thread - Generate a heatmap in MatPlotLib using a scatter data set - which shows how to use python to achieve the end result that I want. However, my current code is in MATLAB and even though I have tried different suggestions such as heatmap, contourf and imagesc, I can't get the result I want.

You can "reduce the resolution" of your data using accumarray, where you specify which output "bin" each point should go in and specify that you wish to take a mean over all points in that bin.
Some example data:
% make points that overlap a lot
n = 10000
% NOTE: your points do not need to be sorted.
% I only sorted so we can visually see if the code worked,
% see the below plot
Xs = sort(rand(n, 1));
Ys = rand(n, 1);
temps = sort(rand(n, 1));
% plot
colormap("hot")
scatter(Xs, Ys, 8, temps)
(I only sorted by Xs and temps in order to get the stripy pattern above so that we can visually verify if the "reduced resolution" worked)
Now, suppose I want to decrease the resolution of my data by getting just one point per 0.05 units in the X and Y direction, being the average of all points in that square (so since my X and Y go from 0 to 1, I'll get 20*20 points total).
% group into bins of 0.05
binsize = 0.05;
% create the bins
xbins = 0:binsize:1;
ybins = 0:binsize:1;
I use histc to work out which bin each X and Y is in (note - in this case since the bins are regular I could also do idxx = floor((Xs - xbins(1))/binsize) + 1)
% work out which bin each X and Y is in (idxx, idxy)
[nx, idxx] = histc(Xs, xbins);
[ny, idxy] = histc(Ys, ybins);
Then I use accumarray to do a mean of temps within each bin:
% calculate mean in each direction
out = accumarray([idxy idxx], temps', [], #mean);
(Note - this means that the point in temps(i) belongs to the "pixel" (of our output matrix) at row idxy(1) column idxx(1). I did [idxy idxx] as opposed to [idxx idxy] so that the resulting matrix has Y == rows and X == columns))
You can plot like this:
% PLOT
imagesc(xbins, ybins, out)
set(gca, 'YDir', 'normal') % flip Y axis back to normal
Or as a scatter plot like this (I plot each point in the midpoint of the 'pixel', and drew the original data points on too for comparison):
xx = xbins(1:(end - 1)) + binsize/2;
yy = ybins(1:(end - 1)) + binsize/2;
[xx, yy] = meshgrid(xx, yy);
scatter(Xs, Ys, 2, temps);
hold on;
scatter(xx(:), yy(:), 20, out(:));

Related

Matlab plot: equal distance between ticks

I have this 3D plot that I'm making but the data are not linear. This implies that on my plot, the distance between the ticks that I want to show is not equal. How can I adapt the scale of the x and y axis so that this is the case, i.e. so that the axis gets divided into equal parts with the current ticks?
I want the same ticks and tick labels, but that they just have an equal distance in between them on the axes, in stead of small between 0.1 and 0.5 and large between 1 and 5.
The current plot looks like this:
RMSEval = xlsread('RMSEvalues.xlsx');
X = RMSEval(:,1);
Y = RMSEval(:,2);
Z = RMSEval(:,3);
figure(1);
xi = linspace(min(X),max(X),30);
yi= linspace(min(Y),max(Y),30);
[XI,YI] = meshgrid(xi,yi);
ZI = griddata(X,Y,Z,XI,YI);
contourf(XI,YI,ZI);
colormap('jet');
xticks([1e-13 5e-13 1e-12 5e-12 1e-11]);
yticks([1e-18 5e-18 1e-17 5e-17 1e-16]);
colorbar;
A plot where the distance between 0.1 and 0.5 is the same as between 1 and 5 would be a log plot, specifically a log-log plot since you want it on both axes. One way to achieve this would be to transform the X and Y values of your data logarithmically and modify the tick mark labels to match the untransformed values rather than the logarithmic ones you're actually plotting.
A rough guess at a solution is below. I say a rough guess because without posting the data you're importing from the xlsx file or a paired down version of that data (as in an MWE) I can't actually test it.
RMSEval = xlsread('RMSEvalues.xlsx');
X = log(RMSEval(:,1));
Y = log(RMSEval(:,2));
Z = RMSEval(:,3);
figure(1);
xi = linspace(min(X),max(X),30);
yi= linspace(min(Y),max(Y),30);
[XI,YI] = meshgrid(xi,yi);
ZI = griddata(X,Y,Z,XI,YI);
contourf(XI,YI,ZI);
colormap('jet');
xticks(log([1e-13 5e-13 1e-12 5e-12 1e-11]));
xticklabels(cellfun(#num2str,num2cell(),'UniformOutput',false));
yticks(log([1e-18 5e-18 1e-17 5e-17 1e-16]));
yticklabels(cellfun(#num2str,num2cell([1e-18 5e-18 1e-17 5e-17 1e-16]),'UniformOutput',false));
colorbar;

In Matlab, how to draw lines from the curve to specific xaxis position?

I have a spectral data (1000 variables on xaxis, and peak intensities as y) and a list of peaks of interest at various specific x locations (a matrix called Peak) which I obtained from a function I made. Here, I would like to draw a line from the maximum value of each peaks to the xaxis - or, eventually, place a vertical arrow above each peaks but I read it is quite troublesome, so just a vertical line is welcome. However, using the following code, I get "Error using line Value must be a vector of numeric type". Any thoughts?
X = spectra;
[Peak,intensity]=PeakDetection(X);
nrow = length(Peak);
Peak2=Peak; % to put inside the real xaxis value
plot(xaxis,X);
hold on
for i = 1 : nbrow
Peak2(:,i) = round(xaxis(:,i)); % to get the real xaxis value and round it
xline = Peak2(:,i);
line('XData',xline,'YData',X,'Color','red','LineWidth',2);
end
hold off
Simple annotation:
Here is a simple way to annotate the peaks:
plot(x,y,x_peak,y_peak+0.1,'v','MarkerFaceColor','r');
where x and y is your data, and x_peak and y_peak is the coordinates of the peaks you want to annotate. The add of 0.1 is just for a better placing of the annotation and should be calibrated for your data.
For example (with some arbitrary data):
x = 1:1000;
y = sin(0.01*x).*cos(0.05*x);
[y_peak,x_peak] = PeakDetection(y); % this is just a sketch based on your code...
plot(x,y,x_peak,y_peak+0.1,'v','MarkerFaceColor','r');
the result:
Line annotation:
This is just a little bit more complicated because we need 4 values for each line. Again, assuming x_peak and y_peak as before:
plot(x,y);
hold on
ax = gca;
ymin = ax.YLim(1);
plot([x_peak;x_peak],[ymin*ones(1,numel(y_peak));y_peak],'r')
% you could write instead:
% line([x_peak;x_peak],[ymin*ones(1,numel(y_peak));y_peak],'Color','r')
% but I prefer the PLOT function.
hold off
and the result:
Arrow annotation:
If you really want those arrows, then you need to first convert the peak location to the normalized figure units. Here how to do that:
plot(x,y);
ylim([-1.5 1.5]) % only for a better look of the arrows
peaks = [x_peak.' y_peak.'];
ax = gca;
% This prat converts the axis unites to the figure normalized unites
% AX is a handle to the figure
% PEAKS is a n-by-2 matrix, where the first column is the x values and the
% second is the y values
pos = ax.Position;
% NORMPEAKS is a matrix in the same size of PEAKS, but with all the values
% converted to normalized units
normpx = pos(3)*((peaks(:,1)-ax.XLim(1))./range(ax.XLim))+ pos(1);
normpy = pos(4)*((peaks(:,2)-ax.YLim(1))./range(ax.YLim))+ pos(2);
normpeaks = [normpx normpy];
for k = 1:size(normpeaks,1)
annotation('arrow',[normpeaks(k,1) normpeaks(k,1)],...
[normpeaks(k,2)+0.1 normpeaks(k,2)],...
'Color','red','LineWidth',2)
end
and the result:

How to make a vector that follows a certain trend?

I have a set of data with over 4000 points. I want to exclude grooves from them, ideally from the point from which they start. The data look for example like this:
The problem with this is the noise I get at the top of the plateaus. I have an idea, in which I would take an average value of the most common within some boundaries (again, ideally sth like the red line here:
and then I would construct a temporary matrix, which would fill up one by one with Y if they are less than this average. If the Y(i) would rise above average, the matrix would find its minima and compare it with the global minima. If the temporary matrix's minima wouldn't be sth like 80% of the global minima, it would be discarded as noise.
I've tried using mean(Y), interpolating and fitting it in a polynomial (the green line) - none of those method would cut it to the point I would be satisfied.
I need this to be extremely robust and it doesn't need to be quick. The top and bottom values can vary a lot, as well as the shape of the plateaus. The groove width is more or less the same.
Do you have any ideas? Again, the point is to extract the values that would make the groove.
How about a median filter?
Let's define some noisy data similar to yours, and plot it in blue:
x = .2*sin((0:9999)/1000); %// signal
x(1000:1099) = x(1000:1099) + sin((0:99)/50*pi); %// noise: spike
x(5000:5199) = x(5000:5199) - sin((0:199)/100*pi); %// noise: wider spike
x = x + .05*sin((0:9999)/10); %// noise: high-freq ripple
plot(x)
Now apply the median filter (using medfilt2 from the Image Processing Toolbox) and plot in red. The parameter k controls the filter memory. It should chosen to be large compared to noise variations, and small compared to signal variations:
k = 500; %// filter memory. Choose as needed
y = medfilt2(x,[1 k]);
hold on
plot(y, 'r', 'linewidth', 2)
In case you don't have the image processing toolbox and can't use medfilt2 a method that's more manual. Skip the extreme values, and do a curve fit with sin1 as curve type. Note that this will only work if the signal is in fact a sine wave!
x = linspace(0,3*pi,1000);
y1 = sin(x) + rand()*sin(100*x).*(mod(round(10*x),5)<3);
y2 = 20*(mod(round(5*x),5) == 0).*sin(20*x);
y = y1 + y2; %// A messy sine-wave
yy = y; %// Store the messy sine-wave
[~, idx] = sort(y);
y(idx(1:round(0.15*end))) = y(idx(round(0.15*end))); %// Flatten out the smallest values
y(idx(round(0.85*end):end)) = y(idx(round(0.85*end)));%// Flatten out the largest values
[foo goodness output] = fit(x.',y.', 'sin1'); %// Do a curve fit
plot(foo,x,y) %// Plot it
hold on
plot(x,yy,'black')
Might not be perfect, but it's a step in the right direction.

Sketch f(x,y)=(21/4)x^2y over the region x^2 <= y <= 1

Can someone share a technique using MATLAB to plot the surface f(x,y)=(21/4)x^2y over the region x^2 <= y <= 1?
Also, if anyone is aware of some tutorials or links that would help with this type of problem, could you please share them?
Thanks.
Here is another approach:
%%
close all
x=linspace(-1,1,40);
g1=x.^2;
g2=ones(1,40);
y=[];
n=20;
for k=0:n
y=[y;g1+(g2-g1)*k/n];
end
x=x(ones(1,n+1),:);
z=21/4*x.^2.*y;
meshz(x,y,z)
axis tight
xlabel('x-axis')
ylabel('y-axis')
view(136,42)
And the result:
And finally, you can map the region (-1,1)x(0,1) in the uv-plane into the region bounded by $y=x^2 and y=1 in the xy-plane with the parametrization:
f(u,v) = (u\sqrt{v},v)
Capture from: https://math.stackexchange.com/questions/823168/transform-rectangular-region-to-region-bounded-by-y-1-and-y-x2
This code produces the same image shown above:
close all
[u,v]=meshgrid(linspace(-1,1,40),linspace(0,1,20));
x=u.*sqrt(v);
y=v;
z=21/4*x.^2.*y;
meshz(x,y,z)
axis tight
xlabel('x-axis')
ylabel('y-axis')
view(136,42)
First off, let's look at your valid region of values. This is telling us that y >= x^2 and also y <= 1. This means that your y values need to be on the positive plane bounded by the parabola x^2 and they also must be less than or equal to 1. In other words, your y values must be bound within the area dictated from y = x^2 to y = 1. Pictorially, your y values are bounded within this shape:
As such, your x values must also be bound between -1 and 1. Therefore, your actual boundaries are: -1 <= x <= 1 and 0 <= y <= 1. However, this only locates our boundaries for x and y but it doesn't handle where the plot has valid values. We'll tackle that later.
Now that we have that established, you can use ezsurf to plot surface plots in MATLAB that are dictated by a 2D equation.
You call ezsurf like so:
ezsurf(FUN, [XMIN,XMAX,YMIN,YMAX]);
FUN is a function or a string that contains the equation you want, and XMIN,XMAX,YMIN,YMAX contain the lowest and highest x and y values you want to plot. Plotting without these values assumes a span from -2*pi to 2*pi in both dimensions. As such, let's create a new function that will handle when we have valid values, and when we don't. Use this code, and save it to a new file called myfun.m. Make sure you save this to your current Working Directory.
function z = myfun(x,y)
z = (21/4)*x.^2.*y;
z(~(x.^2 <= y & y <= 1)) = nan;
end
This will allow you to take a series of x and y values and output values that are dictated by the 2D equation that you have given us. Any values that don't satisfy the condition of x^2 <= y <= 1, you set them to NaN. ezsurf will not plot NaN values.
Now, call ezsurf like so:
ezsurf(#myfun, [-1,1,0,1]);
You thus get:
This will spawn a new figure for you, and there are some tools at the top that will allow you interact with your 3D plot. For instance, you can use the rotation tool that's at the top bar beside the hand to rotate your figure around and see what this looks like. Click on this tool, then left click your mouse and hold the left mouse button anywhere within the surface plot. You can drag around, changing the azimuth and the latitude to get the perspective that you want.
Edit: June 4th, 2014
Noting your comments, we can decrease the jagged edges by increasing the number of points in the plot. As such, you can append a final parameter to ezsurf which is N, the number of points to add in each dimension. Increasing the number of points will decrease the width in between each point and so the plot will look smoother. The default value of N is 60 in both dimensions. Let's try increasing the amount of points in each dimension to 100.
ezsurf(#myfun, [-1,1,0,1], 100);
Your plot will look like:
Hope this helps!
Try the following to make the required function, compute the values, and plot only the region that is desired:
% Make the function. You could put this in a file by itself, if you wanted.
f = #(x,y) (21/4)*x.^2.*y;
[X Y] = meshgrid(linspace(0,1));
Z = f(X,Y);
% compute the values we want to plot:
valsToPlot = (X.^2 <= Y) & (Y <= 1);
% remove the values that we don't want to plot:
X(~valsToPlot) = nan;
Y(~valsToPlot) = nan;
Z(~valsToPlot) = nan;
% And... plot.
figure(59382);
clf;
surf(X,Y,Z);

Calculate circular bins around a point + matlab

My question is related to this link stackoverflow ques
In essence repeating the figure drawn there .. I have a central point ( x , y ) in an image around which I have to draw 4 circles of 1-4 unit radius with 8 angles between them.
In this diagram there are 12 angle bins but I have 8. There is a code solution there but it is for plotting the above figure.
I want to calculate the maximum intensity point in each of the 4 regions of each wedge. Is there any inbuilt function in matlab ? I looked at rose but could'nt understand if it would help me....
I would greatly appreciate if someone could help me how to calculate it in matlab....
Thanks
I put some code below that should be the basic skeleton of what you want to do. But I left an important function unimplemented because I think you will be able to do it and it will help you understand this process better.
% I assume that data_points is an M-by-2 array, where each row corresponds
% to an (x,y) coordinate pair, and M is the number of data points.
data_points = ... ;
% I assume this array stores the intensities at each data point.
intensities = ... ;
% I assume that this stores the total number of gridded polar regions you want
% to find the max intensity in (i.e. 4*(number of cells) in your picture above).
total_num_bins = ... ;
% This will store the max intensities. For places that have no nearby
% data points, the max intensity will remain zero.
max_intensities = zeros(total_num_bins);
% I assume these store the values of the center point.
x = ... ; y = ... ;
% The number of different data points.
num_data_points = length(intensities); % also equals size(data_points,1)
% Now, loop through the data points, decide which polar bin they fall in, and
% update the max intensity of that area if needed.
for ii = 1:num_data_points
% Grab the current point coordinates.
cur_x = data_points[ii,1];
cur_y = data_points[ii,2];
% Convert the current data point to polar coordinates,
% keeping in mind that we are treating (x,y) like the center.
cur_radius = sqrt( (cur_x - x)^2 + (cur_y - y)^2 );
cur_angle = atan2(cur_y - y, cur_x - x)
% You have to write this yourself, but it
% will return an index for the bin that this
% data point falls into, i.e. which of the 4 segments
% of one of the radial cells it falls into.
cur_bin = get_bin_number(cur_radius, cur_angle);
% Check if this data point intensity is larger than
% the current max value for its bin.
if ( intensities(ii) >= max_intensities(cur_bin))
max_intensities(cur_bin) = intensities(ii);
end
end
You will now have to make the function get_bin_number() which takes as its input the angle and radius of the data point away from the center point. It should return just an index between 1 and total_num_bins, because you will be keeping the max intensities in a linear array. So, for example, index number 1 might correspond to the first 1/4 piece of the closest radial cell in the upper right quadrant, index 2 might correspond to the next 1/4 of that same cell, moving counter-clockwise, or something like this. You have to devise your own convention for keeping track of the bins.
A late answer, but I believe an even easier solution would just be to convert your data from (x,y) coordinates to (r,theta) by using (r = sqrt(x.^2 + y.^2), theta = atan(y,x)) then use the hist3 function on the (r,theta) data set to get a radial histogram.
Therefore solution is as follows:
% I assume you have some M-by-2 matrix X that's in the form (x,y)
% Convert (x,y) to (r,theta)
xVect = X(:,1);
yVect = X(:,2);
X = [sqrt(xVect.^2 + yVect.^2), ...%formula for r
atan(yVect,xVect)]; %formula for theta
% 5 is the number of wedges along 'r', your radial axis
% 12 is the number of wedges along 'theta', your theta 'axis'
dist = hist3(X,5,12);
Even if you have solved this, I hope this helps anybody else who wants to create a radial/angular histogram!