MATLAB - Intersect between a surface and a plane - matlab

I have a 3D mesh like in this picture.
Now what I want to do is create a plane that will intersect the surface at a certain Z value. I would then want to get the x and y coordinates of this intersection and have matlab output them.
What I'm planning on doing is that this picture is a model of a lake. This lake will have water evaporating that will be removing a certain z value of water. I would then like to see what the new shoreline would look like by obtaining the x and y coordinates of this intersection.
This is my code for plotting that picture.
function plot(x,y,z)
Contour = xlsread('C:\Users\Joel\Copy\Contour','A2:D4757');
x = Contour(:,1);
y = Contour(:, 2);
z = Contour(:,3);
F = TriScatteredInterp(x,y,z);
[X,Y]=meshgrid(linspace(-600,600,300));
Z=F(X,Y);
surf(X,Y,Z);
end

You can do this by simply thresholding the interpolated Z values
inside = Z < seaLevel; % binary mask of points inside water
shoreline = bwmorph( inside, 'remove' ); % a mask with only shoreline pixels eq to 1
figure;
surf( X, Y, Z, 'EdgeColor', 'none', 'FaceColor', [210,180,140]/255, 'FaceAlpha', .5 );
hold on;
ii = find( shoreline );
plot3( X(ii), Y(ii), seaLevel*ones(size(ii)), 'b', 'LineWidth', 2 );

The contour3 will give nicer boundaries:
[h,c]=contour3(X,Y,Z,[seaLevel seaLevel]);
seaLevel is given twice: otherwise contour3 thinks seaLevel is the number of levels to automatically calibrate. And to nicely annotate with the numeric height of your seaLevel:
clabel(h,c);
you may modify c to print "sea level" instead of num2str(seaLevel).

If you don't have the MATLAB toolbox for image processing you can't use the function "bwmorph". As a solution you can replace line 2 of Shai's code with the following code which reimplements the bwmorph function for parameter 'remove'. (The reimplementation is probably neither very perfomrant nor an exact reimplementatin (the borders of the matrix aren't used) - but the solution should work as a first step. Feel free to improve).
shoreline= zeros(size(inside));
for i_row = 2:size(inside,1)-1
for i_col = 2:size(inside,2)-1
if(inside(i_row,i_col))
if (~( inside(i_row+1,i_col) && ...
inside(i_row-1,i_col) && ...
inside(i_row,i_col+1) && ...
inside(i_row,i_col-1)))
inside2(i_row,i_col) = 1;
end
end
end
end

Related

Creating meshgrid of scattered Cartesian data for plotting on a sphere

I have a set of n=8000 cartesian coordinates X,Y and Z as vectors and also a vector V of same size which I want to use as values to create a heatmap on a sphere.
I saw this link (visualization of scattered data over a sphere surface MATLAB), but I don't understand how I convert this set of data into a meshgrid for plotting using surf.
Almost every example I saw uses meshgrids.
Right now, I am doing by plotting a sphere and then use scatter3 to plot my points as big balls and try to smooth them later. I looks like this:
I would like to get the figure as the plotting of the example in that link, where he uses:
k = 5;
n = 2^k-1;
[x,y,z] = sphere(n);
c = hadamard(2^k);
surf(x,y,z,c);
colormap([1 1 0; 0 1 1])
axis equal
EDIT:
(Sorry for taking so long to reply, the corona crises kept away from work)
What I am actually doing is:
for i=1:numel(pop0n)
ori(i,:)=ori(i,:)/norm(ori(i,:));
end
x = ori(:,1);
y = ori(:,2);
z = ori(:,3);
%// plot
m=100;
[aa,bb,cc] = sphere(m);
surf(aa,bb,cc,ones(m+1,m+1)*min(pop0n))
hold on
colormap jet;
scatter3(x,y,z,400,pop0n/norm(pop0n),'filled');
colorbar
shading interp
The array 'ori' is 8000x3, and contains the x, y and z coordinates of the points I want to plot and pop0n is a 8000 sized vector with the intensities of each coordinate.
My main question is how do I transform my x, y, z and pop0n, that are vectors, into 2D arrays (meshgrid) to use surf?
Because I cannot simply do surf(x,y,z,pop0n) if they are vectors.
Thanks in advance
As David suggested, griddata does the job.
What I did was:
for i=1:numel(pop0n)
ori(i,:)=ori(i,:)/norm(ori(i,:));
end
x = ori(:,1);
y = ori(:,2);
z = ori(:,3);
%// plot
m=100;
[aa,bb,cc] = sphere(m);
v = griddata(x,y,z,pop0n,aa,bb,cc,'nearest');
surf(aa,bb,cc,v)
colormap jet;
colorbar
shading interp

Draw a line with non-Cartesian coordinates in MATLAB

MATLAB's surf command allows you to pass it optional X and Y data that specify non-cartesian x-y components. (they essentially change the basis vectors). I desire to pass similar arguments to a function that will draw a line.
How do I plot a line using a non-cartesian coordinate system?
My apologies if my terminology is a little off. This still might technically be a cartesian space but it wouldn't be square in the sense that one unit in the x-direction is orthogonal to one unit in the y-direction. If you can correct my terminology, I would really appreciate it!
EDIT:
Below better demonstrates what I mean:
The commands:
datA=1:10;
datB=1:10;
X=cosd(8*datA)'*datB;
Y=datA'*log10(datB*3);
Z=ones(size(datA'))*cosd(datB);
XX=X./(1+Z);
YY=Y./(1+Z);
surf(XX,YY,eye(10)); view([0 0 1])
produces the following graph:
Here, the X and Y dimensions are not orthogonal nor equi-spaced. One unit in x could correspond to 5 cm in the x direction but the next one unit in x could correspond to 2 cm in the x direction + 1 cm in the y direction. I desire to replicate this functionality but drawing a line instead of a surf For instance, I'm looking for a function where:
straightLine=[(1:10)' (1:10)'];
my_line(XX,YY,straightLine(:,1),straightLine(:,2))
would produce a line that traced the red squares on the surf graph.
I'm still not certain of what your input data are about, and what you want to plot. However, from how you want to plot it, I can help.
When you call
surf(XX,YY,eye(10)); view([0 0 1]);
and want to get only the "red parts", i.e. the maxima of the function, you are essentially selecting a subset of the XX, YY matrices using the diagonal matrix as indicator. So you could select those points manually, and use plot to plot them as a line:
Xplot = diag(XX);
Yplot = diag(YY);
plot(Xplot,Yplot,'r.-');
The call to diag(XX) will take the diagonal elements of the matrix XX, which is exactly where you'll get the red patches when you use surf with the z data according to eye().
Result:
Also, if you're just trying to do what your example states, then there's no need to use matrices just to take out the diagonal eventually. Here's the same result, using elementwise operations on your input vectors:
datA = 1:10;
datB = 1:10;
X2 = cosd(8*datA).*datB;
Y2 = datA.*log10(datB*3);
Z2 = cosd(datB);
XX2 = X2./(1+Z2);
YY2 = Y2./(1+Z2);
plot(Xplot,Yplot,'rs-',XX2,YY2,'bo--','linewidth',2,'markersize',10);
legend('original','vector')
Result:
Matlab has many built-in function to assist you.
In 2D the easiest way to do this is polar that allows you to make a graph using theta and rho vectors:
theta = linspace(0,2*pi,100);
r = sin(2*theta);
figure(1)
polar(theta, r), grid on
So, you would get this.
There also is pol2cart function that would convert your data into x and y format:
[x,y] = pol2cart(theta,r);
figure(2)
plot(x, y), grid on
This would look slightly different
Then, if we extend this to 3D, you are only left with plot3. So, If you have data like:
theta = linspace(0,10*pi,500);
r = ones(size(theta));
z = linspace(-10,10,500);
you need to use pol2cart with 3 arguments to produce this:
[x,y,z] = pol2cart(theta,r,z);
figure(3)
plot3(x,y,z),grid on
Finally, if you have spherical data, you have sph2cart:
theta = linspace(0,2*pi,100);
phi = linspace(-pi/2,pi/2,100);
rho = sin(2*theta - phi);
[x,y,z] = sph2cart(theta, phi, rho);
figure(4)
plot3(x,y,z),grid on
view([-150 70])
That would look this way

How would I plot the for loop from my code below?

I have 3D flow data of the velocity of a fluid through a tube. I know the diameter of the tube and have looked at the velocity field and found the centre of the field for an xy plane at both ends of the tube. So I essentially have a line through the centre axis of the tube. I want to NaN all data points that are outside of the diameter. For this I am using an equation that gives the distance to a point from a line in 3D which I found here mathworld.wolfram.com/Point-LineDistance3-Dimensional.html. I then created an if statement which states points smaller than diameter will be NaN.
I am new to matlab so I don't know how I would now plot this.
%%
diff_axis = end_axis-start_axis;
diff_axis_mag = (diff_axis(1)^2 + diff_axis(2)^2 + diff_axis(3)^2)^0.5;
[rw col pl] = size(X);
for j = 1:col
for i = 1:rw
for k = 1:pl
x_curr = X(i,j,k);
y_curr = Y(i,j,k);
z_curr= Z(i,j,k);
x0 = [x_curr y_curr z_curr]
t = - dot((start_axis-x0),(diff_axis))./(diff_axis_mag)^2;
d = sqrt(((start_axis(1) - x0(1)) + (end_axis(1) - start_end(1))*t)^2 + ((start_axis(2)-x0(2))+(end_axis(2)-start_end(2))*t)^2+((start_axis(3)-x0(3))+(end_axis(3)-start_end(3))*t)^2);
if (d > D)
x_curr=NaN
y_curr=NaN
z_curr=NaN
end
end
end
end
It were nice to have explanatory names for your X, Y, and Z. I am guessing they are flow components, and diff_axis are axis coordinates? It is a very cumbersome notation.
what you do in your loops is you take point values (X,Y,Z), copy them to temporary constants and then set them to NaN if they fall out. But the problem is that usually you do not plot point-by-point in MATLAB. So these temorary guys like x_curr will be lost.
Also, the most optimal way to do things in MATLAB is to avoid loops whenever possible.
What you can do is to create first a mask
%// remember to put a dot like in `.^` for entrywise array operations
diff_axis_mag = sqrt(diff_axis(1).^2 + diff_axis(2).^2 + diff_axis(3).^2);
%// are you sure you need to include the third axis?
%// then it is a ball, not a tube
%// create a binary mask
mask = diff_axis_mag < tube_radius
X(~mask) = NaN;
Y(~mask) = NaN;
Z(~mask) = NaN;
Then you can plot your data with quiver3 or
stream3

Apply transformation to faces/vertices from isosurface and plotting the result

If I have a contour in Matlab obtained
[f, v] = isosurface(x, y, z, v, isovalue)
is there a clean way to apply a transformation to the surfaces and nicely plot the result as a smooth surface? The transformation T is nonlinear.
I tried to apply the transformation T to both f and vert and use patch but couldn't quite get it to work.
The trick is to apply the transformation on your vertices, but keep the same faces data. This way the faces always link the same points, regardless of their new positions.
Since there are no sample data I took the Matlab example as a starting point. This is coming from the Matlab isosurface page (very slightly modified for this example):
%// Generate an isosurface
[x,y,z,v] = flow;
fv = isosurface(x,y,z,v,-3) ;
figure(1);cla
p1 = patch(fv,'FaceColor','red','EdgeColor','none');
%// refine the view
grid off ; set(gca,'Color','none') ; daspect([1,1,1]) ; view(3) ; axis tight ; camlight ; lighting gouraud
This output:
Nothing original so far. Just note that I used the single structure output type fv instead of the 2 separate arrays [f,v]. It is not critical, just a choice to ease the next call to the patch object.
I need to retrieve the vertices coordinates:
%// Retrieve the vertices coordinates
X = fv.vertices(:,1) ;
Y = fv.vertices(:,2) ;
Z = fv.vertices(:,3) ;
You can then apply your transformation. I choose a simple one in this example, but any transformation function is valid.
%// Transform
X = -X.*Y.^2 ;
Y = Y.*X ;
Z = Z*2 ;
Then I rebuild a new structure for the patch which will display the transformed object.
This is the important bit:
%// create new patch structure
fvt.vertices = [X Y Z] ; %// with the new transformed 'vertices'
fvt.faces = fv.faces ; %// but we keep the same 'faces'
Then I display it the same way (well with a slightly different angle for a better view):
%// Plot the transformed isosurface
figure(2);cla
pt = patch( fvt ,'FaceColor','red','EdgeColor','none');
%// refine the view
grid off ; set(gca,'Color','none') ; daspect([1,1,1]) ; view(-3,4) ; axis tight ; camlight ; lighting gouraud
Which produces the figure:
(If you paste all the code snippet in one file it should run and give you the same output.)

Extract cross sections from a plot of multiple spheres in Matlab

I know the locations of spheres (center and radius) in a box. I want to extract cross sections. I am able to plot the spheres placed in a cube using the following Matlab code:
[X,Y,Z] = sphere;
for SpNum = 1:NumSpheres
surf( X*Radius(SpNum)+Center(SpNum,1), Y*Radius(SpNum)+Center(SpNum,2), Z*Radius(SpNum)+Center(SpNum,3), ...
'FaceColor','r' );
%shading interp;
hold on;
end
axis tight; daspect([1 1 1]);
In the above code, each sphere could have different radius and they do not overlap (so the centers are also different).
The above code does not however generate cross sections. I want to extract cross sections similar to what we get from say X-ray CT data: a series of images in the Z-direction. I think 'interp2/interp3' and 'slice' functions are the relevant functions, but I am not sure how to use them to generate the cross sections. I would appreciate if anyone could give pointers or provide some sample code for my problem?
-- Thanks in advance.
Update:
I tried using meshgrid to generate the grid points followed by the function F(X,Y,Z) as follows:
[X,Y,Z] = meshgrid(1:100,1:100,1:100);
F = zeros(size(X),'uint8');
for SpNum = 1:NumSpheres
F( sqrt((X - Center(SpNum,1)).^2 + (Y - Center(SpNum,2)).^2 + (Z - Center(SpNum,3)).^2) <= Radius(SpNum) ) = 1;
end
surf(F);
followed by:
z = 1;
I = interp3(X, Y, Z, X*Radius(SpNum)+Center(SpNum,1), Y*Radius(SpNum)+Center(SpNum,2), Z*Radius(SpNum)+Center(SpNum,3), z, 'spline');
figure, imshow(I);
I know that interp3 is the function to use since it interpolates the values of the function F(X,Y,Z) which represent the spheres at different location within a bounded box (say 1:100, 1:100, 1:100). The interpolated values at particular 'z' (= 1, 2, 3... 100) should give me 100 cross sections (in the form of 2-D images).
The flaw is in the function F itself, since 'surf' throws an error saying that F should be an array - "CData must be an M-by-N matrix or M-by-N-by-3 array".
Can anyone please help.
I finally figured it. For the benefit of others, here is the code.
% A 3-D matrix 'F' which has its value at particular coordinate set to 255 if it belongs to any one of the spheres and 0 otherwise.
[X,Y,Z] = meshgrid(1:100,1:100,1:100);
F = zeros(size(X));
for SpNum = 1:NumSpheres
F( sqrt((X - Center(SpNum,1)).^2 + (Y - Center(SpNum,2)).^2 + (Z - Center(SpNum,3)).^2) <= Radius(SpNum) ) = 255;
end
% Extract cross sections from F using interp3 function along the z-axis.
I = zeros(size(X));
for z = 1:100
I(:,:,z) = interp3(X, Y, Z, F, 1:100, (1:100)', z, 'spline');
end
implay(I,4);
You could test and visualize the output by setting Center (a 3-D vector) and Radius of each sphere (some arbitrary NumSpheres) to some random values. The above code will display a window with cross-sections.
Previously, I was trying to use 'surf' to render the spheres which is not right. To render, you have to use the first code snippet. Another mistake I made was using a row vector for the 6th argument instead of column vector.
Hope this helps.
--
Cheers,
Ram.