Draw hough transform lines over an image - python

I am trying to implement Hough line transform by myself, but I just couldn't do the last step which is drawing high voted thetas/rhos values. I tried to do its math on my own but It remained to get wrong outputs. When I checked some implementations of others, they were always using this approach to transform from Polar to cartesian coordinates in order to find two points.
for r,theta in lines[0]:
# Stores the value of cos(theta) in a
a = np.cos(theta)
# Stores the value of sin(theta) in b
b = np.sin(theta)
# x0 stores the value rcos(theta)
x0 = a*r
# y0 stores the value rsin(theta)
y0 = b*r
# x1 stores the rounded off value of (rcos(theta)-1000sin(theta))
x1 = int(x0 + 1000*(-b))
# y1 stores the rounded off value of (rsin(theta)+1000cos(theta))
y1 = int(y0 + 1000*(a))
# x2 stores the rounded off value of (rcos(theta)+1000sin(theta))
x2 = int(x0 - 1000*(-b))
# y2 stores the rounded off value of (rsin(theta)-1000cos(theta))
y2 = int(y0 - 1000*(a))
# cv2.line draws a line in img from the point(x1,y1) to (x2,y2).
# (0,0,255) denotes the colour of the line to be
#drawn. In this case, it is red.
cv2.line(img,(x1,y1), (x2,y2), (0,0,255),2)
The previous code from GeeksForGeeks.
What I didn't get are those equations x1 = int(x0 + 1000*(-b)) & y2 = int(y0 - 1000*(a)). By translating these equations as a mathematic form : x1 = r cos(theta) + r (-sin(theta))||
y1 = r sin(theta) + r (cos(theta))
These two equations are just strange to me. 'r cos(theta)' is normal as we transfer from polar to cartesian. However, the next part isn't clear. Could anyone explain the original math behind it?

(r cos, r sin) is the point closest to the origin on the line. It is not a line equation. To draw a line, u need 2 points. Here, he just moved along line 1000 and -1000 to get two points that are guaranteed to be outside a medium sized image
There are many ways to get line equation. Easiest is:
r = x cos + y sin
If x1 = x - t sin
y1 = (1 / sin) (r - x cos + t sin cos)
= y + t cos
He used t=+/-1000 for image size

Related

Common tangent using python

I am trying to find a common tangent to two curves using python but I am not able to solve it.
The equations to the two curves are complicated that involve logarithms.
Is there a way in python to compute the x coordinates of a tangent that is common to both the curves in general. If I have 2 curves f(x) and g(x), I want to find the x-coordinates x1 and x2 on a common tangent where x1 lies on f(x) and x2 on g(x). I am trying f'(x1) = g'(x2) and f'(x1) = f(x1) - f(x2) / (x1 - x2) to get x1 and x2 but I am not able to get values using nonlinsolve as the equations are too complicated.
I want to just find x-coordinates of the common tangent
Can anyone suggest a better way?
import numpy as np
import sympy
from sympy import *
from matplotlib import pyplot as plt
x = symbols('x')
a, b, c, d, e, f = -99322.50019502985, -86864.87072433547, -96876.05627516498, -89703.35055202093, -3390.863799999999, -20942.518
def func(x):
y1_1 = a - a*x + b*x
y1_2 = c - c*x + d*x
c1 = (1 - x) ** (1 - x)
c2 = (x ** x)
y2 = 12471 * (sympy.log((c1*c2)))
y3 = 2*f*x**3 - x**2*(e + 3*f) + x*(e + f)
eqn1 = y1_1 + y2 + y3
eqn2 = y1_2 + y2 + y3
return eqn1, eqn2
val = np.linspace(0, 1)
f1 = sympy.lambdify(x, func(x)[0])(val)
f2 = sympy.lambdify(x, func(x)[1])(val)
plt.plot(val, f1)
plt.plot(val, f2)
plt.show()
I am trying this
x1, x2 = sympy.symbols('x1 x2')
fun1 = func(x1)[0]
fun2 = func(x2)[0]
diff1 = diff(fun1,x1)
diff2 = diff(fun2,x2)
eq1 = diff1 - diff2
eq2 = diff1 - ((fun1 - fun2) / (x1 - x2))
sol = nonlinsolve([eq1, eq2], [x1, x2])
the first thing that needs to be done is to reduce the formulas
for example the first formula is actually this:
formula = x*(1 - x)*(17551.6542 - 41885.036*x) + x*(1 - x)*(41885.036*x - 24333.3818) + 12457.6294706944*x + log((x/(1 - x))**(12000*x)*(1 - x)**12000) - 99322.5001950298
formula = (x-x^2)*(17551.6542 - 41885.036*x) + (x-x^2)*(41885.036*x - 24333.3818) + 12457.6294706944*x + log((x/(1 - x))**(12000*x)*(1 - x)**12000) - 99322.5001950298
# constants
a = 41885.036
b = 17551.6542
c = 24333.3818
d = 12457.6294706944
e = 99322.5001950298
f = 12000
formula = (x-x^2)*(b - a*x) + (x-x^2)*(a*x - c) + d*x + log((x/(1 - x))**(f*x)*(1 - x)**f) - e
formula = (ax^3 -bx^2 + bx - ax^2) + (x-x^2)*(a*x - c) + d*x + log((x/(1 - x))**(f*x)*(1 - x)**f) - e
formula = ax^3 -bx^2 + bx - ax^2 -ax^3 + ax^2 + cx^2 -cx + d*x + log((x/(1 - x))**(f*x)*(1 - x)**f) - e
# collect x terms by power (note how the x^3 tern drops out, so its easier).
formula = (c-b)*x^2 + (b-c+d)*x + log((x/(1 - x))**(f*x)*(1 - x)**f) - e
which is much cleaner and is a quadratic with a log term.
i expect that you can do some work on the log term too, but this is an excercise for the original poster.
likewise the second formula can be reduced in the same way, which is again an excercise for the original poster.
From this, both equations need to be differentiated with respect to x to find the tangent. Then set both formulas to be equal to each other (for a common tangent).
This would completely solve the question.
I actually wonder if this is a python question at all or actually a pure maths question.....
The important point to note is that, since the derivatives are monotonic, for any value of derivative of fun1, there is a solution for fun2. This can be easily seen if you plot both derivatives.
Thus, we want a function that, given an x1, returns an x2 that matches it. I'll use numerical solution because the system is too cumbersome for numerical solution.
import scipy.optimize
def find_equal_value(f1, f2, x, x1):
goal = f1.subs(x, x1)
to_solve = sympy.lambdify(x, (f2 - goal)**2) # Quadratic functions tend to be better behaved, and the result is the same
sol = scipy.optimize.fmin(func=to_solve, x0=x1, ftol=1e-8, disp=False) # The value for f1 is a good starting guess
return sol[0]
I used fmin as the solver above because it worked and I knew how to use it by heart. Maybe root_scalar can give better results.
Using the function above, let's get some pairs (x1, x2) where the derivatives are equal:
df1 = sympy.diff(func(x)[0])
df2 = sympy.diff(func(x)[1])
x1 = 0.25236537 # Close to the zero derivative
x2 = find_equal_value(df1, df2, x, x1)
print(f'Derivative of f1 in x1: {df1.subs(x, x1)}')
print(f'Derivative of f2 in x2: {df2.subs(x, x2)}')
print(f'Error: {df1.subs(x, x1) - df2.subs(x, x2)}')
This results is:
Derivative of f1 in x1: 0.0000768765858083498
Derivative of f2 in x2: 0.0000681969431752805
Error: 0.00000867964263306931
If you want a x2 for several x1s (beware that in some cases the solver hits a value where the logs are invalid. Always check your result for validity):
x1s = np.linspace(0.2, 0.8, 50)
x2s = [find_equal_value(df1, df2, x, x1) for x1 in x1s]
plt.plot(x1s, x2s); plt.grid(); plt.show()

Python Hough Transform implementation issue

There are some implementations of the Hough transform on the internet, only I don't understand a part:
When we find our couple (rho, theta), we have to go back to Cartesian.
To draw the lines here is what is used:
for i in range (len(Liste_p)):
a = cos(Liste_theta[i])
b = sin(Liste_theta[i])
x0 = a*Liste_p[i]
y0 = b*Liste_p[i]
x1 = int(x0 + 1000*(-b)) # x1 stores the rounded off value of (rcos(theta)-1000sin(theta))
y1 = int(y0 + 1000*(a)) # y1 stores the rounded off value of (rsin(theta)+1000cos(theta))
x2 = int(x0 - 1000*(-b)) # x2 stores the rounded off value of (rcos(theta)+1000sin(theta))
y2 = int(y0 - 1000*(a)) # y2 stores the rounded off value of (rsin(theta)-1000cos(theta))
plot([x1,x2],[y1,y2],color="r")
But what is multiplication by 1000?
In fact I do not understand the link with the line that we are supposed to draw

intersection point between 2D arrays

I have two arrays as follows:
X1 = np.array([[x11, x12, x13 ... x1n],[y11, y12, y13, ... , y1n]])
X2 = np.array([[x21, x22, x23 ... x2n],[y21, y22, y23, ... , y2n]])
I would like to basically conceptualize these as piecewise linear functions and come up with an intersection point intercept:
intercept = (x_int, y_int)
Every search I do regarding array intersection gives entirely unrelated results, since the intersection of two arrays also has the meaning of finding elements common to both arrays (rather than an intersection point).
I also found this interesting post, but it seems too complex for my application. If I had to implement this, I think I could since it would involve repeated calculations of line equations and intersections of points between line equations. However I'm first trying to check if some robust implementation already exists in a well-tested library, since my poor attempt might take hours/days to achieve and then not necessarily be applicable for any dataset.
Has this already been implemented in python?
Step 1: work with the union of x1 and x2.
Step 2: linearly interpolate to find y1 and y2 for each point in the union.
Step 3: find where y1 - y2 changes sign.
Step 4: solve the linear equations for the intersection.
import numpy as np
def intersect_piecewise(X1, X2):
x = np.union1d(X1[0], X2[0])
y1 = np.interp(x, X1[0], X1[1])
y2 = np.interp(x, X2[0], X2[1])
dy = y1 - y2
ind = (dy[:-1] * dy[1:] < 0).nonzero()[0]
x1, x2 = x[ind], x[ind+1]
dy1, dy2 = dy[ind], dy[ind+1]
y11, y12 = y1[ind], y1[ind+1]
y21, y22 = y2[ind], y2[ind+1]
x_int = x1 - (x2 - x1) * dy1 / (dy2 - dy1)
y_int = y11 + (y12 - y11) * (x_int - x1) / (x2 - x1)
return x_int, y_int
the equations you need to solve are
(x_int - x1) / (x2 - x1) = (0 - dy1) / (dy2 - dy1)
= (y_int - y11) / (y12 - y11) = (y_int - y21) / (y22 - y21)
Edit: Let's try it out
import matplotlib.pyplot as plt
x = np.linspace(-2, 2, 17)
X1 = np.stack((x[::2], x[::2]**2))
X2 = np.stack((x[1::2], 4 - x[1::2]**2))
x_int, y_int = intersect_piecewise(X1, X2)
plt.plot(X1[0], X1[1], 'bo-', X2[0], X2[1], 'bo-', x_int, y_int, 'rs')

How to generate squares (randomly located, equally sized, randomly rotated) that don't intersect each other?

I've been working on generating a layer of randomly rotated and placed squares on a 1x1 grid. I have been able to generate a single square that is randomly placed and rotated on the grid, but I'm not sure how to improve the code to generate more random squares that do not intersect with each other. Current code seen below:
Example of my One Randomized Square
from math import cos, pi, sin
from random import randint
from matplotlib.mlab import frange
from matplotlib.pyplot import plot, axis, show
def flake_position_layer1(): #Determines the initial position of one corner of the square
x0 = randint(0, 100) / 100
y0 = randint(0, 100) / 100
theta = randint(0, 90) * pi / 180 #Angle of rotation for the square
return x0, y0, theta
def flake_shape(): #generates the other 3 corners of the square
x0, y0, z, theta = flake_position_layer1()
x1 = x0 + (0.1 * cos(theta))
x2 = x1 + (0.1 * cos((90 * pi/180) + theta))
x3 = x2 + (0.1 * cos((180 * pi/180) + theta))
y1 = y0 + (0.1 * sin(theta))
y2 = y1 + (0.1 * sin((90 * pi/180) + theta))
y3 = y2 + (0.1 * sin((180 * pi/180) + theta))
return x0, x1, x2, x3, y0, y1, y2, y3
def display(): #connects the 4 corners on a plot
x0, x1, x2, x3, y0, y1, y2, y3 = flake_shape()
return plot([x0, x1, x2, x3, x0], [y0, y1, y2, y3, y0])
display()
axis([0,1,0,1]) #1x1 grid
show()
I do not have a CS background (I'm an environmental engineering major) and I am extremely inexperienced with coding. Please give me any recommendations that you may have for me to try and tackle this problem with!
Math background
1. Algebra
1st degree function (or [Wikipedia]: Linear function) is a function ([Wikipedia]: function) whose:
Expression can be written as: f(x) = a * x + b (a and b constants, x variable)
Graphical representation is a straight line in the xOy plane ([Wikipedia]: Cartesian coordinate system)
2. Plane Geometry
A plane consists of an (infinite) number of points: let's refer to a point by its coordinates which can be referred to as:
abscissa or horizontal coordinate or (simply) x
ordinate or vertical coordinate or (simply) y
The points in the plane spread across 2 dimensions
In the plane, every point can be uniquely identified by its x and y
Some of the points in the plane might have some common characteristics: e.g. a bunch of points that are on a straight line... a point that is on a straight line satisfies the line straight line equation (which is an expression generally defined as ${function (from previous paragraph) result} = ${value})
So, in short: for a point P0(x0, y0), if y0 == f(x0), the point is located on that straight line (and more: depending on y0 being greater / lower than f(x0), P0 is located above / below the straight line in the xOy plane). Again, I want to state that for non linear functions, y = f(x) still applies (as it's the general equation formula), but other operators (e.g. <, >) don't
! Important Note !: everything discussed here applies to a variety of functions (don't want to say all), but I'm limiting to linear functions, for clarity's sake
A straight line is determined by 2 distinct points (e.g. P0(x0, y0), P1(x1, y1)) - the equation for that straight line would be y = a * x + b (in our example: y = ((y0 - y1) / (x0 - x1)) * x + (y0 - x0 * ((y0 - y1) / (x0 - x1)))); !! Of course it worth mentioning the "vertical" (parallel to Oy) line which is !! not a function !!
Example: having 2 distinct parallel (non Bolyai :) ) lines: f0(x) = a * x + b0 and f1(x) = a * x + b1 (a is the same for both lines - this is the condition for them to be parallel) and an external point P0(x0, y0) (that obviously doesn't belong to any of the lines). How to determine if P0 is between the 2 lines? Well, the point must be above (the lower) one and below the other (the higher one). Translated into math (considering f0 being the lower one):
y0 > f0(x0) (y0 - f0(x0) > 0)
y0 < f1(x0) (y0 - f1(x0) < 0)
From the above observations (and there may be more wisdom), this is the condition that the point coordinates should satisfy: (y0 - f0(x0)) * (y0 - f1(x0)) < 0
Going further: a square consists of 2 sets of parallel lines (its sides); if a point is between each lines pair, then the point is in the square.
code00.py:
#!/usr/bin/env python3
import sys
from random import random, seed
from math import pi, sin, cos, sqrt
import matplotlib.pyplot as plt
pi_2 = pi / 2
MINX = MINY = 0
MAXX = MAXY = 1
DEFAULT_SIDE = 0.1
DEFAULT_SAFETY_MARGIN = DEFAULT_SIDE * sqrt(2)
MAX_SQUARES = 30
__global_generation_counter = 0
def get_func_deg1(p0, p1):
(x0, y0), (x1, y1) = p0, p1
if x0 == x1:
return None
a = (y0 - y1)/(x0 - x1)
b = y0 - x0 * a
return lambda x: a * x + b
def is_point_in_square(p, sq):
x, y = p
p0, p1, p2, p3 = sq
side_func0 = get_func_deg1(p0, p1)
side_func1 = get_func_deg1(p1, p2)
side_func2 = get_func_deg1(p2, p3)
side_func3 = get_func_deg1(p3, p0)
if not side_func0 or not side_func1 or not side_func2 or not side_func3:
xmin = min(p0[0], p2[0])
xmax = max(p0[0], p2[0])
ymin = min(p0[1], p2[1])
ymax = max(p0[1], p2[1])
return xmin <= x <= xmax and ymin <= y <= ymax
return ((y - side_func0(x)) * (y - side_func2(x))) <= 0 and \
((y - side_func1(x)) * (y - side_func3(x))) <= 0
def squares_overlap(square0, square1):
for p0 in square0:
if is_point_in_square(p0, square1):
return True
for p1 in square1:
if is_point_in_square(p1, square0):
return True
xc0 = (square0[0][0] + square0[2][0]) / 2
yc0 = (square0[0][1] + square0[2][1]) / 2
if is_point_in_square((xc0, yc0), square1):
return True
# The "reverse center check" not needed, since squares are congruent
"""
xc1 = (square1[0][0] + square1[2][0]) / 2
yc1 = (square1[0][1] + square1[2][1]) / 2
if is_point_in_square((xc1, yc1), square0):
return True
"""
return False
def __generation_monitor():
global __global_generation_counter
__global_generation_counter += 1
def generate_random_point(minx=MINX, miny=MINY, maxx=MAXX, maxy=MAXY, safety_margin=DEFAULT_SAFETY_MARGIN):
if maxx - minx < 2 * safety_margin or maxy - miny < 2 * safety_margin:
print("MUEEE")
safety_margin = 0
x = safety_margin + random() * (maxx - minx - 2 * safety_margin)
y = safety_margin + random() * (maxy - miny - 2 * safety_margin)
__generation_monitor()
return x, y
def generate_random_angle(max_val=pi_2):
return random() * max_val
def generate_random_square(side=DEFAULT_SIDE, squares_to_avoid=()):
while 1:
restart = False
x0, y0 = generate_random_point()
angle = generate_random_angle()
x1 = x0 + side * cos(angle)
y1 = y0 + side * sin(angle)
angle += pi_2
x2 = x1 + side * cos(angle)
y2 = y1 + side * sin(angle)
angle += pi_2
x3 = x2 + side * cos(angle)
y3 = y2 + side * sin(angle)
ret = (x0, y0), (x1, y1), (x2, y2), (x3, y3)
for square in squares_to_avoid:
if squares_overlap(ret, square):
restart = True
if restart:
continue
return ret
def square_to_plot(square):
xs, ys = zip(square[0], square[1], square[2], square[3])
return xs + (xs[0],), ys + (ys[0],)
def main():
seed()
squares = list()
allow_overlapping = False # CHANGE to True to allow square to overlap
for _ in range(MAX_SQUARES):
#print("Generating:", _)
if allow_overlapping:
square = generate_random_square()
else:
square = generate_random_square(squares_to_avoid=squares)
squares.append(square)
plot_squares = tuple()
for sq in squares:
plot_squares += square_to_plot(sq)
print("STATS:\n Squares: {}\n Allow overlapping: {}\n Generated values: {}".format(MAX_SQUARES, allow_overlapping, __global_generation_counter))
plt.plot(*plot_squares)
plt.axis([MINX, MAXX, MINY, MAXY])
plt.show()
if __name__ == "__main__":
print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
main()
Notes:
I didn't work with matplotlib before (actually, I pip installed it for this task)
General comments:
A point is represented by a tuple representing its coordinates: (x, y)
A square is a tuple consisting of 4 points (p0, p1, p2, p3)
get_func_deg1:
Returns the function that describes the line that contains the 2 points given as arguments
If the 2 points are on a line that is parallel to Oy (there's no "normal" function to describe it), simply return None
is_point_in_square:
Determines if a point is inside a square
Uses the logic explained above, except
For the special case when the square edges are parallel to Ox and Oy when it uses some simple arithmetic operations
squares_overlap:
Determines whether 2 squares overlap (I'm sure there are faster "algorithms")
Checks if any of the 1st square corners are inside the 2nd one
The other way around: checks if any of the 2nd square corners are inside the 1st one
Since the above 2 checks are not enough (imagine a regular [Wikipedia]: Octagon and unifying its vertices every 2nd one: there will be 2 squares neither having its corners in the other one, but sharing their "central" areas), also check that one square's center is inside the other one
generate_random_point:
Generates a point in the given bounding box
safety_margin specifies the (minimum) distance that the generated point should be away from any of the bounding box sides, in order that any square that will have this point as a corner, would fit entirely in the bounding box
generate_random_angle:
Generates a random angle between 0 and (π / 2)
generate_random_square:
Generates a random point, a random angle, and constructs a square starting from there, using the specified side
squares_to_avoid is a list of squares. After the square is generated, it is checked against every square from that list. If the 2 squares overlap, the square is regenerated
square_to_plot:
Converts a square (from a tuple of points) to matplotlib format (2 tuples consisting of xs and ys with the 1st element duplicated as the last)
main:
The main function
__generation_monitor (0):
Internal function used for profiling
In order to change the number of squares, modify MAX_SQUARES
Output:
[cfati#CFATI-5510-0:e:\Work\Dev\StackOverflow\q046081491]> "e:\Work\Dev\VEnvs\py_064_03.05.04_test0\Scripts\python.exe" code00.py
STATS:
Squares: 30
Allow overlapping: False
Generated values: 1135
Few words about the squares generation
As seen in the output, for 30 displayed squares, 1135 were generated (at this run). That is because they were overlapping
If changing from main allow_overlapping = True, Generated values in the output will match the number of squares (MAX_SQUARES)
If increasing MAX_SQUARES to values let's say higher than 50, the number of generated values will increase exponentially (so will the time needed to generate them), and the chance that the program will enter an infinite loop (because it won't be able to generate a square that doesn't overlap to another one) will grow as well
Okay, here is what I've come up with with a little help from the shapely package. Installation help is at the bottom here. The ultimate result:
Code walkthrough
distance is a helper function using the Point class from shapely to find the distance between two coordinates. Just a helper function for later.
Square instantiations a new polygon. It has 4 corners, each an (x,y) pair, one coordinate for its center, and a scalar value equal to half the distance of its diagonal.
test_overlap is pretty self explanatory by title. But logically what it does is this: find the distance from center-to-center between the two shapes. Then find the sum of the half-diagonal of each shape. If the center-to-center distance is greater than the sum, the squares cannot overlap.
Squares starts out with an empty container (empty list) and attempts to add squares to it. But for each possible new addition, it firsts tests that there is no overlap with existing squares.
Code
import math
import random
from shapely.geometry import Polygon, Point
def distance(a, b):
return Point(a).distance(Point(b))
class Square(object):
def __init__(self):
self.x0, self.y0 = random.random(), random.random()
theta = random.randint(0, 90) * math.pi / 180 # Angle of rotation
self.x1 = self.x0 + (0.1 * math.cos(theta))
self.x2 = self.x1 + (0.1 * math.cos((90 * math.pi/180) + theta))
self.x3 = self.x2 + (0.1 * math.cos((180 * math.pi/180) + theta))
self.y1 = self.y0 + (0.1 * math.sin(theta))
self.y2 = self.y1 + (0.1 * math.sin((90 * math.pi/180) + theta))
self.y3 = self.y2 + (0.1 * math.sin((180 * math.pi/180) + theta))
self.corners = ((self.x0, self.y0), (self.x1, self.y1),
(self.x2, self.y2), (self.x3, self.y3))
#property
def center(self):
"""(x, y) of the center of the polygon."""
return Polygon(self.corners).centroid.coords[0]
#property
def half_diag(self):
"""The distance of 1/2 the shape's diagonal (center-to-corner)."""
p0, p1, p2, p3 = self.corners
return 0.5 * distance(p0, p1) * math.sqrt(2)
def test_overlap(square1, square2):
"""Do two shapes overlap?
Note this is a 'conservative' test. May return True if they do not
(false positive), but will never return False if they do (false negative).
"""
# Distance between two centers
ctc = distance(square1.center, square2.center)
# Sum of half-diagonals
halfdiags = square1.half_diag + square2.half_diag
res = ctc < halfdiags
return res
class Squares(object):
def __init__(self):
self.squares = []
def add_square(self):
new_square = Square()
if not self.squares:
# Initial empty list/container - just add without any tests
self.squares.append(new_square)
else:
while True:
# Test that new_square overlaps with existing
res = [test_overlap(square, new_square) for square in self.squares]
if any(res):
# We have at least 1 case of overlap (1 True)
new_square = Square()
else:
# Safe to add
self.squares.append(new_square)
break
def plot_squares(self):
for square in self.squares:
(x0, y0), (x1, y1), (x2, y2), (x3, y3) = square.corners
plt.plot([x0, x1, x2, x3, x0], [y0, y1, y2, y3, y0])
Example
import itertools
%matplotlib inline
for _ in itertools.repeat(None, 10):
# Add 10 squares; you could also just build this into the class
sqs.add_square()
sqs.plot_squares()
Installing shapely
Install the Anaconda distribution if you don't already. Then just use conda-forge to install shapely. From cmd run:
conda config --add channels conda-forge
conda install shapely
Glaring deficiency
At a certain point, your container of squares gets filled up and there is minimal room left to add new shapes. Even if there is available space, the function is basically trial-and-error, so will take long at high shape counts. That happens at around 20-25 squares (in a 1.0x1.0 box) at the moment.
You need a function to determine whether two cube intersect.
from math import cos, pi, sin
from random import random
from matplotlib.mlab import frange
from matplotlib.pyplot import plot, axis, show,axes
LEN = 0.1
def rotate(point, theta):
x = point[0]
y = point[1]
x_ = x * cos(theta) + y * sin(theta)
y_ = - x * sin(theta) + y * cos(theta)
return x_, y_
class CUBE(object):
def __init__(self, x, y, theta):
self.corner = [(LEN / 2, LEN / 2),
(-LEN / 2, LEN / 2),
(-LEN / 2, -LEN / 2),
(LEN / 2, -LEN / 2)
]
self.theta = theta
self.x = x
self.y = y
for i in range(4):
self.corner[i] = rotate(self.corner[i], theta)
self.corner[i] = (self.corner[i][0] + x,
self.corner[i][1] + y)
def is_include(cube, point):
point = [point[0] - cube.x, point[1] - cube.y]
point = rotate(point, -cube.theta)
if (point[0] < -LEN / 2
or point[0] > LEN / 2
or point[1] < -LEN / 2
or point[1] > LEN / 2
):
return False
else:
return True
def is_intersect(cube1, cube2):
if (any([is_include(cube1, point) for point in cube2.corner])
or any([is_include(cube2, point) for point in cube1.corner])
or is_include(cube1, (cube2.x, cube2.y))):
return True
else:
return False
def plot_cube(cube,n):
plot(
[cube.corner[i][0] for i in [0, 1, 2, 3, 0]],
[cube.corner[i][1] for i in [0, 1, 2, 3, 0]])
ax = axes()
ax.text(cube.x,cube.y,str(n))
def display(cubelist): # connects the 4 corners on a plot
for i,cube in enumerate(cubelist):
plot_cube(cube,i)
axis([0, 1, 0, 1]) # 1x1 grid
show()
cubelist = []
for i in range(100):
x0 = random()
y0 = random()
theta = random() * pi
cube = CUBE(x0, y0, theta)
if any(is_intersect(cube,cb) for cb in cubelist):
continue
else:
cubelist.append(cube)
display(cubelist)

How can I rotating a line not at its end point? [duplicate]

I've been trying to rotate a bunch of lines by 90 degrees (that together form a polyline). Each line contains two vertices, say (x1, y1) and (x2, y2). What I'm currently trying to do is rotate around the center point of the line, given center points |x1 - x2| and |y1 - y2|. For some reason (I'm not very mathematically savvy) I can't get the lines to rotate correctly.
Could someone verify that the math here is correct? I'm thinking that it could be correct, however, when I set the line's vertices to the new rotated vertices, the next line may not be grabbing the new (x2, y2) vertex from the previous line, causing the lines to rotate incorrectly.
Here's what I've written:
def rotate_lines(self, deg=-90):
# Convert from degrees to radians
theta = math.radians(deg)
for pl in self.polylines:
self.curr_pl = pl
for line in pl.lines:
# Get the vertices of the line
# (px, py) = first vertex
# (ox, oy) = second vertex
px, ox = line.get_xdata()
py, oy = line.get_ydata()
# Get the center of the line
cx = math.fabs(px-ox)
cy = math.fabs(py-oy)
# Rotate line around center point
p1x = cx - ((px-cx) * math.cos(theta)) - ((py-cy) * math.sin(theta))
p1y = cy - ((px-cx) * math.sin(theta)) + ((py-cy) * math.cos(theta))
p2x = cx - ((ox-cx) * math.cos(theta)) - ((oy-cy) * math.sin(theta))
p2y = cy - ((ox-cx) * math.sin(theta)) + ((oy-cy) * math.cos(theta))
self.curr_pl.set_line(line, [p1x, p2x], [p1y, p2y])
The coordinates of the center point (cx,cy) of a line segment between points (x1,y1) and (x2,y2) are:
cx = (x1 + x2) / 2
cy = (y1 + y2) / 2
In other words it's just the average, or arithmetic mean, of the two pairs of x and y coordinate values.
For a multi-segmented line, or polyline, its logical center point's x and y coordinates are just the corresponding average of x and y values of all the points. An average is just the sum of the values divided by the number of them.
The general formulas to rotate a 2D point (x,y) θ radians around the origin (0,0) are:
x′ = x * cos(θ) - y * sin(θ)
y′ = x * sin(θ) + y * cos(θ)
To perform a rotation about a different center (cx, cy), the x and y values of the point need to be adjusted by first subtracting the coordinate of the desired center of rotation from the point's coordinate, which has the effect of moving (known in geometry as translating) it is expressed mathematically like this:
tx = x - cx
ty = y - cy
then rotating this intermediate point by the angle desired, and finally adding the x and y values of the point of rotation back to the x and y of each coordinate. In geometric terms, it's the following sequence of operations:  Tʀᴀɴsʟᴀᴛᴇ ─► Rᴏᴛᴀᴛᴇ ─► Uɴᴛʀᴀɴsʟᴀᴛᴇ.
This concept can be extended to allow rotating a whole polyline about any arbitrary point—such as its own logical center—by just applying the math described to each point of each line segment within it.
To simplify implementation of this computation, the numerical result of all three sets of calculations can be combined and expressed with a pair of mathematical formulas which perform them all simultaneously. So a new point (x′,y′) can be obtained by rotating an existing point (x,y), θ radians around the point (cx, cy) by using:
x′ = ( (x - cx) * cos(θ) + (y - cy) * sin(θ) ) + cx
y′ = ( -(x - cx) * sin(θ) + (y - cy) * cos(θ) ) + cy
Incorporating this mathematical/geometrical concept into your function produces the following:
from math import sin, cos, radians
def rotate_lines(self, deg=-90):
""" Rotate self.polylines the given angle about their centers. """
theta = radians(deg) # Convert angle from degrees to radians
cosang, sinang = cos(theta), sin(theta)
for pl in self.polylines:
# Find logical center (avg x and avg y) of entire polyline
n = len(pl.lines)*2 # Total number of points in polyline
cx = sum(sum(line.get_xdata()) for line in pl.lines) / n
cy = sum(sum(line.get_ydata()) for line in pl.lines) / n
for line in pl.lines:
# Retrieve vertices of the line
x1, x2 = line.get_xdata()
y1, y2 = line.get_ydata()
# Rotate each around whole polyline's center point
tx1, ty1 = x1-cx, y1-cy
p1x = ( tx1*cosang + ty1*sinang) + cx
p1y = (-tx1*sinang + ty1*cosang) + cy
tx2, ty2 = x2-cx, y2-cy
p2x = ( tx2*cosang + ty2*sinang) + cx
p2y = (-tx2*sinang + ty2*cosang) + cy
# Replace vertices with updated values
pl.set_line(line, [p1x, p2x], [p1y, p2y])
Your center point is going to be:
centerX = (x2 - x1) / 2 + x1
centerY = (y2 - y1) / 2 + y1
because you take half the length (x2 - x1) / 2 and add it to where your line starts to get to the middle.
As an exercise, take two lines:
line1 = (0, 0) -> (5, 5)
then: |x1 - x2| = 5, when the center x value is at 2.5.
line2 = (2, 2) -> (7, 7)
then: |x1 - x2| = 5, which can't be right because that's the center for
the line that's parallel to it but shifted downwards and to the left

Categories

Resources