forked from mapbox/robosat
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcore.py
152 lines (101 loc) · 3.93 KB
/
core.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
148
149
150
151
152
import cv2
import numpy as np
from PIL import Image
from robosat.tiles import pixel_to_location
def visualize(mask, path):
"""Writes a visual representation `.png` file for a binary mask.
Args:
mask: the binary mask to visualize.
path: the path to save the `.png` image to.
"""
out = Image.fromarray(mask, mode="P")
out.putpalette([0, 0, 0, 255, 255, 255])
out.save(path)
def contours_to_mask(contours, shape):
"""Creates a binary mask for contours.
Args:
contours: the contours to create a mask for.
shape: the resulting mask's shape
Returns:
The binary mask with rasterized contours.
"""
canvas = np.zeros(shape, np.uint8)
cv2.drawContours(canvas, contours, contourIdx=-1, color=1)
return canvas
def featurize(tile, polygon, shape):
"""Transforms polygons in image pixel coordinates into world coordinates.
Args:
tile: the tile this polygon is in for coordinate calculation.
polygon: the polygon to transform from pixel to world coordinates.
shape: the image's max x and y coordinates.
Returns:
The closed polygon transformed into world coordinates.
"""
xmax, ymax = shape
feature = []
for point in polygon:
px, py = point[0]
dx, dy = px / xmax, py / ymax
feature.append(pixel_to_location(tile, dx, 1. - dy))
assert feature, "at least one location in polygon"
feature.append(feature[0]) # polygons are closed
return feature
def denoise(mask, eps):
"""Removes noise from a mask.
Args:
mask: the mask to remove noise from.
eps: the morphological operation's kernel size for noise removal, in pixel.
Returns:
The mask after applying denoising.
"""
struct = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (eps, eps))
return cv2.morphologyEx(mask, cv2.MORPH_OPEN, struct)
def grow(mask, eps):
"""Grows a mask to fill in small holes, e.g. to establish connectivity.
Args:
mask: the mask to grow.
eps: the morphological operation's kernel size for growing, in pixel.
Returns:
The mask after filling in small holes.
"""
struct = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (eps, eps))
return cv2.morphologyEx(mask, cv2.MORPH_CLOSE, struct)
def contours(mask):
"""Extracts contours and the relationship between them from a binary mask.
Args:
mask: the binary mask to find contours in.
Returns:
The detected contours as a list of points and the contour hierarchy.
Note: the hierarchy can be used to re-construct polygons with holes as one entity.
"""
contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
return contours, hierarchy
# Todo: should work for lines, too, but then needs other epsilon criterion than arc length
def simplify(polygon, eps):
"""Simplifies a polygon to minimize the polygon's vertices.
Args:
polygon: the polygon made up of a list of vertices.
eps: the approximation accuracy as max. percentage of the arc length, in [0, 1]
"""
assert 0 <= eps <= 1, "approximation accuracy is percentage in [0, 1]"
epsilon = eps * cv2.arcLength(polygon, closed=True)
return cv2.approxPolyDP(polygon, epsilon=epsilon, closed=True)
def parents_in_hierarchy(node, tree):
"""Walks a hierarchy tree upwards from a starting node collecting all nodes on the way.
Args:
node: the index for the starting node in the hierarchy.
tree: the hierarchy tree containing tuples of (next, prev, first child, parent) ids.
Yields:
The node ids on the upwards path in the hierarchy tree.
"""
def parent(n):
# next, prev, fst child, parent
return n[3]
at = tree[node]
up = parent(at)
while up != -1:
index = up
at = tree[index]
up = parent(at)
assert index != node, "upward path does not include starting node"
yield index