helmet-v5 / tools /ui_server.py
vivekvar's picture
Initial push: helmet v5 code + trained models
e90abd8 verified
"""Tiny server that serves the annotated MP4 + live analytics sidebar.
Usage:
uvicorn tools.ui_server:app --host 0.0.0.0 --port 8090
"""
from fastapi import FastAPI
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
from pathlib import Path
import json
ROOT = Path('/home/azureuser/helmet_v5/data/t4_analysis')
VIDEO = ROOT / 'annotated.mp4'
JSONL = ROOT / 'detections.jsonl'
app = FastAPI()
@app.get('/')
def index():
html = """<!doctype html><html><head><title>Helmet analytics</title>
<style>
body{background:#111;color:#eee;font-family:sans-serif;margin:0;display:flex;height:100vh}
#main{flex:1;display:flex;flex-direction:column;padding:16px}
video{width:100%;max-height:75vh;background:#000}
#side{width:320px;background:#1a1a1a;overflow-y:auto;padding:12px;border-left:1px solid #333}
.card{background:#222;padding:10px;margin:6px 0;border-radius:4px;font-size:13px}
.card b{color:#ffd000}.card small{color:#888}
h3{margin:4px 0 10px 0;color:#ffd000}
.metric{font-size:28px;color:#0f0;font-weight:bold}
.metric-label{color:#888;font-size:11px;text-transform:uppercase}
.helmet{color:#0f0}.no_helmet{color:#f66}
</style></head><body>
<div id="main">
<h2 style="margin:4px 0">GNT_TMS_269 • 9-10 AM • Helmet analytics</h2>
<video id="v" controls autoplay src="/video.mp4"></video>
</div>
<div id="side">
<div class="card"><span class="metric-label">total unique bikes</span>
<div class="metric" id="total">0</div></div>
<div class="card"><span class="metric-label">no_helmet</span>
<div class="metric no_helmet" id="nh">0</div></div>
<div class="card"><span class="metric-label">helmet</span>
<div class="metric helmet" id="h">0</div></div>
<div class="card"><span class="metric-label">videonetics (reference)</span>
<div class="metric" style="color:#ff0">326</div></div>
<h3>Latest detections</h3>
<div id="events"></div>
</div>
<script>
const v = document.getElementById('v');
async function refresh() {
const r = await fetch('/stats?t=' + v.currentTime);
const j = await r.json();
document.getElementById('total').textContent = j.total;
document.getElementById('nh').textContent = j.no_helmet;
document.getElementById('h').textContent = j.helmet;
const el = document.getElementById('events');
el.innerHTML = j.recent.map(e => `
<div class="card">
<b class="${e.label}">${e.label}</b> <small>#${e.track_id} @ ${e.video_sec.toFixed(1)}s</small><br>
plate: <b>${e.plate||'—'}</b> ${e.plate_conf?('('+(e.plate_conf*100).toFixed(0)+'%)'):''}
</div>`).join('');
}
setInterval(refresh, 1000); refresh();
</script></body></html>"""
return HTMLResponse(html)
@app.get('/video.mp4')
def video():
return FileResponse(str(VIDEO), media_type='video/mp4')
@app.get('/stats')
def stats(t: float = 0.0):
total = helmet = no_helmet = 0
events = []
if JSONL.exists():
with open(JSONL) as f:
for line in f:
try: r = json.loads(line)
except Exception: continue
if r.get('video_sec', 0) > t + 0.5:
break # don't reveal future events
total += 1
if r['label'] == 'helmet':
helmet += 1
else:
no_helmet += 1
events.append(r)
return JSONResponse({
'total': total,
'helmet': helmet,
'no_helmet': no_helmet,
'recent': events[-20:][::-1],
})