smart-sign / static /script.js
Youssefebrahim1's picture
Upload 3 files
5f7c461 verified
Raw
History Blame Contribute Delete
17.6 kB
// ==================== DOM Elements ====================
// Mode Elements
const letterModeBtn = document.getElementById('letterModeBtn');
const videoModeBtn = document.getElementById('videoModeBtn');
const modeStatus = document.getElementById('modeStatus');
// Modal Elements
const devModal = document.getElementById('devModal');
const modalCloseBtn = document.getElementById('modalCloseBtn');
const continueBtn = document.getElementById('continueBtn');
// Camera & Detection Elements
const cameraSection = document.getElementById('cameraSection');
const controlsSection = document.getElementById('controlsSection');
const statsSection = document.getElementById('statsSection');
const sentenceSection = document.getElementById('sentenceSection');
const clearBtn = document.getElementById('clearBtn');
// Control Elements
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const statusDisplay = document.getElementById('statusDisplay');
// Display Elements
const fpsDisplay = document.getElementById('fpsDisplay');
const confidenceDisplay = document.getElementById('confidenceDisplay');
const detectedLetter = document.getElementById('detectedLetter');
const sentenceOutput = document.getElementById('sentenceOutput');
// ==================== State Management ====================
const state = {
currentMode: 'letter',
isStreaming: false,
sentenceBuffer: [],
model: null,
predHistory: [],
stableLetter: '--',
fps: 0
};
// Constants
const SMOOTH_WINDOW = 8;
let detectionInterval = null;
let videoElement = null;
let lastFrameTime = performance.now();
let frameCount = 0;
// ==================== Modal Functions ====================
function showDevModal() {
devModal.style.display = 'flex';
document.body.style.overflow = 'hidden';
}
function hideDevModal() {
devModal.style.display = 'none';
document.body.style.overflow = 'auto';
}
// ==================== Mode Switching ====================
function switchToLetterMode() {
state.currentMode = 'letter';
if (state.isStreaming) {
stopDetection();
}
state.sentenceBuffer = [];
state.predHistory = [];
sentenceOutput.textContent = 'Your translated text will appear here...';
modeStatus.textContent = '🟢 Letter Detection Mode Active';
letterModeBtn.className = 'primary-btn';
videoModeBtn.className = 'secondary-btn';
cameraSection.classList.add('hidden-section');
statsSection.classList.add('hidden-section');
sentenceSection.classList.add('hidden-section');
clearBtn.classList.add('hidden-section');
controlsSection.classList.remove('hidden-section');
statusDisplay.textContent = '🔴 Ready to start detection';
statusDisplay.style.color = '';
fpsDisplay.textContent = '0';
confidenceDisplay.textContent = '0%';
confidenceDisplay.style.color = '';
detectedLetter.textContent = '--';
startBtn.disabled = false;
stopBtn.disabled = true;
startBtn.classList.replace('secondary-btn', 'primary-btn');
stopBtn.classList.replace('primary-btn', 'secondary-btn');
}
function switchToVideoMode() {
showDevModal();
letterModeBtn.className = 'primary-btn';
videoModeBtn.className = 'secondary-btn';
modeStatus.textContent = '🟢 Letter Detection Mode Active';
}
// ==================== Model Loading ====================
async function loadModel() {
statusDisplay.textContent = '🔴 Ready to start detection';
statusDisplay.style.color = '';
return true;
}
// ==================== Camera Functions ====================
async function startCamera() {
try {
videoElement = document.createElement('video');
videoElement.autoplay = true;
videoElement.playsInline = true;
videoElement.width = 640;
videoElement.height = 480;
videoElement.style.display = 'block';
const stream = await navigator.mediaDevices.getUserMedia({
video: { width: 640, height: 480, facingMode: 'user' }
});
videoElement.srcObject = stream;
const rawCamFrame = document.getElementById('rawCamFrame');
rawCamFrame.innerHTML = ''; // remove placeholder
rawCamFrame.appendChild(videoElement);
await new Promise(resolve => {
videoElement.onloadedmetadata = () => resolve();
});
return true;
} catch (error) {
console.error('❌ Camera error:', error);
statusDisplay.textContent = '❌ Camera not available';
statusDisplay.style.color = '#ff6b6b';
return false;
}
}
function stopCamera() {
if (videoElement && videoElement.srcObject) {
const tracks = videoElement.srcObject.getTracks();
tracks.forEach(track => track.stop());
videoElement.srcObject = null;
}
}
// ==================== Helper Functions ====================
function getStablePrediction() {
if (state.predHistory.length === 0) return 'nothing';
const counts = {};
state.predHistory.forEach(pred => {
counts[pred] = (counts[pred] || 0) + 1;
});
return Object.keys(counts).reduce((a, b) => counts[a] > counts[b] ? a : b);
}
function updateSentence(pred, confidence) {
// Only add to sentence if confidence is high enough
if (confidence < 70) return;
if (pred === 'del') {
if (state.sentenceBuffer.length > 0) {
state.sentenceBuffer.pop();
}
} else if (pred === 'space') {
state.sentenceBuffer.push(' ');
} else if (pred !== 'nothing' && pred !== 'unknown' && pred !== '--') {
if (state.sentenceBuffer.length === 0 ||
state.sentenceBuffer[state.sentenceBuffer.length - 1] !== pred) {
state.sentenceBuffer.push(pred);
if (state.sentenceBuffer.length > 20) {
state.sentenceBuffer.shift();
}
}
}
}
// ==================== Display Processed Image ====================
function displayProcessedImage(base64ImageData) {
const processedCamFrame = document.getElementById('processedCamFrame');
if (!processedCamFrame) return;
let processedImg = document.getElementById('processedImage');
if (!processedImg) {
processedImg = document.createElement('img');
processedImg.id = 'processedImage';
processedImg.style.width = '100%';
processedImg.style.borderRadius = '10px';
processedImg.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
processedCamFrame.innerHTML = ''; // Clear placeholder
processedCamFrame.appendChild(processedImg);
}
processedImg.src = base64ImageData;
}
function updateUIDisplay(result, stablePred) {
// Update detected letter with stable prediction
detectedLetter.textContent = stablePred;
// Update confidence
confidenceDisplay.textContent = `${result.confidence.toFixed(1)}%`;
// Color code confidence
if (result.confidence > 85) {
confidenceDisplay.style.color = '#51cf66';
} else if (result.confidence > 70) {
confidenceDisplay.style.color = '#ffd43b';
} else {
confidenceDisplay.style.color = '#ff6b6b';
}
// Update sentence
sentenceOutput.textContent = state.sentenceBuffer.join('');
// Add current detection info if not already present
let currentDet = document.getElementById('currentDetection');
if (!currentDet) {
currentDet = document.createElement('div');
currentDet.id = 'currentDetection';
currentDet.style.fontSize = '0.9rem';
currentDet.style.color = '#888';
currentDet.style.marginTop = '5px';
document.querySelector('.letter-display').appendChild(currentDet);
}
if (result.detected_letter && result.detected_letter !== '--') {
currentDet.textContent = `Raw: ${result.detected_letter}`;
} else {
currentDet.textContent = '';
}
}
// ==================== Detection Functions ====================
// ==================== Detection Functions ====================
async function predictLetter() {
if (!videoElement) return null;
try {
// Capture current frame
const canvas = document.createElement('canvas');
canvas.width = videoElement.videoWidth;
canvas.height = videoElement.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
// Convert to blob
const blob = await new Promise(resolve => {
canvas.toBlob(resolve, 'image/jpeg', 0.8);
});
const formData = new FormData();
formData.append('file', blob, 'frame.jpg');
console.log('📤 Sending frame for detection...');
const response = await fetch('/api/detect', {
method: 'POST',
body: formData
});
const result = await response.json();
console.log('📥 API Response:', result);
if (result.success) {
// Display the processed image with landmarks
if (result.processed_image) {
displayProcessedImage(result.processed_image);
}
// Update state
if (result.detected_letter !== "nothing") {
state.predHistory.push(result.detected_letter);
if (state.predHistory.length > SMOOTH_WINDOW) {
state.predHistory.shift();
}
}
// Update UI displays
detectedLetter.textContent = result.detected_letter;
confidenceDisplay.textContent = `${result.confidence.toFixed(1)}%`;
// Update sentence
updateSentence(result.detected_letter, result.confidence);
sentenceOutput.textContent = state.sentenceBuffer.join('');
// Color code confidence
if (result.confidence > 85) {
confidenceDisplay.style.color = '#51cf66';
} else if (result.confidence > 70) {
confidenceDisplay.style.color = '#ffd43b';
} else {
confidenceDisplay.style.color = '#ff6b6b';
}
// Update current detection info
let currentDet = document.getElementById('currentDetection');
if (!currentDet) {
currentDet = document.createElement('div');
currentDet.id = 'currentDetection';
currentDet.style.fontSize = '0.9rem';
currentDet.style.color = '#888';
currentDet.style.marginTop = '5px';
document.querySelector('.letter-display').appendChild(currentDet);
}
if (result.detected_letter && result.detected_letter !== '--') {
currentDet.textContent = `Confidence: ${result.confidence.toFixed(1)}%`;
} else {
currentDet.textContent = 'No hand detected';
}
return {
letter: result.detected_letter,
confidence: result.confidence
};
} else {
console.error('Detection failed:', result.error);
confidenceDisplay.textContent = '0%';
confidenceDisplay.style.color = '#ff6b6b';
detectedLetter.textContent = 'error';
}
} catch (error) {
console.error('Prediction error:', error);
confidenceDisplay.textContent = '0%';
confidenceDisplay.style.color = '#ff6b6b';
detectedLetter.textContent = 'error';
}
return null;
}
// ==================== Start Detection ====================
async function startDetection() {
if (state.isStreaming) return;
const modelLoaded = await loadModel();
if (!modelLoaded) return;
const cameraStarted = await startCamera();
if (!cameraStarted) return;
state.isStreaming = true;
state.sentenceBuffer = [];
state.predHistory = [];
state.stableLetter = '--';
startBtn.disabled = true;
stopBtn.disabled = false;
// Show camera & stats sections
cameraSection.classList.remove('hidden-section');
statsSection.classList.remove('hidden-section');
sentenceSection.classList.remove('hidden-section');
clearBtn.classList.remove('hidden-section');
statusDisplay.textContent = '🟢 Detection Active — Show your hand!';
statusDisplay.style.color = '#51cf66';
sentenceOutput.textContent = '';
startBtn.classList.replace('primary-btn', 'secondary-btn');
stopBtn.classList.replace('secondary-btn', 'primary-btn');
lastFrameTime = performance.now();
frameCount = 0;
// Start detection loop
detectionInterval = setInterval(async () => {
if (!state.isStreaming) return;
// Calculate FPS
const now = performance.now();
frameCount++;
if (now - lastFrameTime >= 1000) {
const fps = Math.round((frameCount * 1000) / (now - lastFrameTime));
fpsDisplay.textContent = fps;
state.fps = fps;
lastFrameTime = now;
frameCount = 0;
}
// Make prediction
await predictLetter();
}, 100);
}
// ==================== Stop Detection ====================
function stopDetection() {
if (!state.isStreaming) return;
state.isStreaming = false;
if (detectionInterval) {
clearInterval(detectionInterval);
detectionInterval = null;
}
stopCamera();
startBtn.disabled = false;
stopBtn.disabled = true;
cameraSection.classList.add('hidden-section');
statsSection.classList.add('hidden-section');
sentenceSection.classList.add('hidden-section');
clearBtn.classList.add('hidden-section');
statusDisplay.textContent = '🔴 Detection Stopped';
statusDisplay.style.color = '#ff6b6b';
fpsDisplay.textContent = '0';
confidenceDisplay.textContent = '0%';
confidenceDisplay.style.color = '';
detectedLetter.textContent = '--';
sentenceOutput.textContent = 'Your translated text will appear here...';
startBtn.classList.replace('secondary-btn', 'primary-btn');
stopBtn.classList.replace('primary-btn', 'secondary-btn');
// Clear current detection text
const currentDet = document.getElementById('currentDetection');
if (currentDet) {
currentDet.textContent = '';
}
}
// ==================== Clear Sentence ====================
function clearSentence() {
state.sentenceBuffer = [];
state.predHistory = [];
sentenceOutput.textContent = 'Your translated text will appear here...';
detectedLetter.textContent = '--';
const currentDet = document.getElementById('currentDetection');
if (currentDet) {
currentDet.textContent = '';
}
}
// ==================== Event Listeners ====================
letterModeBtn.addEventListener('click', switchToLetterMode);
videoModeBtn.addEventListener('click', switchToVideoMode);
startBtn.addEventListener('click', startDetection);
stopBtn.addEventListener('click', stopDetection);
clearBtn.addEventListener('click', clearSentence);
modalCloseBtn.addEventListener('click', hideDevModal);
continueBtn.addEventListener('click', hideDevModal);
devModal.addEventListener('click', (e) => {
if (e.target === devModal) {
hideDevModal();
}
});
// ==================== Keyboard Shortcuts ====================
document.addEventListener('keydown', (e) => {
if (e.ctrlKey || e.key === ' ') {
e.preventDefault();
}
if (e.ctrlKey && e.key === 'l') {
switchToLetterMode();
}
if (e.ctrlKey && e.key === 'v') {
switchToVideoMode();
}
if (e.key === ' ' && state.currentMode === 'letter') {
if (state.isStreaming) {
stopDetection();
} else {
startDetection();
}
}
if (e.key === 'Escape') {
if (state.isStreaming) {
stopDetection();
}
if (devModal.style.display === 'flex') {
hideDevModal();
}
}
if (e.ctrlKey && e.key === 'c') {
clearSentence();
}
});
// ==================== Initialize ====================
document.addEventListener('DOMContentLoaded', () => {
switchToLetterMode();
if (typeof tf !== 'undefined') {
tf.setBackend('webgl').then(() => {
console.log('✅ TensorFlow.js backend initialized');
}).catch(error => {
console.warn('⚠️ WebGL not available, falling back to CPU');
console.log('✅ TensorFlow.js backend initialized (CPU)');
});
}
setTimeout(() => {
console.log('ASL Detection System Ready');
console.log('📝 Available shortcuts:');
console.log(' Ctrl+L - Switch to Letter Mode');
console.log(' Ctrl+V - Switch to Video Mode');
console.log(' Space - Start/Stop Detection');
console.log(' Escape - Stop/Close Modal');
console.log(' Ctrl+C - Clear Sentence');
}, 1000);
});
// ==================== Cleanup ====================
window.addEventListener('beforeunload', () => {
if (state.isStreaming) {
stopDetection();
}
if (state.model && typeof tf !== 'undefined') {
state.model.dispose();
}
if (typeof tf !== 'undefined') {
tf.disposeVariables();
}
});