Web platform for sharing free data for ML and research

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