Emotion-engine / static /script.js
Niranjan-ninja's picture
Deploy: AIIO Expression Analyzer - clean build (no venv)
3ee866e
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)}<span style="font-size:12px;color:var(--text-muted)">ms</span>`;
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 = '<li style="color:var(--text-muted); padding:1rem 0;">Synchronizing neuro-links...</li>';
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)}<span style="font-size:12px;color:var(--text-muted)">ms</span>`;
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: <b>${subject.confidence.toFixed(1)}%</b>`;
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 = `
<span class="emotion-name">${emo}</span>
<div class="bar-track">
<div class="bar-fill" style="width: 0%"></div>
</div>
<span class="emotion-value">${val}%</span>
`;
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 = '<li style="color:var(--text-muted); padding:1rem 0;">Awaiting frontal or profile focus...</li>';
}
}
function resetDashboard() {
sysStatus.textContent = "Awaiting Input";
sysStatus.style.color = "var(--text-muted)";
countPeople.textContent = "0";
countAnimals.textContent = "0";
latencyVal.innerHTML = '--<span style="font-size:12px;color:var(--text-muted)">ms</span>';
emotionLabel.innerHTML = "N/A";
confidenceLabel.innerHTML = "Confidence: ---%";
ageLabel.innerHTML = "--";
emotionBars.innerHTML = '<li style="text-align:center; color: var(--text-muted); padding: 1rem 0;">Awaiting neuro-visual data...</li>';
}
// --- 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);
});
}
});