roundabout,
created on Sunday, 6 October 2024, 08:45:53 (1728204353),
received on Saturday, 12 October 2024, 14:56:18 (1728744978)
Author identity: vlad <vlad.muntoiu@gmail.com>
353ff97496f285d486cc5bfac67a91c6799ea27f
static/picture-annotation.py
@@ -1,6 +1,5 @@
from pyscript import document
from pyodide.ffi import create_proxy
from pyodide.http import pyfetch
from pyscript import document, fetch as pyfetch
from pyscript.ffi import create_proxy
import asyncio
import json
@@ -69,40 +68,39 @@ def list_shapes():
json_shapes = []
for shape in shapes:
shape_dict = {}
match shape.tagName:
case "rect":
shape_dict["type"] = "bbox"
shape_dict["shape"] = {
"x": float(shape.getAttribute("x")) / image.naturalWidth,
"y": float(shape.getAttribute("y")) / image.naturalHeight,
"w": float(shape.getAttribute("width")) / image.naturalWidth,
"h": float(shape.getAttribute("height")) / image.naturalHeight
}
case "polygon" | "polyline":
if shape.tagName == "polygon":
shape_dict["type"] = "polygon"
elif shape.tagName == "polyline":
shape_dict["type"] = "polyline"
points = shape.getAttribute("points").split(" ")
json_points = []
for point in points:
x, y = point.split(",")
x, y = float(x), float(y)
json_points.append({
"x": x / image.naturalWidth,
"y": y / image.naturalHeight
})
shape_dict["shape"] = json_points
case "circle" if shape.classList.contains("shape-point"):
shape_dict["type"] = "point"
shape_dict["shape"] = {
"x": float(shape.getAttribute("cx")) / image.naturalWidth,
"y": float(shape.getAttribute("cy")) / image.naturalHeight
}
case _:
continue
if shape.tagName == "rect":
shape_dict["type"] = "bbox"
shape_dict["shape"] = {
"x": float(shape.getAttribute("x")) / image.naturalWidth,
"y": float(shape.getAttribute("y")) / image.naturalHeight,
"w": float(shape.getAttribute("width")) / image.naturalWidth,
"h": float(shape.getAttribute("height")) / image.naturalHeight
}
elif shape.tagName == "polygon" or shape.tagName == "polyline":
if shape.tagName == "polygon":
shape_dict["type"] = "polygon"
elif shape.tagName == "polyline":
shape_dict["type"] = "polyline"
points = shape.getAttribute("points").split(" ")
json_points = []
for point in points:
x, y = point.split(",")
x, y = float(x), float(y)
json_points.append({
"x": x / image.naturalWidth,
"y": y / image.naturalHeight
})
shape_dict["shape"] = json_points
elif shape.tagName == "circle" and shape.classList.contains("shape-point"):
shape_dict["type"] = "point"
shape_dict["shape"] = {
"x": float(shape.getAttribute("cx")) / image.naturalWidth,
"y": float(shape.getAttribute("cy")) / image.naturalHeight
}
else:
continue
shape_dict["object"] = shape.getAttribute("data-object-type")
json_shapes.append(shape_dict)
@@ -340,13 +338,12 @@ def switch_shape(event):
image.addEventListener("click", unselect_shape_proxy)
helper_message.innerText = "Click on a shape to select"
# Cancel the current shape creation
match shape_type:
case "shape-bbox":
cancel_bbox(None)
case "shape-polygon":
cancel_polygon(None)
case "shape-polyline":
cancel_polygon(None)
if shape_type == "shape-bbox":
cancel_bbox(None)
elif shape_type == "shape-polygon":
cancel_polygon(None)
elif shape_type == "shape-polyline":
cancel_polygon(None)
else:
# Remove event listeners for selection
for shape in document.getElementsByClassName("shape"):
@@ -514,75 +511,72 @@ def open_shape(event):
return
print("Creating a new shape of type:", shape_type)
match shape_type:
case "shape-bbox":
helper_message.innerText = ("Define the first point at the intersection of the lines "
"by clicking on the image, or click the cross to cancel")
if shape_type == "shape-bbox":
helper_message.innerText = ("Define the first point at the intersection of the lines "
"by clicking on the image, or click the cross to cancel")
cancel_button.addEventListener("click", cancel_bbox_proxy)
document.addEventListener("keydown", cancel_bbox_proxy)
cancel_button.style.display = "block"
bbox_pos = None
zone.addEventListener("click", make_bbox_proxy)
vertical_ruler.style.display = "block"
horizontal_ruler.style.display = "block"
zone.style.cursor = "crosshair"
elif shape_type == "shape-polygon" or shape_type == "shape-polyline":
if shape_type == "shape-polygon":
helper_message.innerText = ("Click on the image to define the points of the polygon, "
"press escape to cancel, enter to close, or backspace to "
"remove the last point")
elif shape_type == "shape-polyline":
helper_message.innerText = ("Click on the image to define the points of the polyline, "
"press escape to cancel, enter to finish, or backspace to "
"remove the last point")
if not polygon_points and not new_shape:
new_shape = make_shape_container()
zone_rect = zone.getBoundingClientRect()
cancel_button.addEventListener("click", cancel_bbox_proxy)
document.addEventListener("keydown", cancel_bbox_proxy)
if not polygon_points and int(new_shape.children.length) == 0:
zone.addEventListener("click", make_polygon_proxy)
document.addEventListener("keydown", close_polygon_proxy)
document.addEventListener("keydown", cancel_polygon_proxy)
document.addEventListener("keydown", backspace_polygon_proxy)
cancel_button.addEventListener("click", cancel_polygon_proxy)
cancel_button.style.display = "block"
bbox_pos = None
zone.addEventListener("click", make_bbox_proxy)
vertical_ruler.style.display = "block"
horizontal_ruler.style.display = "block"
confirm_button.addEventListener("click", close_polygon_proxy)
confirm_button.style.display = "block"
backspace_button.addEventListener("click", backspace_polygon_proxy)
backspace_button.style.display = "block"
if shape_type == "shape-polygon":
polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon")
polygon.classList.add("shape-polygon")
elif shape_type == "shape-polyline":
polygon = document.createElementNS("http://www.w3.org/2000/svg", "polyline")
polygon.classList.add("shape-polyline")
polygon.setAttribute("fill", "none")
polygon.setAttribute("data-object-type", "")
polygon.classList.add("shape")
new_shape.appendChild(polygon)
zone.appendChild(new_shape)
zone.style.cursor = "crosshair"
case "shape-polygon" | "shape-polyline":
match shape_type:
case "shape-polygon":
helper_message.innerText = ("Click on the image to define the points of the polygon, "
"press escape to cancel, enter to close, or backspace to "
"remove the last point")
case "shape-polyline":
helper_message.innerText = ("Click on the image to define the points of the polyline, "
"press escape to cancel, enter to finish, or backspace to "
"remove the last point")
if not polygon_points and not new_shape:
new_shape = make_shape_container()
zone_rect = zone.getBoundingClientRect()
if not polygon_points and int(len(new_shape.children)) == 0:
zone.addEventListener("click", make_polygon_proxy)
document.addEventListener("keydown", close_polygon_proxy)
document.addEventListener("keydown", cancel_polygon_proxy)
document.addEventListener("keydown", backspace_polygon_proxy)
cancel_button.addEventListener("click", cancel_polygon_proxy)
cancel_button.style.display = "block"
confirm_button.addEventListener("click", close_polygon_proxy)
confirm_button.style.display = "block"
backspace_button.addEventListener("click", backspace_polygon_proxy)
backspace_button.style.display = "block"
match shape_type:
case "shape-polygon":
polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon")
polygon.classList.add("shape-polygon")
case "shape-polyline":
polygon = document.createElementNS("http://www.w3.org/2000/svg", "polyline")
polygon.classList.add("shape-polyline")
polygon.setAttribute("fill", "none")
polygon.setAttribute("data-object-type", "")
polygon.classList.add("shape")
new_shape.appendChild(polygon)
zone.appendChild(new_shape)
zone.style.cursor = "crosshair"
case "shape-point":
point = document.createElementNS("http://www.w3.org/2000/svg", "circle")
zone_rect = zone.getBoundingClientRect()
point.setAttribute("cx", str((event.clientX - zone_rect.left) / zone_rect.width * image.naturalWidth))
point.setAttribute("cy", str((event.clientY - zone_rect.top) / zone_rect.height * image.naturalHeight))
point.setAttribute("r", "0")
point.classList.add("shape-point")
point.classList.add("shape")
point.setAttribute("data-object-type", "")
elif shape_type == "shape-point":
point = document.createElementNS("http://www.w3.org/2000/svg", "circle")
zone_rect = zone.getBoundingClientRect()
point.setAttribute("cx", str((event.clientX - zone_rect.left) / zone_rect.width * image.naturalWidth))
point.setAttribute("cy", str((event.clientY - zone_rect.top) / zone_rect.height * image.naturalHeight))
point.setAttribute("r", "0")
point.classList.add("shape-point")
point.classList.add("shape")
point.setAttribute("data-object-type", "")
new_shape = make_shape_container()
zone_rect = zone.getBoundingClientRect()
new_shape = make_shape_container()
zone_rect = zone.getBoundingClientRect()
new_shape.appendChild(point)
zone.appendChild(new_shape)
new_shape.appendChild(point)
zone.appendChild(new_shape)
new_shape = None
new_shape = None
@@ -594,7 +588,5 @@ zone.addEventListener("mousemove", follow_cursor_proxy)
zone.addEventListener("click", create_proxy(open_shape))
# Load existing annotations, if any
async def load_existing():
put_shapes(await load_shapes())
load_existing()
put_shapes(await load_shapes())
print("Ready!")
templates/picture-annotation.html
@@ -66,5 +66,5 @@
</x-vbox>
</x-frame>
<input type="hidden" id="resource-id" name="resource-id" value="{{ resource.id }}">
<script type="py" src="/static/picture-annotation.py"></script>
{% endblock %}
<script type="mpy" src="/static/picture-annotation.py"></script>
{% endblock %}