Spaces:
Runtime error
Runtime error
| // Browser webcam capture -> POST frames to /api/face -> draw boxes + tally signals. | |
| (function () { | |
| const video = document.getElementById("video"); | |
| const overlay = document.getElementById("overlay"); | |
| const ctx = overlay.getContext("2d"); | |
| const startBtn = document.getElementById("startBtn"); | |
| const stopBtn = document.getElementById("stopBtn"); | |
| const statusEl = document.getElementById("status"); | |
| const resultEl = document.getElementById("result"); | |
| const chip = { Anxious: document.getElementById("c-anx"), | |
| Depressed: document.getElementById("c-dep"), | |
| Normal: document.getElementById("c-nor") }; | |
| const capture = document.createElement("canvas"); | |
| const counts = { Anxious: 0, Depressed: 0, Normal: 0 }; | |
| let stream = null, timer = null, busy = false; | |
| function renderChips() { | |
| Object.keys(counts).forEach((k) => { if (chip[k]) chip[k].textContent = counts[k]; }); | |
| } | |
| startBtn.onclick = async () => { | |
| try { | |
| stream = await navigator.mediaDevices.getUserMedia({ video: true }); | |
| video.srcObject = stream; | |
| Object.keys(counts).forEach((k) => (counts[k] = 0)); | |
| renderChips(); | |
| resultEl.textContent = ""; | |
| startBtn.disabled = true; | |
| stopBtn.disabled = false; | |
| statusEl.textContent = "Detecting… keep your face in view."; | |
| timer = setInterval(tick, 500); | |
| } catch (e) { | |
| statusEl.textContent = "Could not access webcam: " + e.message; | |
| } | |
| }; | |
| stopBtn.onclick = () => { | |
| if (timer) clearInterval(timer); | |
| timer = null; | |
| if (stream) stream.getTracks().forEach((t) => t.stop()); | |
| startBtn.disabled = false; | |
| stopBtn.disabled = true; | |
| ctx.clearRect(0, 0, overlay.width, overlay.height); | |
| const total = counts.Anxious + counts.Depressed + counts.Normal; | |
| const verdict = Object.keys(counts).reduce((a, b) => (counts[a] >= counts[b] ? a : b)); | |
| resultEl.textContent = total ? "Result: " + verdict : "No face detected — try again."; | |
| statusEl.textContent = "Stopped."; | |
| }; | |
| async function tick() { | |
| if (busy || video.readyState < 2) return; | |
| busy = true; | |
| capture.width = video.videoWidth; | |
| capture.height = video.videoHeight; | |
| capture.getContext("2d").drawImage(video, 0, 0); | |
| try { | |
| const blob = await new Promise((r) => capture.toBlob(r, "image/jpeg", 0.7)); | |
| const fd = new FormData(); | |
| fd.append("frame", blob, "frame.jpg"); | |
| const res = await fetch("/api/face", { method: "POST", body: fd }); | |
| const data = await res.json(); | |
| draw(data.faces || []); | |
| } catch (e) { | |
| /* drop the occasional failed frame */ | |
| } | |
| busy = false; | |
| } | |
| function draw(faces) { | |
| const sx = overlay.width / (video.videoWidth || overlay.width); | |
| const sy = overlay.height / (video.videoHeight || overlay.height); | |
| ctx.clearRect(0, 0, overlay.width, overlay.height); | |
| ctx.lineWidth = 2; | |
| ctx.strokeStyle = "#00b3ff"; | |
| ctx.fillStyle = "#00b3ff"; | |
| ctx.font = "18px sans-serif"; | |
| faces.forEach((f) => { | |
| const [x, y, w, h] = f.box; | |
| ctx.strokeRect(x * sx, y * sy, w * sx, h * sy); | |
| const label = f.emotion + " (" + f.signal + ")"; | |
| ctx.fillText(label, x * sx, y * sy > 20 ? y * sy - 6 : y * sy + 16); | |
| if (counts[f.signal] !== undefined) counts[f.signal]++; | |
| }); | |
| renderChips(); | |
| } | |
| })(); | |