-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCanvas.py
147 lines (123 loc) · 5.46 KB
/
Canvas.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import logging
import random
import time
from PIL import Image, ImageDraw
import math
from shapely.geometry import LineString
from itertools import chain
class CanvasModel():
CANVAS_SIZE = (1500, 1200)
CANVAS_BORDER = 100
CURVE_WIDTH = 15
def __init__(self, size=CANVAS_SIZE, border=CANVAS_BORDER, curveWidth=CURVE_WIDTH, seed=time.time(), logLevel=logging.INFO) -> None:
"""
Create a new canvas with a random curve.
If you want a reproducible canvas provide a seed (e.g. 5 or 9)
"""
self.__configLogger(logLevel)
self._size = size
self._imgsize = (size[0]+border*2, size[1]+border*2)
self._border = border
self._curveWidth = curveWidth
self._seed = seed
self.initCurve()
return
def initCurve(self):
"""
initialize a line from left to right that follows bezier curves -
the curves depend on the random seed used to initialize the canvas.
"""
random.seed(self._seed)
# two of the 1-6 points are already used for the start and end
i = random.randint(1, 6)
x = [self._border]
x.extend(sorted(random.sample(
range(self._border, self._size[0]+self._border), i)))
x.append(self._size[0]+self._border)
y = random.sample(range(self._border, self._size[1]+self._border), i+2)
xys = list(zip(x, y))
ts = [t/20.0 for t in range(21)]
bezier = make_bezier(xys)
points = bezier(ts)
# now extend line into 15 width polygon with shapely
line = LineString([list(point) for point in points])
# the buffer() invocation is the essential one extending the line
newpoints = line.buffer(7.5, cap_style=3, join_style=1)
self._curvePoints = list(newpoints.exterior.coords)
# Now compute the car start position and orientation.
# Note that the middle infrared sensor (which is 100 mm from the center of the car)
# should be on top of the curve.
# First we compute a point on the line with a distance of 100 mm from the start of the line
sensorpos = line.interpolate(100)
# then we compute the orientation angle (degrees) of that point relative to the start point
self._delta_y = (sensorpos.y-xys[0][1])/(sensorpos.x-xys[0][0])
self._angle = math.degrees(math.atan(self._delta_y))
self._bezierPoints = xys
self._logger.debug(
f'Canvas: new Curve: {i} bezier control points, orientation {self._angle}, startpoint {xys[0]}, size {self._size}, border {self._border}')
def getCurveStartingPoint(self):
return self._bezierPoints[0]
def getCurveStartingOrientation(self):
return self._angle
def getCurveBoundingPoints(self):
return self._curvePoints
def getCanvasBoundingPoints(self):
return ((self._border, self._border), (self._size[0]+self._border, self._border), (self._size[0]+self._border, self._size[1]+self._border), (self._border, self._size[1]+self._border), (self._border, self._border))
def createImageAndDraw(self):
im = Image.new('RGB', self._imgsize, (128, 128, 128))
imageDraw = ImageDraw.Draw(im)
imageDraw.rectangle((self._border, self._border, self._size[0]+self._border, self._size[1]+self._border), fill=(
255, 255, 255), width=1, outline=(255, 255, 255))
imageDraw.polygon(self._curvePoints, outline='black', fill='black')
return (im, imageDraw)
def __configLogger(self, logLevel):
""" by default log all INFO level and above messages to stderr with timestamp, module and threadname, level and message
"""
self._logger = logging.getLogger(__name__)
self._logger.setLevel(logLevel)
self._logger.handlers.clear()
console_handler = logging.StreamHandler()
console_handler.setLevel(logLevel)
console_formatter = logging.Formatter(
'%(asctime)s - (%(name)s %(threadName)-9s) - %(levelname)s: %(message)s')
console_handler.setFormatter(console_formatter)
self._logger.addHandler(console_handler)
return
# The following functions are copied from
# https://stackoverflow.com/questions/246525/how-can-i-draw-a-bezier-curve-using-pythons-pil
# Copyright https://stackoverflow.com/users/190597/unutbu
def make_bezier(xys):
# xys should be a sequence of 2-tuples (Bezier control points)
n = len(xys)
combinations = pascal_row(n-1)
def bezier(ts):
# This uses the generalized formula for bezier curves
# http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Generalization
result = []
for t in ts:
tpowers = (t**i for i in range(n))
upowers = reversed([(1-t)**i for i in range(n)])
coefs = [c*a*b for c, a, b in zip(combinations, tpowers, upowers)]
result.append(
tuple(sum([coef*p for coef, p in zip(coefs, ps)]) for ps in zip(*xys)))
return result
return bezier
def pascal_row(n, memo={}):
# This returns the nth row of Pascal's Triangle
if n in memo:
return memo[n]
result = [1]
x, numerator = 1, n
for denominator in range(1, n//2+1):
# print(numerator,denominator,x)
x *= numerator
x /= denominator
result.append(x)
numerator -= 1
if n & 1 == 0:
# n is even
result.extend(reversed(result[:-1]))
else:
result.extend(reversed(result))
memo[n] = result
return result