Calculating the curvature of a closed curve ( or polygon) in matlab - matlab

Consider the following set of points
x = [1.34, 0.92, 0.68, 0.25, -0.06, -0.34, -0.49, -0.72, -0.79, -0.94, -1.35, -0.35, 0.54, 0.68, 0.84, 1.20, 1.23, 1.32, 1.34];
y = [0.30, 0.43, 0.90, 1.40, 1.13, 1.08, 1.14, 1.23, 0.52, 0.21, -0.20, -0.73, -0.73, -0.82, -0.71, -0.76, -0.46, -0.13, 0.30];
That give a closed curve (or polygon):
figure(1)
hold on
plot(x,y,'k');
scatter(x,y,'r');
xlim([-2 2]);
ylim([-2 2]);
axis equal
I wish to calculate the curvature (as accurate as possible) at the points on the curve.
what I have until now is a simple calculation of the tangent vector (1st derivate) and then the curvature (2nd derivate):
dsx = diff(x);
dsy = diff(y);
ds = sqrt(dsx.^2+dsy.^2);
Tx = dsx./ds;
Ty = dsy./ds;
ds2 = 0.5*(ds(1:end-1)+ds(2:end));
Hx = diff(Tx)./ds2;
Hy = diff(Ty)./ds2;
But I get a very inaccurate curvature:
figure(1)
quiver(x(1:end-2),y(1:end-2),Hx,Hy,'b','autoscalefactor',1.2);
xlim([-2 2]); ylim([-2 2]);
axis equal
This is suppose to be a simple calculation, however it does not work, please advice: how can I find the curvature in the simplest approximation and have a reasonable accuracy in direction and magnitude?

The curvature computation is correct, it's the plotting that is off. Note that diff computes the difference between subsequent elements, yielding a vector with one fewer element. It estimates the derivative in between pairs of samples. If you repeat this, you'll get the second derivative at samples, but not at the first or last sample (you have 2 fewer elements now).
You did notice this, because you are plotting a curvature at all but one vertex.
So all you need to do is replicate a point after the first derivative (I'm adding the last point to the beginning, so the elements are in the same order as in the input array). The indexing statement Tx([end,1:end]) does just that.
In the code below, I'm plotting also the normals (Ty,-Tx) in black.
x = [1.34, 0.92, 0.68, 0.25, -0.06, -0.34, -0.49, -0.72, -0.79, -0.94, -1.35, -0.35, 0.54, 0.68, 0.84, 1.20, 1.23, 1.32, 1.34];
y = [0.30, 0.43, 0.90, 1.40, 1.13, 1.08, 1.14, 1.23, 0.52, 0.21, -0.20, -0.73, -0.73, -0.82, -0.71, -0.76,-0.46, -0.13, 0.30];
% First derivative
dsx = diff(x);
dsy = diff(y);
ds = sqrt(dsx.^2+dsy.^2);
Tx = dsx./ds;
Ty = dsy./ds;
% Second derivative & curvature
ds2 = 0.5*(ds([end,1:end-1])+ds);
Hx = diff(Tx([end,1:end]))./ds2;
Hy = diff(Ty([end,1:end]))./ds2;
% Plot
clf
hold on
plot(x,y,'ro-');
x = x(1:end-1);
y = y(1:end-1); % remove repeated point
quiver(x+dsx/2,y+dsy/2,Ty,-Tx,'k','autoscalefactor',0.3);
quiver(x,y,Hx,Hy,'b','autoscalefactor',1.2);
set(gca,'xlim',[-2 2],'ylim',[-1.5 2]);
axis equal

Strictly speaking, the question doesn't make sense.
A polygon has no curvature at vertices and zero curvature along the edges.
You need to be more specific about the quantity you want to evaluate, for instance by explaining the purpose.

Related

Extrapolate in log scale on y axis

x = [0,1,2,3,4,5,6,7] y = [0.07, 0.05, 0.03, 0.02, 0.01, 0.005, 0.002, 0.0007]
I want to find what x is when y= 0.000001 and I tried below but it gives me a wrong value.
10^(interp1(log10(y),x,10^-6, 'linear','extrap'))
Also, would linear extrapolation be possible if I only had two points like so,
x = [6,7] y = [0.002, 0.0007]
interp1's linear-extrap function simply extends the last (or first) segment in the piecewise linear fit made from the points. It doesn't actually create a least-squares fit. This is very evident in the image below:
How did I get this image? I fixed the following problems with your code:
You are interpolating log10(y) vs x.
So the third argument for interp1 needs to be log10(new_y). For new_y = 10^-6, you actually need to pass -6.
The call to interp1() will give you new_x. You're raising 10 to the result of interp1, which is wrong.
x = [0,1,2,3,4,5,6,7];
y = [0.07, 0.05, 0.03, 0.02, 0.01, 0.005, 0.002, 0.0007]
logy = log10(y);
plot(logy, x, '-x');
new_y = 10^-6;
new_x = interp1(logy, x, log10(new_y), 'linear', 'extrap')
plot(log10([new_y, y(end)]), [new_x, x(end)], '--r');
plot(log10(new_y), new_x, 'or');
xlabel('log10(y)'); ylabel('x');
The short answer to your second question is yes!.
Longer answer: replace x and y in my code above and see if it works(spoiler: it does).
Note: I ran this code in Octave Online because I don't have a local MATLAB installation. Shouldn't make a difference to the answer though.

Showing 3D data on a patch surface with Matlab

I want to show, with Matlab, a temperature distribution on an object surface.
I've got a 3D data in the form of (x, y, z, V) vectors. I would like to show this object in Matlab, with the colour representing the local total "value".
I can export the object as an STL file. It can be shown easily using the STL plotting (see stldemo):
fv = stlread('file.stl');
patch(fv, 'EdgeColor', 'none', 'FaceLighting', 'gouraud', 'AmbientStrength', 0.15, 'FaceColor', [0.8 0.8 1.0]);
camlight('headlight');
material('dull');
To colour it according to (x,y,z,V), I need to attach each (x, y, z) point to a vertex in the patch (the nearest one would work). If there are many (x,y,z) points for which a single STL vertex is the nearest, I add up the corresponding V values for that vertex.
The number of vertices is thousands. The number of (x, y, z) points is also large. So doing a loop through (x, y, z) points and then an internal loop over vertices to find the nearest one (which involves calculating distances between points) is out of question. Is there any smart way to do it quickly?
Note: I cannot control the location of the data points, they are defined by an external program. The STL points are controlled by another external program. So I have to marry two different point sets.
Here is the code illustrating what I want to achieve, with 4 vertices and 3 data points:
% Create patch
figure;
p = patch;
colorbar
p.Vertices = [...
0, 0, 0; ...
1, 0, 0; ...
1, 1, 0;
0, 1, 0];
p.Faces = [ ...
1, 2, 3; ...
1, 3, 4];
% Data points
x = [0.1, 0.1, 0.25];
y = [0.01, 0.02, 0.75];
z = [0.01, 0.2, -0.01];
v = [1, 1, 1];
p.FaceVertexCData = zeros(size(p.Vertices, 1), 1);
% Point 1 (0.1, 0.01, 0.01) is closest to vertex 1 (0, 0, 0). Its value
% goes to vertex 1.
p.FaceVertexCData(1) = p.FaceVertexCData(1) + v(1);
% Point 2 (0.1, 0.02, 0.2) is also closest to vertex 1 (0, 0, 0). Its
% value also goes to vertex 1
p.FaceVertexCData(1) = p.FaceVertexCData(1) + v(2);
% Point 3 (0.25, 0.75, -0.01) is closest to vertex 4 (0, 1, 0). Its power
% goes to vertex 4.
p.FaceVertexCData(4) = p.FaceVertexCData(4) + v(3);
% Other vertices are left with 0.
p.FaceColor = 'interp';
Attaching a volume scalar value (of Temperature in your case) of a point to a neighbouring point is a tricky exercise, requires complex for loops and defining special case rules (in your case you wanted to attach the value of 2 different points to the same patch vertex, what if the 2 values to attach are different? Do you average? discard ?).
A safer approach is to re-interpolate your temperature field over your object surface. the function griddata can do that for you.
First I had to define a scalar field. Since I do not have your temperature data, I use the flow function from Matlab. I generated a scalar field the same ways than in this article: flow data.
This gave me a scalar field v (flow value but let's say it's your temperature) at for every coordinates x, y, z.
Then I created and introduced a 3D patch which will be your object. I chose a sphere but any 3D patch will work the same way.
The code to get the sphere as a patch is borrowed from surf2patch
you will have to offset and inflate the sphere to get it exactly as in the figure below
Now is the interesting bit. In the following code, v is the value of the scalar field (temperature for you) at the coordinates x, y, z.
%% // Extract patch vertices coordinates in separate variables
xp = fv.vertices(:,1) ;
yp = fv.vertices(:,2) ;
zp = fv.vertices(:,3) ;
%% // interpolate the temperature field over the patch coordinates
Tpv = griddata(x,y,z,v,xp,yp,zp) ;
%% // Set the patch color data to the new interpolated temperature
set(hp,'FaceVertexCData',Tpv) ;
And your object surface is now at the right interpolated temperature:
you can delete the slice plane if you want to observe the patch alone

Meshgrid stretching surface plot

I have been trying to add axis to a surf plot i have. I've tried various suggestions but can't get it to work. I have 3 matrices:
final -> 3460x300 double
spec -> 1x300 double (x-axis)
timedate -> 1x3460 double (y-axis)
The timedate matrix values are converted time and dates with date2num.
I tried
plot = surf(final);
set(plot,'LineStyle','none');
which gives me the correct graph but the axis are wrong. See image:
When i try
[xx,yy] = meshgrid(spec,timedate)
plot2 = surf(xx,yy,final);
set(plot,'LineStyle','none');
It gives me the correct axis but the graph seems stretched
How could i solve this?
Thanks in advance
The second graph seems to be correct, the first one assumes a fixed distance between samples and the second one uses the complete info (x, y and z axis), below is a simplification of your problem:
Let's say you want to graph the curve described by the points (0.5, 1), (1, 2), (1.5, 3), (2, 4), the correct way of doing that is:
x = [0.5, 1, 1.5, 2];
y = [1, 2, 3, 4];
plot(x, y)
But you are doing:
plot(y)
Both graphs will show the same curve (because points are sampled with fixed distance in the x axis) but the points will be scaled and displaced along the X axis.
But, What if the points are now (0.5, 1), (1.2, 2), (1.4, 3), (2.2, 4)?:
plot(x,y) and plot(y) will show different curves because points were not sampled with fixed distance along the x axis, that's what happens in your problem.

"Frequency" shift in discrete FFT in MATLAB

(Disclaimer: I thought about posting this on math.statsexchange, but found similar questions there that were moved to SO, so here I am)
The context:
I'm using fft/ifft to determine probability distributions for sums of random variables.
So e.g. I'm having two uniform probability distributions - in the simplest case two uniform distributions on the interval [0,1].
So to get the probability distribution for the sum of two random variables sampled from these two distributions, one can calculate the product of the fourier-transformed of each probabilty density.
Doing the inverse fft on this product, you get back the probability density for the sum.
An example:
function usumdist_example()
x = linspace(-1, 2, 1e5);
dx = diff(x(1:2));
NFFT = 2^nextpow2(numel(x));
% take two uniform distributions on [0,0.5]
intervals = [0, 0.5;
0, 0.5];
figure();
hold all;
for i=1:size(intervals,1)
% construct the prob. dens. function
P_x = x >= intervals(i,1) & x <= intervals(i,2);
plot(x, P_x);
% for each pdf, get the characteristic function fft(pdf,NFFT)
% and form the product of all char. functions in Y
if i==1
Y = fft(P_x,NFFT) / NFFT;
else
Y = Y .* fft(P_x,NFFT) / NFFT;
end
end
y = ifft(Y, NFFT);
x_plot = x(1) + (0:dx:(NFFT-1)*dx);
plot(x_plot, y / max(y), '.');
end
My issue is, the shape of the resulting prob. dens. function is perfect.
However, the x-axis does not fit to the x I create in the beginning, but is shifted.
In the example, the peak is at 1.5, while it should be 0.5.
The shift changes if I e.g. add a third random variable or if I modify the range of x.
But I can't get figure how.
I'm afraid it might have to do with the fact that I'm having negative x values, while fourier transforms usually work in a time/frequency domain, where frequencies < 0 don't make sense.
I'm aware I could find e.g. the peak and shift it to its proper place, but seems nasty and error prone...
Glad about any ideas!
The problem is that your x origin is -1, not 0. You expect the center of the triangular pdf to be at .5, because that's twice the value of the center of the uniform pdf. However, the correct reasoning is: the center of the uniform pdf is 1.25 above your minimum x, and you get the center of the triangle at 2*1.25 = 2.5 above the minimum x (that is, at 1.5).
In other words: although your original x axis is (-1, 2), the convolution (or the FFT) behave as if it were (0, 3). In fact, the FFT knows nothing about your x axis; it only uses the y samples. Since your uniform is zero for the first samples, that zero interval of width 1 is amplified to twice its width when you do the convolution (or the FFT). I suggest drawing the convolution on paper to see this (draw original signal, reflected signal about y axis, displace the latter and see when both begin to overlap). So you need a correction in the x_plot line to compensate for this increased width of the zero interval: use
x_plot = 2*x(1) + (0:dx:(NFFT-1)*dx);
and then plot(x_plot, y / max(y), '.') will give the correct graph:

How to plot a second graph instead of color coding in matlab

i just started with my master thesis and i already am in trouble with my capability/understanding of matlab.
The thing is, i have a trajectory on a surface of a planet/moon whatever (a .mat with the time, and the coordinates. Then i have some .mat with time and the measurement at that time.
I am able to plot this as a color coded trajectory (using the measurement and the coordinates) in scatter(). This works awesomely nice.
However my problem is that i need something more sophisticated.
I now need to take the trajectory and instead of color-coding it, i am supposed to add the graph (value) of the measurement (which is given for each point) to the trajectory (which is not always a straight line). I will added a little sketch to explain what i want. The red arrow shows what i want to add to my plot and the green shows what i have.
You can always transform your data yourself: (using the same notation as #Shai)
x = 0:0.1:10;
y = x;
m = 10*sin(x);
So what you need is the vector normal to the curve at each datapoint:
dx = diff(x); % backward finite differences for 2:end points
dx = [dx(1) dx]; % forward finite difference for 1th point
dy = diff(y);
dy = [dy(1) dy];
curve_tang = [dx ; dy];
% rotate tangential vectors 90° counterclockwise
curve_norm = [-dy; dx];
% normalize the vectors:
nrm_cn = sqrt(sum(abs(curve_norm).^2,1));
curve_norm = curve_norm ./ repmat(sqrt(sum(abs(curve_norm).^2,1)),2,1);
Multiply that vector with the measurement (m), offset it with the datapoint coordinates and you're done:
mx = x + curve_norm(1,:).*m;
my = y + curve_norm(2,:).*m;
plot it with:
figure; hold on
axis equal;
scatter(x,y,[],m);
plot(mx,my)
which is imo exactly what you want. This example has just a straight line as coordinates, but this code can handle any curve just fine:
x=0:0.1:10;y=x.^2;m=sin(x);
t=0:pi/50:2*pi;x=5*cos(t);y=5*sin(t);m=sin(5*t);
If I understand your question correctly, what you need is to rotate your actual data around an origin point at a certain angle. This is pretty simple, as you only need to multiply the coordinates by a rotation matrix. You can then use hold on and plot to overlay your plot with the rotated points, as suggested in the comments.
Example
First, let's generate some data that resembles yours and create a scatter plot:
% # Generate some data
t = -20:0.1:20;
idx = (t ~= 0);
y = ones(size(t));
y(idx) = abs(sin(t(idx)) ./ t(idx)) .^ 0.25;
% # Create a scatter plot
x = 1:numel(y);
figure
scatter(x, x, 10, y, 'filled')
Now let's rotate the points (specified by the values of x and y) around (0, 0) at a 45° angle:
P = [x(:) * sqrt(2), y(:) * 100] * [1, 1; -1, 1] / sqrt(2);
and then plot them on top of the scatter plot:
hold on
axis square
plot(P(:, 1), P(:, 2))
Note the additional things have been done here for visualization purposes:
The final x-coordinates have been stretched (by sqrt(2)) to the appropriate length.
The final y-coordinates have been magnified (by 100) so that the rotated plot stands out.
The axes have been squared to avoid distortion.
This is what you should get:
It seems like you are interested in 3D plotting.
If I understand your question correctly, you have a 2D curve represented as [x(t), y(t)].
Additionally, you have some value m(t) for each point.
Thus we are looking at the plot of a 3D curve [x(t) y(t) m(t)].
you can easily achieve this using
plot3( x, y, m ); % assuming x,y, and m are sorted w.r.t t
alternatively, you can use the 3D version of scatter
scatter3( x, y, m );
pick your choice.
Nice plot BTW.
Good luck with your thesis.