picture-annotation.py
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
9
10
shape_type = ""
11
12
13
def switch_shape(event):
14
global shape_type
15
shape = event.currentTarget.id
16
shape_type = shape
17
print("Shape is now of type:", shape)
18
19
20
vertical_ruler = document.getElementById("annotation-ruler-vertical")
21
horizontal_ruler = document.getElementById("annotation-ruler-horizontal")
22
vertical_ruler_2 = document.getElementById("annotation-ruler-vertical-secondary")
23
horizontal_ruler_2 = document.getElementById("annotation-ruler-horizontal-secondary")
24
helper_message = document.getElementById("annotation-helper-message")
25
26
helper_message.innerText = "Select a shape type then click on the image to begin defining it"
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
bbox_pos = None
38
39
40
def make_bbox(event):
41
global new_shape, bbox_pos
42
zone_rect = zone.getBoundingClientRect()
43
44
if bbox_pos is None:
45
bbox_pos = [(event.clientX - zone_rect.left) / zone_rect.width,
46
(event.clientY - zone_rect.top) / zone_rect.height]
47
vertical_ruler_2.style.left = str(bbox_pos[0] * 100) + "%"
48
horizontal_ruler_2.style.top = str(bbox_pos[1] * 100) + "%"
49
vertical_ruler_2.style.display = "block"
50
horizontal_ruler_2.style.display = "block"
51
52
else:
53
x0, y0 = bbox_pos
54
x1 = (event.clientX - zone_rect.left) / zone_rect.width
55
y1 = (event.clientY - zone_rect.top) / zone_rect.height
56
57
rectangle = document.createElementNS("http://www.w3.org/2000/svg", "rect")
58
minx = min(x0, x1)
59
miny = min(y0, y1)
60
maxx = max(x0, x1)
61
maxy = max(y0, y1)
62
63
rectangle.setAttribute("x", str(minx * image.naturalWidth))
64
rectangle.setAttribute("y", str(miny * image.naturalHeight))
65
rectangle.setAttribute("width", str((maxx - minx) * image.naturalWidth))
66
rectangle.setAttribute("height", str((maxy - miny) * image.naturalHeight))
67
rectangle.setAttribute("fill", "none")
68
rectangle.classList.add("shape-bbox")
69
70
new_shape.appendChild(rectangle)
71
zone.appendChild(new_shape)
72
vertical_ruler.style.display = "none"
73
horizontal_ruler.style.display = "none"
74
vertical_ruler_2.style.display = "none"
75
horizontal_ruler_2.style.display = "none"
76
document.body.style.cursor = "auto"
77
zone.removeEventListener("click", create_proxy(make_bbox))
78
79
return
80
81
82
zone.addEventListener(
83
"mousemove",
84
create_proxy(follow_cursor)
85
)
86
87
new_shape = None
88
89
def open_shape(event):
90
global last_click_position, new_shape
91
print("Creating a new shape of type:", shape_type)
92
new_shape = document.createElementNS("http://www.w3.org/2000/svg", "svg")
93
new_shape.setAttribute("width", "100%")
94
new_shape.setAttribute("height", "100%")
95
zone_rect = zone.getBoundingClientRect()
96
new_shape.setAttribute("viewBox", f"0 0 {image.naturalWidth} {image.naturalHeight}")
97
98
if shape_type == "shape-bbox":
99
bbox_pos = None
100
zone.addEventListener(
101
"click",
102
create_proxy(make_bbox)
103
)
104
vertical_ruler.style.display = "block"
105
horizontal_ruler.style.display = "block"
106
document.body.style.cursor = "crosshair"
107
108
109
for button in list(document.getElementById("shape-selector").children):
110
button.addEventListener(
111
"click",
112
create_proxy(switch_shape)
113
)
114
print("Shape", button.id, "is available")
115
116
117
118
zone.addEventListener(
119
"click",
120
create_proxy(open_shape)
121
)
122