Web platform for sharing free image data for ML and research

Homepage: https://datasets.roundabout-host.com

By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 picture-annotation.py

View raw Download
text/x-script.python • 8.27 kiB
Python script, ASCII text executable
        
            
1
from pyscript import document
2
from pyodide.ffi import create_proxy
3
from pyodide.http import pyfetch
4
import asyncio
5
6
document.getElementById("shape-options").style.display = "flex"
7
8
image = document.getElementById("annotation-image")
9
zone = document.getElementById("annotation-zone")
10
confirm_button = document.getElementById("annotation-confirm")
11
cancel_button = document.getElementById("annotation-cancel")
12
object_list = document.getElementById("object-types")
13
14
confirm_button.style.display = "none"
15
cancel_button.style.display = "none"
16
17
shape_type = ""
18
bbox_pos = None
19
new_shape = None
20
selected_shape = None
21
22
23
async def get_all_objects():
24
response = await pyfetch("/api/object-types")
25
if response.ok:
26
return await response.json()
27
28
29
def follow_cursor(event):
30
rect = zone.getBoundingClientRect()
31
x = event.clientX - rect.left
32
y = event.clientY - rect.top
33
vertical_ruler.style.left = str(x) + "px"
34
horizontal_ruler.style.top = str(y) + "px"
35
36
37
def change_object_type(event):
38
global selected_shape
39
if selected_shape is None:
40
return
41
selected_shape.setAttribute("data-object-type", event.currentTarget.value)
42
43
44
change_object_type_proxy = create_proxy(change_object_type)
45
46
47
async def select_shape(event):
48
global selected_shape
49
if shape_type != "select":
50
return
51
if selected_shape is not None:
52
selected_shape.classList.remove("selected")
53
54
print("Selected shape:", event.target)
55
56
selected_shape = event.target
57
selected_shape.classList.add("selected")
58
59
objects = await get_all_objects()
60
61
object_list.innerHTML = ""
62
63
new_radio = document.createElement("input")
64
new_radio.setAttribute("type", "radio")
65
new_radio.setAttribute("name", "object-type")
66
new_radio.setAttribute("value", "")
67
new_label = document.createElement("label")
68
new_label.appendChild(new_radio)
69
new_label.append("Undefined")
70
object_list.appendChild(new_label)
71
new_radio.addEventListener("change", change_object_type_proxy)
72
73
for object, description in objects.items():
74
new_radio = document.createElement("input")
75
new_radio.setAttribute("type", "radio")
76
new_radio.setAttribute("name", "object-type")
77
new_radio.setAttribute("value", object)
78
new_label = document.createElement("label")
79
new_label.appendChild(new_radio)
80
new_label.append(object)
81
object_list.appendChild(new_label)
82
new_radio.addEventListener("change", change_object_type_proxy)
83
84
print(objects)
85
86
87
def unselect_shape(event):
88
global selected_shape
89
90
if selected_shape is not None:
91
selected_shape.classList.remove("selected")
92
selected_shape = None
93
94
95
select_shape_proxy = create_proxy(select_shape)
96
unselect_shape_proxy = create_proxy(unselect_shape)
97
98
99
# These are functions usable in JS
100
cancel_bbox_proxy = create_proxy(lambda event: cancel_bbox(event))
101
make_bbox_proxy = create_proxy(lambda event: make_bbox(event))
102
follow_cursor_proxy = create_proxy(follow_cursor)
103
104
105
def switch_shape(event):
106
global shape_type
107
object_list.innerHTML = ""
108
unselect_shape(None)
109
shape = event.currentTarget.id
110
shape_type = shape
111
if shape_type == "select":
112
# Add event listeners to existing shapes
113
print(len(list(document.getElementsByClassName("shape"))), "shapes found")
114
for shape in document.getElementsByClassName("shape"):
115
print("Adding event listener to shape:", shape)
116
shape.addEventListener("click", select_shape_proxy)
117
image.addEventListener("click", unselect_shape_proxy)
118
helper_message.innerText = "Click on a shape to select"
119
else:
120
# Remove event listeners for selection
121
for shape in document.getElementsByClassName("shape"):
122
print("Removing event listener from shape:", shape)
123
shape.removeEventListener("click", select_shape_proxy)
124
image.removeEventListener("click", unselect_shape_proxy)
125
helper_message.innerText = "Select a shape type then click on the image to begin defining it"
126
print("Shape is now of type:", shape)
127
128
vertical_ruler = document.getElementById("annotation-ruler-vertical")
129
horizontal_ruler = document.getElementById("annotation-ruler-horizontal")
130
vertical_ruler_2 = document.getElementById("annotation-ruler-vertical-secondary")
131
horizontal_ruler_2 = document.getElementById("annotation-ruler-horizontal-secondary")
132
helper_message = document.getElementById("annotation-helper-message")
133
134
helper_message.innerText = "Select a shape type then click on the image to begin defining it"
135
136
137
def cancel_bbox(event):
138
global bbox_pos, new_shape
139
140
if new_shape is not None and event is not None:
141
# Require event so the shape is kept when it ends normally
142
new_shape.remove()
143
new_shape = None
144
zone.removeEventListener("click", make_bbox_proxy)
145
document.removeEventListener("keydown", cancel_bbox_proxy)
146
cancel_button.removeEventListener("click", cancel_bbox_proxy)
147
148
bbox_pos = None
149
vertical_ruler.style.display = "none"
150
horizontal_ruler.style.display = "none"
151
vertical_ruler_2.style.display = "none"
152
horizontal_ruler_2.style.display = "none"
153
document.body.style.cursor = "auto"
154
cancel_button.style.display = "none"
155
helper_message.innerText = "Select a shape type then click on the image to begin defining it"
156
157
def make_bbox(event):
158
global new_shape, bbox_pos
159
zone_rect = zone.getBoundingClientRect()
160
161
if bbox_pos is None:
162
helper_message.innerText = "Now define the second point"
163
164
bbox_pos = [(event.clientX - zone_rect.left) / zone_rect.width,
165
(event.clientY - zone_rect.top) / zone_rect.height]
166
vertical_ruler_2.style.left = str(bbox_pos[0] * 100) + "%"
167
horizontal_ruler_2.style.top = str(bbox_pos[1] * 100) + "%"
168
vertical_ruler_2.style.display = "block"
169
horizontal_ruler_2.style.display = "block"
170
171
else:
172
x0, y0 = bbox_pos.copy()
173
x1 = (event.clientX - zone_rect.left) / zone_rect.width
174
y1 = (event.clientY - zone_rect.top) / zone_rect.height
175
176
rectangle = document.createElementNS("http://www.w3.org/2000/svg", "rect")
177
178
new_shape.appendChild(rectangle)
179
zone.appendChild(new_shape)
180
181
minx = min(x0, x1)
182
miny = min(y0, y1)
183
maxx = max(x0, x1)
184
maxy = max(y0, y1)
185
186
rectangle.setAttribute("x", str(minx * image.naturalWidth))
187
rectangle.setAttribute("y", str(miny * image.naturalHeight))
188
rectangle.setAttribute("width", str((maxx - minx) * image.naturalWidth))
189
rectangle.setAttribute("height", str((maxy - miny) * image.naturalHeight))
190
rectangle.setAttribute("fill", "none")
191
rectangle.setAttribute("data-object-type", "")
192
rectangle.classList.add("shape-bbox")
193
rectangle.classList.add("shape")
194
195
# Add event listeners to the new shape
196
rectangle.addEventListener("click", select_shape_proxy)
197
198
cancel_bbox(None)
199
200
def open_shape(event):
201
global new_shape, bbox_pos
202
if bbox_pos or shape_type == "select":
203
return
204
print("Creating a new shape of type:", shape_type)
205
new_shape = document.createElementNS("http://www.w3.org/2000/svg", "svg")
206
new_shape.setAttribute("width", "100%")
207
new_shape.setAttribute("height", "100%")
208
zone_rect = zone.getBoundingClientRect()
209
new_shape.setAttribute("viewBox", f"0 0 {image.naturalWidth} {image.naturalHeight}")
210
new_shape.classList.add("shape-container")
211
212
if shape_type == "shape-bbox":
213
helper_message.innerText = ("Define the first point at the intersection of the lines "
214
"by clicking on the image, or click the cross to cancel")
215
216
cancel_button.addEventListener("click", cancel_bbox_proxy)
217
document.addEventListener("keydown", cancel_bbox_proxy)
218
cancel_button.style.display = "block"
219
bbox_pos = None
220
zone.addEventListener("click", make_bbox_proxy)
221
vertical_ruler.style.display = "block"
222
horizontal_ruler.style.display = "block"
223
document.body.style.cursor = "crosshair"
224
225
226
for button in list(document.getElementById("shape-selector").children):
227
button.addEventListener("click", create_proxy(switch_shape))
228
print("Shape", button.id, "is available")
229
230
231
zone.addEventListener("mousemove", follow_cursor_proxy)
232
zone.addEventListener("click", create_proxy(open_shape))
233