creating a convex hull of irregular shape - sp

I have coordinates from two transect lines and want to create a convex hull around them to use for point pattern analysis. However, so far my convex hull is a rectangle and I don't know how to make it an irregular shape so that it just encompasses the two lines.
Here is a reproducible example:
Here are my x and y coordinates
X <- c(1,2,3,4,5,1,2,3,4,5)
Y <- c(5,5,5,5,5,7,7,7,7,7)
I combine them into a dataframe with columns labeled X and Y:
TransectLines <- as.data.frame(cbind(X,Y))
Here are the two transect lines plotted:
plot(TransectLines)
I made a convex hull using the X and Y coordinates:
twoTransects.chull <- convexhull.xy(x = TransectLines$X, y = TransectLines$Y)
And plotted the convex hull:
plot(twoTransects.chull)
And plotted the transect line points on top of it:
points(TransectLines$X,TransectLines$Y)
I would like the hull to wrap around the transect lines only, instead of being a rectangle. Is that possible?

Related

Perimeter around a 2D grid-shape

Given a 2D grid of square cells of the form grid[x,y], I would like an algorithm that produces an ordered set of points that form the perimeter of the shape. In other words, the algorithm would produce a perimeter route around the corners of the shape as shown in this image:
.
I have looked at posts like this (where I got the above image), and I can find the corners of the shape. I have two questions: 1) once I have found the corners of the shape, how would I iterate over them in order to find a correct (and valid) route? 2) Would such a method to find the route consistently produce clockwise/counterclockwise routes? It does not matter to me that order of the vertices is clockwise/counterclockwise, only that they are consistently either clockwise or counterclockwise.
Thanks for any help
This assumes that any single corner won't be visited more than once per circuit. in other words, no corners where there are two grey and two black with the two grey squares in non adjacent corners.
Get your corners in some data structures that let you quickly :
get a list of all corners with a given x coordinate ordered by y coordinate.
get a list of all corners with a given y coordinate ordered by x coordinate.
Here's the algorithm:
Start with arbitrary corner c.
We'll say it has 3 adjacent black squares and 1 grey, and that the
1 grey square is in the -x,+y direction.
Choose perimeter direction. We'll say clockwise.
Determine which direction the perimeter goes in that corner. This can be done
by looking at the direction of the adjacent tile there's only 1 color of.
In our example, the perimeter goes -x/+y
Determine if c is concave or convex.
Convex has 3 adjacent black squares, concave has 3 adjacent grey squares.
In our example, c is convex because it has 3 adjacent black squares.
Knowing the direction of the perimeter from that corner and if it's concave or
not tells us what direction is clockwise:
clockwise at convex +x/-y is +x,
clockwise at convex +x/+y is +y,
clockwise at convex -x/-y is -y,
clockwise at convex -x/+y is -x
If it is concave clockwise goes the other direction.
(obviously if the desired perimeter direction is counterclockwise, it's the opposite)
Because c in our example is a convex corner and it goes -x/+y,
that means clockwise is along the x wall, so set current_axis = x,
It goes negative in that direction so set current_direction = -1
Otherwise, it would be set to 1
create list ordered_corner_list that only contains c
While length of ordered_corner_list < number of corners:
Get list of all corners with same value of current_axis as c ordered by the other axis.
e.g. for the first iteration, get same x value as c ordered by y
if current_direction = -1:
find node with the next lowest ordered value from c.
e.g. for the first iter, get corner with next lowest x from c
else:
find node with the next highest ordered value from c
assign that node to c
append c to ordered_corner_list
set current_axis = the other axis
e.g. for the first iteration, current_axis = y here
set current_direction to the direction that corner goes in the current_axis

How to Follow the Path of a Super Ellipse with 2 Linked Axis

I have two axis linked together ; Axis A, and Axis B. Axis B is attached to the end of Axis A and so its point of origin can vary with the angle of Axis A . Attached to Axis B is a Circle whose Diameter is 10 (and can become smaller). I need to move the edge point of the circle to intersect a Super-Ellipse at each of 38 Cartesian points x,y. So the end point of my axis B - center of the circle should follow the same basic path as the 38 points of the super ellipse - radius of circle. Once I have these points - I will need to determine the position of Axis A x_2,y_2 and angle (or more appropriately just the distance from 0 degree angle to reach the required angle to position x_2,y_2. I then need to position Axis B with relation to Axis A in order to have Axis B X_3,Y_3 match the following of the Super-Ellipse where the center of the circle is supposed to be.
I have a drawing attached and a plot in Excel where I am off as you can see the bow tie is not what I should have. I have also included the points to the super ellipse along with some quick points on the graph. I am not a math major - I am willing to learn if you post the name of an equation - so far I have learned about carnot, parametric equation for circles and formulas for parabolas - but I am still having trouble.
Axis A Radius 13" image is 90 degree rotation
X_Sub1 , Y_Sub1
-6.5 , 5
Axis B Radius 9" image is 180 degree rotation
X_Sub2 , Y_Sub2
6.5 , 5
Circle Diameter 10"
Circle Radius 5"
Super Ellipse
# 12"width
# 8.75" Deep Vertex -8.75
Points Along the Super Ellipse.
0.0000, 0.0000
0.2188, -0.6250
0.2188, -1.2500
0.2433, -1.8750
0.3290, -2.5000
0.4753, -3.1073
0.6804, -3.7091
0.9424, -4.2990
1.2585, -4.8712
1.6255, -5.4197
2.0397, -5.9388
2.4967, -6.4233
2.9920, -6.8682
3.5203, -7.2889
4.0764, -7.7213
4.6544, -8.0500
5.2285, -8.3553
5.7525, -8.5000
6.2188, -8.5516
6.6851, -8.5000
7.1891, -8.3553
7.7832, -8.0500
8.3612, -7.7213
8.9173, -7.2889
9.4456, -6.8682
9.9409, -6.4233
10.3979,-5.9388
10.8121,-5.4197
11.1791,-4.8712
11.4952,-4.2990
11.7572,-3.7091
11.9623,-3.1073
12.1086,-2.5000
12.1943,-1.8750
12.2188,-1.2500
12.2188,-0.6250
12.4376, 0.0000
First, suppose we have 3 points along the super-ellipse, p0 = (2.4967, -6.4233), p1 = (2.9920, -6.8682), and p2 = (3.5203, -7.2889). Let's figure out how to position it to touch the point p1.
First of all at the point p1 the tangent line should be very close to parallel to the line from p0 to p2. So it should be parallel to p2 - p0 = (3.5203, -7.2889) - (2.4967, -6.4233) = (1.0236, -0.8656). But you want the disk to be perpendicular to this. We can construct a perpendicular to the vector (x, y) by (-y, x), which gives us (0.8656, 1.0236) as a direction. (There are two perpendiculars, looking at the diagram this is obviously the correct one.) This means that we want to put the end of axis B a distance of 5 in that direction. Which means it needs to be a distance of 5 in the direction (0.8656, 1.0236) from p1 = (2.9920, -6.8682). So it should be at the position (2.9920, -6.8682) + 5 * (0.8656, 1.0236) / sqrt(0.8656^2 + 1.0236^2) = (6.22057, -3.050307).
Now that we know where the end of axis B is, assuming we know where the start of axis A is (you didn't specify that), we can use the law of cosines (see http://mathworld.wolfram.com/LawofCosines.html) to figure out the cos of the angles that you want. Now you can use the arccosine function to figure out the angles in question.
This procedure can be followed for every point. Figure out what you think it should be tangent to, find the orthogonal, find where you want the end of B to be, then you have a triangle that you can use the cosine law on.

Computing the area of the cells of a Voronoi diagram for points sampled from the sphere

The bigger picture is that I'm using QHULL to compute the convex hull of points on the sphere (which is a Delaunay Tesselation of the sphere's surface), and projecting Voronoi cells computed from the convex hull up to the surface of the sphere and then computing the area of associated with each node using spherical triangles http://mathworld.wolfram.com/SphericalTriangle.html (splitting each convex hull triangle into 6 fractional triangles and computing the area of the fractional spherical triangles separately, I'm getting the interior angle as the arc cosine of the dot product of unit vectors tangent to the surface of the sphere pointing from nodes to Voronoi vertexes and triangle edge midpoints), but I can deal with that complexity.
The problem I'm encountering is that for some very bad sample designs, the Voronoi vertex is outside the delaunay triangle
yes it's possible see http://www.mathopenref.com/trianglecircumcenter.html but I wasn't expecting it, and I don't know that this answer
Compute the size of Voronoi regions from Delaunay triangulation?
applies to the case when Voronoi vertices are outside the triangle. I'm getting points with negative areas (using the spherical triangle approach based on connecting Voronoi vertexes to triangle edge midpoints and the triangle node to define fractional triangles)
Is the area closer to node than a Voronoi vertex that is outside the triangle (and closer to that node than the other 2 nodes of the same triangle) closer to that node than any other node in the delaunay tesselation? If so should I forget about connecting Voronoi vertices to the triangle edge midpoints and directly connect the Voronoi vertices of adjacent triangles and use that to compute the (spherical surface) area of the Voronoi region? This would still be simple to do because....
in MATLAB (where I'm prototyping the code, before I port it to C++) I get K (from convhulln) as a NK by 3 matrix of node indexes and can connect the Voronoi vertexes like this
KS=sort(K,2);
iK=(1:NK)';
edges=sortrows([KS(:,1:2) iK; KS(:,[1 3]) iK; KS(:,2:3) iK]);
edges=reshape(edges(:,3),2,1.5*NK); %a consequence of this is that there will always be an even number of triangles on the convex hull of a sphere, otherwise Voronoi cells are not defined, which they must be because the convex hull is defined
"edges" contains the indexes of convex hull triangles which defines the Voronoi vertices?
the 2 nodes that would have areas associated with that edge of a Voronoi cell are the ones in the first 2 columns of edges before they are discarded so like I said, that would be a simple modification, but I'm not sure it's mathematically correct. Do you know if it is?
You seem to be making your job more complicated than you need.
Note that if you take the normals to each of the facets of the hull (multiplied by the radius of your sphere), you will get the Voronoi vertices [1].
To compute the area of one of your Voronoi cells, you can iterate around the facets incident to one of your points, sequentially computing the Voronoi vertices. At the end you have a convex polygon forming the Voronoi cell about your point. You may compute its area by summing over each of the triangles defined by your input point along with each pair of adjacent Voronoi vertices.
As to your comment regarding the circumcenter being on the outside of some of your triangles, this is expected. You don't need to deform a triangle much from an equilateral triangle to achieve this condition.
[1] http://www.qhull.org/html/qdelaun.htm
This seemed to work
function [x,y,z,ptArea,K,minRad,xv,yv,zv,xvc,yvc,zvc,VorTriNodes]=voronoiSphereArea2(x,y,z)
%x, y, z are the inputs but normalized to be unit vectors (projected onto
% the surface of the sphere centered at 0,0,0 with radius 1)
%ptArea is a numel(x) by 1 vector (same size as x, y, and z) that contains
% area of the surface of the unit sphere that is closer to the
% corresponding x y z point than any other pt (this is the area of
% projection of the voronoi cell projected onto the surface of the
% sphere with radius of 1)
%K is the output of convhulln([x y z]), it is the tesselation of
% points x y z (the list point indexes that make up the triangles
% on the convex hull of the sphere)
%minRad is the minimum distance between the origin (center of the sphere)
% and any triangle in the tesselation of (normalized) x y z; this is
% less than 1 because the triangles are the equivalent of chords of
% a circle (nodes of the triangle are on the surface of the sphere,
% but the rest of the triangle is below the surface of the sphere,
% the voronoi vertex of a triangle is the closest point in that
% triangle to the center of the sphere), minRad is highly useful
% when doing barycentric interpolation in x y z space (putting
% points to be interpolated at this distance from the origin, [the
% largest distance guaranteed to be in the convex hull regardless of
% angular position, actually you'll want to decrease this slightly
% to protect against round off error] makes tsearchn [of volumetric
% Delaunay tesselation formed augmenting each triangle in the
% convex hull tesselation with the origin] run faster, it's very
% slow if you use an interpolation radius of 0.5 but very fast if
% you put the interpolation points just under the surface of the
% sphere)
%xv, yv, zv are the x y z coordinates of the voronoi vertices of each
% triangle in K projected up to the surface of the sphere (for use
% in plotting "cells" associated with each point) these are listed
% in the same order as K
%xvc, yvc, zvc are spherical triangular patches whose corners are 1 node
% (x, y, z) and 2 voronoi vertices (xv, yv, zv); this is a
% 30 by 3*size(K,1) matrix, each triangle is a fraction of the
% voronoi cell belonging to the node (x, y, z)
%VorTriNodes a 1 by 3*size(K,1) vector that identifies the node owning
% the spherical triangle in the same columns of xvc, yvc, zvc; this
% is used for plotting piecewise constant point values (where the
% pieces are the voronoi cells belonging to their respective points)
Npts=numel(x);
invr=(x.^2+y.^2+z.^2).^-0.5;
x=x.*invr;
y=y.*invr;
z=z.*invr;
i4=[1:3 1]';
i3=[3 1 2]';
K=convhulln([x y z]); %right now this is K
NK=size(K,1);
%determine the voronoi cell edges (between the voronoi vertexes of 2 convex
%hull trianlges that share a convex hull triangle edge) and the 2 nodes (x,
%y, z points) that share that edge, these define triangles that are
%components of the voronoi cells around the 2 nodes
KS=sort(K,2);
iK=(1:NK)';
VorTri=reshape(sortrows([KS(:,1:2) iK; KS(:,[1 3]) iK; KS(:,2:3) iK])',[3 2 1.5*NK]);
VorTriNodes=squeeze(VorTri(1:2,1,:)); %these 2 nodes (in a column) share
VorTriVerts=squeeze(VorTri(3,:,:)); %the cell edge between these 2 vertices
K=K'; %right now this is K transpose
xn=x(K); %a 3 by NK matrix for the x coordinates of a convex hull triangle, n is for "nodes"
yn=y(K); %a 3 by NK matrix for the y coordinates of a convex hull triangle, n is for "nodes"
zn=z(K); %a 3 by NK matrix for the z coordinates of a convex hull triangle, n is for "nodes"
%calculate the 3 edge lengths (in x y and z coordinates) for each
%triangle in the convex hull tesselation
dx=diff(xn(i4,:));
dy=diff(yn(i4,:));
dz=diff(zn(i4,:));
%calculate the voronoi vertices
xv1=-(dz(1,:).*dy(3,:)-dy(1,:).*dz(3,:)); %negative needed to correct sign convention to keep vornoi vertex on same side of sphere as triangle when we normalize it
yv1=-(dx(1,:).*dz(3,:)-dz(1,:).*dx(3,:));
zv1=-(dy(1,:).*dx(3,:)-dx(1,:).*dy(3,:));
normconst=(xv1.^2+yv1.^2+zv1.^2).^-0.5; %voronoi vertex will be on surface of sphere (radius 1), negative needed to correct sign convention to keep vertex from being projected to opposite side of sphere
xv1=xv1.*normconst; %x component of unit vector pointing from origin through the triangle voronoi vertex (voronoi vertex projected up to the surface of the unit sphere)
yv1=yv1.*normconst; %y component of unit vector pointing from origin through the triangle voronoi vertex (voronoi vertex projected up to the surface of the unit sphere)
zv1=zv1.*normconst; %z component of unit vector pointing from origin through the triangle voronoi vertex (voronoi vertex projected up to the surface of the unit sphere)
xv2=-(dz(2,:).*dy(1,:)-dy(2,:).*dz(1,:));
yv2=-(dx(2,:).*dz(1,:)-dz(2,:).*dx(1,:));
zv2=-(dy(2,:).*dx(1,:)-dx(2,:).*dy(1,:));
normconst=(xv2.^2+yv2.^2+zv2.^2).^-0.5; %voronoi vertex will be on surface of sphere (radius 1), negative needed to correct sign convention to keep vertex from being projected to opposite side of sphere
xv2=xv2.*normconst; %x component of unit vector pointing from origin through the triangle voronoi vertex (voronoi vertex projected up to the surface of the unit sphere)
yv2=yv2.*normconst; %y component of unit vector pointing from origin through the triangle voronoi vertex (voronoi vertex projected up to the surface of the unit sphere)
zv2=zv2.*normconst; %z component of unit vector pointing from origin through the triangle voronoi vertex (voronoi vertex projected up to the surface of the unit sphere)
xv3=-(dz(3,:).*dy(2,:)-dy(3,:).*dz(2,:));
yv3=-(dx(3,:).*dz(2,:)-dz(3,:).*dx(2,:));
zv3=-(dy(3,:).*dx(2,:)-dx(3,:).*dy(2,:));
normconst=(xv3.^2+yv3.^2+zv3.^2).^-0.5; %voronoi vertex will be on surface of sphere (radius 1), negative needed to correct sign convention to keep vertex from being projected to opposite side of sphere
xv3=xv3.*normconst; %x component of unit vector pointing from origin through the triangle voronoi vertex (voronoi vertex projected up to the surface of the unit sphere)
yv3=yv3.*normconst; %y component of unit vector pointing from origin through the triangle voronoi vertex (voronoi vertex projected up to the surface of the unit sphere)
zv3=zv3.*normconst; %z component of unit vector pointing from origin through the triangle voronoi vertex (voronoi vertex projected up to the surface of the unit sphere)
%round off error is a big concern average all three possible calculations
xv=(xv1+xv2+xv3)/3;
yv=(yv1+yv2+yv3)/3;
zv=(zv1+zv2+zv3)/3;
normconst=(xv.^2+yv.^2+zv.^2).^-0.5; %use this to project voronoi vertex
%up to surface of (radius 1) sphere
xv=xv.*normconst;
yv=yv.*normconst;
zv=zv.*normconst;
if(any(imag(normconst(:)))||any(isnan(normconst(:))))
error('imaginary numbers or NANs at A');
end
minRad=min(abs(xv.*xn(1,:)+yv.*yn(1,:)+zv.*zn(1,:))); %the minimum distance
%from the origin to any point on the tesselation of the sphere (which will
%be less than 1 because convex hull facets cut below the surface of the
%sphere like a chord on a circle) found by taking the dot product of a node
%and the unit vector that passes through the origin (center of sphere) and
%voronoi vertex of the convex hull triangle determines the distance of the
%convex hull triangle's voronoi vertex from the center of the sphere.
%define shape of fractional spherical triangle patches to be used in
%piecewise constant plotting of point values
ds=(0:0.1:0.9)';
j1=ones(10,1);
j2=2*j1;
xn=x(VorTriNodes);
xV=xv(VorTriVerts);
yn=y(VorTriNodes);
yV=yv(VorTriVerts);
zn=z(VorTriNodes);
zV=zv(VorTriVerts);
VorTriNodes=VorTriNodes(:); %column vector to do the dot product easily
xvc=reshape(...
[xn(j1,:)+ds*(xV(1,:)-xn(1,:)); xV(j1,:)+ds*diff(xV); xV(j2,:)+ds*(xn(1,:)-xV(2,:));...
xn(j2,:)+ds*(xV(1,:)-xn(2,:)); xV(j1,:)+ds*diff(xV); xV(j2,:)+ds*(xn(2,:)-xV(2,:))],...
30,3*NK);
yvc=reshape(...
[yn(j1,:)+ds*(yV(1,:)-yn(1,:)); yV(j1,:)+ds*diff(yV); yV(j2,:)+ds*(yn(1,:)-yV(2,:));...
yn(j2,:)+ds*(yV(1,:)-yn(2,:)); yV(j1,:)+ds*diff(yV); yV(j2,:)+ds*(yn(2,:)-yV(2,:))],...
30,3*NK);
zvc=reshape(...
[zn(j1,:)+ds*(zV(1,:)-zn(1,:)); zV(j1,:)+ds*diff(zV); zV(j2,:)+ds*(zn(1,:)-zV(2,:));...
zn(j2,:)+ds*(zV(1,:)-zn(2,:)); zV(j1,:)+ds*diff(zV); zV(j2,:)+ds*(zn(2,:)-zV(2,:))],...
30,3*NK);
invrvc=(xvc.^2+yvc.^2+zvc.^2).^-0.5;
xvc=xvc.*invrvc;
yvc=yvc.*invrvc;
zvc=zvc.*invrvc;
if(any(imag(invrvc(:)))||any(isnan(invrvc(:))))
error('imaginary numbers or NANs at B');
end
%time to compute the area of the spherical triangle components of the
%voronoi cells areound each point
%first define the corners of the voronoi cell triangles and their edge
%lengths (direction matters)
xt=reshape([xn(1,:); xV; xn(2,:); xV],3,3*NK); dx=diff(xt(i4,:));
yt=reshape([yn(1,:); yV; yn(2,:); yV],3,3*NK); dy=diff(yt(i4,:));
zt=reshape([zn(1,:); zV; zn(2,:); zV],3,3*NK); dz=diff(zt(i4,:));
%now going to compute unit vectors tangent to surface in direction of
%great circles connection nodes of voronoi cell component triangles
%dx, dy, dz are chord distances in forward (a) direction
yada=xt.*dx+yt.*dy+zt.*dz;
dirxa=dx-xt.*yada; %x component of derivative/tangent vector at point
dirya=dy-yt.*yada; %y component of derivative/tangent vector at point
dirza=dz-zt.*yada; %z component of derivative/tangent vector at point
yada=(dirxa.^2+dirya.^2+dirza.^2);
yada=(yada+(yada==0)).^-0.5; %protect against zero magnitude vectors which
%causes NaNs without this protection, yes it happened in a Tensor Product
%grid (vornoi vertex was outside triangle on top of other voronoi vertex)
dirxa=dirxa.*yada; %x component of unit vector direction
dirya=dirya.*yada; %y component of unit vector direction
dirza=dirza.*yada; %z component of unit vector direction
if(any(imag(yada(:)))||any(isnan(yada(:))))
error('imaginary numbers or NANs at C');
end
%make dx, dy, dz chord distances in backward (b) direction
dx=-dx(i3,:); dy=-dy(i3,:); dz=-dz(i3,:);
yada=xt.*dx+yt.*dy+zt.*dz;
dirxb=dx-xt.*yada; %x component of derivative/tangent vector at point
diryb=dy-yt.*yada; %y component of derivative/tangent vector at point
dirzb=dz-zt.*yada; %z component of derivative/tangent vector at point
yada=(dirxb.^2+diryb.^2+dirzb.^2);
yada=(yada+(yada==0)).^-0.5;%protect against zero magnitude vectors which
%causes NaNs without this protection, yes it happened in a Tensor Product
%grid (vornoi vertex was outside triangle on top of other voronoi vertex)
dirxb=dirxb.*yada; %x component of unit vector direction
diryb=diryb.*yada; %y component of unit vector direction
dirzb=dirzb.*yada; %z component of unit vector direction
if(any(imag(yada(:)))||any(isnan(yada(:))))
error('imaginary numbers or NANs at D');
end
%fractional spherical triangle area is the sum of the 3 interior angles
%minus pi times the square of the sphere's radius (which is 1). Interior
%angle is the arc cosine of the dot product of the forward and backward
%unit vectors (in the directions away from one corner of spherical triangle
%to the other 2 corners)
%min and max are needed to fix round off error that could cause imaginary
%angles via arc-hyperbolic-cosine, but max would replace NaN with -1 which
%makes angle pi when should have been zero, see the
%yada=(yada+(yada==0)).^-0.5; protection above
areacontrib=sum(acos(min(max(dirxa.*dirxb+dirya.*diryb+dirza.*dirzb,-1),1)))-pi;
%fix round off error that could cause "negative zero" areas
areacontrib=areacontrib.*~((areacontrib<0)&(areacontrib>-(2^-35)));
if(any(imag(areacontrib))||any(areacontrib<0))
%area might be imaginary or negative if there's a bug (other than round
%off error which should have been fixed).
save DEBUGME_voronoiSphereArea
error('imaginary or negative areas at E Npts=%d',Npts);
end
ptArea=zeros(Npts,1);
for ipt=1:Npts
ptArea(ipt)=areacontrib*(VorTriNodes==ipt); %matrix ops dot product to
%sum the areas of all spherical triangles that make of the point's
%voronoi cell
end
%SphereAreaDiv4pi=sum(ptArea)/(4*pi) %is 1 to 8+ sig figs
ptArea=ptArea*(4*pi/sum(ptArea)); %"fix" round off error in ptArea
K=K'; %this is no longer K transpose
xv=xv(:); %for drawing voronoi cell edges
yv=yv(:);
zv=zv(:);
VorTriNodes=VorTriNodes'; %for plotting piecewise constant voronoi cells as
%being made up of triangles with 1 node and 2 vertices.
end

3D Ellipsoid out of discrete units

I'm trying to draw an ellipsoid in 3d space out of individual blocks.
I have no problem with 2D ellipses, but as far as 3D goes I'm having some trouble. I'm using Bresenham's circle algorithm to draw 2D ellipses. What I'm trying to do is draw 2D ellipses in layers with an increasing (starting from the bottom going up, using symmetry for the other half) radius on both the X radius and Y radius.
It all sounds like it would work, but when I go to implement it, I can't figure out how to alter the x radius and y radius to make the curve of the ellipsoid.
Your 2D slices should all have the same orientation and aspect ratio.
If your ellipsoid is axis-aligned, they should also have the same center.
Your slices should scale proportionally to:
scale = sqrt(1 - ((center-z)/half_vsize)^2)
where:
z = height of the current slice
center = height of the largest slice
half_vsize = half the vertical size of the ellipsoid
If (x0, y0) is the x- and y-width of the largest slice, (x, y) = (scale*x0, scale*y0) is the x- and y-width of the slice at height z.

Finding the area of a 2-D data set

I have a .txt file with about 100,000 points in the 2-D plane. When I plot the points, there is a clearly defined 2-D region (think of a 2-D disc that has been morphed a bit).
What is the easiest way to compute the area of this region? Any way of doing easily in Matlab?
I made a polygonal approximation by finding a bunch (like 40) points on the boundary of the region and computing the area of the polygonal region in Matlab, but I was wondering if there is another, less tedious method than finding 40 points on the boundary.
Consider this example:
%# random points
x = randn(300,1);
y = randn(300,1);
%# convex hull
dt = DelaunayTri(x,y);
k = convexHull(dt);
%# area of convex hull
ar = polyarea(dt.X(k,1),dt.X(k,2))
%# plot
plot(dt.X(:,1), dt.X(:,2), '.'), hold on
fill(dt.X(k,1),dt.X(k,2), 'r', 'facealpha', 0.2);
hold off
title( sprintf('area = %g',ar) )
There is a short screencast By Doug Hull which solves this exact problem.
EDIT:
I am posting a second answer inspired by the solution proposed by #Jean-FrançoisCorbett.
First I create random data, and using the interactive brush tool, I remove some points to make it look like the desired "kidney" shape...
To have a baseline to compare against, we can manually trace the enclosing region using the IMFREEHAND function (I'm doing this using my laptop's touchpad, so not the most accurate drawing!). Then we find the area of this polygon using POLYAREA. Just like my previous answer, I compute the convex hull as well:
Now, and based on a previous SO question I had answered (2D histogram), the idea is to lay a grid over the data. The choice of the grid resolution is very important, mine was numBins = [20 30]; for the data used.
Next we count the number of squares containing enough points (I used at least 1 point as threshold, but you could try a higher value). Finally we multiply this count by the area of one grid square to obtain the approximated total area.
%### DATA ###
%# some random data
X = randn(100000,1)*1;
Y = randn(100000,1)*2;
%# HACK: remove some point to make data look like a kidney
idx = (X<-1 & -4<Y & Y<4 ); X(idx) = []; Y(idx) = [];
%# or use the brush tool
%#brush on
%### imfreehand ###
figure
line('XData',X, 'YData',Y, 'LineStyle','none', ...
'Color','b', 'Marker','.', 'MarkerSize',1);
daspect([1 1 1])
hROI = imfreehand('Closed',true);
pos = getPosition(hROI); %# pos = wait(hROI);
delete(hROI)
%# total area
ar1 = polyarea(pos(:,1), pos(:,2));
%# plot
hold on, plot(pos(:,1), pos(:,2), 'Color','m', 'LineWidth',2)
title('Freehand')
%### 2D histogram ###
%# center of bins
numBins = [20 30];
xbins = linspace(min(X), max(X), numBins(1));
ybins = linspace(min(Y), max(Y), numBins(2));
%# map X/Y values to bin-indices
Xi = round( interp1(xbins, 1:numBins(1), X, 'linear', 'extrap') );
Yi = round( interp1(ybins, 1:numBins(2), Y, 'linear', 'extrap') );
%# limit indices to the range [1,numBins]
Xi = max( min(Xi,numBins(1)), 1);
Yi = max( min(Yi,numBins(2)), 1);
%# count number of elements in each bin
H = accumarray([Yi(:), Xi(:)], 1, [numBins(2) numBins(1)]);
%# total area
THRESH = 0;
sqNum = sum(H(:)>THRESH);
sqArea = (xbins(2)-xbins(1)) * (ybins(2)-ybins(1));
ar2 = sqNum*sqArea;
%# plot 2D histogram/thresholded_histogram
figure, imagesc(xbins, ybins, H)
axis on, axis image, colormap hot; colorbar; %#caxis([0 500])
title( sprintf('2D Histogram, bins=[%d %d]',numBins) )
figure, imagesc(xbins, ybins, H>THRESH)
axis on, axis image, colormap gray
title( sprintf('H > %d',THRESH) )
%### convex hull ###
dt = DelaunayTri(X,Y);
k = convexHull(dt);
%# total area
ar3 = polyarea(dt.X(k,1), dt.X(k,2));
%# plot
figure, plot(X, Y, 'b.', 'MarkerSize',1), daspect([1 1 1])
hold on, fill(dt.X(k,1),dt.X(k,2), 'r', 'facealpha',0.2); hold off
title('Convex Hull')
%### plot ###
figure, hold on
%# plot histogram
imagesc(xbins, ybins, H>=1)
axis on, axis image, colormap gray
%# plot grid lines
xoff = diff(xbins(1:2))/2; yoff = diff(ybins(1:2))/2;
xv1 = repmat(xbins+xoff,[2 1]); xv1(end+1,:) = NaN;
yv1 = repmat([ybins(1)-yoff;ybins(end)+yoff;NaN],[1 size(xv1,2)]);
yv2 = repmat(ybins+yoff,[2 1]); yv2(end+1,:) = NaN;
xv2 = repmat([xbins(1)-xoff;xbins(end)+xoff;NaN],[1 size(yv2,2)]);
xgrid = [xv1(:);NaN;xv2(:)]; ygrid = [yv1(:);NaN;yv2(:)];
line(xgrid, ygrid, 'Color',[0.8 0.8 0.8], 'HandleVisibility','off')
%# plot points
h(1) = line('XData',X, 'YData',Y, 'LineStyle','none', ...
'Color','b', 'Marker','.', 'MarkerSize',1);
%# plot convex hull
h(2) = patch('XData',dt.X(k,1), 'YData',dt.X(k,2), ...
'LineWidth',2, 'LineStyle','-', ...
'EdgeColor','r', 'FaceColor','r', 'FaceAlpha',0.5);
%# plot freehand polygon
h(3) = plot(pos(:,1), pos(:,2), 'g-', 'LineWidth',2);
%# compare results
title(sprintf('area_{freehand} = %g, area_{grid} = %g, area_{convex} = %g', ...
ar1,ar2,ar3))
legend(h, {'Points' 'Convex Jull','FreeHand'})
hold off
Here is the final result of all three methods overlayed, with the area approximations displayed:
My answer is the simplest and perhaps the least elegant and precise. But first, a comment on previous answers:
Since your shape is usually kidney-shaped (not convex), calculating the area of its convex hull won't do, and an alternative is to determine its concave hull (see e.g. http://www.concavehull.com/home.php?main_menu=1) and calculate the area of that. But determining a concave hull is far more difficult than a convex hull. Plus, straggler points will cause trouble in both he convex and concave hull.
Delaunay triangulation followed by pruning, as suggested in #Ed Staub's answer, may a bit be more straightforward.
My own suggestion is this: How precise does your surface area calculation have to be? My guess is, not very. With either concave hull or pruned Delaunay triangulation, you'll have to make an arbitrary choice anyway as to where the "boundary" of your shape is (the edge isn't knife-sharp, and I see there are some straggler points sprinkled around it).
Therefore a simpler algorithm may be just as good for your application.
Divide your image in an orthogonal grid. Loop through all grid "pixels" or squares; if a given square contains at least one point (or perhaps two points?), mark the square as full, else empty. Finally, add the area of all full squares. Bingo.
The only parameter is the resolution length (size of the squares). Its value should be set to something similar to the pruning length in the case of Delaunay triangulation, i.e. "points within my shape are closer to each other than this length, and points further apart than this length should be ignored".
Perhaps an additional parameter is the number of points threshold for a square to be considered full. Maybe 2 would be good to ignore straggler points, but that may define the main shape a bit too tightly for your taste... Try both 1 and 2, and perhaps take an average of both. Or, use 1 and prune away the squares that have no neighbours (game-of-life-style). Simlarly, empty squares whose 8 neighbours are full should be considered full, to avoid holes in the middle of the shape.
There is no end to how much this algorithm can be refined, but due to the arbitrariness intrinsic to the problem definition in your particular application, any refinement is probably the algorithm equivalent of "polishing a turd".
I know next to nothing, so don't put much stock in this... consider doing a Delaunay triangulation. Then remove any hull (outer) edges longer than some maximum. Repeat until nothing to remove. Fill the remaining triangles.
This will orphan some outlier points.
I suggest using a space-filling-curve, for example a z-curve or better a moore curve. A sfc fills the full space and is good to index each points. For example for all f(x)=y you can sort the points of the curve in ascendending order and from that result you take as many points until you get a full roundtrip. These points you can then use to compute the area. Because you have many points maybe you want to use less points and use a cluster which make the result less accurate.
I think you can get the border points using convex hull algorithm with restriction to the edge length (you should sort points by vertical axis). Thus it will follow nonconvexity of your region. I propose length round 0.02. In any case you can experiment a bit with different lengths drawing the result and examining it visually.

Resources