Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>ISL Smart Glove Debugger</title> | |
| <style> | |
| body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: #f4f4f9; color: #333; margin: 0; padding: 20px; display: flex; flex-direction: column; align-items: center; } | |
| .container { max-width: 950px; width: 100%; display: flex; gap: 20px; } | |
| .panel { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); flex: 1; } | |
| h1 { margin-top: 0; color: #222; font-size: 1.5em; width: 100%; max-width: 950px; border-bottom: 2px solid #ddd; padding-bottom: 10px; margin-bottom: 20px; } | |
| h2 { margin-top: 0; color: #444; font-size: 1.2em; border-bottom: 1px solid #eee; padding-bottom: 10px; } | |
| .sensor-list { list-style: none; padding: 0; margin: 0; max-height: 500px; overflow-y: auto; padding-right: 10px; } | |
| .sensor-item { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #f9f9f9; } | |
| .sensor-item label { width: 100px; font-weight: bold; font-size: 0.85em; color: #555; } | |
| .sensor-item input[type=range] { flex: 1; margin: 0 10px; } | |
| .sensor-item input[type=number] { width: 70px; text-align: right; font-family: monospace; font-size: 0.9em; background: #eee; padding: 2px 5px; border: 1px solid #ccc; border-radius: 3px; } | |
| .array-view { background: #1e1e1e; color: #4af626; padding: 15px; border-radius: 6px; font-family: monospace; font-size: 0.9em; width: 100%; height: 100px; resize: none; border: none; outline: none; margin-bottom: 20px; box-shadow: inset 0 2px 4px rgba(0,0,0,0.3); } | |
| .btn { background: #007bff; color: white; border: none; padding: 12px 15px; border-radius: 6px; cursor: pointer; font-weight: bold; font-size: 1em; width: 100%; margin-bottom: 10px; transition: background 0.2s; } | |
| .btn:hover { background: #0056b3; } | |
| .btn-secondary { background: #6c757d; } | |
| .btn-secondary:hover { background: #5a6268; } | |
| .btn-danger { background: #dc3545; } | |
| .btn-danger:hover { background: #c82333; } | |
| .result-box { text-align: center; margin-top: 30px; border-top: 2px dashed #eee; padding-top: 20px; } | |
| .result-box h3 { margin: 0; font-size: 0.9em; color: #888; text-transform: uppercase; letter-spacing: 1px; } | |
| .word { font-size: 3em; font-weight: bold; color: #28a745; margin: 10px 0; } | |
| .buffer { background: #e9ecef; padding: 15px; border-radius: 6px; min-height: 24px; font-size: 1.1em; color: #333; margin-top: 10px; border: 1px solid #ddd; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>ISL Smart Glove Debugger</h1> | |
| <div class="container"> | |
| <div class="panel"> | |
| <h2>Sensor List Controls</h2> | |
| <ul class="sensor-list" id="sensorList"></ul> | |
| </div> | |
| <div class="panel"> | |
| <h2>Raw Features Array (Edit/Paste)</h2> | |
| <textarea class="array-view" id="arrayView" oninput="updateFromText(this.value)">[0.0, 0.0, ...]</textarea> | |
| <button class="btn" onclick="makePrediction()">Send to API / Predict</button> | |
| <button class="btn btn-secondary" onclick="loadExampleData()">Generate Random Valid Gesture</button> | |
| <button class="btn btn-danger" onclick="resetData()">Reset to Zeros</button> | |
| <div class="result-box"> | |
| <h3>Predicted Gesture</h3> | |
| <div class="word" id="predictedWord">--</div> | |
| <h3 style="margin-top: 20px;">Sentence Buffer</h3> | |
| <div class="buffer" id="sentenceBuffer">...</div> | |
| <button class="btn btn-danger" style="margin-top: 10px;" onclick="clearBuffer()">Clear Sentence</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const featureNames = [ | |
| "Flex 1", "Flex 2", "Flex 3", "Flex 4", "Flex 5", | |
| "Flex 6", "Flex 7", "Flex 8", "Flex 9", "Flex 10", | |
| "MPU1 Ac_X", "MPU1 Ac_Y", "MPU1 Ac_Z", | |
| "MPU1 Gy_X", "MPU1 Gy_Y", "MPU1 Gy_Z", | |
| "MPU2 Ac_X", "MPU2 Ac_Y", "MPU2 Ac_Z", | |
| "MPU2 Gy_X", "MPU2 Gy_Y", "MPU2 Gy_Z" | |
| ]; | |
| let sensors = new Array(22).fill(0.0); | |
| let wordBuffer = []; | |
| const listEl = document.getElementById('sensorList'); | |
| const arrayView = document.getElementById('arrayView'); | |
| function renderList() { | |
| listEl.innerHTML = ''; | |
| featureNames.forEach((name, i) => { | |
| const li = document.createElement('li'); | |
| li.className = 'sensor-item'; | |
| // Flex sensors typically map from 0 to 90 degrees or arbitrary voltage. | |
| // MPUs map from -2g to +2g or similar. We use -30 to +30 as a general sandbox. | |
| li.innerHTML = ` | |
| <label>${name}</label> | |
| <input type="range" id="slider${i}" min="-30" max="30" step="0.01" value="${sensors[i]}" oninput="updateSensor(${i}, this.value, 'slider')"> | |
| <input type="number" id="val${i}" step="0.01" value="${sensors[i].toFixed(2)}" oninput="updateSensor(${i}, this.value, 'number')"> | |
| `; | |
| listEl.appendChild(li); | |
| }); | |
| updateArrayView(); | |
| } | |
| function updateSensor(i, val, source) { | |
| sensors[i] = parseFloat(val) || 0.0; | |
| // Only update the UI elements that AREN'T the source of the change | |
| if (source !== 'slider') { | |
| const slider = document.getElementById(`slider${i}`); | |
| if (slider) slider.value = sensors[i]; | |
| } | |
| if (source !== 'number') { | |
| const numInput = document.getElementById(`val${i}`); | |
| if (numInput) numInput.value = sensors[i].toFixed(2); | |
| } | |
| updateArrayView('internal'); | |
| } | |
| function updateArrayView(source) { | |
| if (source === 'internal') { | |
| const text = '[\n ' + sensors.map(s => s.toFixed(2)).join(', ') + '\n]'; | |
| arrayView.value = text; | |
| } | |
| } | |
| function updateFromText(val) { | |
| try { | |
| // Try to extract numbers from the string (handles brackets, commas, spaces) | |
| const matches = val.match(/-?\d+(\.\d+)?/g); | |
| if (matches && matches.length >= 22) { | |
| matches.slice(0, 22).forEach((num, i) => { | |
| sensors[i] = parseFloat(num); | |
| const slider = document.getElementById(`slider${i}`); | |
| const numInput = document.getElementById(`val${i}`); | |
| if (slider) slider.value = sensors[i]; | |
| if (numInput) numInput.value = sensors[i].toFixed(2); | |
| }); | |
| } | |
| } catch (e) { | |
| console.error("Parse error", e); | |
| } | |
| } | |
| function loadExampleData() { | |
| // Pick a random predefined sign from the dictionary | |
| const signKeys = Object.keys(preDefinedSigns); | |
| const randomKey = signKeys[Math.floor(Math.random() * signKeys.length)]; | |
| const baseValues = preDefinedSigns[randomKey]; | |
| for (let i = 0; i < 22; i++) { | |
| // Apply correct noise scales based on sensor type (matching your Python dictionary) | |
| let noise = 0; | |
| if (i < 10) { | |
| // Flex Sensors: +/- 0.05 | |
| noise = (Math.random() * 0.10) - 0.05; | |
| } else if ((i - 10) % 6 < 3) { | |
| // Accelerometers (Indices 10,11,12 and 16,17,18): +/- 0.05 | |
| // 1.0 noise was destroying the gravity vector! | |
| noise = (Math.random() * 0.10) - 0.05; | |
| } else { | |
| // Gyroscopes (Indices 13,14,15 and 19,20,21): +/- 2.0 | |
| noise = (Math.random() * 4.0) - 2.0; | |
| } | |
| sensors[i] = parseFloat((baseValues[i] + noise).toFixed(2)); | |
| const slider = document.getElementById(`slider${i}`); | |
| if (slider) slider.value = sensors[i]; | |
| const numInput = document.getElementById(`val${i}`); | |
| if (numInput) numInput.value = sensors[i].toFixed(2); | |
| } | |
| updateArrayView(); | |
| // Removed auto-predict so you have to click Predict manually | |
| } | |
| function resetData() { | |
| sensors = new Array(22).fill(0.0); | |
| renderList(); | |
| } | |
| const preDefinedSigns = { | |
| "namaskar": [0.10, 0.10, 0.10, 0.10, 0.10, 0.00, 0.80, 0.60, 0.0, 0.0, 0.0, 0.10, 0.10, 0.10, 0.10, 0.10, 0.00, 0.80, 0.60, 0.0, 0.0, 0.0], | |
| "alvida": [0.10, 0.10, 0.10, 0.10, 0.10, 0.85, 0.00, 0.50, 0.0, 0.0, 20.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "shukriya": [0.10, 0.10, 0.10, 0.10, 0.10, 0.30, 0.80, 0.50, 0.0, 15.0, 5.0, 0.55, 0.55, 0.55, 0.55, 0.55, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "maafi": [0.85, 0.85, 0.85, 0.85, 0.85, -0.40, 0.00, 0.90, 3.0, 3.0, 10.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "haan": [0.90, 0.90, 0.90, 0.90, 0.90, 0.00, 0.50, 0.85, 0.0, 20.0, 0.0, 0.65, 0.65, 0.65, 0.65, 0.65, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "nahi": [0.85, 0.10, 0.90, 0.90, 0.90, 0.00, 0.00, 0.95, 0.0, 0.0, 25.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "madad": [0.10, 0.85, 0.85, 0.85, 0.85, 0.00, 0.60, 0.80, 0.0, 10.0, 5.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "doctor": [0.50, 0.10, 0.85, 0.85, 0.85, 0.00, 0.30, 0.95, 0.0, 5.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "dard": [0.60, 0.60, 0.60, 0.60, 0.60, -0.50, 0.00, 0.85, 10.0, 0.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, -0.50, 0.00, 0.85, 10.0, 0.0, 0.0], | |
| "call": [0.10, 0.85, 0.85, 0.85, 0.10, 0.50, 0.80, 0.30, 0.0, 0.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "aag": [0.60, 0.55, 0.55, 0.55, 0.55, 0.10, 0.10, 0.98, 0.0, 0.0, 18.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "accident": [0.85, 0.85, 0.85, 0.85, 0.85, 0.00, 0.10, 0.99, 15.0, 0.0, 0.0, 0.85, 0.85, 0.85, 0.85, 0.85, 0.00, 0.10, 0.99, -15.0, 0.0, 0.0], | |
| "chor": [0.85, 0.55, 0.90, 0.90, 0.90, 0.00, 0.00, 0.95, 0.0, 0.0, 20.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "rona": [0.85, 0.10, 0.90, 0.90, 0.90, 0.40, 0.90, 0.15, 0.0, -15.0, 0.0, 0.85, 0.10, 0.90, 0.90, 0.90, 0.40, 0.90, 0.15, 0.0, -15.0, 0.0], | |
| "paani": [0.90, 0.10, 0.10, 0.10, 0.90, 0.30, 0.80, 0.50, 0.0, -8.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "khana": [0.40, 0.40, 0.40, 0.40, 0.40, 0.00, 0.75, 0.65, 0.0, -8.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "ruko": [0.10, 0.10, 0.10, 0.10, 0.10, 0.85, 0.00, 0.50, 20.0, 5.0, 0.0, 0.10, 0.10, 0.10, 0.10, 0.10, 0.80, 0.00, 0.55, 0.0, 0.0, 0.0], | |
| "aur": [0.40, 0.40, 0.40, 0.40, 0.40, 0.00, 0.20, 0.95, 5.0, 5.0, 0.0, 0.40, 0.40, 0.40, 0.40, 0.40, 0.00, 0.20, 0.95, 5.0, 5.0, 0.0], | |
| "main": [0.85, 0.10, 0.90, 0.90, 0.90, -0.60, 0.00, 0.80, 0.0, 0.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "tum": [0.85, 0.10, 0.90, 0.90, 0.90, 0.00, 0.00, 0.95, 0.0, 0.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "ghar": [0.10, 0.10, 0.10, 0.10, 0.10, 0.00, 0.95, 0.30, 0.0, 0.0, 0.0, 0.10, 0.10, 0.10, 0.10, 0.10, 0.00, 0.95, 0.30, 0.0, 0.0, 0.0], | |
| "school": [0.10, 0.10, 0.10, 0.10, 0.10, 0.00, 0.10, 0.99, 0.0, -12.0, 0.0, 0.10, 0.10, 0.10, 0.10, 0.10, 0.00, 0.10, 0.99, 0.0, 0.0, 0.0], | |
| "khushi": [0.10, 0.10, 0.10, 0.10, 0.10, -0.50, 0.00, 0.87, 0.0, 12.0, 0.0, 0.10, 0.10, 0.10, 0.10, 0.10, -0.50, 0.00, 0.87, 0.0, 12.0, 0.0], | |
| "dukh": [0.10, 0.10, 0.10, 0.10, 0.10, 0.40, 0.90, 0.15, 0.0, -10.0, 0.0, 0.10, 0.10, 0.10, 0.10, 0.10, 0.40, 0.90, 0.15, 0.0, -10.0, 0.0], | |
| "gussa": [0.70, 0.70, 0.70, 0.70, 0.70, 0.00, 0.00, 0.98, 0.0, -18.0, 0.0, 0.70, 0.70, 0.70, 0.70, 0.70, 0.00, 0.00, 0.98, 0.0, -18.0, 0.0], | |
| "neend": [0.10, 0.10, 0.10, 0.10, 0.10, 0.70, 0.70, 0.15, 0.0, 0.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "ek": [0.85, 0.10, 0.90, 0.90, 0.90, 0.00, 0.00, 0.98, 0.0, 0.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "do": [0.85, 0.10, 0.10, 0.90, 0.90, 0.00, 0.00, 0.98, 0.0, 0.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "paanch": [0.10, 0.10, 0.10, 0.10, 0.10, 0.85, 0.00, 0.53, 0.0, 0.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "kya": [0.20, 0.20, 0.20, 0.20, 0.20, 0.00, 0.00, 0.95, 0.0, 0.0, 15.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "kahan": [0.85, 0.10, 0.90, 0.90, 0.90, 0.00, 0.00, 0.95, 0.0, 0.0, 18.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "dekhna": [0.85, 0.10, 0.10, 0.90, 0.90, 0.40, 0.80, 0.45, 0.0, 0.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "upar": [0.85, 0.10, 0.90, 0.90, 0.90, 0.00, 0.00, 0.98, 0.0, 15.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0], | |
| "neeche": [0.85, 0.10, 0.90, 0.90, 0.90, 0.00, 0.00, 0.98, 0.0, -15.0, 0.0, 0.60, 0.60, 0.60, 0.60, 0.60, 0.00, 0.00, 1.00, 0.0, 0.0, 0.0] | |
| }; | |
| function clearBuffer() { | |
| wordBuffer = []; | |
| document.getElementById('sentenceBuffer').innerText = '...'; | |
| document.getElementById('predictedWord').innerText = '--'; | |
| } | |
| async function makePrediction() { | |
| const btn = document.querySelector('.btn'); | |
| const oldText = btn.innerText; | |
| btn.innerText = 'Predicting...'; | |
| try { | |
| const response = await fetch('/predict', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ features: sensors, word_buffer: wordBuffer }) | |
| }); | |
| if (!response.ok) { | |
| const err = await response.json(); | |
| throw new Error(err.detail || 'API Error'); | |
| } | |
| const data = await response.json(); | |
| document.getElementById('predictedWord').innerText = data.predicted_word; | |
| wordBuffer = data.word_buffer; | |
| document.getElementById('sentenceBuffer').innerText = wordBuffer.join(' ') || '...'; | |
| } catch (e) { | |
| alert('Failed: ' + e.message); | |
| } finally { | |
| btn.innerText = oldText; | |
| } | |
| } | |
| // Initialize UI on load | |
| renderList(); | |
| </script> | |
| </body> | |
| </html> | |