Spaces:
Sleeping
Sleeping
| 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", | |
| ) | |