Given a set of points, how do I approximate the major axis of its shape? - graphics

Given a "shape" drawn by the user, I would like to "normalize" it so they all have similar size and orientation. What we have is a set of points. I can approximate the size using bounding box or circle, but the orientation is a bit more tricky.
The right way to do it, I think, is to calculate the majoraxis of its bounding ellipse. To do that you need to calculate the eigenvector of the covariance matrix. Doing so likely will be way too complicated for my need, since I am looking for some good-enough estimate. Picking min, max, and 20 random points could be some starter. Is there an easy way to approximate this?
Edit:
I found Power method to iteratively approximate eigenvector. Wikipedia article.
So far I am liking David's answer.

You'd be calculating the eigenvectors of a 2x2 matrix, which can be done with a few simple formulas, so it's not that complicated. In pseudocode:
// sums are over all points
b = -(sum(x * x) - sum(y * y)) / (2 * sum(x * y))
evec1_x = b + sqrt(b ** 2 + 1)
evec1_y = 1
evec2_x = b - sqrt(b ** 2 + 1)
evec2_y = 1
You could even do this by summing over only some of the points to get an estimate, if you expect that your chosen subset of points would be representative of the full set.
Edit: I think x and y must be translated to zero-mean, i.e. subtract mean from all x, y first (eed3si9n).

Here's a thought... What if you performed a linear regression on the points and used the slope of the resulting line? If not all of the points, at least a sample of them.
The r^2 value would also give you information about the general shape. The closer to 0, the more circular/uniform the shape is (circle/square). The closer to 1, the more stretched out the shape is (oval/rectangle).

The ultimate solution to this problem is running PCA
I wish I could find a nice little implementation for you to refer to...

Here you go! (assuming x is a nx2 vector)
def majAxis(x):
e,v = np.linalg.eig(np.cov(x.T)); return v[:,np.argmax(e)]

Related

Computing the area filled by matplotlib.pyplot.fill(...)

I'd like to compute the area inside of a curve defined by two vectors a and b. For your reference the curve looks something like this (pyplot.plot(a,b)):
I saw matplotlib has a fill functionality that let you fill the area enclosed by the curve:
I'm wondering, there's any way to obtain the area filled using that same function? It would be very useful as the other way I'm thinking of computing that area is through numerical integration, much more cumbersome.
Thank you for your time.
If you really want to find the area that was filled by matplotlib.pyplot.fill(a, b), you can use its output as follows:
def computeArea(pos):
x, y = (zip(*pos))
return 0.5 * numpy.abs(numpy.dot(x, numpy.roll(y, 1)) - numpy.dot(y, numpy.roll(x, 1)))
# pyplot.fill(a, b) will return a list of matplotlib.patches.Polygon.
polygon = matplotlib.pyplot.fill(a, b)
# The area of the polygon can be computed as follows:
# (you could also sum the areas of all polygons in the list).
print(computeArea(polygon[0].xy))
This method is based on this answer,
and it is not the most efficient one.

Given these static points, which is Bezier Curve

I have this question that is to determine the bezier curve that goes through theses points: (1,1), (2,-1), (3,1). How can we find the curve? I dont get how to use the equation. And how could we find the curves degree?
There is infinity of Bezier curves through these points. You need to be more specific.
For example, one may define that curve is quadratic, the first point is starting one (t=0), the third is ending one (t=1) and the second is exact middle (t=0.5).
Then build equations (substituting t value and points coordinates) and solve them for coefficients
p[0].x * (1-t)^2 + p[1].x * 2 * t * (1-t) + p[2].x * t^2 = X(t)
example for the first point:
p[0].x * (1-0)^2 + p[1].x * 2 * 0 * (1-0) + p[2].x * (0)^2 = 1
p[0].x = 2
A Bezier curve of degree N is a weighted sum of N+1 control points in 2D
Sum(i=0,N) Wi(t).Ci
If you have N+1 points Pj for which the values of t are known, you get a linear system of 2(N+1) equations in 2(N+1) unknowns
Sum(I=0,N) Wi(tj).Ci = Pj
The tj can be chosen uniformly in [0,1]. Another choice is using the cumulated linear distances between the control points (also reduced to [0,1]).
It is a very different matter if you don't want to provide the tj yourself. Then you can reduce the degree of the curve and trade control points for the values of t. Generally, the number of equations and unknowns cannot match and the system will be over or underdetermined.
Another difficulty is if you impose the order of the points. Then the unknowns t are constrained to be increasing, leading to a difficult system of equations and inequations.

What is the fastest way to find the center of an irregular convex polygon?

I'm interested in a fast way to calculate the rotation-independent center of a simple, convex, (non-intersecting) 2D polygon.
The example below (on the left) shows the mean center (sum of all points divided by the total), and the desired result on the right.
Some options I've already considered.
bound-box center (depends on rotation, and ignores points based on their relation to the axis).
Straight skeleton - too slow to calculate.
I've found a way which works reasonably well, (weight the points by the edge-lengths) - but this means a square-root call for every edge - which I'd like to avoid.(Will post as an answer, even though I'm not entirely satisfied with it).
Note, I'm aware of this questions similarity with:What is the fastest way to find the "visual" center of an irregularly shaped polygon?
However having to handle convex polygons increases the complexity of the problem significantly.
The points of the polygon can be weighted by their edge length which compensates for un-even point distribution.
This works for convex polygons too but in that case the center point isn't guaranteed to be inside the polygon.
Psudo-code:
def poly_center(poly):
sum_center = (0, 0)
sum_weight = 0.0
for point in poly:
weight = ((point - point.next).length +
(point - point.prev).length)
sum_center += point * weight
sum_weight += weight
return sum_center / sum_weight
Note, we can pre-calculate all edge lengths to halve the number of length calculations, or reuse the previous edge-length for half+1 length calculations. This is just written as an example to show the logic.
Including this answer for completeness since its the best method I've found so far.
There is no much better way than the accumulation of coordinates weighted by the edge length, which indeed takes N square roots.
If you accept an approximation, it is possible to skip some of the vertices by curve simplification, as follows:
decide of a deviation tolerance;
start from vertex 0 and jump to vertex M (say M=N/2);
check if the deviation along the polyline from 0 to M exceeds the tolerance (for this, compute the height of the triangle formed by the vertices 0, M/2, M);
if the deviation is exceeded, repeat recursively with 0, M/4, M/2 and M/2, 3M/4, M;
if the deviation is not exceeded, assume that the shape is straight between 0 and M.
continue until the end of the polygon.
Where the points are dense (like the left edge on your example), you should get some speedup.
I think its easiest to do something with the center of masses of the delaunay triangulation of the polygon points. i.e.
def _centroid_poly(poly):
T = spatial.Delaunay(poly).simplices
n = T.shape[0]
W = np.zeros(n)
C = 0
for m in range(n):
sp = poly[T[m,:],:]
W[m] = spatial.ConvexHull(sp).volume
C += W[m] +np.mean(sp, axis = 0)
return C / np.sum(W)
This works well for me!

Generate random points inside a rectangle (uniformly)?

I am trying to generate a certain amount of random uniform points inside a rectangle (I know the pair of coordinates for each corner).
Let our rectangle be
ABCD
My idea is:
Divide the rectangle into two triangles by the AC diagonal. Find the slope and the intercept of the diagonal.
Then, generate two random numbers from [0,1] interval, let them be a,b.
Evaluate x = aAB and y = bAD (AB, AD, distances). If A is not (0,0), then we can add to x and y A's coordinates.
Now we have a point (x,y). If it is not in the lower triangle (ABC), skip to the next step.
Else, add the point to our plot and also add the symmetric of (x,y) vs. the AC diagonal so that we can fill the upper triangle (ADC) too.
I have implemented this, but I highly doubt that the points are uniformly generated (judging from the plot). How should I modify my algorithm? I guess that the issue is related to how I pick the triangle and the symmetric thing.
Why not just generate x=random([A.x, B.x]) and y=random([B.y, C.y]) and put them together as (x,y)? A n-dimensional uniform distribution is simply the product of the n uniform distributions of the components.
This is referred to as point picking and other similar terms. You seem to be on the right track in that the points should come from the uniform distribution. Your plot looks reasonably random to me.
What are you doing with upper and lower triangles? They seem unnecessary and would certainly make things less random. Is this some form variance reduction along the lines of antithetic variates? If #Paddy3118 is right an you really just need random-ish points to fill the space, then you should look into low-discrepancy sequences. The Halton sequence generalizes the van der Corput sequence to multiple dimensions. If you have Matlab's Statistics Toolbox check out the sobolset and haltonset functions or qrandstream and qrand.
This approach (from #Xipan Xiao & #bonanova.) should be reproducible in many languages. MATLAB code below.
a = 0; b = 1;
n = 2000;
X = a + (b-a)*rand(n,1);
Y = a + (b-a)*rand(n,1);
Newer versions of MATLAB can make use of the makedist and random commands.
pdX = makedist('Uniform',a,b);
pdY = makedist('Uniform',a,b);
X = random(pdX,n,1);
Y = random(pdY,n,1);
The points (X,Y) will be uniformly in the rectangle with corner points (a,a), (a,b), (b,a), (b,b).
For verification, we can observe the marginal distributions for X and Y and see that those are uniform as well.
scatterhist(X,Y,'Marker','.','Direction','out')
Update: Using haltonset (suggested by #horchler)
p = haltonset(2);
XY = net(p,2000);
scatterhist(XY(:,1),XY(:,2),'Marker','.','Direction','out')
If you are after a more uniform density then you might consider a Van der Corput sequence. The sequence finds use in Monte-Carlo simulations and Wolfram Mathworld calls them a quasi-random sequence.
Generate two random numbers in the interval [0,1] translate and scale them to your rectangle as x and y.
There is just my thought, i haven't test with code yet.
1.Divide the rectangle to grid with N x M cells, depends on variable density.
2.loop through the cell and pick a random point in the cell until it reached your target point quantity.

Rotating 3D cube perspective problem

Since I was 13 and playing around with AMOS 3D I've been wanting to learn how to code 3D graphics. Now, 10 years later, I finally think I have have accumulated enough maths to give it a go.
I have followed various tutorials, and defined screenX (and screenY, equivalently) as
screenX = (pointX * cameraX) / distance
(Plus offsets and scaling.)
My problem is with what the distance variable actually refers to. I have seen distance being defined as the difference in z between the camera and the point. However, that cannot be completely right though, since x and y have the same effect as z on the actual distance from the camera to the point. I implemented distance as the actual distance, but the result gives a somewhat skewed perspective, as if it had "too much" perspective.
My "actual distance" implementation was along the lines of:
distance = new Vector(pointX, pointY, cameraZ - pointZ).magnitude()
Playing around with the code, I added an extra variable to my equation, a perspectiveCoefficient as follows:
distance = new Vector(pointX * perspectiveCoefficient,
pointY * perspectiveCoefficient, cameraZ - pointZ).magnitude()
For some reason, that is beyond me, I tend to get the best result setting the perspectiveCoefficient to 1/sqrt(2).
My 3D test cube is at http://vega.soi.city.ac.uk/~abdv866/3dcubetest/3dtest.svg. (Tested in Safari and FF.) It prompts you for a perspectiveCoefficient, where 0 gives a perspective without taking x/y distance into consideration, and 1 gives you a perspective where x, y and z distance is equally considered. It defaults to 1/sqrt(2). The cube can be rotated about x and y using the arrow keys. (For anyone interested, the relevant code is in update() in the View.js file.)
Grateful for any ideas on this.
Usually, projection is done on the Z=0 plane from an eye position behind this plane. The projected point is the intersection of the line (Pt,Eye) with the Z=0 plane. At the end you get something like:
screenX = scaling * pointX / (1 + pointZ/eyeDist)
screenY = scaling * pointY / (1 + pointZ/eyeDist)
I assume here the camera is at (0,0,0) and eye at (0,0,-eyeDist). If eyeDist becomes infinite, you obtain a parallel projection.

Resources