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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
from dataclasses import dataclass
from random import randint, uniform
import cv2
import numpy as np
@dataclass
class node:
"""node class cardinal links"""
# pixel position
x: float
y: float
# used to have setters that asserted object had different ID
# north easth, south, west
n = None
e = None
s = None
w = None
def pos(self):
return (int(self.x), int(self.y))
def __str__(self):
return f'x: {self.x} y: {self.y} n: {type(self.n)} e: {type(self.e)} s: {type(self.s)} w: {type(self.w)}'
@dataclass
class rectangle:
"""nodes to form a rectangle, top left, top right, bottom right, bottom left"""
tl: node
tr: node
br: node
bl: node
def node_average(node1: node, node2: node, weight=.5):
"""create node with weighted average of given node positions"""
return node(node1.x * weight + node2.x * (1-weight),
node1.y * weight + node2.y * (1-weight))
def rectangle_from_dimensions(w: float, h: float, ox: float = 0, oy: float = 0):
"""create initial rectangle from width, height, offset"""
tl, tr, br, bl = node(ox, oy), node(ox+w, oy), node(ox+w, oy+h), node(ox, oy+h)
tl.e, tr.w = tr, tl
tr.s, br.n = br, tr
br.w, bl.e = bl, br
bl.n, tl.s = tl, bl
return rectangle(tl, tr, br, bl)
def vertical_split(rect: rectangle, weight=(.5, .5)):
tl, tr, br, bl = rect.tl, rect.tr, rect.br, rect.bl
weight = uniform(*weight)
mt = node_average(tl, tr, weight=weight) # use the same weight for both to create rectangular images
mb = node_average(bl, br, weight=weight)
# ======== left ========
# iterate down the linked list until pixel height is lower
n1 = tl
while n1.x < mt.x: n1 = n1.e
# if pixel high it the same us present node (prevent multiple nodes in same location)
if n1.x == mt.x:
mt = n1
# insert node in between nodes
else:
mt.e, mt.w = n1, n1.w
n1.w.e, n1.w = mt, mt
# =======================
# ======== right ========
n2 = bl
while n2.x < mb.x: n2 = n2.e
if n2.x == bl.x:
bl = n2
else:
mb.e, mb.w = n2, n2.w
n2.w.e, n2.w = mb, mb
# =======================
mt.s, mb.n = mb, mt
return rectangle(tl, mt, mb, bl), rectangle(mt, tr, br, mb)
def horizontal_split(rect: rectangle, weight=(.5, .5)):
tl, tr, br, bl = rect.tl, rect.tr, rect.br, rect.bl
weight = uniform(*weight)
ml = node_average(tl, bl, weight=weight)
mr = node_average(tr, br, weight=weight)
# top
n1 = tl
while n1.y < ml.y: n1 = n1.s
if n1.y == ml.y:
ml = n1
else:
ml.s, ml.n = n1, n1.n
n1.n.s, n1.n = ml, ml
# bottom
n2 = tr
while n2.y < mr.y: n2 = n2.s
if n2.y == mr.y:
mr = n2
else:
mr.s, mr.n = n2, n2.n
n2.n.s, n2.n = mr, mr
ml.e, mr.w = mr, ml
return rectangle(tl, tr, mr, ml), rectangle(ml, mr, br, bl)
def random_n_deep(rect: rectangle, n: int, weight=(.5, .5)):
"""recursive rectangle splitting"""
rect1, rect2 = vertical_split(rect, weight=weight) if randint(0, 1) else horizontal_split(rect, weight=weight)
if n > 0:
s1 = random_n_deep(rect1, n-1, weight=weight)
s2 = random_n_deep(rect2, n-1, weight=weight)
return [*s1, *s2]
return rect1, rect2
def draw_rect(canvas, rect: rectangle, color=(0, 0, 0), thickness=1, shrink=0):
"""helper function for drawing rectangles"""
p1, p2, p3, p4 = rect.tl.pos(), rect.tr.pos(), rect.br.pos(), rect.bl.pos()
cv2.line(canvas, p1, p2, color=color, thickness=max(thickness, 1))
cv2.line(canvas, p2, p3, color=color, thickness=max(thickness, 1))
cv2.line(canvas, p3, p4, color=color, thickness=max(thickness, 1))
cv2.line(canvas, p4, p1, color=color, thickness=max(thickness, 1))
# cv2.polylines(canvas, [np.array([p1, p2, p3, p4], dtype=np.int32)], color=color, thickness=min(thickness, 1), isClosed=True)
# p1, p2 = rect.tl.pos(), rect.br.pos()
# p1 = (p1[0] + shrink, p1[1] + shrink)
# p2 = (p2[0] - shrink, p2[1] - shrink)
# cv2.rectangle(canvas, p1, p2, color=color, thickness=thickness)
if __name__ == "__main__":
import time
# white space around image
offset = 50
# image dimensions
aspect = (700, 500)
# generate rectangular slices
rects = random_n_deep(rectangle_from_dimensions(aspect[1], aspect[0], offset, offset), 6, weight=(.40, .60))
# create canvas
canvas = np.ones((aspect[0] + offset*2, aspect[1] + offset*2, 3), dtype=np.uint8) * 255
# # draw filled rectanges
# for rect in rects:
# color = list(map(int, (uniform(0, 255), uniform(0, 255), uniform(0, 255))))
# colors = [(0, 0, 200), (0, 200, 200), (200, 0, 0), (255, 255, 255)]
# # draw_rect(canvas, rect, color=colors[min(randint(0, 4), 3)], thickness=-1)
# draw_rect(canvas, rect, color=color, thickness=-1)
# draw shrunk outlines
for rect in rects:
color = list(map(int, (uniform(0, 255), uniform(0, 255), uniform(0, 255))))
draw_rect(canvas, rect, color=color, thickness=5, shrink=3)
# display image
cv2.imshow('image', canvas)
q = cv2.waitKey(0)
# save if 's' pressed
if q == ord('s'):
cv2.imwrite(f"./image_{str(time.time()).replace('.', '')}.png", canvas)
|