I am try to find the angle of line
i know the coordinate points
Start Point : 404, 119
Mid Point : 279, 214
End Point : 154, 310
import numpy as np
def findangle(x1,y1,x2,y2,x3,y3):
ria = np.arctan2(y2 - y1, x2 - x1) - np.arctan2(y3 - y1, x3 - x1)
webangle = int(np.abs(ria * 180 / np.pi))
return webangle
result
Its return 270. But actual angle is 85-90.
Now, I want formula to calculate the angle (Either i Will rotate the image clockwise or Anticlockwise that time also return actual angle) in python code
I think you need to adjust the returned angle based on the range you wish the result to be in.
The following assumes you desire results between -180 (inclusive) and 180 (exclusive).
def adjust(a, degrees=True):
v = 180 if degrees else np.pi
return (a + v) % (2 * v) - v
def angle(p, degrees=True):
assert p.shape == (3, 2), 'p should have 3 points, in 2D'
yx = p[:, ::-1]
a0 = np.arctan2(*(yx[1] - yx[0]))
a1 = np.arctan2(*(yx[2] - yx[1]))
a = np.rad2deg(a1 - a0) if degrees else a1 - a0
return adjust(a, degrees)
And here is some code to test validity:
def rot(a, degrees=True):
a = np.deg2rad(a) if degrees else a
sa, ca = np.sin(a), np.cos(a)
return np.array([[ca, sa], [-sa, ca]])
def genp(angle, initial=0, lengths=(1, 1), degrees=True):
p0 = np.array((0, 0))
p1 = np.array((lengths[0], 0))
p2 = p1 + np.array((lengths[1], 0)) # rot(angle, degrees=degrees)
p = np.vstack((p0, p1, p2)) # rot(initial, degrees=degrees)
return p
def plot_problem(p, ax=None):
ax = plt.gca() if ax is None else ax
ax.plot(*p.T, '-o')
for s, xy in zip(list('abc'), p):
ax.annotate(s, xy, xytext=(0, 5), textcoords='offset points')
ax.set_aspect('equal')
Example:
p = genp(60, initial=30, lengths=(2, 1))
>>> angle(p)
60.0
plot_problem(p)
Now, a stress test:
n = 1000
np.random.seed(0)
for _ in range(n):
a = np.random.uniform(-180, 180)
initial = np.random.uniform(-180, 180)
lengths = np.random.uniform(1, 10, size=2)
p = genp(a, initial, lengths)
ahat = angle(p)
assert np.allclose(a, ahat)
Which passes.
It's a simple math. The numpy.arctan2() returns a value in the range [-π, π]. So a difference of two values is in the range [-2π, 2π], and the absolute value of that is in the range [0, 2π]. You want an angle x in the range [0, π], and x is equivalent to (2π - x) in your context. So you can take x if x < π, and take (2π - x) otherwise.
So, just do like this using min() for example.
def findangle(x1,y1,x2,y2,x3,y3):
...
return min(webangle, 360 - webangle)
As a side note, you don't need to use Numpy functions for a scalar input. Just use the math.atan2() and math.fabs().
Related
I'm trying to make a program to get azimuth value from two points using this code:
import math
def azimuth(x1, x2, y1, y2):
temp = (y2 - y1) / (x2 - x1)
res = math.degrees(abs(math.atan(temp)))
if x2 > x1 and y2 > y1: #Q1
res = 90 - res
elif x2 < x1 and y2 > y1: #Q2
res = (90 - res) * -1
elif x2 < x1 and y2 < y1: #Q3
res = (90 + res) * -1
elif x2 > x1 and y2 < y1: #Q4
res += 90
else:
raise ValueError('No point difference.')
return res
I've been able to get azimuth value with range (-180)-180. Next, I need to split it into two groups of azimuth value equaly based on range of value. The goal is to get two group which get the closest azimuth value.
The problem is that pairs of points on quadrant III ((-180)-(-90)) and quadrant IV (90-180) logically can be consider as close (e.g:(-179) and 179). About how to split it equally, I've been thinking of using sorted list of azimuth and using index idx = int(math.ceil((len(lst)-1)/2)) as the median to split the list. This is how I do it on code:
lst = [-60, -50, -40, -30, -20, -10, 10, 20, 30, 40, 50, 60]
def split_list(lst):
lst.sort()
idx = int(math.ceil((len(lst)-1)/2))
left = []
right = []
for i in lst:
if i < lst[idx]:
left.append(i)
else:
right.append(i)
print(left)
print(right)
return left, right
split_list(lst)
The code above will return list [-60, -50, -40, -30, -20, -10] and [10, 20, 30, 40, 50, 60] as the result.
Is there any idea of how to do this? With the current condition, I don't have any idea of how to make quadrant III and IV considered as close.
You might be interested in math.atan2(y, x).
The point of atan2() is that the signs of both inputs are known to it,
so it can compute the correct quadrant for the angle.
Which means that you can remove all the quadrant logic from your code, and basically replace the whole function with math.degrees(math.atan2(y2 - y1, x2 - x1)). It also avoids a division by 0 if x1 == x2.
If you have two azimuths, you can calculate the minimum rotation between both with:
def shortest_rotation(start_angle, end_angle):
return (end_angle - start_angle + 180) % 360 - 180
You can split the list of azimuths depending on the sign of the output.
Just for fun, here's a small script to split azimuths left and right of split_at_azimuth, and display them on a polar plot:
def shortest_rotation(start_angle, end_angle):
return (end_angle - start_angle + 180) % 360 - 180
azimuths = list(range(0, 370, 10))
split_at_azimuth = 60
left, right = [], []
for azimuth in azimuths:
(left, right)[shortest_rotation(azimuth, split_at_azimuth) >= 0].append(azimuth)
import numpy as np
import matplotlib.pyplot as plt
t1 = [np.pi * a / 180 for a in right]
t2 = [np.pi * a / 180 for a in left]
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.plot(t1, [1 for _ in t1], '+')
ax.plot(t2, [1 for _ in t2], '+')
ax.set_rmax(2)
ax.set_rticks([])
ax.grid(True)
ax.set_title("Left and right azimuth", va='bottom')
plt.show()
Your problem can be reduced to finding a distance between two phase points. I say phase because phase is wrapped (in your case % 360). Thus, you need to compare the distance both ways: clockwise and counter-clockwise. The simplest might be:
dist = min(abs(A2-A1), abs(360+A1-A2)) # requiring A1 <= A2
If the clockwise distance is the smallest, then the first term in min() will be your solution. If counter-clockwise, then the second term in min() will be your solution. To do grouping, you'd then calculate the distances between desired points. Note that arrays don't need to be sorted for this to work, just that A1 <= A2.
I have a simple 2D ray-casting routine that gets terribly slow as soon as the number of obstacles increases.
This routine is made up of:
2 for loops (outer loop iterates over each ray/direction, then inner loop iterates over each line obstacle)
multiple if statements (check if a value is > or < than another value or if an array is empty)
Question: How can I condense all these operations into 1 single block of vectorized instructions using Numpy ?
More specifically, I am facing 2 issues:
I have managed to vectorize the inner loop (intersection between a ray and each obstacle) but I am unable to run this operation for all rays at once.
The only workaround I found to deal with the if statements is to use masked arrays. Something tells me it is not the proper way to handle these statements in this case (it seems clumsy, cumbersome and unpythonic)
Original code:
from math import radians, cos, sin
import matplotlib.pyplot as plt
import numpy as np
N = 10 # dimensions of canvas (NxN)
sides = np.array([[0, N, 0, 0], [0, N, N, N], [0, 0, 0, N], [N, N, 0, N]])
edges = np.random.rand(5, 4) * N # coordinates of 5 random segments (x1, x2, y1, y2)
edges = np.concatenate((edges, sides))
center = np.array([N/2, N/2]) # coordinates of center point
directions = np.array([(cos(radians(a)), sin(radians(a))) for a in range(0, 360, 10)]) # vectors pointing in all directions
intersections = []
# for each direction
for d in directions:
min_dist = float('inf')
# for each edge
for e in edges:
p1x, p1y = e[0], e[2]
p2x, p2y = e[1], e[3]
p3x, p3y = center
p4x, p4y = center + d
# find intersection point
den = (p1x - p2x) * (p3y - p4y) - (p1y - p2y) * (p3x - p4x)
if den:
t = ((p1x - p3x) * (p3y - p4y) - (p1y - p3y) * (p3x - p4x)) / den
u = -((p1x - p2x) * (p1y - p3y) - (p1y - p2y) * (p1x - p3x)) / den
# if any:
if t > 0 and t < 1 and u > 0:
sx = p1x + t * (p2x - p1x)
sy = p1y + t * (p2y - p1y)
isec = np.array([sx, sy])
dist = np.linalg.norm(isec-center)
# make sure to select the nearest one (from center)
if dist < min_dist:
min_dist = dist
nearest = isec
# store nearest interesection point for each ray
intersections.append(nearest)
# Render
plt.axis('off')
for x, y in zip(edges[:,:2], edges[:,2:]):
plt.plot(x, y)
for isec in np.array(intersections):
plt.plot((center[0], isec[0]), (center[1], isec[1]), '--', color="#aaaaaa", linewidth=.8)
Vectorized version (attempt):
from math import radians, cos, sin
import matplotlib.pyplot as plt
from scipy import spatial
import numpy as np
N = 10 # dimensions of canvas (NxN)
sides = np.array([[0, N, 0, 0], [0, N, N, N], [0, 0, 0, N], [N, N, 0, N]])
edges = np.random.rand(5, 4) * N # coordinates of 5 random segments (x1, x2, y1, y2)
edges = np.concatenate((edges, sides))
center = np.array([N/2, N/2]) # coordinates of center point
directions = np.array([(cos(radians(a)), sin(radians(a))) for a in range(0, 360, 10)]) # vectors pointing in all directions
intersections = []
# Render edges
plt.axis('off')
for x, y in zip(edges[:,:2], edges[:,2:]):
plt.plot(x, y)
# for each direction
for d in directions:
p1x, p1y = edges[:,0], edges[:,2]
p2x, p2y = edges[:,1], edges[:,3]
p3x, p3y = center
p4x, p4y = center + d
# denominator
den = (p1x - p2x) * (p3y - p4y) - (p1y - p2y) * (p3x - p4x)
# first 'if' statement -> if den > 0
mask = den > 0
den = den[mask]
p1x = p1x[mask]
p1y = p1y[mask]
p2x = p2x[mask]
p2y = p2y[mask]
t = ((p1x - p3x) * (p3y - p4y) - (p1y - p3y) * (p3x - p4x)) / den
u = -((p1x - p2x) * (p1y - p3y) - (p1y - p2y) * (p1x - p3x)) / den
# second 'if' statement -> if (t>0) & (t<1) & (u>0)
mask2 = (t > 0) & (t < 1) & (u > 0)
t = t[mask2]
p1x = p1x[mask2]
p1y = p1y[mask2]
p2x = p2x[mask2]
p2y = p2y[mask2]
# x, y coordinates of all intersection points in the current direction
sx = p1x + t * (p2x - p1x)
sy = p1y + t * (p2y - p1y)
pts = np.c_[sx, sy]
# if any:
if pts.size > 0:
# find nearest intersection point
tree = spatial.KDTree(pts)
nearest = pts[tree.query(center)[1]]
# Render
plt.plot((center[0], nearest[0]), (center[1], nearest[1]), '--', color="#aaaaaa", linewidth=.8)
Reformulation of the problem – Finding the intersection between a line segment and a line ray
Let q and q2 be the endpoints of a segment (obstacle). For convenience let's define a class to represent points and vectors in the plane. In addition to the usual operations, a vector multiplication is defined by u × v = u.x * v.y - u.y * v.x.
Caution: here Coord(2, 1) * 3 returns Coord(6, 3) while Coord(2, 1) * Coord(-1, 4) outputs 9. To avoid this confusion it might have been possible to restrict * to the scalar multiplication and use ^ via __xor__ for the vector multiplication.
class Coord:
def __init__(self, x, y):
self.x = x
self.y = y
#property
def radius(self):
return np.sqrt(self.x ** 2 + self.y ** 2)
def _cross_product(self, other):
assert isinstance(other, Coord)
return self.x * other.y - self.y * other.x
def __mul__(self, other):
if isinstance(other, Coord):
# 2D "cross"-product
return self._cross_product(other)
elif isinstance(other, int) or isinstance(other, float):
# scalar multiplication
return Coord(self.x * other, self.y * other)
def __rmul__(self, other):
return self * other
def __sub__(self, other):
return Coord(self.x - other.x, self.y - other.y)
def __add__(self, other):
return Coord(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Coord({self.x}, {self.y})"
Now, I find it easier to handle a ray in polar coordinates: For a given angle theta (direction) the goal is to determine if it intersects the segment, and if so determine the corresponding radius. Here is a function to find that. See here for an explanation of why and how. I tried to use the same variable names as in the previous link.
def find_intersect_btw_ray_and_sgmt(q, q2, theta):
"""
Args:
q (Coord): first endpoint of the segment
q2 (Coord): second endpoint of the segment
theta (float): angle of the ray
Returns:
(float): np.inf if the ray does not intersect the segment,
the distance from the origin of the intersection otherwise
"""
assert isinstance(q, Coord) and isinstance(q2, Coord)
s = q2 - q
r = Coord(np.cos(theta), np.sin(theta))
cross = r * s # 2d cross-product
t_num = q * s
u_num = q * r
## the intersection point is roughly at a distance t_num / cross
## from the origin. But some cases must be checked beforehand.
## (1) the segment [PQ2] is aligned with the ray
if np.isclose(cross, 0) and np.isclose(u_num, 0):
return min(q.radius, q2.radius)
## (2) the segment [PQ2] is parallel with the ray
elif np.isclose(cross, 0):
return np.inf
t, u = t_num / cross, u_num / cross
## There is actually an intersection point
if t >= 0 and 0 <= u <= 1:
return t
## (3) No intersection point
return np.inf
For instance find_intersect_btw_ray_and_sgmt(Coord(1, 2), Coord(-1, 2), np.pi / 2) should returns 2.
Note that here for simplicity, I only considered the case where the origin of the rays is at Coord(0, 0). This can be easily extended to the general case by setting t_num = (q - origin) * s and u_num = (q - origin) * r.
Let's vectorize it!
What is very interesting here is that the operations defined in the Coord class also apply to cases where x and y are numpy arrays! Hence applying any defined operation on Coord(np.array([1, 2, 0]), np.array([2, -1, 3])) amounts applying it elementwise to the points (1, 2), (2, -1) and (0, 3). The operations of Coord are therefore already vectorized. The constructor can be modified into:
def __init__(self, x, y):
x, y = np.array(x), np.array(y)
assert x.shape == y.shape
self.x, self.y = x, y
self.shape = x.shape
Now, we would like the function find_intersect_btw_ray_and_sgmt to be able to handle the case where the parameters q and q2contains sequences of endpoints. Before the sanity checks, all the operations are working properly since, as we have mentioned, they are already vectorized. As you mentionned the conditional statements can be "vectorized" using masks. Here is what I propose:
def find_intersect_btw_ray_and_sgmts(q, q2, theta):
assert isinstance(q, Coord) and isinstance(q2, Coord)
assert q.shape == q2.shape
EPS = 1e-14
s = q2 - q
r = Coord(np.cos(theta), np.sin(theta))
cross = r * s
cross_sign = np.sign(cross)
cross = cross * cross_sign
t_num = (q * s) * cross_sign
u_num = (q * r) * cross_sign
radii = np.zeros_like(t_num)
mask = ~np.isclose(cross, 0) & (t_num >= -EPS) & (-EPS <= u_num) & (u_num <= cross + EPS)
radii[~mask] = np.inf # no intersection
radii[mask] = t_num[mask] / cross[mask] # intersection
return radii
Note that cross, t_num and u_num are multiplied by the sign of cross to ensure that the division by cross keeps the sign of the dividends. Hence conditions of the form ((t_num >= 0) & (cross >= 0)) | ((t_num <= 0) & (cross <= 0)) can be replaced by (t_num >= 0).
For simplicity, we omitted the case (1) where the radius and the segment were aligned ((cross == 0) & (u_num == 0)). This could be incorporated by carefully adding a second mask.
For a given value of theta, we are able to determine if the corresponing ray intersects with several segments at once.
## Some useful functions
def polar_to_cartesian(r, theta):
return Coord(r * np.cos(theta), r * np.sin(theta))
def plot_segments(p, q, *args, **kwargs):
plt.plot([p.x, q.x], [p.y, q.y], *args, **kwargs)
def plot_rays(radii, thetas, *args, **kwargs):
endpoints = polar_to_cartesian(radii, thetas)
n = endpoints.shape
origin = Coord(np.zeros(n), np.zeros(n))
plot_segments(origin, endpoints, *args, **kwargs)
## Data generation
M = 5 # size of the canvas
N = 10 # number of segments
K = 16 # number of rays
q = Coord(*np.random.uniform(-M/2, M/2, size=(2, N)))
p = q + Coord(*np.random.uniform(-M/2, M/2, size=(2, N)))
thetas = np.linspace(0, 2 * np.pi, K, endpoint=False)
## For each ray, find the minimal distance of intersection
## with all segments
plt.figure(figsize=(5, 5))
plot_segments(p, q, "royalblue", marker=".")
for theta in thetas:
radii = find_intersect_btw_ray_and_sgmts(p, q, theta)
radius = np.min(radii)
if not np.isinf(radius):
plot_rays(radius, theta, color="orange")
else:
plot_rays(2*M, theta, ':', c='orange')
plt.plot(0, 0, 'kx')
plt.xlim(-M, M)
plt.ylim(-M, M)
And that's not all! Thanks to the broadcasting of python, it is possible to avoid iteration on theta values. For example, recall that np.array([1, 2, 3]) * np.array([[1], [2], [3], [4]]) produces a matrix of size 4 × 3 of the pairwise products. In the same way Coord([[5],[7]], [[5],[1]]) * Coord([2, 4, 6], [-2, 4, 0]) outputs a 2 × 3 matrix containing all the pairwise cross product between vectors (5, 5), (7, 1) and (2, -2), (4, 4), (6, 0).
Finally, the intersections can be determined in the following way:
radii_all = find_intersect_btw_ray_and_sgmts(p, q, np.vstack(thetas))
# p and q have a shape of (N,) and np.vstack(thetas) of (K, 1)
# this radii_all have a shape of (K, N)
# radii_all[k, n] contains the distance from the origin of the intersection
# between k-th ray and n-th segment (or np.inf if there is no intersection point)
radii = np.min(radii_all, axis=1)
# radii[k] contains the distance from the origin of the closest intersection
# between k-th ray and all segments
do_intersect = ~np.isinf(radii)
plot_rays(radii[do_intersect], thetas[do_intersect], color="orange")
plot_rays(2*M, thetas[~do_intersect], ":", color="orange")
Given a unit circle and two polygons inscribed, how can I go about calculating the Intersection over Union(IoU) of the two polygons?
Assume I have a Numpy array of the polygon coordinates with respect to the unit circle as:
Polygon 1: [
(-0.708, 0.707),
(0.309, -0.951),
(0.587, -0.809),
]
Polygon 2: [
(1, 0),
(0, 1),
(-1, 0),
(0, -1),
(0.708, -0.708),
]
The expected IoU output should be: 0.124
Three Approaches
Shapely
Redo RaffleBuffle approach in Python rather than Java (check his/her post for description)
Monte Carlo Simulation
Shapely
from shapely.geometry import Polygon
p1 = [ (-0.708, 0.707),
(0.309, -0.951),
(0.587, -0.809)]
p2 = [ (1, 0),
(0, 1),
(-1, 0),
(0, -1),
(0.708, -0.708)]
# Create polygons from coordinates
poly1 = Polygon(p1)
poly2 = Polygon(p2)
# ratio of intersection area to union area
print(poly1.intersection(poly2).area/poly1.union(poly2).area)
# Output: 0.12403616470027264
RaffleBuffle Approach in Python
import math
import numpy as np
class Point:
def __init__(self, x, y, id = None):
self.x = x
self.y = y
self.id = id
self.prev = None
self.next = None
def __repr__(self):
result = f"{self.x} {self.y} {self.id}"
result += f" Prev: {self.prev.x} {self.prev.y} {self.prev.id}" if self.prev else ""
result += f" Next: {self.next.x} {self.next.y} {self.next.id}" if self.next else ""
return result
class Poly:
def __init__(self, pts):
self.pts = [p for p in pts]
' Sort polynomial coordinates based upon angle and radius in clockwize direction'
# Origin is the centroid of points
origin = Point(*[sum(pt.x for pt in self.pts)/len(self.pts), sum(pt.y for pt in self.pts)/len(self.pts)])
# Sort points by incresing angle around centroid based upon angle and distance to centroid
self.pts.sort(key=lambda p: clockwiseangle_and_distance(p, origin))
def __add__(self, other):
' Overload for adding two polynomials '
return Poly(self.pts + other.pts)
def assign_chain(self):
' Assign prev and next '
n = len(self.pts)
for i in range(n):
self.pts[i].next = self.pts[ (i + 1) % n]
self.pts[i].prev = self.pts[(i -1) % n]
return self
def area(self):
'''
area enclosed by polynomial coordinates '
Source: https://stackoverflow.com/questions/24467972/calculate-area-of-polygon-given-x-y-coordinates
'''
x = np.array([p.x for p in self.pts])
y = np.array([p.y for p in self.pts])
return 0.5*np.abs(np.dot(x,np.roll(y,1))-np.dot(y,np.roll(x,1)))
def intersection(self):
' Intersection of coordinates with different ids '
pts = self.pts
n = len(pts)
intersections = []
for i in range(n):
j = (i -1) % n
if pts[j].id != pts[i].id:
get_j = [pts[j], pts[j].next]
get_i = [pts[i].prev, pts[i]]
pt_intersect = line_intersect(get_j, get_i)
if pt_intersect:
intersections.append(pt_intersect)
return intersections
def __str__(self):
return '\n'.join(str(pt) for pt in self.pts)
def __repr__(self):
return '\n'.join(str(pt) for pt in self.pts)
def clockwiseangle_and_distance(point, origin):
# Source: https://stackoverflow.com/questions/41855695/sorting-list-of-two-dimensional-coordinates-by-clockwise-angle-using-python
# Vector between point and the origin: v = p - o
vector = [point.x-origin.x, point.y-origin.y]
refvec = [0, 1]
# Length of vector: ||v||
lenvector = math.hypot(vector[0], vector[1])
# If length is zero there is no angle
if lenvector == 0:
return -math.pi, 0
# Normalize vector: v/||v||
normalized = [vector[0]/lenvector, vector[1]/lenvector]
dotprod = normalized[0]*refvec[0] + normalized[1]*refvec[1] # x1*x2 + y1*y2
diffprod = refvec[1]*normalized[0] - refvec[0]*normalized[1] # x1*y2 - y1*x2
angle = math.atan2(diffprod, dotprod)
# Negative angles represent counter-clockwise angles so we need to subtract them
# from 2*pi (360 degrees)
if angle < 0:
return 2*math.pi+angle, lenvector
# I return first the angle because that's the primary sorting criterium
# but if two vectors have the same angle then the shorter distance should come first.
return angle, lenvector
def line_intersect(segment1, segment2):
""" returns a (x, y) tuple or None if there is no intersection
segment1 and segment2 are two line segements
specified by their starting/ending points
Source: https://rosettacode.org/wiki/Find_the_intersection_of_two_lines#Python
"""
Ax1, Ay1 = segment1[0].x, segment1[0].y # starting point in line segment 1
Ax2, Ay2 = segment1[1].x, segment1[1].y # ending point in line segment 1
Bx1, By1 = segment2[0].x, segment2[0].y # starting point in line segment 2
Bx2, By2 = segment2[1].x, segment2[1].y # ending point in line segment 2
d = (By2 - By1) * (Ax2 - Ax1) - (Bx2 - Bx1) * (Ay2 - Ay1)
if d:
uA = ((Bx2 - Bx1) * (Ay1 - By1) - (By2 - By1) * (Ax1 - Bx1)) / d
uB = ((Ax2 - Ax1) * (Ay1 - By1) - (Ay2 - Ay1) * (Ax1 - Bx1)) / d
else:
return
if not(0 <= uA <= 1 and 0 <= uB <= 1):
return
x = Ax1 + uA * (Ax2 - Ax1)
y = Ay1 + uA * (Ay2 - Ay1)
return Point(x, y, None)
def polygon_iou(coords1, coords2):
'''
Calculates IoU of two 2D polygons based upon coordinates
'''
# Make polynomials ordered clockwise and assign ID (0 and 1)
poly1 = Poly(Point(*p, 0) for p in coords1) # counter clockwise with ID 0
poly2 = Poly(Point(*p, 1) for p in coords2) # counter clockwise with ID 1
# Assign previous and next coordinates in polynomial chain
poly1.assign_chain()
poly2.assign_chain()
# Polygon areas
area1 = poly1.area()
area2 = poly2.area()
# Combine both polygons into one counter clockwise
poly = poly1 + poly2
# Get interesections
intersections = poly.intersection()
# IoU based upon intersection and sum of areas
if intersections:
area_intersection = Poly(intersections).area()
result = area_intersection/(area1 + area2 - area_intersection)
else:
result = 0.0
return result
print(polygon_iou(p1, p2))
Test
p1 = [ (-0.708, 0.707),
(0.309, -0.951),
(0.587, -0.809)]
p2 = [ (1, 0),
(0, 1),
(-1, 0),
(0, -1),
(0.708, -0.708)]
print(polygon_iou(p1, p2))
# Output: 0.12403616470027277
Monte Carlo Simulation
Generate random points in the min/max x and y range of the points
Count the number of points in either polygon (i.e. union)
Count the number of points in both polygon (i.e. intersection)
Ratio of the number of points in the intersection to the number in the union is the answer
Code
import math
from random import uniform
def ray_tracing_method(x, y, poly):
'''
Determines if point x, y is inside polygon poly
Source: "What's the fastest way of checking if a point is inside a polygon in python"
at URL: https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
'''
n = len(poly)
inside = False
p1x,p1y = poly[0]
for i in range(n+1):
p2x,p2y = poly[i % n]
if y > min(p1y,p2y):
if y <= max(p1y,p2y):
if x <= max(p1x,p2x):
if p1y != p2y:
xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
if p1x == p2x or x <= xints:
inside = not inside
p1x,p1y = p2x,p2y
return inside
def intersection_union(p1, p2, num_throws = 1000000):
'''
Computes the intersection untion for polygons p1, p2
(assuming that p1, and p2 are inside at polygon of radius 1)
'''
# Range of values
p = p1 + p2
xmin = min(x[0] for x in p)
xmax = max(x[0] for x in p)
ymin = min(x[1] for x in p)
ymax = max(x[1] for x in p)
# Init counts
in_union = 0
in_intersect = 0
throws = 0
while (throws < num_throws):
# Choose random x, y position in rectangle
x_pos = uniform (xmin, xmax)
y_pos = uniform (ymin, ymax)
# Only test points inside unit circle
# Check if points are inside p1 & p2
in_p1 = ray_tracing_method(x_pos, y_pos, p1)
in_p2 = ray_tracing_method(x_pos, y_pos, p2)
if in_p1 or in_p2:
in_union += 1 # in union
if in_p1 and in_p2:
in_intersect += 1 # in intersetion
throws += 1
return in_intersect/in_union
print(intersection_union(p1, p2))
Test
p1 = [ (-0.708, 0.707),
(0.309, -0.951),
(0.587, -0.809)]
p2 = [ (1, 0),
(0, 1),
(-1, 0),
(0, -1),
(0.708, -0.708)]
intersection_union(p1, p2)
# Out: 0.12418051627698147
Assuming polygons are non self-intersecting, i.e. the ordering of the points around the circle is monotonic, then I believe there is a relatively simple way to determine the IoU value without requiring a general shape package.
Assume the points of each polygon are ordered clockwise around the circle. We can ensure this by sorting by increasing angle w.r.t the x-axis or reversing the points if we find the signed area is negative.
Combine points from both polygons into a single list, L, keeping track of which polygon each point belongs to. We also need to be able to determine the previous and next point in the original polygon for each point.
Sort L by increasing angle w.r.t the x-axis.
If the input polygons intersect the number of transitions from one polygon to the other while iterating through L will be greater than two.
Iterate through L. If successive points are encountered belonging to different polygons then the intersection of the lines between the 1st point and its next point and the 2nd point and its previous point will belong to the intersection between the two polygons.
Add each point identified in step 4 to a new polygon, I. Points of I will be encountered in order.
The sum of the areas of each polygon will be equal to their union plus the area of the intersection, since this will be counted twice.
The value of IoU will therefore be given by the area of I divided by the sum of the areas of the two polygons minus the area of I.
The only geometry required is to calculate the area of a simple polygon using the Shoelace formula, and to determine the point of intersection between two line segments, required by step 5.
Here's some Java code (Ideone) to illustrate - you can probably make it a lot more compact in Python.
double[][] coords = {{-0.708, 0.707, 0.309, -0.951, 0.587, -0.809},
{1, 0, 0, 1, -1, 0, 0, -1, 0.708, -0.708}};
double areaSum = 0;
List<CPoint> pts = new ArrayList<>();
for(int p=0; p<coords.length; p++)
{
List<CPoint> poly = new ArrayList<>();
for(int j=0; j<coords[p].length; j+=2)
{
poly.add(new CPoint(p, coords[p][j], coords[p][j+1]));
}
double area = area(poly);
if(area < 0)
{
area = -area;
Collections.reverse(poly);
}
areaSum += area;
pts.addAll(poly);
int n = poly.size();
for(int i=0, j=n-1; i<n; j=i++)
{
poly.get(i).prev = poly.get(j);
poly.get(j).next = poly.get(i);
}
}
pts.sort((a, b) -> Double.compare(a.theta, b.theta));
List<Point2D> intersections = new ArrayList<>();
int n = pts.size();
for(int i=0, j=n-1; i<n; j=i++)
{
if(pts.get(j).id != pts.get(i).id)
{
intersections.add(intersect(pts.get(j), pts.get(j).next, pts.get(i).prev, pts.get(i)));
}
}
double areaInt = area(intersections);
double iou = areaInt/(areaSum - areaInt);
System.out.println(iou);
Output:
0.12403616470027268
And supporting code:
static class CPoint extends Point2D.Double
{
int id;
double theta;
CPoint prev, next;
public CPoint(int id, double x, double y)
{
super(x, y);
this.id = id;
theta = Math.atan2(y, x);
if(theta < 0) theta = 2*Math.PI + theta;
}
}
static double area(List<? extends Point2D> poly)
{
double area = 0;
for(int i=0, j=poly.size()-1; i<poly.size(); j=i++)
area += (poly.get(j).getX() * poly.get(i).getY()) - (poly.get(i).getX() * poly.get(j).getY());
return Math.abs(area)/2;
}
// https://rosettacode.org/wiki/Find_the_intersection_of_two_lines#Java
static Point2D intersect(Point2D p1, Point2D p2, Point2D p3, Point2D p4)
{
double a1 = p2.getY() - p1.getY();
double b1 = p1.getX() - p2.getX();
double c1 = a1 * p1.getX() + b1 * p1.getY();
double a2 = p4.getY() - p3.getY();
double b2 = p3.getX() - p4.getX();
double c2 = a2 * p3.getX() + b2 * p3.getY();
double delta = a1 * b2 - a2 * b1;
return new Point2D.Double((b2 * c1 - b1 * c2) / delta, (a1 * c2 - a2 * c1) / delta);
}
from matplotlib import pyplot as plt
import numpy as np
# Python program for slope of line
def slope(x1, y1, x2, y2):
return (float)(y2-y1)/(x2-x1)
midPoint = 400
tenPercentOfMidpoint = .10 * midPoint
x = np.linspace(midPoint - tenPercentOfMidpoint, midPoint + tenPercentOfMidpoint, 50)
m = np.empty(x.shape)
c = np.empty(x.shape)
m[(x>=midPoint)] = 1.0
m[(x<midPoint)] = 1.0
c[x<midPoint] = 0
c[x>=midPoint] = 0
y=m*x+c
lenOfx = len(x)
lenOfy = len(y)
s = slope(0, y[0], lenOfx, y[lenOfy - 1])
angle = np.rad2deg(np.arctan2(x[-1] - x[0], lenOfy - 0))
If I now print s
print(s)
the result is 1.6
If I print the angle
print(angle)
the result is 57.99
I want a line between midPoint - tenPercent and midPoint + tenPercent that has slope 1 and angle 45
I believe this is just a problem of plugging in things incorrectly. The points you are testing in your slope are (0, y[0]) == (0, 360.0) and (lenOfx, y[lenOfy - 1]) == (50, 440.0); these points are not on the line defined by y=m*x+c. I believe you instead want to pull the two endpoints of the segment:
>>> slope(x[0], y[0], x[-1], y[-1]) # (360.0, 360.0, 440.0, 440.0)
1.0
Similar problem for calculating the angle; you want to use the change in y and the change in x:
>>> np.rad2deg(np.arctan2(y[-1] - y[0], x[-1] - x[0]))
45.0
The number of coordinates (len) should not be relevant to the slope calculation.
Note that x and y are the same (x == y returns a vector of True).
For a line to have a slope of 1, the change in y should be same as change in x i.e. by definition of slope
I want a line between midPoint - tenPercent and midPoint + tenPercent that has slope 1 and angle 45
The closest thing to a line of slope 1 would be by changing x
x = np.linspace(midPoint - tenPercentOfMidpoint, midPoint + tenPercentOfMidpoint, int(2*tenPercentOfMidpoint))
or by changing tenPercentOfMidpoint to a number such that
2*tenPercentOfMidpoint=50 ## because you mentioned 50 in np.linspace
tenPercentOfMidpoint=25
I have been not using math for a long time and this should be a simple problem to solve.
Suppose I have two points A: (1, 0) and B: (1, -1).
I want to use a program (Python or whatever programming language) to calculate the clockwise angle between A, origin (0, 0) and B. It will be something like this:
angle_clockwise(point1, point2)
Note that the order of the parameters matters. Since the angle calculation will be clockwise:
If I call angle_clockwise(A, B), it returns 45.
If I call angle_clockwise(B, A), it returns 315.
In other words, the algorithm is like this:
Draw a line (line 1) between the first point param with (0, 0).
Draw a line (line 2) between the second point param with (0, 0).
Revolve line 1 around (0, 0) clockwise until it overlaps line 2.
The angular distance line 1 traveled will be the returned angle.
Is there any way to code this problem?
Numpy's arctan2(y, x) will compute the counterclockwise angle (a value in radians between -π and π) between the origin and the point (x, y).
You could do this for your points A and B, then subtract the second angle from the first to get the signed clockwise angular difference. This difference will be between -2π and 2π, so in order to get a positive angle between 0 and 2π you could then take the modulo against 2π. Finally you can convert radians to degrees using np.rad2deg.
import numpy as np
def angle_between(p1, p2):
ang1 = np.arctan2(*p1[::-1])
ang2 = np.arctan2(*p2[::-1])
return np.rad2deg((ang1 - ang2) % (2 * np.pi))
For example:
A = (1, 0)
B = (1, -1)
print(angle_between(A, B))
# 45.
print(angle_between(B, A))
# 315.
If you don't want to use numpy, you could use math.atan2 in place of np.arctan2, and use math.degrees (or just multiply by 180 / math.pi) in order to convert from radians to degrees. One advantage of the numpy version is that you can also pass two (2, ...) arrays for p1 and p2 in order to compute the angles between multiple pairs of points in a vectorized way.
Use the inner product and the determinant of the two vectors. This is really what you should understand if you want to understand how this works. You'll need to know/read about vector math to understand.
See: https://en.wikipedia.org/wiki/Dot_product and https://en.wikipedia.org/wiki/Determinant
from math import acos
from math import sqrt
from math import pi
def length(v):
return sqrt(v[0]**2+v[1]**2)
def dot_product(v,w):
return v[0]*w[0]+v[1]*w[1]
def determinant(v,w):
return v[0]*w[1]-v[1]*w[0]
def inner_angle(v,w):
cosx=dot_product(v,w)/(length(v)*length(w))
rad=acos(cosx) # in radians
return rad*180/pi # returns degrees
def angle_clockwise(A, B):
inner=inner_angle(A,B)
det = determinant(A,B)
if det<0: #this is a property of the det. If the det < 0 then B is clockwise of A
return inner
else: # if the det > 0 then A is immediately clockwise of B
return 360-inner
In the determinant computation, you're concatenating the two vectors to form a 2 x 2 matrix, for which you're computing the determinant.
Here's a solution that doesn't require cmath.
import math
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
v1 = Vector(0, 1)
v2 = Vector(0, -1)
v1_theta = math.atan2(v1.y, v1.x)
v2_theta = math.atan2(v2.y, v2.x)
r = (v2_theta - v1_theta) * (180.0 / math.pi)
if r < 0:
r += 360.0
print r
A verified 0° to 360° solution
It is an old thread, but for me the other solutions didn't work well, so I implemented my own version.
My function will return a number between 0 and 360 (excluding 360) for two points on the screen (i.e. 'y' starts at the top and increasing towards the bottom), where results are as in a compass, 0° at the top, increasing clockwise:
def angle_between_points(p1, p2):
d1 = p2[0] - p1[0]
d2 = p2[1] - p1[1]
if d1 == 0:
if d2 == 0: # same points?
deg = 0
else:
deg = 0 if p1[1] > p2[1] else 180
elif d2 == 0:
deg = 90 if p1[0] < p2[0] else 270
else:
deg = math.atan(d2 / d1) / pi * 180
lowering = p1[1] < p2[1]
if (lowering and deg < 0) or (not lowering and deg > 0):
deg += 270
else:
deg += 90
return deg
Check out the cmath python library.
>>> import cmath
>>> a_phase = cmath.phase(complex(1,0))
>>> b_phase = cmath.phase(complex(1,-1))
>>> (a_phase - b_phase) * 180 / cmath.pi
45.0
>>> (b_phase - a_phase) * 180 / cmath.pi
-45.0
You can check if a number is less than 0 and add 360 to it if you want all positive angles, too.
Chris St Pierre: when using your function with:
A = (x=1, y=0)
B = (x=0, y=1)
This is supposed to be a 90 degree angle from A to B. Your function will return 270.
Is there an error in how you process the sign of the det or am I missing something?
A formula that calculates an angle clockwise, and is used in surveying:
f(E,N)=pi()-pi()/2*(1+sign(N))* (1-sign(E^2))-pi()/4*(2+sign(N))*sign(E)
-sign(N*E)*atan((abs(N)-abs(E))/(abs(N)+abs(E)))
The formula gives angles from 0 to 2pi,start from the North and
is working for any value of N and E. (N=N2-N1 and E=E2-E1)
For N=E=0 the result is undefined.
in radians, clockwise, from 0 to PI * 2
static angle(center:Coord, p1:Coord, p2:Coord) {
var a1 = Math.atan2(p1.y - center.y, p1.x - center.x);
var a2 = Math.atan2(p2.y - center.y, p2.x -center.x);
a1 = a1 > 0 ? a1 : Math.PI * 2 + a1;//make angle from 0 to PI * 2
a2 = a2 > 0 ? a2 : Math.PI * 2 + a2;
if(a1 > a2) {
return a1 - a2;
} else {
return Math.PI * 2 - (a2 - a1)
}
}