I have irregular 3D cartesian coordinates that make up the one eighth of the surface of a sphere. Thanks to Benoit_11 Answer to a previously posed question the surface can now be plotted outside of the MATLAB cftool in a normal command line script
Since this point I have been attempting to calculate the area of the surface using the following code patched together from other answers within the area. The code basically calculates the area from the vertices that produce the surface and then sums them all to produce an area.
surface = [ansx1,ansy1,ansz1];
[m,n] = size(zdata1);
area = 0;
for i = 1:m-1
for j = 1:n-1
v0_1 = [xdata1(i,j) ydata1(i,j) zdata1(i,j) ];
v1_1 = [xdata1(i,j+1) ydata1(i,j+1) zdata1(i,j+1) ];
v2_1 = [xdata1(i+1,j) ydata1(i+1,j) zdata1(i+1,j) ];
v3_1 = [xdata1(i+1,j+1) ydata1(i+1,j+1) zdata1(i+1,j+1)];
a_1= v1_1 - v0_1;
b_1 = v2_1 - v0_1;
c_1 = v3_1 - v0_1;
A_1 = 1/2*(norm(cross(a_1, c_1)) + norm(cross(b_1, c_1)));
area = area + A_1;
end
end
fprintf('\nTotal area is: %f\n\n', area);`
However the issue I am having is that the calculated surface over estimates the possible surface. This is due to the removal of NaN from the original matrix and their replacement with 0 this results in the figure 1. Figure 2 provides the only area I would like to calculate
Does anybody have a way of ignoring the zeros within the code provided to calculate the surface area of the data that generates Figure 1?
Thanks in advance
I think you only have to check if one of the four points of an field equal zero.
What about this:
% example surface
[X,Y,Z] = peaks(30);
% manipulate it
[lza, lzb] = size(Z);
for nza = 1:lza
for nzb = 1:lzb
if Z(nza,nzb) < 0
Z(nza,nzb) = Z(nza,nzb)-1;
else
Z(nza,nzb) = 0;
end
end
end
surfc(X,Y,Z)
% start calculating the surface area
A = 0;
lX = length(X);
lY = length(Y);
for nx = 1:lX-1
for ny = 1:lY-1
eX = [X(ny,nx) X(ny,nx+1)
X(ny+1,nx) X(ny+1,nx+1)];
eY = [Y(ny,nx) Y(ny,nx+1)
Y(ny+1,nx) Y(ny+1,nx+1)];
eZ = [Z(ny,nx) Z(ny,nx+1)
Z(ny+1,nx) Z(ny+1,nx+1)];
% check the field
if eZ(1,1)==0 || eZ(1,2)==0 || eZ(2,1)==0 || eZ(2,2)==0
continue
end
% take two triangles, calculate the cross product to get the surface area
% and sum them.
v1 = [eX(1,1) eY(1,1) eZ(1,1)];
v2 = [eX(1,2) eY(1,2) eZ(1,2)];
v3 = [eX(2,1) eY(2,1) eZ(2,1)];
v4 = [eX(2,2) eY(2,2) eZ(2,2)];
A = A + norm(cross(v2-v1,v3-v1))/2;
A = A + norm(cross(v2-v4,v3-v4))/2;
end
end
Related
A question I strangely could not find on the internet. Given a complicated curve C (i.e. a curve that you can't fit with polynomials) defined by N points and centered around x0=0.5,0 (blue curve in figure), how can I rescale the curve so that the center is the same and the new curve is located at a constant distance d from the curve C (e.g. green curve in figure)?
So far the only way I could find is using the MATLAB function bwdist (https://fr.mathworks.com/help/images/ref/bwdist.html) which computes the Euclidean distance map of a binary image (see code below). However, I'm constrained by the size of my matrix i.e. a curve of 1e5 points is fine but a matrix of size (1e5,1e5) is big for bwdist...so the results using a coarse matrix is an ugly step-wise function. The code is
%%% profile
x = linspace(0,1,1e5);
y = -(x-0.5).^2/0.5^2 + 1 - 0.5*(exp(-(x-0.5).^2/2/0.2^2) - exp(-(-0.5).^2/2/0.2^2));
%%% define mask on a region that encompasses the curve
N=512;
mask = ones(N,N);
xm = linspace(0.9*min(x),1.1*max(x),N);
ym = linspace(0.9*min(y),1.1*max(y),N);
[Xm,Ym] = meshgrid(xm,ym);
%%% project curve on mask (i.e. put 0 below curve)
% get point of mask closer to each point of y
DT = delaunayTriangulation(Xm(:),Ym(:));
vi = nearestNeighbor(DT,x',y');
[iv,jv] = ind2sub(size(mask),vi);
% put 1 to indices of mask that are below projected curve
for p=1:length(iv)
mask(1:iv(p)-1,jv(p)) = 0;
end
%%% get euclidean distance
Ed = bwdist(logical(mask));
Ed = double(Ed);
%%% get contours of Ed at given values (i.e. distances)
cont = contour(Ed,linspace(0,1,50));
% cont has the various curves at given distances from original curve y
I add that I first tried moving a point of curve C for a distance d using the normal of the tangent but since the curve is non-linear, this direction is actually not necessarily the one giving the appropriate point. So at some distance, the curve becomes discontinuous because using the tangent does not give the point at a given distance from the curve, only from the considered point on curve C.
The code is
% profil
x = linspace(0,1,1e5);
y = -(x-0.5).^2/0.5^2 + 1 - 0.5*(exp(-(x-0.5).^2/2/0.2^2) - exp(-(-0.5).^2/2/0.2^2));
% create lines at Dist from original line
Dist = linspace(0,2e-1,6);
Dist = Dist(2:end);
Cdist(1).x = x;
Cdist(1).y = y;
Cdist(1).v = 0;
step = 10; % every step points compute normal to point and move points
points = [1:1:length(y)];
for d=1:length(Dist)
xd = x;
yd = y;
for p=1:length(points)
if points(p)==1
tang = [-(y(2)-y(1)) (x(2)-x(1))];
tang = tang/norm(tang);
xd(1) = xd(1) - Dist(d)*tang(1);
yd(1) = yd(1) - Dist(d)*tang(2);
elseif points(p)==length(y)
tang = [-(y(end)-y(end-1)) (x(end)-x(end-1))];
tang = tang/norm(tang);
xd(end) = xd(end) - Dist(d)*tang(1);
yd(end) = yd(end) - Dist(d)*tang(2);
else
tang = [-(y(p+1)-y(p-1)) (x(p+1)-x(p-1))];
tang = tang/norm(tang);
xd(p) = xd(p) - Dist(d)*tang(1);
yd(p) = yd(p) - Dist(d)*tang(2);
end
end
yd(yd<0)=NaN;
Cdist(d+1).x = xd;
Cdist(d+1).y = yd;
Cdist(d+1).v = Dist(d);
end
% plot
cmap=lines(10);
hold on
for c=1:length(Cdist)
plot(Cdist(c).x,Cdist(c).y,'linewidth',2,'color',cmap(c,:))
end
axis tight
axis equal
axis tight
Any idea ?
What you want to do is not possible.
Scaling a curve with respect to a center point while remaining equal distance to the original curve means that all the points on this curve are moving along its normal direction towards the center of scaling, and will eventually, reduce to a point.
Imagine drawing the normal direction of each point on this curve, and extend them to infinity. All these lines should pass through a same point, which is the center of scaling. Unfortunately, this is not the case for your curve.
I've been working on bilinear interpolation based on wiki example in matlab. I followed the example to the T, but when comparing the outputs from my function and the in-built matlab function, the results are vastly different and I can't figure out why or how that happens.
Using inbuilt matlab function:
Result of my function below:
function T = bilinear(X,h,w)
%pre-allocating the output size
T = uint8(zeros(h,w));
%padding the original image with 0 so i don't go out of bounds
X = padarray(X,[2,2],'both');
%calculating dimension ratios
hr = h/size(X,1);
wr = w/size(X,2);
for row = 3:h-3
for col = 3:w-3
%for calculating equivalent position on the original image
o_row = ceil(row/hr);
o_col = ceil(col/wr);
%getting the intensity values from horizontal neighbors
Q12=X(o_row+1,o_col-1);
Q22=X(o_row+1,o_col+1);
Q11=X(o_row-1,o_col-1);
Q21=X(o_row-1,o_col+1);
%calculating the relative positions to the enlarged image
y2=round((o_row-1)*hr);
y=round(o_row*hr);
y1=round((o_row+1)*hr);
x1=round((o_col-1)*wr);
x=round(o_col*wr);
x2=round((o_col+1)*wr);
%interpolating on 2 first axis and the result between them
R1=((x2-x)/(x2-x1))*Q11+((x-x1)/(x2-x1))*Q21;
R2=((x2-x)/(x2-x1))*Q12+((x-x1)/(x2-x1))*Q22;
P=round(((y2-y)/(y2-y1))*R1+((y-y1)/(y2-y1))*R2);
T(row,col) = P;
T = uint8(T);
end
end
end
The arguments passed to the function are step4 = bilinear(Igray,1668,1836); (scale factor of 3).
You are finding the pixel nearest to the point you want to interpolate, then find 4 of this pixel’s neighbors and interpolate between them:
o_row = ceil(row/hr);
o_col = ceil(col/wr);
Q12=X(o_row+1,o_col-1);
Q22=X(o_row+1,o_col+1);
Q11=X(o_row-1,o_col-1);
Q21=X(o_row-1,o_col+1);
Instead, find the 4 pixels nearest the point you want to interpolate:
o_row = ceil(row/hr);
o_col = ceil(col/wr);
Q12=X(o_row,o_col-1);
Q22=X(o_row,o_col);
Q11=X(o_row-1,o_col-1);
Q21=X(o_row-1,o_col);
The same pixel’s coordinates then need to be used when computing distances. The easiest way to do that is to separate out the floating-point coordinates of the output pixel ((row,col)) in the input image (o_row,o_col), and the location of the nearest pixel in the input image (fo_row,fo_col). Then, the distances are simply d_row = o_row - fo_row and 1-d_row, etc.
This is how I would write this function:
function T = bilinear(X,h,w)
% Pre-allocating the output size
T = zeros(h,w,'uint8'); % Create the matrix in the right type, rather than cast !!
% Calculating dimension ratios
hr = h/size(X,1); % Not with the padded sizes!!
wr = w/size(X,2);
% Padding the original image with 0 so I don't go out of bounds
pad = 2;
X = padarray(X,[pad,pad],'both');
% Loop
for col = 1:w % Looping over the row in the inner loop is faster!!
for row = 1:h
% For calculating equivalent position on the original image
o_row = row/hr;
o_col = col/wr;
fo_row = floor(o_row); % Code is simpler when using floor here !!
fo_col = floor(o_col);
% Getting the intensity values from horizontal neighbors
Q11 = double(X(fo_row +pad, fo_col +pad)); % Indexing taking padding into account !!
Q21 = double(X(fo_row+1+pad, fo_col +pad)); % Casting to double might not be necessary, but MATLAB does weird things with integer computation !!
Q12 = double(X(fo_row +pad, fo_col+1+pad));
Q22 = double(X(fo_row+1+pad, fo_col+1+pad));
% Calculating the relative positions to the enlarged image
d_row = o_row - fo_row;
d_col = o_col - fo_col;
% Interpolating on 2 first axis and the result between them
R1 = (1-d_row)*Q11 + d_row*Q21;
R2 = (1-d_row)*Q12 + d_row*Q22;
T(row,col) = round((1-d_col)*R1 + d_col*R2);
end
end
end
pointA=[9.62579 15.7309 3.3291];
pointB=[13.546 25.6869 3.3291];
pointC=[23.502 21.7667 -3.3291];
pointD=[19.5818 11.8107 -3.3291];
points=[pointA' pointB' pointC' pointD'];
fill3(points(1,:),points(2,:),points(3,:),'r')
grid on
alpha(0.3)
This code will show a filled plane(Cant add images yet T.T)
Now here is my problem. On a spreadsheet, I have x,y,z coordinates of thousands of points. The 4 consecutive points form a plane like the one shown. How do I make a code such that for every 4 consecutive points, it makes a filled plane.
Basically, if I have 400 points, I want the code to plot 100 planes.
Assuming your data are a matrix, m = (400,3)
m = rand(400,3);
for i = 1:length(m);
m2 = m'; % Transpose
end
Create a 3-D matrix in which 'j' represents each set of points:
m3=[];
%Not the most elegant way to cycle through every four points but it works!
z = 0:(length(m2)/4); z1 = (z*4)+1; z1 = z1(:,1:length(z)-1);
for j = 1:length(z1);
m3(:,:,j) = m2(:,z1(j):(z1(j)+3));
end
'j' now has a total length = 100 - representing the amount planes;
fill3(m3(1,:,1),m3(2,:,1),m3(3,:,1),'r');
% Cycle through planes- make a new figure for each plane;
for j = 1:length(z1);
fill3(m3(1,:,j),m3(2,:,j),m3(3,:,j),'r');
end
clear all, close all, clc
pointA=rand(99,1);
pointB=rand(99,1);
pointC=rand(99,1);
pointD=rand(99,1);
pointAmat = reshape(pointA,3,1,[]);
pointBmat = reshape(pointB,3,1,[]);
pointCmat = reshape(pointC,3,1,[]);
pointDmat = reshape(pointD,3,1,[]);
points=[pointAmat pointBmat pointCmat pointDmat];
for i = 1:size(points,3)
fill3(points(1,:,i),points(2,:,i),points(3,:,i),'r')
hold all
end
grid on
alpha(0.3)
Hope this helps.
I would like to create plots using matlab that represent a numerical assessment of quality in a radial fashion.
The best method I've found seems to not work properly. One runs the following code:
theta = (0 : (360/11) : 360)*pi/180;
r = 0 : 2 : 20 ;
[TH,R] = meshgrid(theta,r);
[X,Y] = pol2cart(TH,R);
Z = meshgrid(Data);
surf(X,Y,Z);
Data is a vector of data containing 11 numbers, an example dataset being the following:
Data = 0.884, 0.882, 0.879, 0.880, 0.8776, 0.871, 0.8587, 0.829, 0.811, 0.803, 0.780
the output of surf here is this:
I would like to produce a more refined version of this type of image:
which I have generated with the following code:
for theta = 0 : pi/100 : pi;
v = [InterpolatedImageHeight;LengthVector];
x_center = InterpolatedImageHeight((HorizontalRes+1)/2);
y_center = 0; %InterpolatedImageHeight((HorizontalRes+1)/2);
center = repmat([x_center; y_center], 1, length(InterpolatedImageHeight));
R = [cos(theta) -sin(theta); sin(theta) cos(theta)];
vo = R*(v - center) + center;
x_rotated = vo(1,:);
y_rotated = vo(2,:);
scatter(x_rotated,y_rotated,DotSize,InterpolatedData,'filled'); %x,y,area,color,properties
end
The issue with this is that it is a scatter plot where I am essentially using plot(r,Data), plotting many many copies, and increasing the dot size. The graphic itself has many seams, this takes an enormous amount of memory, and is time intensive where surf or mesh will run extremely fast and take minimal memory.
How does one produce concentric rings with a variable input for color?
There are two completely different plots in your question. The first one represents the data as rays from the origin towards the outside of the circle. The data-points are placed anti-clockwise. A refined version of this can be achieved like this:
Data = [0.884, 0.882, 0.879, 0.880, 0.8776, 0.871,...
0.8587, 0.829, 0.811, 0.803, 0.780];
theta = linspace(0,2*pi,length(Data));
r = linspace(0,20,length(Data));
[TH,R] = meshgrid(theta,r);
Z = meshgrid(Data);
[X,Y,Z] = pol2cart(TH,R,Z);
surf(X,Y,Z);
view(2);
shading interp
Note that I used linspace to generate theta and r to always match the length of Data. Z is also passed trough pol2cart. Then you can use shading interp to remove the lines between the patches and interpolate the color. With view(2) you can set the perspective as you would have a 2d-plot.
This is the result:
It's relatively easy to get a result like in your second example. There the data-points represent concentric circles around the origin and are placed from the origin towards the outside. Therefore, just transpose the meshgrid of Z by using the following line:
Z = meshgrid(Data)';
This is the result then:
based on the code by Darren Rowland in this thread I have come up with the following solution:
x = interp1(1:length(data),datax,(datax(1):datax(end)/f:datax(end)),'linear');
y = interp1(1:length(datay),datay,datay(1):datay(end)/f:datay(end),'spline');
theta = linspace(0,2*pi,n);
xr = x.'*cos(theta);
zr = x.'*sin(theta);
yr = repmat(y.',1,n);
figure;
surf(xy,yr,zr,zr*numcolors);
which is elegant, runs quickly, and produces beautiful figures. This is a sample of the output with some extra chart elements:
!1I have the Canny edge output of a ear... i have connected the farthest two boundaries with a line(green). Now I want to draw a normal from the midpoint of this line to the outer boundary(left side).
The code i have written helps me to plot a normal but i want the red line to exactly meet the white boundary. Also I want the point of intersection at the point where it meets. I have also thought about another method for the same.By changing 50 to 60 pixels (in the code) the red line crosses the white boundary. If I get the point of intersection of the same then I can easily plot the line of the desired length. I found some code on the internet and Mathworks, but it is for intersection of 2 lines....Can anybody plz help.
for i=1:numel(p)
x = [ p{i}(1), p{i}(3)];
y = [p{i}(2), p{i}(4)];
line(x,y,'color','g','LineWidth',2);
m = (diff(y)/diff(x));
minv = -1/m;
line([mean(x) mean(x)-50],[mean(y) mean(y)-50*minv],'Color','red')
axis equal
end ;
![][2]
Picked up the input image from here.
This is the code to get the intersection point and plot it-
%% Read image and convert to BW
img1 = imread('ear.png');
BW = im2bw(img1);
L = bwlabel(BW,8);
[bw_rows,bw_cols] =find(L==1);
bw_rowcol = [bw_rows bw_cols];
bw_rowcol(:,1) = size(BW,1) - bw_rowcol(:,1); % To offset for the MATLAB's terminology of showing height on graphs
%% Get the farthest two points on the outer curve and midpoint of those points
distmat = dist2s(bw_rowcol,bw_rowcol);
[maxdist_val,maxdist_ind] = max(distmat(:),[],1);
[R,C] = ind2sub(size(distmat),maxdist_ind);
farther_pt1 = bw_rowcol(R,:);
farther_pt2 = bw_rowcol(C,:);
midpoint = round(mean([farther_pt1 ; farther_pt2]));
%% Draw points on the normal from the midpoint across the image
slope_farthest_pts = (farther_pt1(1) - farther_pt2(1)) / (farther_pt1(2) - farther_pt2(2));
slope_normal = -1/slope_farthest_pts;
y1 = midpoint(1);
x1 = midpoint(2);
c1 = y1 -slope_normal*x1;
x_arr = [1:size(BW,2)]';
y_arr = slope_normal*x_arr + c1;
yx_arr = round([y_arr x_arr]);
%% Finally get the intersection point
distmat2 = dist2s(bw_rowcol,yx_arr);
[mindist_val2,mindist_ind2] = min(distmat2(:),[],1);
[R2,C2] = ind2sub(size(distmat2),mindist_ind2);
intersection_pt = bw_rowcol(R2,:); % Verify that this is equal to -> yx_arr(C2,:)
%% Plot
figure,imshow(img1)
hold on
x=[farther_pt1(2),farther_pt2(2)];
y=size(BW,1)-[farther_pt1(1),farther_pt2(1)];
plot(x,y)
hold on
x=[intersection_pt(2),midpoint(2)];
y=size(BW,1)-[intersection_pt(1),midpoint(1)];
plot(x,y,'r')
text(x(1),y(1),strcat('Int Pt = ','[',num2str(x(1)),',',num2str(y(1)),']'),'Color','green','FontSize',24,'EdgeColor','red','LineWidth',3)
Don't forget to use this associated function-
function out = dist2s(pt1,pt2)
out = NaN(size(pt1,1),size(pt2,1));
for m = 1:size(pt1,1)
for n = 1:size(pt2,1)
if(m~=n)
out(m,n) = sqrt( (pt1(m,1)-pt2(n,1)).^2 + (pt1(m,2)-pt2(n,2)).^2 );
end
end
end
return;
The output -
Hope this helps and let us know it goes. Interesting project!
EDIT 1: Based on the edited photo shown below, I got few questions for you.
Questions: Do you want a line from PT1 to MP and let it extend and touch the outer ear at PT3? If so, how do you define PT1? How do you differentiate between PT1 and PT2? You could have tried to plot a line from PT2 to MP and let it touch the outer ear at some other point, so why not PT2 instead of PT1?