Finding clusters of line segments in a list - python

I have a list of line segments represented as x and y coordinates and I'm trying to find groups of lines such that all angles within the group are within 20 degrees. But my problem is that math.degrees(math.atan2(-0.1,-1)) (=-174.29) and math.degrees(math.atan2(0.1,-1)) (=174.29) should make two points in the same group, but the difference in face value is greater than 20. I wonder if my code should do what I want and avoids the 180/-180 problem:
import math
endpoints = [((2, 11), (2, 8)), ((11, 3), (2, 5)), ((13, 7), (9, 12)), ((5, 5), (15, 12)), ((15, 4), (8, 1)), ((15, 14), (14, 3)), ((2, 4), (6, 5)), ((1, 13), (13, 11)), ((8, 11), (12, 15)), ((12, 4), (7, 1))]
def find_angle(p1,p2):
x1 = p1[0]
y1 = p1[1]
x2 = p2[0]
y2 = p2[1]
dx = max(x2-x1,x1-x2)
if dx == x2-x1:
dy = y2-y1
else:
dy = y1-y2
return math.degrees(math.atan2(dy,dx))
endpointsbyangle = sorted([(find_angle(p1,p2), (p1,p2)) for p1, p2 in endpoints], key=lambda x: x[0])
prev = -190
group = []
allgroups = []
for (theta, (p1, p2)) in endpointsbyangle:
if prev == -190:
group.append((p1,p2))
prev = theta
else:
if abs(prev - theta) < 20:
group.append((p1,p2))
else:
allgroups.append(group)
group = [(p1,p2)]
prev = theta
print dict(enumerate(allgroups))
Any thought appreciated.

One way is to replace your line
if abs(prev - theta) < 20:
with
if abs(prev - theta) < 20 or abs(prev - theta) > 340:
This then captures the situations where the calculated angle is near 360 degrees.
However, if I understand you, you have another problem. If the angle ABC is exactly 5 degrees (for example), and angle CAB is also 5 degrees, then angle ACB is 170 degrees and would fail your test. In other words, it is not possible for "all angles within the group are within 20 degrees" for a group of 3 points. You should also allow an angle to be within 20 degrees of 180 degrees. So perhaps you should use the line
if (abs(prev - theta) < 20
or abs(prev - theta) > 340
or abs(prev - theta - 180) < 20):
That depends on exactly what you mean by your requirement "all angles within the group are within 20 degrees."

Related

Determining neighbours of cell as diamond shape in python

I have a matrix variable in size where 1 indicates the cell such as:
Cells = [[0,0,0,0,0],
[0,0,0,0,0],
[0,0,1,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
]
I need to find neigbours in a parametric sized diamond shape. Not a box as answer given in here or not a fixed sized 1 diamond, answer given here. For example, N=2 I want to know the column, rows for below:
Mask = [[0,0,1,0,0],
[0,1,1,1,0],
[1,1,0,1,1],
[0,1,1,1,0],
[0,0,1,0,0],
]
The function should receive x and y for the requested column and row, (for above I will input 2,2) and N (input 2) the size of diamond. The function should return list of tuples (x,y) for the given diamond size.
I struggled at defining the shape as a function of x, y and k in for loops. I need to know both numpy (if there is anything that helps) and non-numpy solution.
For an iterative approach where you just construct the diamond:
def get_neighbors(center, n=1):
ret = []
for dx in range(-n, n + 1):
ydiff = n - abs(dx)
for dy in range(-ydiff, ydiff + 1):
ret.append((center[0] + dx, center[1] + dy))
return ret
Result of get_neighbors((2, 2), 2):
[(0, 2), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (3, 1), (3, 2), (3, 3), (4, 2)]
Or, for a recursive approach:
dirs = [(1, 0), (0, 1), (-1, 0), (0, -1)]
def add_tuples(a, b):
return tuple([x + y for (x, y) in zip(a, b)])
def get_neighbors(center, n=1, seen=set()):
seen.add(center)
if n <= 0:
return seen
for dir in dirs:
newpos = add_tuples(center, dir)
if newpos in seen:
continue
get_neighbors(newpos, n - 1, seen)
return seen
I would start by taking out a "sub-matrix" that is the smallest square that can contain your result cells. This is the part that numpy should be able to help with.
Then define a function that calculates the manhattan distance between two cells (abs(x - x_p) + abs(y - y_p)) and iterate through the cells of your sub-matrix and return the values with a manhattan distance of less than N from your origin.
Make mask with rotation
Convolute cell and mask
Fix the result
import numpy as np
from scipy.ndimage import rotate, convolve
import matplotlib.pyplot as plt
def diamond_filter(radius):
s = radius * 2 + 1
x = np.ones((s, s), dtype=int)
x[radius, radius] = 0
return rotate(x, angle=45)
def make_diamonds(x, radius):
filter = diamond_filter(radius)
out = convolve(x, filter)
out[out > 1] = 1
out -= x
out[out < 0] = 0
return out
def plot(x):
plt.imshow(x)
plt.show()
plt.close()
def main():
cell = np.random.choice([0, 1], size=(200, 200), p=[0.95, 0.05])
plot(diamond_filter(2))
plot(cell)
result = make_diamonds(cell, 2)
plot(result)
if __name__ == '__main__':
main()

How to store an inner list to an outer list

#Total distance values are stored here
total_dis = []
#Permutation of cooridnates
perm = permutations(result_List, num_dot)
for i in perm:
route = list(i)
route.append(route[0])
print(route)
for (x1, y1), (x2, y2) in zip(route, route[1:]):
distance_formula = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
print(distance_formula)
I am calculating the distance between the points of each of the generated permutations.
[(5, 0), (4, 5), (2, 9), (5, 0)]
5.0990195135927845
4.47213595499958
9.486832980505138
[(5, 0), (2, 9), (4, 5), (5, 0)]
9.486832980505138
4.47213595499958
5.0990195135927845
[(4, 5), (5, 0), (2, 9), (4, 5)]
5.0990195135927845
9.486832980505138
4.47213595499958
...
I am trying to store the values of distance_formula in lists within the list total_dis. (I figured storing the floats in a list will allow me to find the sums of each list.) Like so:
[[5.0990195135927845, 4.47213595499958, 9.486832980505138],[9.486832980505138, 4.47213595499958, 5.0990195135927845], [5.0990195135927845, 9.486832980505138, 4.47213595499958],[...]]
I am having trouble creating the new lists for the distances between each point of each permutations to be stored.
Just add three lines
#Total distance values are stored here
total_dis = []
#Permutation of cooridnates
perm = permutations(result_List, num_dot)
for i in perm:
route = list(i)
route.append(route[0])
print(route)
dis_list = [] # <---- Add this line
for (x1, y1), (x2, y2) in zip(route, route[1:]):
distance_formula = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
print(distance_formula)
dis_list.append(distance_formula) # <---- Add this line
total_dis.append(dis_list) # <---- Add this line (outside the "distance" loop)
You might even opt to do it as a nested list comprehension, though it might be a lot less readable.
total_dis = [
[
math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
for (x1, y1), (x2, y2) in zip(
i + (i[0], )[:-1],
i + (i[0], )[1:]
)
[
for i in permutations(result_List, num_dot)
]

How to get value of pixels which are at a certain angle from another pixel?

I'm trying to implement a method where I have to obtain the values of all those pixels which forms a line at a certain angle through a pixel (i, j)
Consider the following code snippet
sum = image.getpixel(((i - 7), (j + 2))) + image.getpixel(((i - 6), (j + 2))) + image.getpixel(
((i - 5), (j + 1))) + image.getpixel(
((i - 4), (j + 1))) + image.getpixel(((i - 3), (j + 1))) + image.getpixel(((i - 2), (j + 1))) + image.getpixel(
((i - 1), j)) + image.getpixel((i, j)) + image.getpixel(((i + 1), j)) + image.getpixel(
((i + 2), (j - 1))) + image.getpixel(((i + 3), (j - 1))) + image.getpixel(((i + 4), (j - 1))) + image.getpixel(
((i + 5), (j - 1))) + image.getpixel(((i + 6), (j - 2))) + image.getpixel(((i + 7), (j - 2)))
avg_sum = sum / 15
Now in the above code I know which pixels relative to i, j forms the line at angle 15 degrees. Hence I am able to get the values of all those pixels.
Now is there an easy way to do this because currently this code find the sum of the grey level of 15 pixels that forms this line at 15 degree through i, j. I want this code to be flexible so that I can easily find the sum of line of length 15 pixels or 13 pixels or 11 pixels and so on.
You can use some trigonometry. Recall that sin(angle) = y/x, so y = x*sin(angle). Just create an array of x values however long you want, and then plug it into the formula for the y values. You'll of course need to round the y values afterwards. And then you can translate all of them to the starting location for the line.
>>> import numpy as np
>>> angle = 15*np.pi/180
>>> x = np.arange(0,10)
>>> y = np.round(np.sin(angle)*x).astype(int)
>>> [(x,y) for x, y in zip(x, y)]
[(0, 0), (1, 0), (2, 1), (3, 1), (4, 1), (5, 1), (6, 2), (7, 2), (8, 2), (9, 2)]

combine two loops in python

suppose to have two polygons p1 and p2, where p2 is completely inside p1
p1 = [(0, 10), (10, 10), (10, 0), (0, 0)]
p2 = [(2, 6), (6, 6), (6, 2), (2, 2)]
degree_of_contact = 0
xyarrays = [p1,p2]
p1_degree_of_contact = 0
for x,y in xyarrays[0]:
if point_inside_polygon(x,y,xyarrays[1]):
p1_degree_of_contact += 1
p2_degree_of_contact = 0
for x,y in xyarrays[1]:
if point_inside_polygon(x,y,xyarrays[0]):
p2_degree_of_contact += 1
degree_of_contact = p1_degree_of_contact + p2_degree_of_contact
where point_inside_polygon is to deciding if a point is inside (True, False otherwise) a polygon,
where poly is a list of pairs (x,y) containing the coordinates of the polygon's vertices. The algorithm is called the "Ray Casting Method
i wish to combine in an elegant way (line coding save) both loops in one.
The following should work:
degree_of_contact = 0
for tmp1, tmp2 in [(p1, p2), (p2, p1)]:
for x,y in tmp1:
if point_inside_polygon(x, y, tmp2):
degree_of_contact += 1
degree_of_contact = sum(point_inside_polygon(x, y, i) for i, j in ((p1, p2), (p2, p1)) for x, y in j)

divide ways on a map in parts of length one

I have these coordinates:
coord = [(10,10), (13,10), (13,13)]
Now i need new coordinates.
The way between two coordinates is always one.
For example:
(10,10)
(11,10)
(12,10)
(13,10)
(13,11)
(13,12)
(13,13)
Any ideas?
#
I found the solution.
for n in range(len(coord)-1):
lengthx = coord[n+1][0] - coord[n][0]
lengthy = coord[n+1][1] - coord[n][1]
length = (lengthx**2 + lengthy**2)**.5
for m in range(length):
print coord[n][0]+lengthx/length*m, coord[n][1]+lengthy/length*m
A simple variation on Bresenham's line algorithm will achieve what you want using integer arithmetic only (so it should be noticeably faster):
def steps(path):
if len(path) > 0:
for i in range(1, len(path)):
for step in steps_between(path[i - 1], path[i]):
yield step
yield path[-1]
def steps_between(start, end):
x0, y0 = start
x1, y1 = end
steep = abs(y1 - y0) > abs(x1 - x0)
if steep:
x0, y0 = y0, x0
x1, y1 = y1, x1
if y0 > y1:
x0, x1 = x1, x0
y0, y1 = y1, y0
if y0 < y1:
ystep = 1
else:
ystep = -1
deltax = x1 - x0
deltay = abs(y1 - y0)
error = -deltax / 2
y = y0
for x in range(x0, x1):
if steep:
yield (y, x)
else:
yield (x, y)
error += deltay
if error > 0:
y += ystep
error -= deltax
if steep:
yield (y, x)
else:
yield (x, y)
coords = [(10, 10), (13, 10), (13, 13)]
print "\n".join(str(step) for step in steps(coords))
The above prints:
(10, 10)
(11, 10)
(12, 10)
(13, 10)
(13, 11)
(13, 12)
(13, 13)
Of course, Bresenham works as expected when both x and y change between two points on the path:
coords = [(10, 10), (13, 12), (15, 13)]
print "\n".join(str(step) for step in steps(coords))
That prints:
(10, 10)
(11, 10)
(11, 11)
(12, 11)
(12, 12)
(13, 12)
(14, 12)
(14, 13)
(15, 13)

Categories

Resources