File size: 3,220 Bytes
3a66575
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import json
import os

import streamlit as st
from PIL import Image, ImageDraw

try:
    from streamlit_image_coordinates import streamlit_image_coordinates
except Exception:
    streamlit_image_coordinates = None


st.set_page_config(page_title="Point Annotation Tool", layout="wide")
st.title("Point Annotation Tool")

image_dir = st.sidebar.text_input("Image folder", value=".")
output_json = st.sidebar.text_input("Output JSON", value="annotations.json")

if "index" not in st.session_state:
    st.session_state.index = 0
if "annotations" not in st.session_state:
    st.session_state.annotations = {}


def list_images(folder):
    if not os.path.isdir(folder):
        return []
    exts = {".jpg", ".jpeg", ".png", ".bmp", ".webp"}
    return sorted([name for name in os.listdir(folder) if os.path.splitext(name.lower())[1] in exts])


def annotated_image(image, points):
    preview = image.copy()
    draw = ImageDraw.Draw(preview)
    for x, y in points:
        r = 5
        draw.ellipse((x - r, y - r, x + r, y + r), fill="red", outline="white")
    return preview


images = list_images(image_dir)
if not images:
    st.warning("No images found.")
    st.stop()

st.session_state.index = max(0, min(st.session_state.index, len(images) - 1))
image_name = images[st.session_state.index]
image_path = os.path.join(image_dir, image_name)
image = Image.open(image_path).convert("RGB")
points = st.session_state.annotations.setdefault(image_name, [])

nav1, nav2, nav3, nav4 = st.columns(4)
if nav1.button("Prev"):
    st.session_state.index = max(0, st.session_state.index - 1)
    st.rerun()
if nav2.button("Next"):
    st.session_state.index = min(len(images) - 1, st.session_state.index + 1)
    st.rerun()
if nav3.button("Delete Last") and points:
    points.pop()
    st.rerun()
if nav4.button("Clear"):
    st.session_state.annotations[image_name] = []
    st.rerun()

st.write(f"{st.session_state.index + 1}/{len(images)} | {image_name} | Count: {len(points)}")
preview = annotated_image(image, points)

if streamlit_image_coordinates is not None:
    clicked = streamlit_image_coordinates(preview, key=f"img_{image_name}_{len(points)}")
    if clicked is not None and "x" in clicked and "y" in clicked:
        points.append([int(clicked["x"]), int(clicked["y"])])
        st.rerun()
else:
    st.image(preview, caption="Install streamlit-image-coordinates for direct click annotation.", use_container_width=True)
    c1, c2, c3 = st.columns(3)
    x = c1.number_input("x", min_value=0, max_value=image.width, value=0, step=1)
    y = c2.number_input("y", min_value=0, max_value=image.height, value=0, step=1)
    if c3.button("Add Point"):
        points.append([int(x), int(y)])
        st.rerun()

export_data = [
    {"image": name, "points": pts, "count": len(pts)}
    for name, pts in sorted(st.session_state.annotations.items())
]
json_text = json.dumps(export_data, indent=2)

if st.sidebar.button("Save JSON"):
    with open(output_json, "w", encoding="utf-8") as f:
        f.write(json_text)
    st.sidebar.success(output_json)

st.sidebar.download_button(
    "Download JSON",
    data=json_text.encode("utf-8"),
    file_name=os.path.basename(output_json),
    mime="application/json",
)