Spaces:
Paused
Paused
| <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> | |