Related
I am not that experienced in python but improving it thanks to this community! I desperately need a function which takes the input and gives the ouput below:
Input:
1- Latitude/longitude coordinates of the center of circle 1 (e.g. (50.851295, 5.667969) )
2- The radius of circle 1 in meters (e.g. 200)
3- Latitude/longitude coordinates of the center of circle 2 (e.g. (50.844101, 5.725889) )
4- The radius of circle 2 in meters (e.g. 300)
Output: Possible output examples can be;
The intersection points are (50.848295, 5.707969) and (50.849295, 5.717969)
The circles are overlapping
The circles are tangential and the intersection point is (50.847295, 5.705969)
The circles do not intersect
I have examined the similar topics in this platform, other platforms, libraries, tried to combine different solutions but couldn't succeed. Any help is much appreciated!
EDIT:
The problem is solved many thanks to Ture Pålsson who commented below and directed me to whuber's brilliant work in this link https://gis.stackexchange.com/questions/48937/calculating-intersection-of-two-circles Based on that work, I wrote the code below and as far as I tested it works. I want to share it here in case someone might find it helpful. Any feedback is appreciated.
'''
FINDING THE INTERSECTION COORDINATES (LAT/LON) OF TWO CIRCLES (GIVEN THE COORDINATES OF THE CENTER AND THE RADII)
Many thanks to Ture Pålsson who directed me to the right source, the code below is based on whuber's brilliant logic and
explanation here https://gis.stackexchange.com/questions/48937/calculating-intersection-of-two-circles
The idea is that;
1. The points in question are the mutual intersections of three spheres: a sphere centered beneath location x1 (on the
earth's surface) of a given radius, a sphere centered beneath location x2 (on the earth's surface) of a given radius, and
the earth itself, which is a sphere centered at O = (0,0,0) of a given radius.
2. The intersection of each of the first two spheres with the earth's surface is a circle, which defines two planes.
The mutual intersections of all three spheres therefore lies on the intersection of those two planes: a line.
Consequently, the problem is reduced to intersecting a line with a sphere.
Note that "Decimal" is used to have higher precision which is important if the distance between two points are a few
meters.
'''
from decimal import Decimal
from math import cos, sin, sqrt
import math
import numpy as np
def intersection(p1, r1_meter, p2, r2_meter):
# p1 = Coordinates of Point 1: latitude, longitude. This serves as the center of circle 1. Ex: (36.110174, -90.953524)
# r1_meter = Radius of circle 1 in meters
# p2 = Coordinates of Point 2: latitude, longitude. This serves as the center of circle 1. Ex: (36.110174, -90.953524)
# r2_meter = Radius of circle 2 in meters
'''
1. Convert (lat, lon) to (x,y,z) geocentric coordinates.
As usual, because we may choose units of measurement in which the earth has a unit radius
'''
x_p1 = Decimal(cos(math.radians(p1[1]))*cos(math.radians(p1[0]))) # x = cos(lon)*cos(lat)
y_p1 = Decimal(sin(math.radians(p1[1]))*cos(math.radians(p1[0]))) # y = sin(lon)*cos(lat)
z_p1 = Decimal(sin(math.radians(p1[0]))) # z = sin(lat)
x1 = (x_p1, y_p1, z_p1)
x_p2 = Decimal(cos(math.radians(p2[1]))*cos(math.radians(p2[0]))) # x = cos(lon)*cos(lat)
y_p2 = Decimal(sin(math.radians(p2[1]))*cos(math.radians(p2[0]))) # y = sin(lon)*cos(lat)
z_p2 = Decimal(sin(math.radians(p2[0]))) # z = sin(lat)
x2 = (x_p2, y_p2, z_p2)
'''
2. Convert the radii r1 and r2 (which are measured along the sphere) to angles along the sphere.
By definition, one nautical mile (NM) is 1/60 degree of arc (which is pi/180 * 1/60 = 0.0002908888 radians).
'''
r1 = Decimal(math.radians((r1_meter/1852) / 60)) # r1_meter/1852 converts meter to Nautical mile.
r2 = Decimal(math.radians((r2_meter/1852) / 60))
'''
3. The geodesic circle of radius r1 around x1 is the intersection of the earth's surface with an Euclidean sphere
of radius sin(r1) centered at cos(r1)*x1.
4. The plane determined by the intersection of the sphere of radius sin(r1) around cos(r1)*x1 and the earth's surface
is perpendicular to x1 and passes through the point cos(r1)x1, whence its equation is x.x1 = cos(r1)
(the "." represents the usual dot product); likewise for the other plane. There will be a unique point x0 on the
intersection of those two planes that is a linear combination of x1 and x2. Writing x0 = ax1 + b*x2 the two planar
equations are;
cos(r1) = x.x1 = (a*x1 + b*x2).x1 = a + b*(x2.x1)
cos(r2) = x.x2 = (a*x1 + b*x2).x2 = a*(x1.x2) + b
Using the fact that x2.x1 = x1.x2, which I shall write as q, the solution (if it exists) is given by
a = (cos(r1) - cos(r2)*q) / (1 - q^2),
b = (cos(r2) - cos(r1)*q) / (1 - q^2).
'''
q = Decimal(np.dot(x1, x2))
if q**2 != 1 :
a = (Decimal(cos(r1)) - Decimal(cos(r2))*q) / (1 - q**2)
b = (Decimal(cos(r2)) - Decimal(cos(r1))*q) / (1 - q**2)
'''
5. Now all other points on the line of intersection of the two planes differ from x0 by some multiple of a vector
n which is mutually perpendicular to both planes. The cross product n = x1~Cross~x2 does the job provided n is
nonzero: once again, this means that x1 and x2 are neither coincident nor diametrically opposite. (We need to
take care to compute the cross product with high precision, because it involves subtractions with a lot of
cancellation when x1 and x2 are close to each other.)
'''
n = np.cross(x1, x2)
'''
6. Therefore, we seek up to two points of the form x0 + t*n which lie on the earth's surface: that is, their length
equals 1. Equivalently, their squared length is 1:
1 = squared length = (x0 + t*n).(x0 + t*n) = x0.x0 + 2t*x0.n + t^2*n.n = x0.x0 + t^2*n.n
'''
x0_1 = [a*f for f in x1]
x0_2 = [b*f for f in x2]
x0 = [sum(f) for f in zip(x0_1, x0_2)]
'''
The term with x0.n disappears because x0 (being a linear combination of x1 and x2) is perpendicular to n.
The two solutions easily are t = sqrt((1 - x0.x0)/n.n) and its negative. Once again high precision
is called for, because when x1 and x2 are close, x0.x0 is very close to 1, leading to some loss of
floating point precision.
'''
if (np.dot(x0, x0) <= 1) & (np.dot(n,n) != 0): # This is to secure that (1 - np.dot(x0, x0)) / np.dot(n,n) > 0
t = Decimal(sqrt((1 - np.dot(x0, x0)) / np.dot(n,n)))
t1 = t
t2 = -t
i1 = x0 + t1*n
i2 = x0 + t2*n
'''
7. Finally, we may convert these solutions back to (lat, lon) by converting geocentric (x,y,z) to geographic
coordinates. For the longitude, use the generalized arctangent returning values in the range -180 to 180
degrees (in computing applications, this function takes both x and y as arguments rather than just the
ratio y/x; it is sometimes called "ATan2").
'''
i1_lat = math.degrees( math.asin(i1[2]))
i1_lon = math.degrees( math.atan2(i1[1], i1[0] ) )
ip1 = (i1_lat, i1_lon)
i2_lat = math.degrees( math.asin(i2[2]))
i2_lon = math.degrees( math.atan2(i2[1], i2[0] ) )
ip2 = (i2_lat, i2_lon)
return [ip1, ip2]
elif (np.dot(n,n) == 0):
return("The centers of the circles can be neither the same point nor antipodal points.")
else:
return("The circles do not intersect")
else:
return("The centers of the circles can be neither the same point nor antipodal points.")
'''
Example: the output of below is [(36.989311051533505, -88.15142628069133), (38.2383796094578, -92.39048549120287)]
intersection_points = intersection((37.673442, -90.234036), 107.5*1852, (36.109997, -90.953669), 145*1852)
print(intersection_points)
'''
Depending on the precision you need, you may or may not consider the Earth as a sphere. In the second case, calculations become more complex.
The best option for precise measurements when the radius is small (as in your example) is to use a projection (UTM for example) and then apply the common flat euclidean calculations.
Let's first copy the flat circle intersection function from https://stackoverflow.com/a/55817881/2148416:
def circle_intersection(x0, y0, r0, x1, y1, r1):
d = math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)
if d > r0 + r1: # non intersecting
return None
if d < abs(r0 - r1): # one circle within other
return None
if d == 0 and r0 == r1: # coincident circles
return None
a = (r0 ** 2 - r1 ** 2 + d ** 2) / (2 * d)
h = math.sqrt(r0 ** 2 - a ** 2)
x2 = x0 + a * (x1 - x0) / d
y2 = y0 + a * (y1 - y0) / d
x3 = x2 + h * (y1 - y0) / d
y3 = y2 - h * (x1 - x0) / d
x4 = x2 - h * (y1 - y0) / d
y4 = y2 + h * (x1 - x0) / d
return (x3, y3), (x4, y4)
The precise calculation for a small radius (up to a few kilometers) can be done in UTM coordinates with the help of the utm library. It handles all the complications regarding the fact the the Earth is more an ellipsoid than a sphere:
import utm
def geo_circle_intersection(latlon0, radius0, latlon1, radius1):
# Convert lat/lon to UTM
x0, y0, zone, letter = utm.from_latlon(latlon0[0], latlon0[1])
x1, y1, _, _ = utm.from_latlon(latlon1[0], latlon1 [1], force_zone_number=zone)
# Calculate intersections in UTM coordinates
a_utm, b_utm = circle_intersection(x0, y0, r0, x1, y1, r1)
# Convert intersections from UTM back to lat/lon
a = utm.to_latlon(a_utm[0], a_utm[1], zone, letter)
b = utm.to_latlon(b_utm[0], b_utm[1], zone, letter)
return a, b
Using your example (with slightly larger radii):
>>> p0 = 50.851295, 5.667969
>>> r0 = 2000
>>> p1 = 50.844101, 5.725889
>>> r1 = 3000
>>> a, b = geo_circle_intersection(p0, r0, p1, r1)
>>> print(a)
(50.836848562566004, 5.684869539768468)
>>> print(b)
(50.860635308778285, 5.692236858407678)
Im working on a python turtle game where the turtle can move with commands but it has to be able to detect collision with rectangles and circles on the screen as well as the border. I have no idea how to do this can anyone help?
Collision is easy! Before the nitty gritty, you need to understand how to obtain the distance between two points. If you have not done this before it is just pythag!
If you picture two points on a plane (red points on the picture), the shortest distance to travel between them, is directly from one point to another, without needing to make any turns, this is the distance between the points. In the picture above, let y be the vertical axis and x the horizontal axis. The horizontal distance between points d and e is represented by the value b. The vertical distance between points d and e is represented by the value a. As such...
a = d.y - e.y
b = d.x - e.x
Although a and be might be negative, it doesn't matter, because we sqaure them in a the next step.
To get the value of c, we then have to get the square root of the sum of the squares of a and b. Might sound tricky at first, but very easy!
Python code
To do this in python is simple.
c = ((a**2)+(b**2))**0.5
# a**2 is a squared
# anything to the power of 0.5 is square rooted, test it in console
# 25**0.5 = 5.0
# 5**2 = 25
We now have the distance between the two points d and e. Lets say d and e have the radius rd and re. We can then check if the circle d is colliding with circle e, by subtracting each radius from the distance between the centre of the circles. So c becomes...
c -= rd - re
If c is less than or equal to zero, then you have a collision between circles!
def collision(d, e, rd, re):
a = d.y-e.y
b = d.x-e.x
c = ((a**2)+(b**2))**0.5
if c > 0:
# no collision
return False
return True
Rectangles
Rectangles are a little easier, to check if a point is inside a rectangle all you need is some if statements. Let these variables represent the rectangle x = x location, y = y location, w = width, h = height. Suppose you want to check if point p is colliding with the rectangle.
def check_rect_collision(p, x, y, w, h):
if p.x >= x and p.x <= x+w and p.y >= y and p.y <= y+h:
# collision between p and rectangle
return True
return False
To test collision against a circle is trivial -- use the distance() method which measures from the center of the cursor to a position or center of another turtle. Given a circle's center position and its radius:
def circle_collision(the_turtle, center, radius):
return the_turtle.distance(center) <= radius
If you need to know if the turtle's nose has touched the circle, you could add half the turtle's size to the radius, for a (possibly resized) cursor which would be very roughly:
def circle_collision(the_turtle, center, radius):
dx, dy, border = the_turtle.shapesize()
return the_turtle.distance(center) <= radius + 5 * (dx + dy) + border
I.e. half the default turtle size of 20 pixels times the average of dx and dy plus the width of the border around the turtle. Or some such approximation.
To detect rectangle collision is also reasonably simple:
def rectangle_collision(the_turtle, x, y, width, height):
tx, ty = the_turtle.position()
return x <= tx <= x + width and y <= ty <= y + height
Adjust to whatever rectangle measure you're using:
def rectangle_collision(the_turtle, llx, lly, urx, ury):
x, y = the_turtle.position()
return llx <= x <= urx and lly <= y <= ury
a la the coordinate arguments for setworldcoordinates().
I have this triangle in pygame
triangle = pygame.draw.polygon(window, (210,180,140), [[x, y], [x -10, y -10], [x + 10, y - 10]], 5)
that i need to rotate towards the mouse, very much like the center arrow in this gif: http://i.stack.imgur.com/yxsV1.gif. Pygame doesn't have a built in function for rotating polygons, so I'll need to manually move the three points in a circle, with the lowermost point [x,y] pointing towards the coords of the mouse. The variables I have are:
the distance between the center of the triangle and the circle i want it to rotate along (i.e. the radius)
the distance from the center to the mouse coordinates
the coordinates of the lowermost point of the triangle [x,y] and the other two sides
with this information, how can I use trigonometry to rotate all three sides of the triangle so that the bottom point allways faces the mouse position?
EDIT: this is what I've got so far, but it only manages to move the triangle back and forth along a diagonal instead of rotating.
def draw(self):
curx,cury = cur
#cur is a global var that is mouse coords
angle = math.atan2(self.x - curx, self.y - cury)
distance = math.sqrt(200 - (200 * math.cos(angle)))
x = self.x + distance
y = self.y + distance
triangle = pygame.draw.polygon(window, (210,180,140), [[x, y], [x - 10,y - 10], [x + 10,y - 10]], 5)
Edit: Thinking about this again this morning there's another way to do this since the polygon is a triangle. Also the math is potentially easier to understand, and it requires less calculation for each point.
Let Cx and Cy be the center of the circle inscribing the triangle. We can describe the equation of a circle using the parametric equation:
F(t) = { x = Cx + r * cos(t)
{ y = Cy + r * sin(t)
Where r is the radius of the circle, and t represents the angle along the circle.
Using this equation we can describe the triangle using the points that touch the circle, in this case we'll use t = { 0, 3 * pi / 4, 5 * pi / 4 } as our points.
We also need to calculate the angle that we need to rotate the triangle so that the point that was at t = (0) is on a line from (Cx, Cy) to the mouse location. The angle between two (normalized) vectors can be calculated by:
t = acos(v1 . v2) = acos(<x1, y1> . <x2, y2>) = acos(x1 * x2 + y1 * y2)
where . represents the dot product, and acos is the inverse cosine (arccos or cos^-1).
From these two equations we can easily create a python function which, given the center of the triangle/circle, the radius of the circle, and the location of the mouse, returns a list of tuples representing the x-y coordinates of the triangle. (For the example the center and mouse position are tuples of the form (x, y))
def get_points(center, radius, mouse_position):
# calculate the normalized vector pointing from center to mouse_position
length = math.hypot(mouse_position[0] - center[0], mouse_position[1] - center[1])
# (note we only need the x component since y falls
# out of the dot product, so we won't bother to calculate y)
angle_vector_x = (mouse_position[0] - center[0]) / length
# calculate the angle between that vector and the x axis vector (aka <1,0> or i)
angle = math.acos(angle_vector_x)
# list of un-rotated point locations
triangle = [0, (3 * math.pi / 4), (5 * math.pi / 4)]
result = list()
for t in triangle:
# apply the circle formula
x = center[0] + radius * math.cos(t + angle)
y = center[1] + radius * math.sin(t + angle)
result.append((x, y))
return result
Calling this function like this:
from pprint import pprint
center = (0,0)
radius = 10
mouse_position = (50, 50)
points = get_points(center, radius, mouse_position)
pprint(points)
produces:
[(7.071067811865475, 7.0710678118654755),
(-10.0, 1.2246467991473533e-15),
(-1.8369701987210296e-15, -10.0)]
which is the three points (x, y) of the triangle.
I'm going to leave the original method below, since it's the way that modern computer graphics systems (OpenGL, DirectX, etc.) do it.
Rotation about the centroid of a arbitrary polygon is a sequence of three distinct matrix operations, Translating the object so that the centroid is at the origin (0,0), applying a rotation, and translating back to the original position.
Calculating the centroid for an arbitrary n-gon is probably outside the scope of an answer here, (Google will reveal many options), but it could be done completely by hand using graph paper. Call that point C.
To simplify operations, and to enable all transformations to be applied using simple matrix multiplications, we use so called Homogeneous coordinates, which are of the form:
[ x ]
p = | y |
[ 1 ]
for 2d coordinates.
Let
[ Cx ]
C = | Cy |
[ 1 ]
The general form of the translation matrix is:
[ 1 0 Vx ]
T = | 0 1 Vy |
[ 0 0 1 ]
Where <Vx, Vy> represents the translation vector. Since the goal of the translation is to move the centroid C to the origin, Vx = -Cx and Vy = -Cy. The inverse translation T' is simply Vx = Cx, Vy = Cy
Next the rotation matrix is needed. Let r be the desired clockwise rotation angle, and R be the general form of the rotation matrix. Then,
[ cos(r) sin(r) 0 ]
R = | -sin(r) cos(r) 0 |
[ 0 0 1 ]
The final transformation matrix is therefore:
[ 1 0 -Cx ] [ cos(r) sin(r) 0 ] [ 1 0 Cx ]
TRT' = | 0 1 -Cy | * | -sin(r) cos(r) 0 | * | 0 1 Cy |
[ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ]
Which simplifies to:
[ cos(r) sin(r) cos(r)*Cx-Cx+Cy*sin(r) ]
|-sin(r) cos(r) cos(r)*Cy-Cy-Cx*sin(r) |
[ 0 0 1 ]
Applying this to a point p = (x,y) we obtain the following equation:
p' = { x' = Cx*cos(r)-Cx+Cy*sin(r)+x*cos(r)+y*sin(r)
{ y' = -Cx*sin(r)+Cy*cos(r)-Cy-x*sin(r)+y*cos(r)
In Python:
def RotatePoint(c, p, r):
x = c[0]*math.cos(r)-c[0]+c[1]*math.sin(r)+p[0]*math.cos(r)+p[1]*math.sin(r)
y = -c[0]*math.sin(r)+c[1]*math.cos(r)-c[1]-p[0]*math.sin(r)+p[1]*math.cos(r)
return (x, y)
After typing all that I realize that your object may already be centered on the origin, in which case the function above simplifies to x=p[0]*math.cos(r)+p[1]*math.sin(r) and y=p[0]*math.sin(r)+p[1]*math.cos(r)
I put some faith in Wolfram Alpha here, rather than multiplying everything out by hand. If anyone notices any issues, feel free to make the edit.
I have a polygon consists of lots of points. I want to find the intersection of the polygon and a circle. Providing the circle center of [x0,y0] and radius of r0, I have wrote a rough function to simply solve the quadratic equation of the circle and a line. But what about the efficiency of find the intersection of every line segment of the polygon one by one? Is there more efficient way?
I know sympy already provide the feature to get the intersections of different geometry. But also what about the efficiency of external library like sympy compared to calculate it by my own function, if I want to deal with lots of polygons?
def LineIntersectCircle(p,lsp,lep):
# p is the circle parameter, lsp and lep is the two end of the line
x0,y0,r0 = p
x1,y1 = lsp
x2,y2 = esp
if x1 == x2:
if abs(r0) >= abs(x1 - x0):
p1 = x1, y0 - sqrt(r0**2 - (x1-x0)**2)
p2 = x1, y0 + sqrt(r0**2 - (x1-x0)**2)
inp = [p1,p2]
# select the points lie on the line segment
inp = [p for p in inp if p[1]>=min(y1,y2) and p[1]<=max(y1,y2)]
else:
inp = []
else:
k = (y1 - y2)/(x1 - x2)
b0 = y1 - k*x1
a = k**2 + 1
b = 2*k*(b0 - y0) - 2*x0
c = (b0 - y0)**2 + x0**2 - r0**2
delta = b**2 - 4*a*c
if delta >= 0:
p1x = (-b - sqrt(delta))/(2*a)
p2x = (-b + sqrt(delta))/(2*a)
p1y = k*x1 + b0
p2y = k*x2 + b0
inp = [[p1x,p1y],[p2x,p2y]]
# select the points lie on the line segment
inp = [p for p in inp if p[0]>=min(x1,x2) and p[0]<=max(x1,x2)]
else:
inp = []
return inp
I guess maybe your question is about how to in theory do this in the fastest manner. But if you want to do this quickly, you should really use something which is written in C/C++.
I am quite used to Shapely, so I will provide an example of how to do this with this library. There are many geometry libraries for python. I will list them at the end of this answer.
from shapely.geometry import LineString
from shapely.geometry import Point
p = Point(5,5)
c = p.buffer(3).boundary
l = LineString([(0,0), (10, 10)])
i = c.intersection(l)
print i.geoms[0].coords[0]
(2.8786796564403576, 2.8786796564403576)
print i.geoms[1].coords[0]
(7.121320343559642, 7.121320343559642)
What is a little bit counter intuitive in Shapely is that circles are the boundries of points with buffer areas. This is why I do p.buffer(3).boundry
Also the intersection i is a list of geometric shapes, both of them points in this case, this is why I have to get both of them from i.geoms[]
There is another Stackoverflow question which goes into details about these libraries for those interested.
SymPy
CGAL Python bindings
PyEuclid
PythonOCC
Geometry-simple
EDIT because comments:
Shapely is based on GEOS (trac.osgeo.org/geos) which is built in C++ and considerably faster than anything you write natively in python. SymPy seems to be based on mpmath (mpmath.org) which also seems to be python, but seems to have lots of quite complex math integrated into it. Implementing that yourself may require a lot of work, and will probably not be as fast as GEOS C++ implementations.
Here's a solution that computes the intersection of a circle with either a line or a line segment defined by two (x, y) points:
def circle_line_segment_intersection(circle_center, circle_radius, pt1, pt2, full_line=True, tangent_tol=1e-9):
""" Find the points at which a circle intersects a line-segment. This can happen at 0, 1, or 2 points.
:param circle_center: The (x, y) location of the circle center
:param circle_radius: The radius of the circle
:param pt1: The (x, y) location of the first point of the segment
:param pt2: The (x, y) location of the second point of the segment
:param full_line: True to find intersections along full line - not just in the segment. False will just return intersections within the segment.
:param tangent_tol: Numerical tolerance at which we decide the intersections are close enough to consider it a tangent
:return Sequence[Tuple[float, float]]: A list of length 0, 1, or 2, where each element is a point at which the circle intercepts a line segment.
Note: We follow: http://mathworld.wolfram.com/Circle-LineIntersection.html
"""
(p1x, p1y), (p2x, p2y), (cx, cy) = pt1, pt2, circle_center
(x1, y1), (x2, y2) = (p1x - cx, p1y - cy), (p2x - cx, p2y - cy)
dx, dy = (x2 - x1), (y2 - y1)
dr = (dx ** 2 + dy ** 2)**.5
big_d = x1 * y2 - x2 * y1
discriminant = circle_radius ** 2 * dr ** 2 - big_d ** 2
if discriminant < 0: # No intersection between circle and line
return []
else: # There may be 0, 1, or 2 intersections with the segment
intersections = [
(cx + (big_d * dy + sign * (-1 if dy < 0 else 1) * dx * discriminant**.5) / dr ** 2,
cy + (-big_d * dx + sign * abs(dy) * discriminant**.5) / dr ** 2)
for sign in ((1, -1) if dy < 0 else (-1, 1))] # This makes sure the order along the segment is correct
if not full_line: # If only considering the segment, filter out intersections that do not fall within the segment
fraction_along_segment = [(xi - p1x) / dx if abs(dx) > abs(dy) else (yi - p1y) / dy for xi, yi in intersections]
intersections = [pt for pt, frac in zip(intersections, fraction_along_segment) if 0 <= frac <= 1]
if len(intersections) == 2 and abs(discriminant) <= tangent_tol: # If line is tangent to circle, return just one point (as both intersections have same location)
return [intersections[0]]
else:
return intersections
A low cost spacial partition might be to divide the plane into 9 pieces
Here is a crappy diagram. Imagine, if you will, that the lines are just touching the circle.
| |
__|_|__
__|O|__
| |
| |
8 of the areas we are interested in are surrounding the circle. The square in the centre isn't much use for a cheap test, but you can place a square of r/sqrt(2) inside the circle, so it's corners just touch the circle.
Lets label the areas
A |B| C
__|_|__
D_|O|_E
| |
F |G| H
And the square of r/sqrt(2) in the centre we'll call J
We will call the set of points in the centre square shown in the diagram that aren't in J, Z
Label each vertex of the polygon with it's letter code.
Now we can quickly see
AA => Outside
AB => Outside
AC => Outside
...
AJ => Intersects
BJ => Intersects
...
JJ => Inside
This can turned into a lookup table
So depending on your dataset, you may have saved yourself a load of work. Anything with an endpoint in Z will need to be tested however.
I think that the formula you use to find the coordinates of the two intersections cannot be optimized further. The only improvement (which is numerically important) is to distinguish the two cases: |x_2-x_1| >= |y_2-y_1| and |x_2-x1| < |y_2-y1| so that the quantity k is always between -1 and 1 (in your computation you can get very high numerical errors if |x_2-x_1| is very small). You can swap x-s and y-s to reduce one case to the other.
You could also implement a preliminary check: if both endpoints are internal to the circle there are no intersection. By computing the squared distance from the points to the center of the circle this becomes a simple formula which does not use the square root function. The other check: "whether the line is outside the circle" is already implemented in your code and corresponds to delta < 0. If you have a lot of small segments these two check should give a shortcut answer (no intersection) in most cases.
In testing an object detection algorithm in large images, we check our detected bounding boxes against the coordinates given for the ground truth rectangles.
According to the Pascal VOC challenges, there's this:
A predicted bounding box is considered correct if it overlaps more
than 50% with a ground-truth bounding box, otherwise the bounding box
is considered a false positive detection. Multiple detections are
penalized. If a system predicts several bounding boxes that overlap
with a single ground-truth bounding box, only one prediction is
considered correct, the others are considered false positives.
This means that we need to calculate the percentage of overlap. Does this mean that the ground truth box is 50% covered by the detected boundary box? Or that 50% of the bounding box is absorbed by the ground truth box?
I've searched but I haven't found a standard algorithm for this - which is surprising because I would have thought that this is something pretty common in computer vision. (I'm new to it). Have I missed it? Does anyone know what the standard algorithm is for this type of problem?
For axis-aligned bounding boxes it is relatively simple. "Axis-aligned" means that the bounding box isn't rotated; or in other words that the boxes lines are parallel to the axes. Here's how to calculate the IoU of two axis-aligned bounding boxes.
def get_iou(bb1, bb2):
"""
Calculate the Intersection over Union (IoU) of two bounding boxes.
Parameters
----------
bb1 : dict
Keys: {'x1', 'x2', 'y1', 'y2'}
The (x1, y1) position is at the top left corner,
the (x2, y2) position is at the bottom right corner
bb2 : dict
Keys: {'x1', 'x2', 'y1', 'y2'}
The (x, y) position is at the top left corner,
the (x2, y2) position is at the bottom right corner
Returns
-------
float
in [0, 1]
"""
assert bb1['x1'] < bb1['x2']
assert bb1['y1'] < bb1['y2']
assert bb2['x1'] < bb2['x2']
assert bb2['y1'] < bb2['y2']
# determine the coordinates of the intersection rectangle
x_left = max(bb1['x1'], bb2['x1'])
y_top = max(bb1['y1'], bb2['y1'])
x_right = min(bb1['x2'], bb2['x2'])
y_bottom = min(bb1['y2'], bb2['y2'])
if x_right < x_left or y_bottom < y_top:
return 0.0
# The intersection of two axis-aligned bounding boxes is always an
# axis-aligned bounding box
intersection_area = (x_right - x_left) * (y_bottom - y_top)
# compute the area of both AABBs
bb1_area = (bb1['x2'] - bb1['x1']) * (bb1['y2'] - bb1['y1'])
bb2_area = (bb2['x2'] - bb2['x1']) * (bb2['y2'] - bb2['y1'])
# compute the intersection over union by taking the intersection
# area and dividing it by the sum of prediction + ground-truth
# areas - the interesection area
iou = intersection_area / float(bb1_area + bb2_area - intersection_area)
assert iou >= 0.0
assert iou <= 1.0
return iou
Explanation
Images are from this answer
The top-voted answer has a mathematical error if you are working with screen (pixel) coordinates! I submitted an edit a few weeks ago with a long explanation for all readers so that they would understand the math. But that edit wasn't understood by the reviewers and was removed, so I've submitted the same edit again, but more briefly summarized this time. (Update: Rejected 2vs1 because it was deemed a "substantial change", heh).
So I will completely explain the BIG problem with its math here in this separate answer.
So, yes, in general, the top-voted answer is correct and is a good way to calculate the IoU. But (as other people have pointed out too) its math is completely incorrect for computer screens. You cannot just do (x2 - x1) * (y2 - y1), since that will not produce the correct area calculations whatsoever. Screen indexing starts at pixel 0,0 and ends at width-1,height-1. The range of screen coordinates is inclusive:inclusive (inclusive on both ends), so a range from 0 to 10 in pixel coordinates is actually 11 pixels wide, because it includes 0 1 2 3 4 5 6 7 8 9 10 (11 items). So, to calculate the area of screen coordinates, you MUST therefore add +1 to each dimension, as follows: (x2 - x1 + 1) * (y2 - y1 + 1).
If you're working in some other coordinate system where the range is not inclusive (such as an inclusive:exclusive system where 0 to 10 means "elements 0-9 but not 10"), then this extra math would NOT be necessary. But most likely, you are processing pixel-based bounding boxes. Well, screen coordinates start at 0,0 and go up from there.
A 1920x1080 screen is indexed from 0 (first pixel) to 1919 (last pixel horizontally) and from 0 (first pixel) to 1079 (last pixel vertically).
So if we have a rectangle in "pixel coordinate space", to calculate its area we must add 1 in each direction. Otherwise, we get the wrong answer for the area calculation.
Imagine that our 1920x1080 screen has a pixel-coordinate based rectangle with left=0,top=0,right=1919,bottom=1079 (covering all pixels on the whole screen).
Well, we know that 1920x1080 pixels is 2073600 pixels, which is the correct area of a 1080p screen.
But with the wrong math area = (x_right - x_left) * (y_bottom - y_top), we would get: (1919 - 0) * (1079 - 0) = 1919 * 1079 = 2070601 pixels! That's wrong!
That is why we must add +1 to each calculation, which gives us the following corrected math: area = (x_right - x_left + 1) * (y_bottom - y_top + 1), giving us: (1919 - 0 + 1) * (1079 - 0 + 1) = 1920 * 1080 = 2073600 pixels! And that's indeed the correct answer!
The shortest possible summary is: Pixel coordinate ranges are inclusive:inclusive, so we must add + 1 to each axis if we want the true area of a pixel coordinate range.
For a few more details about why +1 is needed, see Jindil's answer: https://stackoverflow.com/a/51730512/8874388
As well as this pyimagesearch article:
https://www.pyimagesearch.com/2016/11/07/intersection-over-union-iou-for-object-detection/
And this GitHub comment:
https://github.com/AlexeyAB/darknet/issues/3995#issuecomment-535697357
Since the fixed math wasn't approved, anyone who copies the code from the top-voted answer hopefully sees this answer, and will be able to bugfix it themselves, by simply copying the bugfixed assertions and area-calculation lines below, which have been fixed for inclusive:inclusive (pixel) coordinate ranges:
assert bb1['x1'] <= bb1['x2']
assert bb1['y1'] <= bb1['y2']
assert bb2['x1'] <= bb2['x2']
assert bb2['y1'] <= bb2['y2']
................................................
# The intersection of two axis-aligned bounding boxes is always an
# axis-aligned bounding box.
# NOTE: We MUST ALWAYS add +1 to calculate area when working in
# screen coordinates, since 0,0 is the top left pixel, and w-1,h-1
# is the bottom right pixel. If we DON'T add +1, the result is wrong.
intersection_area = (x_right - x_left + 1) * (y_bottom - y_top + 1)
# compute the area of both AABBs
bb1_area = (bb1['x2'] - bb1['x1'] + 1) * (bb1['y2'] - bb1['y1'] + 1)
bb2_area = (bb2['x2'] - bb2['x1'] + 1) * (bb2['y2'] - bb2['y1'] + 1)
A Simple way for any kind of polygon.
(Image is not drawn to scale)
from shapely.geometry import Polygon
def calculate_iou(box_1, box_2):
poly_1 = Polygon(box_1)
poly_2 = Polygon(box_2)
iou = poly_1.intersection(poly_2).area / poly_1.union(poly_2).area
return iou
box_1 = [[511, 41], [577, 41], [577, 76], [511, 76]]
box_2 = [[544, 59], [610, 59], [610, 94], [544, 94]]
print(calculate_iou(box_1, box_2))
The result will be 0.138211... which means 13.82%.
You can also use shapely.geometry.box if your box is rectangular [minx, miny, maxx, maxy] shape.
Note: The origin of Coordinate Systems in shapely library is left-bottom where origin in computer graphics is left-top. This difference does not affect the IoU calculation, but if you do other types of calculation, this information might be helpful.
You can calculate with torchvision as follows. The bbox is prepared in the format of [x1, y1, x2, y2].
import torch
import torchvision.ops.boxes as bops
box1 = torch.tensor([[511, 41, 577, 76]], dtype=torch.float)
box2 = torch.tensor([[544, 59, 610, 94]], dtype=torch.float)
iou = bops.box_iou(box1, box2)
# tensor([[0.1382]])
For the intersection distance, shouldn't we add a +1 so as to have
intersection_area = (x_right - x_left + 1) * (y_bottom - y_top + 1)
(same for the AABB)
Like on this pyimage search post
I agree (x_right - x_left) x (y_bottom - y_top) works in mathematics with point coordinates but since we deal with pixels it is I think different.
Consider a 1D example :
2 points : x1 = 1 and x2 = 3, the distance is indeed x2-x1 = 2
2 pixels of index : i1 = 1 and i2 = 3, the segment from pixel i1 to i2 contains 3 pixels ie l = i2 - i1 + 1
EDIT: I recently got to know that this is a "little-square" approach.
If however you consider pixels as point-samples (ie the bounding box corner would be at the centre of the pixel as apparently in matplotlib) then you don't need the +1.
See this comment and this illustration
import numpy as np
def box_area(arr):
# arr: np.array([[x1, y1, x2, y2]])
width = arr[:, 2] - arr[:, 0]
height = arr[:, 3] - arr[:, 1]
return width * height
def _box_inter_union(arr1, arr2):
# arr1 of [N, 4]
# arr2 of [N, 4]
area1 = box_area(arr1)
area2 = box_area(arr2)
# Intersection
top_left = np.maximum(arr1[:, :2], arr2[:, :2]) # [[x, y]]
bottom_right = np.minimum(arr1[:, 2:], arr2[:, 2:]) # [[x, y]]
wh = bottom_right - top_left
# clip: if boxes not overlap then make it zero
intersection = wh[:, 0].clip(0) * wh[:, 1].clip(0)
#union
union = area1 + area2 - intersection
return intersection, union
def box_iou(arr1, arr2):
# arr1[N, 4]
# arr2[N, 4]
# N = number of bounding boxes
assert(arr1[:, 2:] > arr[:, :2]).all()
assert(arr2[:, 2:] > arr[:, :2]).all()
inter, union = _box_inter_union(arr1, arr2)
iou = inter / union
print(iou)
box1 = np.array([[10, 10, 80, 80]])
box2 = np.array([[20, 20, 100, 100]])
box_iou(box1, box2)
reference: https://pytorch.org/vision/stable/_modules/torchvision/ops/boxes.html#nms
In the snippet below, I construct a polygon along the edges of the first box. I then use Matplotlib to clip the polygon to the second box. The resulting polygon contains four vertices, but we are only interested in the top left and bottom right corners, so I take the max and the min of the coordinates to get a bounding box, which is returned to the user.
import numpy as np
from matplotlib import path, transforms
def clip_boxes(box0, box1):
path_coords = np.array([[box0[0, 0], box0[0, 1]],
[box0[1, 0], box0[0, 1]],
[box0[1, 0], box0[1, 1]],
[box0[0, 0], box0[1, 1]]])
poly = path.Path(np.vstack((path_coords[:, 0],
path_coords[:, 1])).T, closed=True)
clip_rect = transforms.Bbox(box1)
poly_clipped = poly.clip_to_bbox(clip_rect).to_polygons()[0]
return np.array([np.min(poly_clipped, axis=0),
np.max(poly_clipped, axis=0)])
box0 = np.array([[0, 0], [1, 1]])
box1 = np.array([[0, 0], [0.5, 0.5]])
print clip_boxes(box0, box1)
Maybe one for the more visually inclined, like me . . .
Say your ROIs are atop an HD Rez surface. You can make a matrix for each in numpy like . .
roi1 = np.zeros((1080, 1920))
Then "fill in" ROI area like . . .
roi1[y1:y2, x1:x2] = 1 # y1,x1 & y2,x2 are the ROI corners
Repeat for roi2. Then calculate IoU with a this function . . .
def calc_iou(roi1, roi2):
# Sum all "white" pixels clipped to 1
U = np.sum(np.clip(roi1 + roi2, 0 , 1))
# +1 for each overlapping white pixel (these will = 2)
I = len(np.where(roi1 + roi2 == 2)[0])
return(I/U)
how about this approach? Could be extended to any number of unioned shapes
surface = np.zeros([1024,1024])
surface[1:1+10, 1:1+10] += 1
surface[100:100+500, 100:100+100] += 1
unionArea = (surface==2).sum()
print(unionArea)