handsign_ml_api / static /index.html
sanketskg's picture
Upload folder using huggingface_hub
da4d2e2 verified
Raw
History Blame Contribute Delete
14.8 kB
<!DOCTYPE html>
<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>