Filling a polygon with lines parallel to its longest side - python

Given a list of points that form a polygon, how can I create evenly spaced lines within that polygon that are parallel to it's longest side?
I am able to rotate the lines and get the even spacing, but I can't seem to place them within the polygon. My intention after getting the lines within the polygon is to find where they intercept it.
Here is the point at which I am now stuck:
import matplotlib.pyplot as plt
import numpy as np
import math
def longest_side(points):
"""
Returns the points of the longest side
"""
max_length = 0
for i in range(len(points)-1):
cur_length = np.linalg.norm(np.array(points[i])-np.array(points[i+1]))
if cur_length > max_length:
max_length = cur_length
cur_longest = [points[i], points[i+1]]
return cur_longest
def rotate(origin, point, angle):
"""
Rotate point around origin
"""
ox, oy = origin
px, py = point
qx = ox + math.cos(angle) * (px - ox) - math.sin(angle) * (py - oy)
qy = oy + math.sin(angle) * (px - ox) + math.cos(angle) * (py - oy)
return qx, qy
def create_lines(points, spacing):
"""
Fill polygon with lines
"""
# Get the longest side
longest_lines = longest_side(points)
x1,y1 = longest_lines[0]
x2,y2 = longest_lines[1]
# Arrange the points in acending x-value
if x2 < x1:
tmp = (x1, y1)
x1 = x2
y1 = y2
x2 = tmp[0]
y2 = tmp[1]
# Get the angle between the longest line and the horizontal axis
angle = math.atan2(y2 - y1, x2 - x1)
# Create lines parallel to the longest line with given spacing
for y in np.arange(min(y1, y2), max(y1, y2), spacing):
xr, yr = rotate(origin=[min(x), y], point=[max(x), y], angle=angle)
plt.plot([min(x), xr], [y, yr])
if __name__ == "__main__":
points = ([0, 8], [2, 10], [10, 4], [10, 0], [0, 8])
x = [p[0] for p in points]
y = [p[1] for p in points]
create_lines(points=points, spacing=1)
plt.plot(x, y, 'ro-')
plt.axis('scaled')
plt.show()
Is there a general way this problem can be solved given any list of points?

The short answer: you need to do some geometry.
The long answer:
Create a Line Segment class to easily calculate line intersections and acceptable range of intercepts of lines with fixed slope m that still intersect with the line segment.
Turn your points into Line Segments, find the longest line, find the range of intercepts needed to fill the polygon, then find the intersections for each line generated by each intercept.
Class definition:
import matplotlib.pyplot as plt
import numpy as np
import math
# Represent a non-vertical line segment from start_pt to end_pt
# as y = mx + b and minv <= x <= maxv.
# For vertical lines x = b, m = None and minv <= y <= maxv
class LineSeg():
def __init__(self, start_pt, end_pt):
self.x, self.y = start_pt
self.x2, self.y2 = end_pt
if self.x != self.x2:
self.m = (self.y2 - self.y) / (self.x2 - self.x)
self.b = self.y - self.m*self.x
self.minv = min(self.x, self.x2)
self.maxv = max(self.x, self.x2)
else:
self.m = None
self.b = self.x
self.minv = min(self.y, self.y2)
self.maxv = max(self.y, self.y2)
def length(self):
return np.linalg.norm([self.x2-self.x, self.y2-self.y])
# Find intersection (x, y) with line y = mx + b
def intersect_w_line(self, m, b):
# Parallel lines
if m == self.m:
return (None, None)
# Line is vertical but line segment is not
elif m == None:
if self.minv <= b <= self.maxv:
return (b, self.m*b + self.b)
else:
return (None, None)
# Line segment is vertical, but line is not
elif self.m == None:
y = m*self.b + b
if self.minv <= y <= self.maxv:
return (self.b, y)
else:
return (None, None)
else:
x = (b - self.b) / (self.m - m)
y = self.m*x + self.b
if self.minv <= x <= self.maxv:
return (x, y)
else:
return (None, None)
# Find intercept range with line y = mx + b
def intercept_range(self, m):
if self.m == m:
return (self.b, self.b)
# Line is vertical, but segment is not
elif m == None:
return sorted([self.x, self.x2])
# Line is not vertical
else:
b = self.y - m*self.x
b2 = self.y2 - m*self.x2
return sorted([b, b2])
Plotting:
points = ([0, 8], [2, 10], [10, 4], [10, 0])
linesegs = [LineSeg(points[i], points[i+1]) if i+1 < len(points) else LineSeg(points[i], points[0]) for i in range(len(points))]
lengths = [lineseg.length() for lineseg in linesegs]
longest_seg = [lineseg for lineseg in linesegs if lineseg.length() == max(lengths)]
m = longest_seg[0].m
b = longest_seg[0].b
intercept_ranges = [lineseg.intercept_range(m) for lineseg in linesegs]
max_intercept = np.max(intercept_ranges)
min_intercept = np.min(intercept_ranges)
num_lines = 10
spacing = (max_intercept - min_intercept) / (num_lines+1)
intercepts = np.arange(min_intercept + spacing, max_intercept, spacing)
line_pts = [[lineseg.intersect_w_line(m, intercept) for lineseg in linesegs if lineseg.intersect_w_line(m, intercept)[0] is not None] for intercept in intercepts]
plt.close('all')
fig, ax = plt.subplots(1, 1)
polygon = mpl.patches.Polygon(points, closed = True, fill = False)
ax.add_artist(polygon)
for start, end in line_pts:
line = mpl.lines.Line2D([start[0], end[0]], [start[1], end[1]])
ax.add_artist(line)
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)

Related

Missalgined circles in Fourier Series/Transform using Python and Tkinter

I made a Fourier Series/Transform Tkinter app, and so far everything works as I want it to, except that I am having issues with the circles misaligning.
Here is an image explaining my issue (the green and pink were added after the fact to better explain the issue):
I have narrowed down the problem to the start of the lines, as it seems that they end in the correct place, and the circles are in their correct places.
The distance between the correct positions and the position where the lines start seems to grow, but is actually proportional to the speed of the circle rotating, as the circle rotates by larger amounts, thus going faster.
Here is the code:
from tkinter import *
import time
import math
import random
root = Tk()
myCanvas = Canvas(root, width=1300, height=750)
myCanvas.pack()
myCanvas.configure(bg="#0A2239")
global x,y, lines, xList, yList
NumOfCircles = 4
rList = [200]
n=3
for i in range(0, NumOfCircles):
rList.append(rList[0]/n)
n=n+2
print(rList)
num = 250/sum(rList)
for i in range(0, NumOfCircles):
rList[i] = rList[i]*num
x=0
y=0
lines = []
circles = []
centerXList = [300]
for i in range(0,NumOfCircles):
centerXList.append(0)
centerYList = [300]
for i in range(0,NumOfCircles):
centerYList.append(0)
xList = [0]*NumOfCircles
yList = [0]*NumOfCircles
waveLines = []
wavePoints = []
con=0
endCoord = []
for i in range(0, NumOfCircles):
endCoord.append([0,0])
lastX = 0
lastY = 0
count = 0
randlist = []
n=1
for i in range(0, NumOfCircles):
randlist.append(200/n)
n=n+2
def createCircle(x, y, r, canvasName):
x0 = x - r
y0 = y - r
x1 = x + r
y1 = y + r
return canvasName.create_oval(x0, y0, x1, y1, width=r/50, outline="#094F9A")
def updateCircle(i):
newX = endCoord[i-1][0]
newY = endCoord[i-1][1]
centerXList[i] = newX
centerYList[i] = newY
x0 = newX - rList[i]
y0 = newY - rList[i]
x1 = newX + rList[i]
y1 = newY + rList[i]
myCanvas.coords(circles[i], x0, y0, x1, y1)
def circleWithLine(i):
global line, lines
circle = createCircle(centerXList[i], centerYList[i], rList[i], myCanvas)
circles.append(circle)
line = myCanvas.create_line(centerXList[i], centerYList[i], centerXList[i], centerYList[i], width=2, fill="#1581B7")
lines.append(line)
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
myCanvas.coords(lines[i], x, y, endCoord[i][0], endCoord[i][1])
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
def lineBetweenTwoPoints(x, y, x2, y2):
line = myCanvas.create_line(x, y, x2, y2, fill="white")
return line
def lineForWave(y1, y2, y3, y4, con):
l = myCanvas.create_line(700+con, y1, 702+con, y2, 704+con, y3, 706+con, y4, smooth=1, fill="white")
waveLines.append(l)
for i in range(0,NumOfCircles):
circleWithLine(i)
myCanvas.create_line(700, 20, 700, 620, fill="black", width = 3)
myCanvas.create_line(700, 300, 1250, 300, fill="red")
myCanvas.create_line(0, 300, 600, 300, fill="red", width = 0.5)
myCanvas.create_line(300, 0, 300, 600, fill="red", width = 0.5)
while True:
for i in range(0, len(lines)):
update(i, centerXList[i], centerYList[i])
for i in range(1, len(lines)):
updateCircle(i)
if count >= 8:
lineBetweenTwoPoints(lastX, lastY, endCoord[i][0], endCoord[i][1])
if count % 6 == 0 and con<550:
lineForWave(wavePoints[-7],wavePoints[-5],wavePoints[-3],wavePoints[-1], con)
con += 6
wavePoints.append(endCoord[i][1])
myCanvas.update()
lastX = endCoord[i][0]
lastY = endCoord[i][1]
if count != 108:
count += 1
else:
count = 8
time.sleep(0.01)
root.mainloop()
I am aware that this is not the best way to achieve what I am trying to achieve, as using classes would be much better. I plan to do that in case nobody can find a solution, and hope that when it is re-written, this issue does not persist.
The main problem that you are facing is that you receive floating point numbers from your calculations but you can only use integers for pixels. In the following I will show you where you fail and the quickest way to solve the issue.
First your goal is to have connected lines and you calculate the points here:
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
myCanvas.coords(lines[i], x, y, endCoord[i][0], endCoord[i][1])
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
when you add the following code into this function you see that it fails there.
if i != 0:
print(i,x,y)
print(i,endCoord[i-1][0], endCoord[i-1][1])
Because x and y should always match with the last point (end of the previous line) that will be endCoord[i-1][0] and endCoord[i-1][1].
to solve your problem I simply skipt the match for the sarting point of the follow up lines and took the coordinates of the previous line with the following alternated function:
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
if i == 0:
points = x, y, endCoord[i][0], endCoord[i][1]
else:
points = endCoord[i-1][0], endCoord[i-1][1], endCoord[i][0], endCoord[i][1]
myCanvas.coords(lines[i], *points)
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
Additional proposals are:
don't use wildcard imports
import just what you really use in the code random isnt used in your example
the use of global in the global namespace is useless
create functions to avoid repetitive code
def listinpt_times_circles(inpt):
return [inpt]*CIRCLES
x_list = listinpt_times_circles(0)
y_list = listinpt_times_circles(0)
center_x_list = listinpt_times_circles(0)
center_x_list.insert(0,300)
center_y_list = listinpt_times_circles(0)
center_y_list.insert(0,300)
use .after(ms,func,*args) instead of a interrupting while loop and blocking call time.sleep
def animate():
global count,con,lastX,lastY
for i in range(0, len(lines)):
update(i, centerXList[i], centerYList[i])
for i in range(1, len(lines)):
updateCircle(i)
if count >= 8:
lineBetweenTwoPoints(lastX, lastY, endCoord[i][0], endCoord[i][1])
if count % 6 == 0 and con<550:
lineForWave(wavePoints[-7],wavePoints[-5],wavePoints[-3],wavePoints[-1], con)
con += 6
wavePoints.append(endCoord[i][1])
myCanvas.update_idletasks()
lastX = endCoord[i][0]
lastY = endCoord[i][1]
if count != 108:
count += 1
else:
count = 8
root.after(10,animate)
animate()
root.mainloop()
read the PEP 8 -- Style Guide for Python
use intuitive variable names to make your code easier to read for others and yourself in the future
list_of_radii = [200] #instead of rList
as said pixels will be expressed with integers not with floating point numbers
myCanvas.create_line(0, 300, 600, 300, fill="red", width = 1) #0.5 has no effect compare 0.1 to 1
using classes and a canvas for each animation will become handy if you want to show more cycles
dont use tkinters update method
As #Thingamabobs said, the main reason for the misalignment is that pixel coordinates work with integer values. I got excited about your project and decided to make an example using matplotlib, this way I do not have to work with integer values for the coordinates. The example was made to work with any function, I implemented samples with sine, square and sawtooth functions.
I also tried to follow some good practices for naming, type annotations and so on, I hope this helps you
from numbers import Complex
from typing import Callable, Iterable, List
import matplotlib.pyplot as plt
import numpy as np
def fourier_series_coeff_numpy(f: Callable, T: float, N: int) -> List[Complex]:
"""Get the coefficients of the Fourier series of a function.
Args:
f (Callable): function to get the Fourier series coefficients of.
T (float): period of the function.
N (int): number of coefficients to get.
Returns:
List[Complex]: list of coefficients of the Fourier series.
"""
f_sample = 2 * N
t, dt = np.linspace(0, T, f_sample + 2, endpoint=False, retstep=True)
y = np.fft.fft(f(t)) / t.size
return y
def evaluate_fourier_series(coeffs: List[Complex], ang: float, period: float) -> List[Complex]:
"""Evaluate a Fourier series at a given angle.
Args:
coeffs (List[Complex]): list of coefficients of the Fourier series.
ang (float): angle to evaluate the Fourier series at.
period (float): period of the Fourier series.
Returns:
List[Complex]: list of complex numbers representing the Fourier series.
"""
N = np.fft.fftfreq(len(coeffs), d=1/len(coeffs))
N = filter(lambda x: x >= 0, N)
y = 0
radius = []
for n, c in zip(N, coeffs):
r = 2 * c * np.exp(1j * n * ang / period)
y += r
radius.append(r)
return radius
def square_function_factory(period: float):
"""Builds a square function with given period.
Args:
period (float): period of the square function.
"""
def f(t):
if isinstance(t, Iterable):
return [1.0 if x % period < period / 2 else -1.0 for x in t]
elif isinstance(t, float):
return 1.0 if t % period < period / 2 else -1.0
return f
def saw_tooth_function_factory(period: float):
"""Builds a saw-tooth function with given period.
Args:
period (float): period of the saw-tooth function.
"""
def f(t):
if isinstance(t, Iterable):
return [1.0 - 2 * (x % period / period) for x in t]
elif isinstance(t, float):
return 1.0 - 2 * (t % period / period)
return f
def main():
PERIOD = 1
GRAPH_RANGE = 3.0
N_COEFFS = 30
f = square_function_factory(PERIOD)
# f = lambda t: np.sin(2 * np.pi * t / PERIOD)
# f = saw_tooth_function_factory(PERIOD)
coeffs = fourier_series_coeff_numpy(f, 1, N_COEFFS)
radius = evaluate_fourier_series(coeffs, 0, 1)
fig, axs = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=(10, 5))
ang_cum = []
amp_cum = []
for ang in np.linspace(0, 2*np.pi * PERIOD * 3, 200):
radius = evaluate_fourier_series(coeffs, ang, 1)
x = np.cumsum([x.imag for x in radius])
y = np.cumsum([x.real for x in radius])
x = np.insert(x, 0, 0)
y = np.insert(y, 0, 0)
axs[0].plot(x, y)
axs[0].set_ylim(-GRAPH_RANGE, GRAPH_RANGE)
axs[0].set_xlim(-GRAPH_RANGE, GRAPH_RANGE)
ang_cum.append(ang)
amp_cum.append(y[-1])
axs[1].plot(ang_cum, amp_cum)
axs[0].axhline(y=y[-1],
xmin=x[-1] / (2 * GRAPH_RANGE) + 0.5,
xmax=1.2,
c="black",
linewidth=1,
zorder=0,
clip_on=False)
min_x, max_x = axs[1].get_xlim()
line_end_x = (ang - min_x) / (max_x - min_x)
axs[1].axhline(y=y[-1],
xmin=-0.2,
xmax=line_end_x,
c="black",
linewidth=1,
zorder=0,
clip_on=False)
plt.pause(0.01)
axs[0].clear()
axs[1].clear()
if __name__ == '__main__':
main()

How to vectorize a nested "for" loop with multiple "if" statements using Numpy?

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")

Find the intersection and union of two rectangles

I need to find the area of ​​intersection and union of two rectangles, as well as the ratio of the intersection area to the union area. What is the best way to do this?
Picture
def intersection_over_union(a, b):
ax1 = a['left']
ay1 = a['top']
aw = a['width']
ah = a['height']
ax2 = ax1 + aw
ay2 = ay1 + ah
bx1 = b['left']
by1 = b['top']
bw = b['width']
bh = b['height']
bx2 = bx1 + bw
by2 = by1 + bh
delta_x = max(ax1, bx1) - min(ax2, bx2)
delta_y = max(ay1, by1) - min(ay2, by2)
overlap = delta_x * delta_y
union = aw * ah + bw * bh - overlap
print(overlap, union)
return overlap / union
Assuming you are dealing with AABB (axis aligned bounding box) this little class defines everything you need:
class Rect:
def __init__(self, x, y, w, h):
self.x = x
self.y = y
self.w = w
self.h = h
def bottom(self):
return self.y + self.h
def right(self):
return self.x + self.w
def area(self):
return self.w * self.h
def union(self, b):
posX = min(self.x, b.x)
posY = min(self.y, b.y)
return Rect(posX, posY, max(self.right(), b.right()) - posX, max(self.bottom(), b.bottom()) - posY)
def intersection(self, b):
posX = max(self.x, b.x)
posY = max(self.y, b.y)
candidate = Rect(posX, posY, min(self.right(), b.right()) - posX, min(self.bottom(), b.bottom()) - posY)
if candidate.w > 0 and candidate.h > 0:
return candidate
return Rect(0, 0, 0, 0)
def ratio(self, b):
return self.intersection(b).area() / self.union(b).area()
if __name__ == "__main__":
assert Rect(1, 1, 1, 1).ratio(Rect(1, 1, 1, 1)) == 1
assert round(Rect(1, 1, 2, 2).ratio(Rect(2, 2, 2, 2)), 4) == 0.1111
One way I believe you could do this (although I am not sure it is the best solution as you've asked for), is to develop a function that creates a rectangle in a plane, and stores all of its corners' coordinates in a list.
Every time you create a rectangle (let's say you create it from the bottom left, increasing its x and y coordinates as you go), check for each coordinate if the x coordinate is within the other rectangle's x-range i.e. x_min < x < x_max. If so, check the y-coordinate. If the coordinate is within the x and y ranges of another rectangle, it's part of the intersection.
Repeat for all coordinates as you create them and save the intersection coordinates, and find the intersection's corners. With that, you can calculate the area of intersection and union between the two triangles, and their ratio to one another.

How did we give these values of the eight directions in A star algorithm?

I'm trying to implement my own code based on this code. While i'm reading this code, I got confused here about this part. The part of the below function:
def get_motion_model():
# dx, dy, cost
motion = [[1, 0, 1],
[0, 1, 1],
[-1, 0, 1],
[0, -1, 1],
[-1, -1, math.sqrt(2)],
[-1, 1, math.sqrt(2)],
[1, -1, math.sqrt(2)],
[1, 1, math.sqrt(2)]]
return motion
How did the author give these values?. I know when we start checking the 8 neighbors, but what are these numbers?. I mean these [1, 0, 1] and so on. And If I want to generate for 3D what are they going to be?.
This is below A star algorithm Python code:
"""
A* grid based planning
See Wikipedia article (https://en.wikipedia.org/wiki/A*_search_algorithm)
"""
import matplotlib.pyplot as plt
import math
show_animation = True
class Node:
def __init__(self, x, y, cost, pind):
self.x = x
self.y = y
self.cost = cost
self.pind = pind
def __str__(self):
return str(self.x) + "," + str(self.y) + "," + str(self.cost) + "," + str(self.pind)
def calc_final_path(ngoal, closedset, reso):
# generate final course
rx, ry = [ngoal.x * reso], [ngoal.y * reso]
pind = ngoal.pind
while pind != -1:
n = closedset[pind]
rx.append(n.x * reso)
ry.append(n.y * reso)
pind = n.pind
return rx, ry
def a_star_planning(sx, sy, gx, gy, ox, oy, reso, rr):
"""
gx: goal x position [m]
gx: goal x position [m]
ox: x position list of Obstacles [m]
oy: y position list of Obstacles [m]
reso: grid resolution [m]
rr: robot radius[m]
"""
nstart = Node(round(sx / reso), round(sy / reso), 0.0, -1)
ngoal = Node(round(gx / reso), round(gy / reso), 0.0, -1)
ox = [iox / reso for iox in ox]
oy = [ioy / reso for ioy in oy]
obmap, minx, miny, maxx, maxy, xw, yw = calc_obstacle_map(ox, oy, reso, rr)
motion = get_motion_model()
openset, closedset = dict(), dict()
openset[calc_index(nstart, xw, minx, miny)] = nstart
while 1:
c_id = min(
openset, key=lambda o: openset[o].cost + calc_heuristic(ngoal, openset[o]))
current = openset[c_id]
# show graph
if show_animation: # pragma: no cover
plt.plot(current.x * reso, current.y * reso, "xc")
if len(closedset.keys()) % 10 == 0:
plt.pause(0.001)
if current.x == ngoal.x and current.y == ngoal.y:
print("Find goal")
ngoal.pind = current.pind
ngoal.cost = current.cost
break
# Remove the item from the open set
del openset[c_id]
# Add it to the closed set
closedset[c_id] = current
# expand search grid based on motion model
for i, _ in enumerate(motion):
node = Node(current.x + motion[i][0],
current.y + motion[i][1],
current.cost + motion[i][2], c_id)
n_id = calc_index(node, xw, minx, miny)
if n_id in closedset:
continue
if not verify_node(node, obmap, minx, miny, maxx, maxy):
continue
if n_id not in openset:
openset[n_id] = node # Discover a new node
else:
if openset[n_id].cost >= node.cost:
# This path is the best until now. record it!
openset[n_id] = node
rx, ry = calc_final_path(ngoal, closedset, reso)
return rx, ry
def calc_heuristic(n1, n2):
w = 1.0 # weight of heuristic
d = w * math.sqrt((n1.x - n2.x)**2 + (n1.y - n2.y)**2)
return d
def verify_node(node, obmap, minx, miny, maxx, maxy):
if node.x < minx:
return False
elif node.y < miny:
return False
elif node.x >= maxx:
return False
elif node.y >= maxy:
return False
if obmap[node.x][node.y]:
return False
return True
def calc_obstacle_map(ox, oy, reso, vr):
minx = round(min(ox))
miny = round(min(oy))
maxx = round(max(ox))
maxy = round(max(oy))
# print("minx:", minx)
# print("miny:", miny)
# print("maxx:", maxx)
# print("maxy:", maxy)
xwidth = round(maxx - minx)
ywidth = round(maxy - miny)
# print("xwidth:", xwidth)
# print("ywidth:", ywidth)
# obstacle map generation
obmap = [[False for i in range(ywidth)] for i in range(xwidth)]
for ix in range(xwidth):
x = ix + minx
for iy in range(ywidth):
y = iy + miny
# print(x, y)
for iox, ioy in zip(ox, oy):
d = math.sqrt((iox - x)**2 + (ioy - y)**2)
if d <= vr / reso:
obmap[ix][iy] = True
break
return obmap, minx, miny, maxx, maxy, xwidth, ywidth
def calc_index(node, xwidth, xmin, ymin):
return (node.y - ymin) * xwidth + (node.x - xmin)
def get_motion_model():
# dx, dy, cost
motion = [[1, 0, 1],
[0, 1, 1],
[-1, 0, 1],
[0, -1, 1],
[-1, -1, math.sqrt(2)],
[-1, 1, math.sqrt(2)],
[1, -1, math.sqrt(2)],
[1, 1, math.sqrt(2)]]
return motion
def main():
print(__file__ + " start!!")
# start and goal position
sx = 10.0 # [m]
sy = 10.0 # [m]
gx = 50.0 # [m]
gy = 50.0 # [m]
grid_size = 1.0 # [m]
robot_size = 1.0 # [m]
ox, oy = [], []
for i in range(60):
ox.append(i)
oy.append(0.0)
for i in range(60):
ox.append(60.0)
oy.append(i)
for i in range(61):
ox.append(i)
oy.append(60.0)
for i in range(61):
ox.append(0.0)
oy.append(i)
for i in range(40):
ox.append(20.0)
oy.append(i)
for i in range(40):
ox.append(40.0)
oy.append(60.0 - i)
if show_animation: # pragma: no cover
plt.plot(ox, oy, ".k")
plt.plot(sx, sy, "xr")
plt.plot(gx, gy, "xb")
plt.grid(True)
plt.axis("equal")
rx, ry = a_star_planning(sx, sy, gx, gy, ox, oy, grid_size, robot_size)
if show_animation: # pragma: no cover
plt.plot(rx, ry, "-r")
plt.show()
if __name__ == '__main__':
main()
The comment just above those lines gives the clue:
# dx, dy, cost
The cost here apparently corresponds to the Euclidean distance, i.e. the square root of dx²+dy².
Each triplet represent one direction:
The change on the x-coordinate: can be -1 (going left), 0 (not moving along the X axis), (going right)
The simultaneous change on the y-coordinate: can be -1 (going up), 0 (not moving along the Y axis), 1 (going down)
The distance represented by this move: if one of the two above numbers is 0, then the direction is either up, down, left, right, and the distance is exactly 1. If however both numbers are non-zero, then we have diagonal move (up-left, up-right, down-left, down-right) and in that case the distance is the square root of 2.
motion could also have been defined as:
motion = [[
[dx, dy, math.sqrt(dx*dx+dy*dy)]
for dx in ([-1,0,1] if dy else [-1, 1])
] for dy in [-1,0,1]]
Note how this Euclidean distance formula also appears later in the code, e.g. for calculating the heuristic cost for the remainder of the path as the distance as-the-crow-flies between the current and target cell.
If you want to go for 3D, then you would have 4 values per direction: dx, dy, dz and the distance would be the square root of dx²+dy²+dz²:
1 if only one of those three is 1
square root of 2 if two of those three are 1
square root of 3 if all three are non-zero

Calculate the area of intersection of two rotated rectangles in python

I have two 2D rotated rectangles, defined as an (center x,center y, height, width) and an angle of rotation (0-360°). How would I calculate the area of intersection of these two rotated rectangles.
Such tasks are solved using computational geometry packages, e.g. Shapely:
import shapely.geometry
import shapely.affinity
class RotatedRect:
def __init__(self, cx, cy, w, h, angle):
self.cx = cx
self.cy = cy
self.w = w
self.h = h
self.angle = angle
def get_contour(self):
w = self.w
h = self.h
c = shapely.geometry.box(-w/2.0, -h/2.0, w/2.0, h/2.0)
rc = shapely.affinity.rotate(c, self.angle)
return shapely.affinity.translate(rc, self.cx, self.cy)
def intersection(self, other):
return self.get_contour().intersection(other.get_contour())
r1 = RotatedRect(10, 15, 15, 10, 30)
r2 = RotatedRect(15, 15, 20, 10, 0)
from matplotlib import pyplot
from descartes import PolygonPatch
fig = pyplot.figure(1, figsize=(10, 4))
ax = fig.add_subplot(121)
ax.set_xlim(0, 30)
ax.set_ylim(0, 30)
ax.add_patch(PolygonPatch(r1.get_contour(), fc='#990000', alpha=0.7))
ax.add_patch(PolygonPatch(r2.get_contour(), fc='#000099', alpha=0.7))
ax.add_patch(PolygonPatch(r1.intersection(r2), fc='#009900', alpha=1))
pyplot.show()
Here is a solution that does not use any libraries outside of Python's standard library.
Determining the area of the intersection of two rectangles can be divided in two subproblems:
Finding the intersection polygon, if any;
Determine the area of the intersection polygon.
Both problems are relatively easy when you work with the
vertices (corners) of the rectangles. So first you have to determine
these vertices. Assuming the coordinate origin is in the center
of the rectangle, the vertices are,
starting from the lower left in a counter-clockwise direction:
(-w/2, -h/2), (w/2, -h/2), (w/2, h/2), and (-w/2, h/2).
Rotating this over the angle a, and translating them
to the proper position of the rectangle's center, these become:
(cx + (-w/2)cos(a) - (-h/2)sin(a), cy + (-w/2)sin(a) + (-h/2)cos(a)), and similar for the other corner points.
A simple way to determine the intersection polygon is the following:
you start with one rectangle as the candidate intersection polygon.
Then you apply the process of sequential cutting (as described here.
In short: you take each edges of the second rectangle in turn,
and remove all parts from the candidate intersection polygon that are on the "outer" half plane defined by the edge
(extended in both directions).
Doing this for all edges leaves the candidate intersection polygon
with only the parts that are inside the second rectangle or on its boundary.
The area of the resulting polygon (defined by a series of vertices) can be calculated
from the coordinates of the vertices.
You sum the cross products of the vertices
of each edge (again in counter-clockwise order),
and divide that by two. See e.g. www.mathopenref.com/coordpolygonarea.html
Enough theory and explanation. Here is the code:
from math import pi, cos, sin
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, v):
if not isinstance(v, Vector):
return NotImplemented
return Vector(self.x + v.x, self.y + v.y)
def __sub__(self, v):
if not isinstance(v, Vector):
return NotImplemented
return Vector(self.x - v.x, self.y - v.y)
def cross(self, v):
if not isinstance(v, Vector):
return NotImplemented
return self.x*v.y - self.y*v.x
class Line:
# ax + by + c = 0
def __init__(self, v1, v2):
self.a = v2.y - v1.y
self.b = v1.x - v2.x
self.c = v2.cross(v1)
def __call__(self, p):
return self.a*p.x + self.b*p.y + self.c
def intersection(self, other):
# See e.g. https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Using_homogeneous_coordinates
if not isinstance(other, Line):
return NotImplemented
w = self.a*other.b - self.b*other.a
return Vector(
(self.b*other.c - self.c*other.b)/w,
(self.c*other.a - self.a*other.c)/w
)
def rectangle_vertices(cx, cy, w, h, r):
angle = pi*r/180
dx = w/2
dy = h/2
dxcos = dx*cos(angle)
dxsin = dx*sin(angle)
dycos = dy*cos(angle)
dysin = dy*sin(angle)
return (
Vector(cx, cy) + Vector(-dxcos - -dysin, -dxsin + -dycos),
Vector(cx, cy) + Vector( dxcos - -dysin, dxsin + -dycos),
Vector(cx, cy) + Vector( dxcos - dysin, dxsin + dycos),
Vector(cx, cy) + Vector(-dxcos - dysin, -dxsin + dycos)
)
def intersection_area(r1, r2):
# r1 and r2 are in (center, width, height, rotation) representation
# First convert these into a sequence of vertices
rect1 = rectangle_vertices(*r1)
rect2 = rectangle_vertices(*r2)
# Use the vertices of the first rectangle as
# starting vertices of the intersection polygon.
intersection = rect1
# Loop over the edges of the second rectangle
for p, q in zip(rect2, rect2[1:] + rect2[:1]):
if len(intersection) <= 2:
break # No intersection
line = Line(p, q)
# Any point p with line(p) <= 0 is on the "inside" (or on the boundary),
# any point p with line(p) > 0 is on the "outside".
# Loop over the edges of the intersection polygon,
# and determine which part is inside and which is outside.
new_intersection = []
line_values = [line(t) for t in intersection]
for s, t, s_value, t_value in zip(
intersection, intersection[1:] + intersection[:1],
line_values, line_values[1:] + line_values[:1]):
if s_value <= 0:
new_intersection.append(s)
if s_value * t_value < 0:
# Points are on opposite sides.
# Add the intersection of the lines to new_intersection.
intersection_point = line.intersection(Line(s, t))
new_intersection.append(intersection_point)
intersection = new_intersection
# Calculate area
if len(intersection) <= 2:
return 0
return 0.5 * sum(p.x*q.y - p.y*q.x for p, q in
zip(intersection, intersection[1:] + intersection[:1]))
if __name__ == '__main__':
r1 = (10, 15, 15, 10, 30)
r2 = (15, 15, 20, 10, 0)
print(intersection_area(r1, r2))
intersection, pnt = contourIntersection(rect1, rect2)
After looking at the possible duplicate page for this problem I couldn't find a completed answer for python so here is my solution using masking. This function will work with complex shapes on any angle, not just rectangles
You pass in the 2 contours of your rotated rectangles as parameters and it returns 'None' if no intersection occurs or an image of the intersected area and the left/top position of that image in relation to the original image the contours were taken from
Uses python, cv2 and numpy
import cv2
import math
import numpy as np
def contourIntersection(con1, con2, showContours=False):
# skip if no bounding rect intersection
leftmost1 = tuple(con1[con1[:, :, 0].argmin()][0])
topmost1 = tuple(con1[con1[:, :, 1].argmin()][0])
leftmost2 = tuple(con2[con2[:, :, 0].argmin()][0])
topmost2 = tuple(con2[con2[:, :, 1].argmin()][0])
rightmost1 = tuple(con1[con1[:, :, 0].argmax()][0])
bottommost1 = tuple(con1[con1[:, :, 1].argmax()][0])
rightmost2 = tuple(con2[con2[:, :, 0].argmax()][0])
bottommost2 = tuple(con2[con2[:, :, 1].argmax()][0])
if rightmost1[0] < leftmost2[0] or rightmost2[0] < leftmost1[0] or bottommost1[1] < topmost2[1] or bottommost2[1] < topmost1[1]:
return None, None
# reset top / left to 0
left = leftmost1[0] if leftmost1[0] < leftmost2[0] else leftmost2[0]
top = topmost1[1] if topmost1[1] < topmost2[1] else topmost2[1]
newCon1 = []
for pnt in con1:
newLeft = pnt[0][0] - left
newTop = pnt[0][1] - top
newCon1.append([newLeft, newTop])
# next
con1_new = np.array([newCon1], dtype=np.int32)
newCon2 = []
for pnt in con2:
newLeft = pnt[0][0] - left
newTop = pnt[0][1] - top
newCon2.append([newLeft, newTop])
# next
con2_new = np.array([newCon2], dtype=np.int32)
# width / height
right1 = rightmost1[0] - left
bottom1 = bottommost1[1] - top
right2 = rightmost2[0] - left
bottom2 = bottommost2[1] - top
width = right1 if right1 > right2 else right2
height = bottom1 if bottom1 > bottom2 else bottom2
# create images
img1 = np.zeros([height, width], np.uint8)
cv2.drawContours(img1, con1_new, -1, (255, 255, 255), -1)
img2 = np.zeros([height, width], np.uint8)
cv2.drawContours(img2, con2_new, -1, (255, 255, 255), -1)
# mask images together using AND
imgIntersection = cv2.bitwise_and(img1, img2)
if showContours:
img1[img1 > 254] = 128
img2[img2 > 254] = 100
imgAll = cv2.bitwise_or(img1, img2)
cv2.imshow('Merged Images', imgAll)
# end if
if not imgIntersection.sum():
return None, None
# trim
while not imgIntersection[0].sum():
imgIntersection = np.delete(imgIntersection, (0), axis=0)
top += 1
while not imgIntersection[-1].sum():
imgIntersection = np.delete(imgIntersection, (-1), axis=0)
while not imgIntersection[:, 0].sum():
imgIntersection = np.delete(imgIntersection, (0), axis=1)
left += 1
while not imgIntersection[:, -1].sum():
imgIntersection = np.delete(imgIntersection, (-1), axis=1)
return imgIntersection, (left, top)
# end function
To complete the answer so you can use the above function with the values of CenterX, CenterY, Width, Height and Angle of 2 rotated rectangles I have added the below functions. Simple change the Rect1 and Rect2 properties at the bottom of the code to your own
def pixelsBetweenPoints(xy1, xy2):
X = abs(xy1[0] - xy2[0])
Y = abs(xy1[1] - xy2[1])
return int(math.sqrt((X ** 2) + (Y ** 2)))
# end function
def rotatePoint(angle, centerPoint, dist):
xRatio = math.cos(math.radians(angle))
yRatio = math.sin(math.radians(angle))
xPotted = int(centerPoint[0] + (dist * xRatio))
yPlotted = int(centerPoint[1] + (dist * yRatio))
newPoint = [xPotted, yPlotted]
return newPoint
# end function
def angleBetweenPoints(pnt1, pnt2):
A_B = pixelsBetweenPoints(pnt1, pnt2)
pnt3 = (pnt1[0] + A_B, pnt1[1])
C = pixelsBetweenPoints(pnt2, pnt3)
angle = math.degrees(math.acos((A_B * A_B + A_B * A_B - C * C) / (2.0 * A_B * A_B)))
# reverse if above horizon
if pnt2[1] < pnt1[1]:
angle = angle * -1
# end if
return angle
# end function
def rotateRectContour(xCenter, yCenter, height, width, angle):
# calc positions
top = int(yCenter - (height / 2))
left = int(xCenter - (width / 2))
right = left + width
rightTop = (right, top)
centerPoint = (xCenter, yCenter)
# new right / top point
rectAngle = angleBetweenPoints(centerPoint, rightTop)
angleRightTop = angle + rectAngle
angleRightBottom = angle + 180 - rectAngle
angleLeftBottom = angle + 180 + rectAngle
angleLeftTop = angle - rectAngle
distance = pixelsBetweenPoints(centerPoint, rightTop)
rightTop_new = rotatePoint(angleRightTop, centerPoint, distance)
rightBottom_new = rotatePoint(angleRightBottom, centerPoint, distance)
leftBottom_new = rotatePoint(angleLeftBottom, centerPoint, distance)
leftTop_new = rotatePoint(angleLeftTop, centerPoint, distance)
contourList = [[leftTop_new], [rightTop_new], [rightBottom_new], [leftBottom_new]]
contour = np.array(contourList, dtype=np.int32)
return contour
# end function
# rect1
xCenter_1 = 40
yCenter_1 = 20
height_1 = 200
width_1 = 80
angle_1 = 45
rect1 = rotateRectContour(xCenter_1, yCenter_1, height_1, width_1, angle_1)
# rect2
xCenter_2 = 80
yCenter_2 = 25
height_2 = 180
width_2 = 50
angle_2 = 123
rect2 = rotateRectContour(xCenter_2, yCenter_2, height_2, width_2, angle_2)
intersection, pnt = contourIntersection(rect1, rect2, True)
if intersection is None:
print('No intersection')
else:
print('Area of intersection = ' + str(int(intersection.sum() / 255)))
cv2.imshow('Intersection', intersection)
# end if
cv2.waitKey(0)

Categories