Create a cycle out of scattered points - python

I know this sounds trivial, but my head is refusing to give an algorithm for this.
I have a bunch of points scattered on a 2-D plane and want to store them in a list such that they create a ring. The points do not belong on a cycle.
Start from the first point in the list (red in this pic) and sequentially add the rest based on their distance.
Since I cannot answer my question I will post here a possible answer.
This is an approach that seems to do the job.
V.pos holds the positions of the nodes and distance() is just a function that returns the Euclidean distance between two points. A faster approach would also delete the next_node after appending it to the ring so that you don't have to go through the already connected points
ring = [nodes[0]]
while len(ring) < len(nodes):
minl=99999
for i in range(len(nodes)):
dist = distance(V.pos[ring[-1]],V.pos[nodes[i]])
if dist<minl and nodes[i] not in ring:
minl = dist
next_node = nodes[i]
ring.append(next_node)

Here's an idea that will give okay-ish results if your point cloud is already ring-shaped like your example:
Determine a centre point; this can either be the centre of gravity of all points or the centre of the bounding box.
Represent all points in radial coordinates (radius, angle) with reference to the centre
Sort by angle
This will, of course, produce jagged stars for random clouds, but it is not clear, what exactly a "ring" is. You could probably use this as a first draft and start swapping nodes if that gives you a shorter overall distance. Maybe this simple code is all you need short of implementing the minimum distance over all nodes of a graph.
Anayway, here goes:
import math
points = [(0, 4), (2, 2), ...] # original points in Cartesian coords
radial = [] # list of tuples(index, angle)
# find centre point (centre of gravity)
x0, y0 = 0, 0
for x, y in points:
x0 += x
y0 += y
x0 = 1.0 * x0 / len(points)
y0 = 1.0 * y0 / len(points)
# calculate angles
for i, p in enumerate(points):
x, y = p
phi = math.atan2(y - y0, x - x0)
radial += [(i, phi)]
# sort by angle
def rsort(a, b):
"""Sorting criterion for angles"""
return cmp(a[1], b[1])
radial.sort(rsort)
# extract indices
ring = [a[0] for a in radial]

Related

Finding points from one point with certain angle and certain distance python

I want to find red points count below picture. Using python. How can I do that? Using given angle, and distance according to placemark..
Every point has coordinate(latitude,longitude). Placemark also has coordinate.
Fro example, You can use angle 85 degress and its beamwitdh can be taken 50 degress.
there are many red dots in given area. I have some limitations (angle and distance). How can I count red dots?
BR,
Your question is relatively vague so for the time being I am going to assume that this is a circle.
Well, using some math, we can figure out how to get the distance, d, and angle, theta, coordinates based off of the x and y coordinates.
We know:
x^2 + y^2 = d^2
tan(theta) = y/x
For the actual math behind this, you can draw a right triangle with angle theta, hypotenuse length d, horizontal length x, and vertical length y.
Solving for theta and d:
d = sqrt(x^2 + y^2)
theta = arctan(y/x)
Our algorithm will be taking in a list of tuples (to represent points), and seeing if the angle each point makes is within a certain range. Additionally, we will check if the distance each point makes is less than our limit. If both of these conditions are met, then we will increase our total.
Now that we know how to find our coordinates based on the distance and angle, we can easily make our function:
import math
def countPoints(maxDist, angle1, angle2, points):
tot = 0
for (x,y) in points:
theta = math.atan(y/x)
dist = math.sqrt(x ** 2 + y ** 2)
if theta > angle1 and theta < angle2:
if dist < maxDist:
tot += 1
return tot
Sample:
points = [(1,1),(2,3),(3,5),(4,6)]
print(countPoints(4,0,1.2,points))
Output:
2
The first two points have an angle within 0 and 1.2 radians and a distance less than 4.
Note: For this function, we are assuming angle1 < angle2, and that if angle1 dips below the horizontal axis, it will be negative. If your input has angle1 as positive, but is below the horizontal axis, just multiply it by -1 before inputting it into the function.
I hope this helped! Please let me know if you need any further help or clarification!

Find new position for overlapping circles

I am trying to write a code that, for a given list of circles (list1), it is able to find the positions for new circles (list2). list1 and list2 have the same length, because for each circle in list1 there must be a circle from list2.
Each pair of circles (let's say circle1 from list1 and circle2 from list2), must be as close together as possible,
circles from list2 must not overlap with circles from list1, while circles of the single lists can overlap each other.
list1 is fixed, so now I have to find the right position for circles from list2.
I wrote this simple function to recognize if 2 circles overlap:
def overlap(x1, y1, x2, y2, r1, r2):
distSq = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)
radSumSq = (r1 + r2) * (r1 + r2)
if (distSq >= radSumSq):
return False # no overlap
else:
return True #overlap
and this is the list1:
with:
x=[14.11450195 14.14184093 14.15435028 14.16206741 14.16951752 14.17171097
14.18569565 14.19700241 14.23129082 14.24083233 14.24290752 14.24968338
14.2518959 14.26536751 14.27209759 14.27612877 14.2904377 14.29187012
14.29409599 14.29618549 14.30615044 14.31624985 14.3206892 14.3228569
14.36143875 14.36351967 14.36470699 14.36697292 14.37235737 14.41422081
14.42583466 14.43226814 14.43319225 14.4437027 14.4557848 14.46592999
14.47036076 14.47452068 14.47815609 14.52229309 14.53059006 14.53404236
14.5411644 ]
y=[-0.35319126 -0.44222349 -0.44763246 -0.35669261 -0.24366629 -0.3998799
-0.38940558 -0.57744932 -0.45223859 -0.21021004 -0.44250247 -0.45866323
-0.47203487 -0.51684451 -0.44884869 -0.2018993 -0.40296811 -0.23641759
-0.18019417 -0.33391538 -0.53565156 -0.45215255 -0.40939832 -0.26936951
-0.30894437 -0.55504167 -0.47177047 -0.45573688 -0.43100587 -0.5805912
-0.21770373 -0.199422 -0.17372169 -0.38522363 -0.56950212 -0.56947368
-0.48770753 -0.24940367 -0.31492445 -0.54263926 -0.53460872 -0.4053807
-0.43733299]
radius = 0.014
Copy and pasteable...
x = [14.11450195,14.14184093,14.15435028,14.16206741,14.16951752,
14.17171097,14.18569565,14.19700241,14.23129082,14.24083233,
14.24290752,14.24968338,14.2518959,14.26536751,14.27209759,
14.27612877,14.2904377,14.29187012,14.29409599,14.29618549,
14.30615044,14.31624985,14.3206892,14.3228569,14.36143875,
14.36351967,14.36470699,14.36697292,14.37235737,14.41422081,
14.42583466,14.43226814,14.43319225,14.4437027,14.4557848,
14.46592999,14.47036076,14.47452068,14.47815609,14.52229309,
14.53059006,14.53404236,14.5411644]
y = [-0.35319126,-0.44222349,-0.44763246,-0.35669261,-0.24366629,
-0.3998799,-0.38940558,-0.57744932,-0.45223859,-0.21021004,
-0.44250247,-0.45866323,-0.47203487,-0.51684451,-0.44884869,
-0.2018993,-0.40296811,-0.23641759,-0.18019417,-0.33391538,
-0.53565156,-0.45215255,-0.40939832,-0.26936951,-0.30894437,
-0.55504167,-0.47177047,-0.45573688,-0.43100587,-0.5805912,
-0.21770373,-0.199422,-0.17372169,-0.38522363,-0.56950212,
-0.56947368,-0.48770753,-0.24940367,-0.31492445,-0.54263926,
-0.53460872,-0.4053807,-0.43733299]
Now I am not sure about what I have to do, my first idea is to draw circles of list2 taking x and y from list one and do something like x+c and y+c, where c is a fixed value. Then I can call my overlapping function and, if there is overlap I can increase the c value.
In this way I have 2 for loops. Now, my questions are:
There is a way to avoid for loops?
Is there a smart solution to find a neighbor (circle from list2) for each circle from list1 (without overlaps with other circles from list2)?
Using numpy arrays, you can avoid for loops.
Setup from your example.
import numpy as np
#Using your x and y
c1 = np.array([x,y]).T
# random set of other centers within the same range as c1
c2 = np.random.random((10,2))
np.multiply(c2, c1.max(0)-c1.min(0),out = c2)
np.add(c2, c1.min(0), out=c2)
radius = 0.014
r = radius
min_d = (2*r)*(2*r)
plot_circles(c1,c2) # see function at end
An array of distances from each center in c1 to each center in c2
def dist(c1,c2):
dx = c1[:,0,None] - c2[:,0]
dy = c1[:,1,None] - c2[:,1]
return dx*dx + dy*dy
d = dist(c1,c2)
Or you could use scipy.spatial
from scipy.spatial import distance
d = distance.cdist(c1,c2,'sqeuclidean')
Create a 2d Boolean array for circles that intersect.
intersect = d <= min_d
Find the indices of overlapping circles from the two sets.
a,b = np.where(intersect)
plot_circles(c1[a],c2[b])
Using intersect or a and b to index c1,c2, and d you should be able to get groups of intersecting circles then figure out how to move the c2 centers - but I'll leave that for another question/answer. If a list2 circle intersects one list1 circle - find the line between the two and move along that line. If a list2 circle intersects more than one list1 circle - find the line between the two closestlist1circles and move thelitst2` circle along a line perpendicular to that. You didn't mention any constraints on moving the circles so maybe random movement then find the intersects again but that might be problematic. In the following image, it may be trivial to figure out how to move most of the red circles but the group circled in blue might require a different strategy.
Here are some examples for getting groups:
>>> for f,g,h in zip(c1[a],c2[b],d[a,b]):
print(f,g,h)
>>> c1[intersect.any(1)],c2[intersect.any(0)]
>>> for (f,g) in zip(c2,intersect.T):
if g.any():
print(f.tolist(),c1[g].tolist())
import matplotlib as mpl
from matplotlib import pyplot as plt
def plot_circles(c1,c2):
bounds = np.array([c1.min(0),c2.min(0),c1.max(0),c2.max(0)])
xmin, ymin = bounds.min(0)
xmax, ymax = bounds.max(0)
circles1 = [mpl.patches.Circle(xy,radius=r,fill=False,edgecolor='g') for xy in c1]
circles2 = [mpl.patches.Circle(xy,radius=r,fill=False,edgecolor='r') for xy in c2]
fig = plt.figure()
ax = fig.add_subplot(111)
for c in circles2:
ax.add_artist(c)
for c in circles1:
ax.add_artist(c)
ax.set_xlim(xmin-r,xmax+r)
ax.set_ylim(ymin-r,ymax+r)
plt.show()
plt.close()
This problem can very well be seen as an optimization problem. To be more precise, a nonlinear optimization problem with constraints.
Since optimization strategies are not always so easy to understand, I will define the problem as simply as possible and also choose an approach that is as general as possible (but less efficient) and does not involve a lot of mathematics. As a spoiler: We are going to formulate the problem and the minimization process in less than 10 lines of code using the scipy library.
However, I will still provide hints on where you can get your hands even dirtier.
Formulating the problem
As a guide for a formulation of an NLP-class problem (Nonlinear Programming), you can go directly to the two requirements in the original post.
Each pair of circles must be as close together as possible -> Hint for a cost-function
Circles must not overlap with other (moved) circles -> Hint for a constraint
Cost function
Let's start with the formulation of the cost function to be minimized.
Since the circles should be moved as little as possible (resulting in the closest possible neighborhood), a quadratic penalty term for the distances between the circles of the two lists can be chosen for the cost function:
import scipy.spatial.distance as sd
def cost_function(new_positions, old_positions):
new_positions = np.reshape(new_positions, (-1, 2))
return np.trace(sd.cdist(new_positions, old_positions, metric='sqeuclidean'))
Why quadratic? Partly because of differentiability and for stochastic reasons (think of the circles as normally distributed measurement errors -> least squares is then a maximum likelihood estimator). By exploiting the structure of the cost function, the efficiency of the optimization can be increased (elimination of sqrt). By the way, this problem is related to nonlinear regression, where (nonlinear) least squares are also used.
Now that we have a cost function at hand, we also have a good way to evaluate our optimization. To be able to compare solutions of different optimization strategies, we simply pass the newly calculated positions to the cost function.
Let's give it a try: For example, let us use the calculated positions from the Voronoi approach (by Paul Brodersen).
print(cost_function(new_positions, old_positions))
# prints 0.007999244511697411
That's a pretty good value if you ask me. Considering that the cost function spits out zero when there is no displacement at all, this cost is pretty close. We can now try to outperform this value by using classical optimization!
Non-linear constraint
We know that circles must not overlap with other circles in the new set. If we translate this into a constraint, we find that the lower bound for the distance is 2 times the radius and the upper bound is simply infinity.
import scipy.spatial.distance as sd
from scipy.optimize import NonlinearConstraint
def cons_f(x):
x = np.reshape(x, (-1, 2))
return sd.pdist(x)
nonlinear_constraint = NonlinearConstraint(cons_f, 2*radius, np.inf, jac='2-point')
Here we make life easy by approximating the Jacobi matrix via finite differences (see parameter jac='2-point'). At this point it should be said that we can increase the efficiency here, by formulating the derivatives of the first and second order ourselves instead of using approximations. But this is left to the interested reader. (It is not that hard, because we use quite simple mathematical expressions for distance calculation here.)
One additional note: You can also set a boundary constraint for the positions themselves not to exceed a specified region. This can then be used as another parameter. (See scipy.optimize.Bounds)
Minimizing the cost function under constraints
Now we have both ingredients, the cost function and the constraint, in place. Now let's minimize the whole thing!
from scipy.optimize import minimize
res = minimize(lambda x: cost_function(x, positions), positions.flatten(), method='SLSQP',
jac="2-point", constraints=[nonlinear_constraint])
As you can see, we approximate the first derivatives here as well. You can also go deeper here and set up the derivatives yourself (analytically).
Also note that we must always pass the parameters (an nx2 vector specifying the positions of the new layout for n circles) as a flat vector. For this reason, reshaping can be found several times in the code.
Evaluation, summary and visualization
Let's see how the optimization result performs in our cost function:
new_positions = np.reshape(res.x, (-1,2))
print(cost_function(new_positions, old_positions))
# prints 0.0010314079483565686
Starting from the Voronoi approach, we actually reduced the cost by another 87%! Thanks to the power of modern optimization strategies, we can solve a lot of problems in no time.
Of course, it would be interesting to see how the shifted circles look now:
Circles after Optimization
Performance: 77.1 ms ± 1.17 ms
The entire code:
from scipy.optimize import minimize
import scipy.spatial.distance as sd
from scipy.optimize import NonlinearConstraint
# Given by original post
positions = np.array([x, y]).T
def cost_function(new_positions, old_positions):
new_positions = np.reshape(new_positions, (-1, 2))
return np.trace(sd.cdist(new_positions, old_positions, metric='sqeuclidean'))
def cons_f(x):
x = np.reshape(x, (-1, 2))
return sd.pdist(x)
nonlinear_constraint = NonlinearConstraint(cons_f, 2*radius, np.inf, jac='2-point')
res = minimize(lambda x: cost_function(x, positions), positions.flatten(), method='SLSQP',
jac="2-point", constraints=[nonlinear_constraint])
One solution could be to follow the gradient of the unwanted spacing between each circle, though maybe there is a better way. This approach has a few parameters to tune and takes some time to run.
import matplotlib.pyplot as plt
from scipy.optimize import minimize as mini
import numpy as np
from scipy.optimize import approx_fprime
x = np.array([14.11450195,14.14184093,14.15435028,14.16206741,14.16951752,
14.17171097,14.18569565,14.19700241,14.23129082,14.24083233,
14.24290752,14.24968338,14.2518959,14.26536751,14.27209759,
14.27612877,14.2904377,14.29187012,14.29409599,14.29618549,
14.30615044,14.31624985,14.3206892,14.3228569,14.36143875,
14.36351967,14.36470699,14.36697292,14.37235737,14.41422081,
14.42583466,14.43226814,14.43319225,14.4437027,14.4557848,
14.46592999,14.47036076,14.47452068,14.47815609,14.52229309,
14.53059006,14.53404236,14.5411644])
y = np.array([-0.35319126,-0.44222349,-0.44763246,-0.35669261,-0.24366629,
-0.3998799,-0.38940558,-0.57744932,-0.45223859,-0.21021004,
-0.44250247,-0.45866323,-0.47203487,-0.51684451,-0.44884869,
-0.2018993,-0.40296811,-0.23641759,-0.18019417,-0.33391538,
-0.53565156,-0.45215255,-0.40939832,-0.26936951,-0.30894437,
-0.55504167,-0.47177047,-0.45573688,-0.43100587,-0.5805912,
-0.21770373,-0.199422,-0.17372169,-0.38522363,-0.56950212,
-0.56947368,-0.48770753,-0.24940367,-0.31492445,-0.54263926,
-0.53460872,-0.4053807,-0.43733299])
radius = 0.014
x0, y0 = (x, y)
def plot_circles(x, y, name='initial'):
fig, ax = plt.subplots()
for ii in range(x.size):
ax.add_patch(plt.Circle((x[ii], y[ii]), radius, color='b', fill=False))
ax.set_xlim(x.min() - radius, x.max() + radius)
ax.set_ylim(y.min() - radius, y.max() + radius)
fig.savefig(name)
plt.clf()
def spacing(s):
x, y = np.split(s, 2)
dX, dY = [np.subtract(*np.meshgrid(xy, xy, indexing='ij')).T
for xy in [x, y]]
dXY2 = dX**2 + dY**2
return np.minimum(dXY2[np.triu_indices(x.size, 1)] - (2 * radius) ** 2, 0).sum()
plot_circles(x, y)
def spacingJ(s):
return approx_fprime(s, spacing, 1e-8)
s = np.append(x, y)
for ii in range(50):
j = spacingJ(s)
if j.sum() == 0: break
s += .01 * j
x_new, y_new = np.split(s, 2)
plot_circles(x_new, y_new, 'new%i' % ii)
plot_circles(x_new, y_new, 'new%i' % ii)
https://giphy.com/gifs/x0lWDLZBz5O3gWTbLa
This answer implements a variation of the Lloyds algorithm. The basic idea is to compute the Voronoi diagram for your points / circles. This assigns each point a cell, which is a region that includes the point and which has a center that is maximally far away from all other points.
In the original algorithm, we would move each point towards the center of its Voronoi cell. Over time, this results in an even spread of points, as illustrated here.
In this variant, we only move points that overlap another point.
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Voronoi
from scipy.spatial.distance import cdist
def remove_overlaps(positions, radii, tolerance=1e-6):
"""Use a variation of Lloyds algorithm to move circles apart from each other until none overlap.
Parameters
----------
positions : array
The (x, y) coordinates of the circle origins.
radii : array
The radii for each circle.
tolerance : float
If all circles overlap less than this threshold, the computation stops.
Higher values leads to faster convergence.
Returns
-------
new_positions : array
The (x, y) coordinates of the circle origins.
See also
--------
https://en.wikipedia.org/wiki/Lloyd%27s_algorithm
"""
positions = np.array(positions)
radii = np.array(radii)
minimum_distances = radii[np.newaxis, :] + radii[:, np.newaxis]
minimum_distances[np.diag_indices_from(minimum_distances)] = 0 # ignore distances to self
# Initialize the first loop.
distances = cdist(positions, positions)
displacements = np.max(np.clip(minimum_distances - distances, 0, None), axis=-1)
while np.any(displacements > tolerance):
centroids = _get_voronoi_centroids(positions)
# Compute the direction from each point towards its corresponding Voronoi centroid.
deltas = centroids - positions
magnitudes = np.linalg.norm(deltas, axis=-1)
directions = deltas / magnitudes[:, np.newaxis]
# Mask NaNs that arise if the magnitude is zero, i.e. the point is already center of the Voronoi cell.
directions[np.isnan(directions)] = 0
# Step into the direction of the centroid.
# Clipping prevents overshooting of the centroid when stepping into the direction of the centroid.
# We step by half the displacement as the other overlapping point will be moved in approximately the opposite direction.
positions = positions + np.clip(0.5 * displacements, None, magnitudes)[:, np.newaxis] * directions
# Initialize next loop.
distances = cdist(positions, positions)
displacements = np.max(np.clip(minimum_distances - distances, 0, None), axis=-1)
return positions
def _get_voronoi_centroids(positions):
"""Construct a Voronoi diagram from the given positions and determine the center of each cell."""
voronoi = Voronoi(positions)
centroids = np.zeros_like(positions)
for ii, idx in enumerate(voronoi.point_region):
region = [jj for jj in voronoi.regions[idx] if jj != -1] # i.e. ignore points at infinity; TODO: compute correctly clipped regions
centroids[ii] = np.mean(voronoi.vertices[region], axis=0)
return centroids
if __name__ == '__main__':
x = np.array([14.11450195,14.14184093,14.15435028,14.16206741,14.16951752,
14.17171097,14.18569565,14.19700241,14.23129082,14.24083233,
14.24290752,14.24968338,14.2518959,14.26536751,14.27209759,
14.27612877,14.2904377,14.29187012,14.29409599,14.29618549,
14.30615044,14.31624985,14.3206892,14.3228569,14.36143875,
14.36351967,14.36470699,14.36697292,14.37235737,14.41422081,
14.42583466,14.43226814,14.43319225,14.4437027,14.4557848,
14.46592999,14.47036076,14.47452068,14.47815609,14.52229309,
14.53059006,14.53404236,14.5411644])
y = np.array([-0.35319126,-0.44222349,-0.44763246,-0.35669261,-0.24366629,
-0.3998799,-0.38940558,-0.57744932,-0.45223859,-0.21021004,
-0.44250247,-0.45866323,-0.47203487,-0.51684451,-0.44884869,
-0.2018993,-0.40296811,-0.23641759,-0.18019417,-0.33391538,
-0.53565156,-0.45215255,-0.40939832,-0.26936951,-0.30894437,
-0.55504167,-0.47177047,-0.45573688,-0.43100587,-0.5805912,
-0.21770373,-0.199422,-0.17372169,-0.38522363,-0.56950212,
-0.56947368,-0.48770753,-0.24940367,-0.31492445,-0.54263926,
-0.53460872,-0.4053807,-0.43733299])
radius = 0.014
positions = np.c_[x, y]
radii = np.full(len(positions), radius)
fig, axes = plt.subplots(1, 2, sharex=True, sharey=True, figsize=(14, 7))
for position, radius in zip(positions, radii):
axes[0].add_patch(plt.Circle(position, radius, fill=False))
axes[0].set_xlim(x.min() - radius, x.max() + radius)
axes[0].set_ylim(y.min() - radius, y.max() + radius)
axes[0].set_aspect('equal')
new_positions = remove_overlaps(positions, radii)
for position, radius in zip(new_positions, radii):
axes[1].add_patch(plt.Circle(position, radius, fill=False))
for ax in axes.ravel():
ax.set_aspect('equal')
plt.show()

Most efficient way to remove x,y coordinates in a specified area

I have two arrays of x and y coordinates:
x = np.round(np.arange(-4.67,-1.32,0.01),2)
y = np.round(np.arange(-3.32,3.32,0.01),2)
I'm using these arrays to randomly plot squares of .65 width/height within the specified range, which represents an area on the left side of a quadrant. The values indicate distance from 0 in visual degrees, and an x,y pair is what the square is centered on. Here is how I'm picking the coordinates at random:
x1, y1 = random.choice(x), random.choice(y)
position1 = np.append(x1,y1)
The problem is that sometimes there are multiple squares to be plotted, and they cannot overlap. A minimum distance of 2 degrees (center to center) must be maintained between squares within the defined area.
I thought about creating an array containing all of the possible coordinate pairs within that area:
coords = np.array(list(itertools.product(x,y))
I could then randomly pick a pair from that array, produce another array of all the coordinate points in the square area centered on that pair, and remove them from coords. This would ensure that no other square can be plotted within 2 degrees of that square. I suppose that this could be done for multiple squares.
This produces some massive arrays. This code is being run within PsychoPy (experiment builder), so its important that assigning those coordinates to squares happens quickly. What is the most efficient way to accomplish this task? Perhaps there is a more obvious method that I am missing?
You could use a grid map to be able to do a quick check.
When you select a point first check looking only a nearby cells in the grid if there's any other point that is too close, otherwise you accept the point and also add the point to the grid:
grid = {}
points = []
def addPoint(x, y):
# returns True if the point was accepted
# compute the cell of the point
ix = int(math.floor(x / dist))
iy = int(math.floor(y / dist))
# check cell and all neighbors
for nhcell in ((ix-1, iy-1), (ix, iy-1), (ix+1, iy-1),
(ix-1, iy ), (ix, iy ), (ix+1, iy ),
(ix-1, iy+1), (ix, iy+1), (ix+1, iy+1)):
if nhcell in grid:
for xx, yy in grid[nhcell]:
if (x - xx)**2 + (y - yy)**2 < dist2:
# anoter existing point is too close
return False
# the new point is fine
points.add((x, y))
# we should also add it to the grid for future checks
if (ix, iy) in grid:
grid[(ix, iy)].append((x, y))
else:
grid[(ix, iy)] = [(x, y)]
return True

How to calculate centroid of x,y coordinates in python

I have got a lot of x,y coordinates which I have clustered based on the distance between them.
Now I would like to calculate a centroid measure for each cluster of x,y coordinates.
Is there a way to do this?
My coordinates are in the format:
coordinates_cluster = [[x1,x2,x3,...],[y1,y2,y3,...]]
Each cluster has a minimum length of three points, and all points can have both negative and positive x and y values.
I hope that someone can help me.
Best,
Martin
(I am using python 2.7 with canopy 1.1.1 (32 bit) on a Windows 7 system.)
The accepted answer given here does IMHO not apply to typical real life use cases where you want to calculate the centroid of a shape defined by a set of (x,y) vertices (aka polygon). So please excuse me answering a question that was asked almost 8 years ago, but it still came out on top in my SO search, so it might come up for others as well. I'm not saying the accepted answer is wrong in the specific case of the question, but I think, most people who find this thread actually look for centroid according to a different definition.
Centroid is not defined as arithmetic Mean of Vertices
...which is contrary to common opinion.
We have to acknowledge that usually by centroid, we think of “the arithmetic mean position of all the points in the figure. Informally, it is the point at which a cutout of the shape could be perfectly balanced on the tip of a pin” (quoting Wikipedia that’s quoting actual literature here). Note here that it is ALL the points IN the figure and not just the mean of the coordinates of the vertices.
And this is exactly, where you will go wrong if you accept most of SO answers, that imply that the centroid is the arithmetic mean of x and y coordinates of vertices and apply this to real life data that you might have collected by performing an experiment.
The density of points describing your shape might vary along the line of your shape. This is only one of many possible limitations of said method. The simple mean of coordinates then surely is not what you want. I’ll illustrate this with an example.
Example
Here we see a polygon that is made up of 8 vertices. Our intuition rightly tells us, that we could balance this shape on the tip of a pin at (x,y)=(0,0), making the centroid (0,0). But in the area around (-1,1) the density of points/vertices that we were given to describe this polygon is higher than in other areas along the line. Now if we calculate the centroid by taking the mean of the vertices, the result will be pulled towards the high density area.
The point “centroid poly“ corresponds to the true centroid. This point was calculated by implementing the algorithm described here: https://en.wikipedia.org/wiki/Centroid#Of_a_polygon (only difference: it returns the absolute value of the area)
It applies to figures described by x and y coordinates of N vertices like X = x_0, x_1, …, x_(N-1), same for Y. This figure can be any polygon as long as it is non-self-intersecting and the vertices are given in order of occurrence.
This can be used to calculate e.g. the “real” centroid of a matplotlib contour line.
Code
Here is the code for the example above and the implementation of said algorithm:
import matplotlib.pyplot as plt
def centroid_poly(X, Y):
"""https://en.wikipedia.org/wiki/Centroid#Of_a_polygon"""
N = len(X)
# minimal sanity check
if not (N == len(Y)): raise ValueError('X and Y must be same length.')
elif N < 3: raise ValueError('At least 3 vertices must be passed.')
sum_A, sum_Cx, sum_Cy = 0, 0, 0
last_iteration = N-1
# from 0 to N-1
for i in range(N):
if i != last_iteration:
shoelace = X[i]*Y[i+1] - X[i+1]*Y[i]
sum_A += shoelace
sum_Cx += (X[i] + X[i+1]) * shoelace
sum_Cy += (Y[i] + Y[i+1]) * shoelace
else:
# N-1 case (last iteration): substitute i+1 -> 0
shoelace = X[i]*Y[0] - X[0]*Y[i]
sum_A += shoelace
sum_Cx += (X[i] + X[0]) * shoelace
sum_Cy += (Y[i] + Y[0]) * shoelace
A = 0.5 * sum_A
factor = 1 / (6*A)
Cx = factor * sum_Cx
Cy = factor * sum_Cy
# returning abs of A is the only difference to
# the algo from above link
return Cx, Cy, abs(A)
# ********** example ***********
X = [-1, -0.8, -0.6, 1, 2, 1, -1, -2]
Y = [ 1, 1, 1, 1, 0.5, -1, -1, -0.5]
Cx, Cy, A = centroid_poly(X, Y)
# calculating centroid as shown by the accepted answer
Cx_accepted = sum(X)/len(X)
Cy_accepted = sum(Y)/len(Y)
fig, ax = plt.subplots()
ax.scatter(X, Y, label='vertices')
ax.scatter(Cx_accepted, Cy_accepted, label="mean of vertices")
ax.scatter(Cx, Cy, label='centroid poly')
# just so the line plot connects xy_(N-1) and xy_0
X.append(X[0]), Y.append(Y[0])
ax.plot(X, Y, label='polygon')
ax.legend(bbox_to_anchor=(1, 1))
ax.grid(), ax.set_aspect('equal')
I realized that it was not that hard, but here is the code for calculating centroids of x,y coordinates:
>>> c = [[1,4,-5],[3,-2,9]] # of the form [[x1,x2,x3],[y1,y2,y3]]
>>> centroide = (sum(c[0])/len(c[0]),sum(c[1])/len(c[1]))
>>> centroide
(0, 3)
If you are interested in calculating centroid as defined in geometry or signal processing [1, 2] :
import numpy as np
# a line from 0,0 to 1,1
x = np.linspace(0, 1, 100)
y = np.linspace(0, 1, 100)
cx = np.dot(x, y) / np.sum(y)
0.67003367003367

Retrieving the actual 3D coordinates of a point on a triangle that has been flattened to 2 dimensions

This is a bit of a complicated problem, so I'll do my best to break it down into chunks.
I'm writing a 3D Python library for the sake of learning / fun (as opposed to one that I'd intend for others to use). In the system I've developed, three-dimensional points are generally flattened to the image as follows:
Increasing the Z index by width moves the point halfway to the vanishing point in the center.
At Z = 0, the X and Y values correspond directly to the pixel at X, Y.
(There might be a name for this method, but if there is, I'm not familiar with it.)
In Python:
# vx and vy are the vanishing point's coordinates
def flatten_point(width, vx, vy, x, y, z):
distance = (x - vx, y - vy)
flat_distance = [d / (1 + float(z) / width) for d in distance]
return (vx + flat_distance[0], vx + flat_distance[1])
At this point, I'm able to create triangles somewhat efficiently by flattening its vertices and using barycentric coordinates to find and fill in the pixels that fall between those three points. This works well enough if I don't need to know anything about the actual points on the triangle that those pixels correspond to, but if I want to shade the triangle so that deeper points are drawn darker, I need to know what unflattened point on the triangle the pixel corresponds to.
joriki on math.stackexchange recommended using the barycentric coordinates as weights to find the original point. This did appear to work for awhile -- and it probably would work if I were using a linear depth system -- but it falls apart when the depths of the triangle's points differ by enough. The triangle appears to approach the greatest depth more quickly than it actually does, as if it were curved backwards.
So, in short: how can I reverse the point flattening function to get the actual 3D point of an arbitrary 2D pixel on a flattened triangle? Alternatively, if there is a better / more efficient way to flatten triangles without losing the depth of each pixel, that would work too.
You are right that the problem lies in your depth values not being linear. Fortunately, the solution is simple, but a little expensive if calculated per pixels.
Using your barycentric coordinates, rather than interpolating the three Z components directly, you need to interpolate their inverse and reinverse the result. This is called perspective correction.
Example for Z only :
def GetInterpolatedZ(triangle, u, v):
z0 = 1.0 / triangle[0].z
z1 = 1.0 / triangle[1].z
z2 = 1.0 / triangle[2].z
z = z0 + u * (z1-z0) + v * (z2-z0)
return 1.0/z
With triangle a list of three vectors and u and v the barycentric coordinates for triangle[1] and triangle[2] respectively. You will need to remap your Zs before and after the divisions if they are offset.
If you want to interpolate the actual X and Y coordinates, you do something similar. You will need to interpolate x/z and y/z and relinearize the result by multiplying by z.
def GetInterpolatedZ(tri, u, v):
t0 = Vec3(tri[0].x/tri[0].z, tri[0].y/tri[0].z, 1.0/tri[0].z)
t1 = Vec3(tri[1].x/tri[1].z, tri[1].y/tri[1].z, 1.0/tri[1].z)
t2 = Vec3(tri[2].x/tri[2].z, tri[2].y/tri[2].z, 1.0/tri[2].z)
inter = t0 + u * (t1-t0) + v * (t2-t0)
inter.z = 1.0 / inter.z
inter.x *= inter.z
inter.y *= inter.z
return inter
Again, tri is a list of the three vectors and u, v are the barycentric coordinates for tri[1], tri[2]. Vec3 is a regular 3 components Euclidean vector type.

Categories