detection_base / index.html
Zhen Ye
refactor: replace frontend SPA with minimal detection UI
165863b
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Detection Base</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; background: #111; color: #eee; padding: 24px; max-width: 960px; margin: 0 auto; }
h1 { margin-bottom: 16px; font-size: 1.4em; }
label { display: block; margin-top: 12px; font-size: 0.85em; color: #aaa; }
select, input[type="file"], input[type="number"] { width: 100%; padding: 8px; margin-top: 4px; background: #222; color: #eee; border: 1px solid #444; border-radius: 4px; }
.row { display: flex; gap: 12px; flex-wrap: wrap; }
.row > div { flex: 1; min-width: 140px; }
button { margin-top: 16px; padding: 10px 24px; background: #2563eb; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 1em; }
button:disabled { background: #555; cursor: not-allowed; }
#status { margin-top: 12px; padding: 8px; font-size: 0.9em; color: #aaa; }
.preview { margin-top: 16px; }
.preview img, .preview video { max-width: 100%; border-radius: 4px; border: 1px solid #333; }
#detections { margin-top: 12px; background: #1a1a1a; padding: 12px; border-radius: 4px; max-height: 300px; overflow: auto; font-family: monospace; font-size: 0.8em; white-space: pre-wrap; display: none; }
.toggle { margin-top: 8px; font-size: 0.85em; color: #6ba3ff; cursor: pointer; text-decoration: underline; }
.hidden { display: none; }
#segOpts { display: none; }
</style>
</head>
<body>
<h1>Detection Base</h1>
<label for="video">Video</label>
<input type="file" id="video" accept="video/*">
<div class="row">
<div>
<label>Mode</label>
<select id="mode">
<option value="object_detection">Object Detection</option>
<option value="segmentation">Segmentation</option>
<option value="drone_detection">Drone Detection</option>
</select>
</div>
<div>
<label>Detector</label>
<select id="detector">
<option value="yolo11">YOLO11</option>
<option value="detr_resnet50">DETR</option>
<option value="grounding_dino">Grounding DINO</option>
<option value="drone_yolo">Drone YOLO</option>
</select>
</div>
</div>
<div id="segOpts" class="row">
<div>
<label>Segmenter</label>
<select id="segmenter">
<option value="GSAM2-L">GSAM2-L</option>
<option value="GSAM2-B">GSAM2-B</option>
<option value="GSAM2-S">GSAM2-S</option>
<option value="YSAM2-L">YSAM2-L</option>
<option value="YSAM2-B">YSAM2-B</option>
<option value="YSAM2-S">YSAM2-S</option>
</select>
</div>
<div>
<label>Step (keyframe interval)</label>
<input type="number" id="step" value="7" min="1" max="60">
</div>
</div>
<label>Queries (comma-separated, optional)</label>
<input type="text" id="queries" placeholder="person, car, truck" style="width:100%;padding:8px;margin-top:4px;background:#222;color:#eee;border:1px solid #444;border-radius:4px;">
<div class="row">
<div>
<label><input type="checkbox" id="enableDepth"> Enable depth estimation</label>
</div>
</div>
<button id="submit" onclick="submit()">Run Detection</button>
<div id="status"></div>
<div class="preview">
<div id="firstFrameBox" class="hidden">
<p>First Frame</p>
<img id="firstFrame" alt="First frame detection preview">
</div>
<div id="streamBox" class="hidden">
<p>Live Stream</p>
<img id="stream" alt="Live MJPEG detection stream">
</div>
<div id="videoBox" class="hidden">
<p>Processed Video</p>
<video id="result" controls type="video/mp4"></video>
</div>
</div>
<span class="toggle hidden" id="detToggle" onclick="toggleDets()">Show detections JSON</span>
<pre id="detections"></pre>
<script>
const $ = id => document.getElementById(id);
$('mode').onchange = () => {
const seg = $('mode').value === 'segmentation';
$('segOpts').style.display = seg ? 'flex' : 'none';
$('detector').parentElement.style.display = seg ? 'none' : '';
};
let pollTimer = null;
async function submit() {
const file = $('video').files[0];
if (!file) return alert('Select a video file');
$('submit').disabled = true;
$('status').textContent = 'Uploading...';
hide('firstFrameBox'); hide('streamBox'); hide('videoBox'); hide('detToggle');
$('detections').style.display = 'none';
const fd = new FormData();
fd.append('video', file);
fd.append('mode', $('mode').value);
fd.append('queries', $('queries').value);
fd.append('detector', $('detector').value);
fd.append('segmenter', $('segmenter').value);
fd.append('step', $('step').value);
fd.append('enable_depth', $('enableDepth').checked);
try {
const res = await fetch('/detect/async', { method: 'POST', body: fd });
if (!res.ok) { const e = await res.json(); throw new Error(e.detail || res.statusText); }
const data = await res.json();
$('status').textContent = 'Processing...';
// Show first frame
$('firstFrame').src = data.first_frame_url;
show('firstFrameBox');
// Show detections
if (data.first_frame_detections && data.first_frame_detections.length) {
$('detections').textContent = JSON.stringify(data.first_frame_detections, null, 2);
show('detToggle');
}
// Start stream
$('stream').src = data.stream_url;
show('streamBox');
// Poll for completion
pollTimer = setInterval(async () => {
try {
const sr = await fetch(data.status_url);
const st = await sr.json();
$('status').textContent = `Status: ${st.status}`;
if (st.status === 'completed') {
clearInterval(pollTimer);
hide('streamBox');
$('result').src = data.video_url;
show('videoBox');
$('submit').disabled = false;
// Update detections with final data
if (st.first_frame_detections && st.first_frame_detections.length) {
$('detections').textContent = JSON.stringify(st.first_frame_detections, null, 2);
show('detToggle');
}
} else if (st.status === 'failed') {
clearInterval(pollTimer);
$('status').textContent = `Failed: ${st.error}`;
$('submit').disabled = false;
} else if (st.status === 'cancelled') {
clearInterval(pollTimer);
$('status').textContent = 'Cancelled';
$('submit').disabled = false;
}
} catch(e) { /* keep polling */ }
}, 2000);
} catch(e) {
$('status').textContent = `Error: ${e.message}`;
$('submit').disabled = false;
}
}
function show(id) { $(id).classList.remove('hidden'); }
function hide(id) {
$(id).classList.add('hidden');
const img = $(id).querySelector('img');
if (img && img.src) img.src = '';
}
function toggleDets() {
const d = $('detections');
d.style.display = d.style.display === 'none' ? 'block' : 'none';
}
</script>
</body>
</html>