AsianPlayer's picture
download
raw
5.21 kB
#!/usr/bin/env python3
"""tools/review_tool/serve_review.py — tiny HTTP service for the review tool (separate from the 8772 annotation service).
Serves results/review_tool/ (index.html, img/) and persists labels server-side:
POST /save body {id,label,note,rater,ts} -> appended to labels.jsonl, merged into labels_latest.json
GET /labels -> labels_latest.json (dict by item id)
GET /export.csv -> csv of all items with their latest label
python tools/review_tool/serve_review.py --host 0.0.0.0 --port 8791 --root results/review_tool
"""
from __future__ import annotations
import argparse, csv, io, json, threading, time
from functools import partial
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
LOCK = threading.Lock()
class Handler(SimpleHTTPRequestHandler):
root: Path = Path(".")
def log_message(self, fmt, *args): # quiet access log -> file
with open(self.root / "server_access.log", "a", encoding="utf-8") as f:
f.write("%s %s\n" % (time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), fmt % args))
def _json(self, code: int, obj) -> None:
body = json.dumps(obj, ensure_ascii=False).encode("utf-8")
self.send_response(code)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(body)
def _latest(self) -> dict:
p = self.root / "labels_latest.json"
return json.load(open(p, encoding="utf-8")) if p.exists() else {}
def do_GET(self):
if self.path.split("?")[0] == "/labels":
with LOCK:
return self._json(200, self._latest())
if self.path.split("?")[0] == "/export.csv":
with LOCK:
latest = self._latest()
items = json.load(open(self.root / "items.json", encoding="utf-8"))["items"]
buf = io.StringIO()
w = csv.writer(buf)
w.writerow(["id", "task", "uid", "regime", "event", "t_event", "label", "note", "rater", "ts"])
for it in items:
r = latest.get(it["id"], {})
m = it["meta"]
w.writerow([it["id"], it["task"], m["uid"], m.get("regime") or "|".join(m.get("regimes", [])), m.get("event", ""), m.get("t_event", ""),
r.get("label", ""), r.get("note", ""), r.get("rater", ""), r.get("ts", "")])
body = buf.getvalue().encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "text/csv; charset=utf-8")
self.send_header("Content-Disposition", "attachment; filename=cwm3_review_labels.csv")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
return
if self.path in ("/", ""):
self.path = "/index.html"
return super().do_GET()
def do_POST(self):
if self.path.split("?")[0] != "/save":
return self._json(404, {"error": "unknown endpoint"})
n = int(self.headers.get("Content-Length", "0"))
try:
rec = json.loads(self.rfile.read(n).decode("utf-8"))
except Exception as e: # noqa: BLE001
return self._json(400, {"error": str(e)})
if not isinstance(rec, dict) or "id" not in rec:
return self._json(400, {"error": "need id"})
rec["server_ts"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
rec["client"] = self.client_address[0]
with LOCK:
with open(self.root / "labels.jsonl", "a", encoding="utf-8") as f:
f.write(json.dumps(rec, ensure_ascii=False) + "\n")
latest = self._latest()
cur = latest.get(rec["id"], {})
cur.update({k: v for k, v in rec.items() if k in ("label", "note", "rater", "ts", "server_ts", "client") and v is not None})
latest[rec["id"]] = cur
tmp = self.root / "labels_latest.json.tmp"
json.dump(latest, open(tmp, "w", encoding="utf-8"), ensure_ascii=False, indent=0)
tmp.replace(self.root / "labels_latest.json")
n_lab = sum(1 for v in latest.values() if v.get("label"))
return self._json(200, {"ok": True, "n_labelled": n_lab})
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--host", default="0.0.0.0")
ap.add_argument("--port", type=int, default=8791)
ap.add_argument("--root", default=str(Path(__file__).resolve().parents[2] / "results/review_tool"))
a = ap.parse_args()
root = Path(a.root).resolve()
Handler.root = root
handler = partial(Handler, directory=str(root))
srv = ThreadingHTTPServer((a.host, a.port), handler)
(root / "server.pid").write_text(str(__import__("os").getpid()))
print(f"serving {root} on http://{a.host}:{a.port}/ (labels -> {root/'labels.jsonl'})", flush=True)
try:
srv.serve_forever()
except KeyboardInterrupt:
pass
return 0
if __name__ == "__main__":
raise SystemExit(main())

Xet Storage Details

Size:
5.21 kB
·
Xet hash:
16c8000644ff4ac7fc276822e3f450807f706fbc2383308415b3290e4e9052a2

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.