document.addEventListener('DOMContentLoaded', () => { // Toggle logic const btnWebcam = document.getElementById('btn-webcam'); const btnUpload = document.getElementById('btn-upload'); const webcamContainer = document.getElementById('webcam-container'); const uploadContainer = document.getElementById('upload-container'); // Stats layout const sysStatus = document.getElementById('system-status'); const countPeople = document.getElementById('count-people'); const countAnimals = document.getElementById('count-animals'); const latencyVal = document.getElementById('latency-val'); // Emotion Display const emotionLabel = document.getElementById('emotion-label'); const confidenceLabel = document.getElementById('confidence-label'); const ageLabel = document.getElementById('age-label'); const emotionBars = document.getElementById('emotion-bars'); // Upload components const dropZone = document.getElementById('drop-zone'); const fileInput = document.getElementById('file-input'); const browseBtn = document.getElementById('browse-btn'); const previewArea = document.getElementById('preview-area'); const imagePreview = document.getElementById('image-preview'); const uploadCanvas = document.getElementById('upload-canvas'); const resetBtn = document.getElementById('reset-btn'); const analyzeBtn = document.getElementById('analyze-btn'); // Webcam components const webcamVideo = document.getElementById('webcam-video'); const webcamCanvas = document.getElementById('webcam-canvas'); const startWebcamBtn = document.getElementById('start-webcam-btn'); const stopWebcamBtn = document.getElementById('stop-webcam-btn'); let stream = null; let webcamInterval = null; const WEBCAM_FPS = 5; let isPredicting = false; let selectedFile = null; // --- VIEW TOGGLER --- btnWebcam.addEventListener('click', () => { btnWebcam.classList.add('active'); btnUpload.classList.remove('active'); webcamContainer.classList.remove('hidden'); uploadContainer.classList.add('hidden'); }); btnUpload.addEventListener('click', () => { btnUpload.classList.add('active'); btnWebcam.classList.remove('active'); uploadContainer.classList.remove('hidden'); webcamContainer.classList.add('hidden'); stopWebcam(); }); // --- UPLOAD LOGIC --- browseBtn.addEventListener('click', (e) => { e.preventDefault(); fileInput.click(); }); dropZone.addEventListener('click', (e) => { if (e.target !== browseBtn) fileInput.click(); }); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.style.borderColor = "#3b82f6"; }); dropZone.addEventListener('dragleave', () => { dropZone.style.borderColor = "rgba(255,255,255,0.15)"; }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.style.borderColor = "rgba(255,255,255,0.15)"; if (e.dataTransfer.files.length) handleFile(e.dataTransfer.files[0]); }); fileInput.addEventListener('change', () => { if (fileInput.files.length) handleFile(fileInput.files[0]); }); function handleFile(file) { if (!file.type.startsWith('image/')) return alert('Target must be an image.'); selectedFile = file; const reader = new FileReader(); reader.onload = (e) => { imagePreview.src = e.target.result; imagePreview.onload = () => { uploadCanvas.width = imagePreview.width; uploadCanvas.height = imagePreview.height; uploadCanvas.getContext('2d').clearRect(0, 0, uploadCanvas.width, uploadCanvas.height); }; dropZone.classList.add('hidden'); previewArea.classList.remove('hidden'); }; reader.readAsDataURL(file); } resetBtn.addEventListener('click', () => { selectedFile = null; fileInput.value = ''; previewArea.classList.add('hidden'); dropZone.classList.remove('hidden'); uploadCanvas.getContext('2d').clearRect(0, 0, uploadCanvas.width, uploadCanvas.height); resetDashboard(); }); analyzeBtn.addEventListener('click', async () => { if (!selectedFile) return; analyzeBtn.disabled = true; sysStatus.textContent = "Processing Static Target..."; const formData = new FormData(); formData.append('file', selectedFile); try { const startTick = performance.now(); const res = await fetch('/api/predict', { method: 'POST', body: formData }); const data = await res.json(); const endTick = performance.now(); latencyVal.innerHTML = `${Math.round(endTick - startTick)}ms`; if (data.success) { renderDashboard(data.data, uploadCanvas, imagePreview, false); sysStatus.textContent = "Analysis Complete"; } else { alert('Inference architecture failed: ' + data.error); sysStatus.textContent = "Inference Failed"; } } catch (error) { sysStatus.textContent = "Network Timeout"; } finally { analyzeBtn.disabled = false; } }); // --- WEBCAM LOGIC --- startWebcamBtn.addEventListener('click', async () => { try { stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "user" } }); webcamVideo.srcObject = stream; webcamVideo.onloadedmetadata = () => { webcamVideo.play(); webcamCanvas.width = webcamVideo.clientWidth; webcamCanvas.height = webcamVideo.clientHeight; startWebcamBtn.classList.add('hidden'); stopWebcamBtn.classList.remove('hidden'); sysStatus.textContent = "Tracking Active Stream..."; sysStatus.style.color = "#34d399"; emotionBars.innerHTML = '
  • Synchronizing neuro-links...
  • '; webcamInterval = setInterval(processWebcamFrame, 1000 / WEBCAM_FPS); }; } catch (e) { alert("Unable to access camera hardware."); } }); stopWebcamBtn.addEventListener('click', stopWebcam); function stopWebcam() { if (stream) stream.getTracks().forEach(t => t.stop()); stream = null; if (webcamInterval) clearInterval(webcamInterval); webcamInterval = null; startWebcamBtn.classList.remove('hidden'); stopWebcamBtn.classList.add('hidden'); webcamCanvas.getContext('2d').clearRect(0, 0, webcamCanvas.width, webcamCanvas.height); resetDashboard(); } async function processWebcamFrame() { if (isPredicting) return; isPredicting = true; const hiddenCanvas = document.createElement('canvas'); hiddenCanvas.width = webcamVideo.videoWidth; hiddenCanvas.height = webcamVideo.videoHeight; hiddenCanvas.getContext('2d').drawImage(webcamVideo, 0, 0, hiddenCanvas.width, hiddenCanvas.height); const dataUrl = hiddenCanvas.toDataURL('image/jpeg', 0.8); const formData = new FormData(); formData.append('image_base64', dataUrl); try { const sTick = performance.now(); const res = await fetch('/api/predict_frame', { method: 'POST', body: formData }); const data = await res.json(); const eTick = performance.now(); latencyVal.innerHTML = `${Math.round(eTick - sTick)}ms`; if (data.success) { // Ensure layout scaling matches strictly to responsive design flexbox elements! webcamCanvas.width = webcamVideo.clientWidth; webcamCanvas.height = webcamVideo.clientHeight; renderDashboard(data.data, webcamCanvas, webcamVideo, true); } } catch (e) { console.error(e); } finally { isPredicting = false; } } // --- UNIFIED DASHBOARD RENDERING ROUTINE --- function renderDashboard(data, canvas, sourceMedia, isWebcam) { countPeople.textContent = data.counts.people; countAnimals.textContent = data.counts.animals || 0; // Upload payload supports animal counts // 1. Plot bounding boxes on the grid layout drawBoxes(canvas, sourceMedia, data, isWebcam); // 2. Update Primary Subject Display Block if (data.faces.length > 0) { const subject = data.faces[0]; emotionLabel.innerHTML = subject.prediction; confidenceLabel.innerHTML = `Confidence: ${subject.confidence.toFixed(1)}%`; ageLabel.innerHTML = subject.age; emotionBars.innerHTML = ''; const scores = subject.all_scores; if (scores) { const sorted = Object.keys(scores).sort((a, b) => scores[b] - scores[a]); sorted.forEach(emo => { const val = scores[emo]; const li = document.createElement('li'); li.className = 'emotion-item'; li.innerHTML = ` ${emo}
    ${val}% `; emotionBars.appendChild(li); setTimeout(() => { li.querySelector('.bar-fill').style.width = `${val}%`; }, 10); }); } } else { emotionLabel.innerHTML = "N/A"; confidenceLabel.innerHTML = "Tracking Empty Sequence"; ageLabel.innerHTML = "--"; emotionBars.innerHTML = '
  • Awaiting frontal or profile focus...
  • '; } } function resetDashboard() { sysStatus.textContent = "Awaiting Input"; sysStatus.style.color = "var(--text-muted)"; countPeople.textContent = "0"; countAnimals.textContent = "0"; latencyVal.innerHTML = '--ms'; emotionLabel.innerHTML = "N/A"; confidenceLabel.innerHTML = "Confidence: ---%"; ageLabel.innerHTML = "--"; emotionBars.innerHTML = '
  • Awaiting neuro-visual data...
  • '; } // --- CANVAS BOX DRAWING LAYER --- function drawBoxes(canvas, media, data, isWebcam) { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); let iW = isWebcam ? media.videoWidth : media.naturalWidth; let iH = isWebcam ? media.videoHeight : media.naturalHeight; const scaleX = canvas.width / iW; const scaleY = canvas.height / iH; // Overlay YOLO Objects in the background hierarchy data.objects.forEach(obj => { const [x, y, w, h] = obj.box; const sx = x * scaleX, sy = y * scaleY, sw = w * scaleX, sh = h * scaleY; ctx.lineWidth = 2; ctx.strokeStyle = "rgba(16, 185, 129, 0.4)"; ctx.strokeRect(sx, sy, sw, sh); ctx.fillStyle = "rgba(16, 185, 129, 0.4)"; ctx.font = "bold 12px Inter"; const txt = `${obj.label} ${obj.confidence.toFixed(0)}%`; ctx.fillRect(sx, sy - 18, ctx.measureText(txt).width + 8, 18); ctx.fillStyle = "#fff"; ctx.fillText(txt, sx + 4, sy - 4); }); // Target Deep Learning Faces directly connected to BiLSTM outputs data.faces.forEach((face, idx) => { const [x, y, w, h] = face.box; const sx = x * scaleX, sy = y * scaleY, sw = w * scaleX, sh = h * scaleY; const boxCol = idx === 0 ? '#3b82f6' : '#ec4899'; ctx.strokeStyle = boxCol; ctx.lineWidth = 3; ctx.strokeRect(sx, sy, sw, sh); const fontSize = Math.max(12, Math.min(sh * 0.15, 20)); ctx.font = `bold ${fontSize}px Inter`; const txt = `${face.prediction}`; ctx.shadowColor = "rgba(0,0,0,0.5)"; ctx.shadowBlur = 10; ctx.fillStyle = boxCol; ctx.fillRect(sx, sy - fontSize - 10, ctx.measureText(txt).width + 16, fontSize + 10); ctx.shadowBlur = 0; ctx.fillStyle = "#fff"; ctx.fillText(txt, sx + 8, sy - 8); }); } });