#!/usr/bin/env python3 """ Anotátor dlaždic pro trénink YOLOv8 Formát: YOLO class cx cy w h (normalizovaný 0–1) Anotace se ukládají do data/vrchlabi_custom/train/labels/ Obrázky se kopírují do data/vrchlabi_custom/train/images/ """ import json, shutil, urllib.parse from http.server import HTTPServer, BaseHTTPRequestHandler from pathlib import Path TILES_DIR = Path("tiles_vrchlabi") OUT_IMG_DIR = Path("data/vrchlabi_custom/train/images") OUT_LBL_DIR = Path("data/vrchlabi_custom/train/labels") OUT_IMG_DIR.mkdir(parents=True, exist_ok=True) OUT_LBL_DIR.mkdir(parents=True, exist_ok=True) TILE_SIZE = 256 CLASS_NAMES = ["car", "van", "truck", "bus"] CLASS_COLORS = ["#00dc00", "#ffdc00", "#dc0000", "#0078ff"] HTML = r""" Anotátor vozidel
""" class Handler(BaseHTTPRequestHandler): def log_message(self, format, *args): pass # potlač logy def send_json(self, data, code=200): body = json.dumps(data).encode() self.send_response(code) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(body) def do_GET(self): path = urllib.parse.urlparse(self.path) qs = urllib.parse.parse_qs(path.query) if path.path == "/": body = HTML.encode() self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) elif path.path == "/api/tiles": filt = qs.get("filter", ["all"])[0] all_tiles = sorted(p.name for p in TILES_DIR.glob("*.jpg")) if filt == "annotated": all_tiles = [t for t in all_tiles if (OUT_LBL_DIR / (Path(t).stem + ".txt")).exists()] elif filt == "unannotated": all_tiles = [t for t in all_tiles if not (OUT_LBL_DIR / (Path(t).stem + ".txt")).exists()] self.send_json(all_tiles) elif path.path.startswith("/api/ann/"): name = urllib.parse.unquote(path.path[len("/api/ann/"):]) lbl = OUT_LBL_DIR / (Path(name).stem + ".txt") if not lbl.exists(): self.send_json([]) else: anns = [] for line in lbl.read_text().splitlines(): parts = line.strip().split() if len(parts) == 5: cls, cx, cy, w, h = int(parts[0]), *map(float, parts[1:]) anns.append({"cls": cls, "cx": cx, "cy": cy, "w": w, "h": h}) self.send_json(anns) elif path.path.startswith("/tiles/"): name = urllib.parse.unquote(path.path[len("/tiles/"):]) img_path = TILES_DIR / name if not img_path.exists(): self.send_response(404); self.end_headers(); return data = img_path.read_bytes() self.send_response(200) self.send_header("Content-Type", "image/jpeg") self.send_header("Content-Length", str(len(data))) self.end_headers() self.wfile.write(data) else: self.send_response(404); self.end_headers() def do_POST(self): if self.path.startswith("/api/ann/"): name = urllib.parse.unquote(self.path[len("/api/ann/"):]) length = int(self.headers.get("Content-Length", 0)) body = json.loads(self.rfile.read(length)) # Ulož label lbl = OUT_LBL_DIR / (Path(name).stem + ".txt") lines = [f"{a['cls']} {a['cx']:.6f} {a['cy']:.6f} {a['w']:.6f} {a['h']:.6f}" for a in body] lbl.write_text("\n".join(lines) + ("\n" if lines else "")) # Zkopíruj obrázek pokud ještě není dst = OUT_IMG_DIR / name if not dst.exists(): shutil.copy2(TILES_DIR / name, dst) self.send_json({"ok": True}) else: self.send_response(404); self.end_headers() def do_OPTIONS(self): self.send_response(200) self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") self.send_header("Access-Control-Allow-Headers", "Content-Type") self.end_headers() if __name__ == "__main__": import webbrowser, threading port = 8765 server = HTTPServer(("127.0.0.1", port), Handler) print(f"Anotátor běží na http://127.0.0.1:{port}") print(f"Anotace → {OUT_LBL_DIR}") print(f"Obrázky → {OUT_IMG_DIR}") print("Ctrl+C pro zastavení") threading.Timer(0.5, lambda: webbrowser.open(f"http://127.0.0.1:{port}")).start() try: server.serve_forever() except KeyboardInterrupt: print("\nZastaveno.")