roundabout,
created on Tuesday, 3 September 2024, 13:24:32 (1725369872),
received on Wednesday, 4 September 2024, 09:03:53 (1725440633)
Author identity: vlad <vlad.muntoiu@gmail.com>
eb02773a8b58365bdf220f029b24a2e8508ada27
static/picture-annotation.py
@@ -11,6 +11,8 @@ confirm_button = document.getElementById("annotation-confirm")
cancel_button = document.getElementById("annotation-cancel")
backspace_button = document.getElementById("annotation-backspace")
delete_button = document.getElementById("annotation-delete")
previous_button = document.getElementById("annotation-previous")
next_button = document.getElementById("annotation-next")
object_list = document.getElementById("object-types")
@@ -18,6 +20,8 @@ confirm_button.style.display = "none"
cancel_button.style.display = "none"
backspace_button.style.display = "none"
delete_button.style.display = "none"
previous_button.style.display = "none"
next_button.style.display = "none"
shape_type = ""
bbox_pos = None
new_shape = None
@@ -48,12 +52,23 @@ def change_object_type(event):
change_object_type_proxy = create_proxy(change_object_type)
async def focus_shape(selected_shape):
async def focus_shape(shape):
global selected_shape
if shape_type != "select":
return
if selected_shape is not None:
selected_shape.classList.remove("selected")
selected_shape = shape
selected_shape.classList.add("selected")
objects = await get_all_objects()
delete_button.style.display = "block"
next_button.style.display = "block"
previous_button.style.display = "block"
object_list.innerHTML = ""
@@ -86,17 +101,61 @@ async def focus_shape(selected_shape):
async def select_shape(event):
await focus_shape(event.target)
async def next_shape(event):
global selected_shape
if shape_type != "select":
if selected_shape is None:
return
selected_svg = selected_shape.parentNode
while selected_svg is not None:
next_sibling = selected_svg.nextElementSibling
if next_sibling and next_sibling.classList.contains("shape-container"):
selected_svg = next_sibling
break
elif next_sibling is None:
# If no more siblings, loop back to the first child
selected_svg = selected_svg.parentNode.firstElementChild
while selected_svg is not None and not selected_svg.classList.contains(
"shape-container"):
selected_svg = selected_svg.nextElementSibling
break
else:
selected_svg = next_sibling
if selected_svg:
shape = selected_svg.firstElementChild
await focus_shape(shape)
async def previous_shape(event):
global selected_shape
if selected_shape is None:
return
if selected_shape is not None:
selected_shape.classList.remove("selected")
print("Selected shape:", event.target)
selected_svg = selected_shape.parentNode
selected_shape = event.target
while selected_svg is not None:
next_sibling = selected_svg.previousElementSibling
if next_sibling and next_sibling.classList.contains("shape-container"):
selected_svg = next_sibling
break
elif next_sibling is None:
# If no more siblings, loop back to the last child
selected_svg = selected_svg.parentNode.lastElementChild
while selected_svg is not None and not selected_svg.classList.contains(
"shape-container"):
selected_svg = selected_svg.previousElementSibling
break
else:
selected_svg = next_sibling
await focus_shape(selected_shape)
if selected_svg:
shape = selected_svg.firstElementChild
await focus_shape(shape)
def unselect_shape(event):
@@ -108,6 +167,8 @@ def unselect_shape(event):
object_list.innerHTML = ""
delete_button.style.display = "none"
next_button.style.display = "none"
previous_button.style.display = "none"
def delete_shape(event):
@@ -119,14 +180,19 @@ def delete_shape(event):
selected_shape = None
object_list.innerHTML = ""
delete_button.style.display = "none"
next_button.style.display = "none"
previous_button.style.display = "none"
select_shape_proxy = create_proxy(select_shape)
unselect_shape_proxy = create_proxy(unselect_shape)
delete_shape_proxy = create_proxy(delete_shape)
next_shape_proxy = create_proxy(next_shape)
previous_shape_proxy = create_proxy(previous_shape)
delete_button.addEventListener("click", delete_shape_proxy)
next_button.addEventListener("click", next_shape_proxy)
previous_button.addEventListener("click", previous_shape_proxy)
# These are functions usable in JS
cancel_bbox_proxy = create_proxy(lambda event: cancel_bbox(event))
@@ -149,6 +215,12 @@ def switch_shape(event):
shape.addEventListener("click", select_shape_proxy)
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)
else:
# Remove event listeners for selection
for shape in document.getElementsByClassName("shape"):
@@ -158,6 +230,7 @@ def switch_shape(event):
helper_message.innerText = "Select a shape type then click on the image to begin defining it"
print("Shape is now of type:", shape)
vertical_ruler = document.getElementById("annotation-ruler-vertical")
horizontal_ruler = document.getElementById("annotation-ruler-horizontal")
vertical_ruler_2 = document.getElementById("annotation-ruler-vertical-secondary")
@@ -191,6 +264,7 @@ def cancel_bbox(event):
cancel_button.style.display = "none"
helper_message.innerText = "Select a shape type then click on the image to begin defining it"
def make_bbox(event):
global new_shape, bbox_pos
zone_rect = zone.getBoundingClientRect()
@@ -237,6 +311,7 @@ def make_bbox(event):
polygon_points = []
def make_polygon(event):
global new_shape, polygon_points
@@ -249,7 +324,9 @@ def make_polygon(event):
(event.clientY - zone_rect.top) / zone_rect.height))
# Update the polygon
polygon.setAttribute("points", " ".join([f"{point[0] * image.naturalWidth},{point[1] * image.naturalHeight}" for point in polygon_points]))
polygon.setAttribute("points", " ".join(
[f"{point[0] * image.naturalWidth},{point[1] * image.naturalHeight}" for point in
polygon_points]))
def reset_polygon():
@@ -290,7 +367,9 @@ def backspace_polygon(event):
return
polygon_points.pop()
polygon = new_shape.children[0]
polygon.setAttribute("points", " ".join([f"{point[0] * image.naturalWidth},{point[1] * image.naturalHeight}" for point in polygon_points]))
polygon.setAttribute("points", " ".join(
[f"{point[0] * image.naturalWidth},{point[1] * image.naturalHeight}" for point in
polygon_points]))
close_polygon_proxy = create_proxy(close_polygon)
@@ -353,6 +432,5 @@ for button in list(document.getElementById("shape-selector").children):
button.addEventListener("click", create_proxy(switch_shape))
print("Shape", button.id, "is available")
zone.addEventListener("mousemove", follow_cursor_proxy)
zone.addEventListener("click", create_proxy(open_shape))
templates/picture-annotation.html
@@ -54,6 +54,12 @@
<button class="button-flat" title="delete shape" id="annotation-delete">
<iconify-icon icon="mdi:delete"></iconify-icon>
</button>
<button class="button-flat" title="select previous shape" id="annotation-previous">
<iconify-icon icon="mdi:chevron-left"></iconify-icon>
</button>
<button class="button-flat" title="select next shape" id="annotation-next">
<iconify-icon icon="mdi:chevron-right"></iconify-icon>
</button>
</x-buttonbox>
<x-vbox id="object-types" style="--gap-box: 0.25rem; --padding-box: 1rem;">
</x-vbox>