Web platform for sharing free image data for ML and research

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

Important information: Google announced that, from September 2026, Android devices will require ALL apps to be signed by Google, effectively leading to an iOS situation. Value your right to a computer that does what you want; do not tolerate this monopolistic practice! Contact me if you don't understand why it is bad. Click to learn more.

 picture-annotation.py

View raw Download
text/x-script.python • 5.03 kiB
Python script, ASCII text executable
        
            
1
from pyscript import document
2
from pyodide.ffi import create_proxy
3
4
document.getElementById("shape-options").style.display = "flex"
5
6
image = document.getElementById("annotation-image")
7
zone = document.getElementById("annotation-zone")
8
confirm_button = document.getElementById("annotation-confirm")
9
cancel_button = document.getElementById("annotation-cancel")
10
11
confirm_button.style.display = "none"
12
cancel_button.style.display = "none"
13
14
shape_type = ""
15
bbox_pos = None
16
new_shape = None
17
18
19
def follow_cursor(event):
20
rect = zone.getBoundingClientRect()
21
x = event.clientX - rect.left
22
y = event.clientY - rect.top
23
vertical_ruler.style.left = str(x) + "px"
24
horizontal_ruler.style.top = str(y) + "px"
25
26
27
# These are functions usable in JS
28
cancel_bbox_proxy = create_proxy(lambda event: cancel_bbox(event))
29
make_bbox_proxy = create_proxy(lambda event: make_bbox(event))
30
follow_cursor_proxy = create_proxy(follow_cursor)
31
32
33
def switch_shape(event):
34
global shape_type
35
shape = event.currentTarget.id
36
shape_type = shape
37
print("Shape is now of type:", shape)
38
39
vertical_ruler = document.getElementById("annotation-ruler-vertical")
40
horizontal_ruler = document.getElementById("annotation-ruler-horizontal")
41
vertical_ruler_2 = document.getElementById("annotation-ruler-vertical-secondary")
42
horizontal_ruler_2 = document.getElementById("annotation-ruler-horizontal-secondary")
43
helper_message = document.getElementById("annotation-helper-message")
44
45
helper_message.innerText = "Select a shape type then click on the image to begin defining it"
46
47
48
def cancel_bbox(event):
49
global bbox_pos, new_shape
50
51
if new_shape is not None and event is not None:
52
# Require event so the shape is kept when it ends normally
53
new_shape.remove()
54
new_shape = None
55
zone.removeEventListener("click", make_bbox_proxy)
56
document.removeEventListener("keydown", cancel_bbox_proxy)
57
cancel_button.removeEventListener("click", cancel_bbox_proxy)
58
59
bbox_pos = None
60
vertical_ruler.style.display = "none"
61
horizontal_ruler.style.display = "none"
62
vertical_ruler_2.style.display = "none"
63
horizontal_ruler_2.style.display = "none"
64
document.body.style.cursor = "auto"
65
cancel_button.style.display = "none"
66
helper_message.innerText = "Select a shape type then click on the image to begin defining it"
67
68
def make_bbox(event):
69
global new_shape, bbox_pos
70
zone_rect = zone.getBoundingClientRect()
71
72
if bbox_pos is None:
73
helper_message.innerText = "Now define the second point"
74
75
bbox_pos = [(event.clientX - zone_rect.left) / zone_rect.width,
76
(event.clientY - zone_rect.top) / zone_rect.height]
77
vertical_ruler_2.style.left = str(bbox_pos[0] * 100) + "%"
78
horizontal_ruler_2.style.top = str(bbox_pos[1] * 100) + "%"
79
vertical_ruler_2.style.display = "block"
80
horizontal_ruler_2.style.display = "block"
81
82
else:
83
x0, y0 = bbox_pos.copy()
84
x1 = (event.clientX - zone_rect.left) / zone_rect.width
85
y1 = (event.clientY - zone_rect.top) / zone_rect.height
86
87
rectangle = document.createElementNS("http://www.w3.org/2000/svg", "rect")
88
89
new_shape.appendChild(rectangle)
90
zone.appendChild(new_shape)
91
92
minx = min(x0, x1)
93
miny = min(y0, y1)
94
maxx = max(x0, x1)
95
maxy = max(y0, y1)
96
97
rectangle.setAttribute("x", str(minx * image.naturalWidth))
98
rectangle.setAttribute("y", str(miny * image.naturalHeight))
99
rectangle.setAttribute("width", str((maxx - minx) * image.naturalWidth))
100
rectangle.setAttribute("height", str((maxy - miny) * image.naturalHeight))
101
rectangle.setAttribute("fill", "none")
102
rectangle.classList.add("shape-bbox")
103
104
cancel_bbox(None)
105
106
def open_shape(event):
107
global new_shape, bbox_pos
108
if bbox_pos:
109
return
110
print("Creating a new shape of type:", shape_type)
111
new_shape = document.createElementNS("http://www.w3.org/2000/svg", "svg")
112
new_shape.setAttribute("width", "100%")
113
new_shape.setAttribute("height", "100%")
114
zone_rect = zone.getBoundingClientRect()
115
new_shape.setAttribute("viewBox", f"0 0 {image.naturalWidth} {image.naturalHeight}")
116
117
if shape_type == "shape-bbox":
118
helper_message.innerText = ("Define the first point at the intersection of the lines "
119
"by clicking on the image, or click the cross to cancel")
120
121
cancel_button.addEventListener("click", cancel_bbox_proxy)
122
document.addEventListener("keydown", cancel_bbox_proxy)
123
cancel_button.style.display = "block"
124
bbox_pos = None
125
zone.addEventListener("click", make_bbox_proxy)
126
vertical_ruler.style.display = "block"
127
horizontal_ruler.style.display = "block"
128
document.body.style.cursor = "crosshair"
129
130
131
for button in list(document.getElementById("shape-selector").children):
132
button.addEventListener("click", create_proxy(switch_shape))
133
print("Shape", button.id, "is available")
134
135
136
zone.addEventListener("mousemove", follow_cursor_proxy)
137
zone.addEventListener("click", create_proxy(open_shape))
138