VISHAL18for4 commited on
Commit
40884f1
Β·
verified Β·
1 Parent(s): afe3ae7

Update public/script.js

Browse files
Files changed (1) hide show
  1. public/script.js +102 -148
public/script.js CHANGED
@@ -44,14 +44,15 @@ let cameraVisible = true;
44
  let savedCount = 0;
45
  let frameCount = 0;
46
  let lastFpsUpdate = performance.now();
47
- let handLandmarker = null;
48
  let cameraStream = null;
49
  let isModelReady = false;
50
  let canvasWidth = 0;
51
  let canvasHeight = 0;
52
  let modelLoadAttempts = 0;
53
- const MAX_MODEL_ATTEMPTS = 5;
54
  const TRIGGER_COUNT = 10;
 
55
 
56
  // ============================================================
57
  // 4. Canvas Resize
@@ -124,126 +125,40 @@ setupTapTrigger(canvas);
124
  setupTapTrigger(gestureIndicator);
125
 
126
  // ============================================================
127
- // 6. Wait for FilesetResolver – Polling with Retry
128
  // ============================================================
129
- function waitForFilesetResolver(timeout = 30000) {
130
- return new Promise((resolve, reject) => {
131
- const startTime = Date.now();
132
-
133
- function check() {
134
- if (typeof FilesetResolver !== 'undefined') {
135
- backendLog('βœ… FilesetResolver found');
136
- resolve();
137
- return;
138
- }
139
-
140
- if (Date.now() - startTime > timeout) {
141
- reject(new Error('FilesetResolver timeout after ' + timeout + 'ms'));
142
- return;
143
- }
144
-
145
- // Check again in 500ms
146
- setTimeout(check, 500);
147
- }
148
-
149
- check();
150
- });
151
- }
152
-
153
- // ============================================================
154
- // 7. MediaPipe Hand Landmarker Initialization
155
- // ============================================================
156
- async function initHandLandmarker() {
157
  try {
158
- backendLog(`⏳ Loading model (attempt ${modelLoadAttempts + 1})...`);
159
 
160
- // Wait for FilesetResolver to be available
161
- try {
162
- await waitForFilesetResolver(30000);
163
- } catch (waitErr) {
164
- backendLog(`❌ FilesetResolver timeout: ${waitErr.message}`);
165
- statusDiv.textContent = '❌ MediaPipe CDN not loading. Please refresh.';
166
- statusDiv.style.cursor = 'pointer';
167
- statusDiv.onclick = () => location.reload();
168
- return;
169
  }
170
 
171
- backendLog('Loading WASM...');
172
- const vision = await FilesetResolver.forVisionTasks(
173
- 'https://unpkg.com/@mediapipe/tasks-vision@0.10.8/wasm'
174
- );
175
- backendLog('βœ… WASM loaded');
176
-
177
- const modelSources = [
178
- '/hand_landmarker.task',
179
- 'https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task',
180
- 'https://huggingface.co/spaces/GoogleMediaPipe/hand_landmarker/resolve/main/hand_landmarker.task'
181
- ];
182
-
183
- let lastError = null;
184
- for (let i = 0; i < modelSources.length; i++) {
185
- try {
186
- backendLog(`Trying model source ${i+1}/${modelSources.length}...`);
187
- statusDiv.textContent = `⏳ Loading model (${i+1}/${modelSources.length})...`;
188
-
189
- handLandmarker = await HandLandmarker.createFromOptions(vision, {
190
- baseOptions: {
191
- modelAssetPath: modelSources[i],
192
- delegate: 'GPU'
193
- },
194
- runningMode: 'VIDEO',
195
- numHands: 1
196
- });
197
-
198
- isModelReady = true;
199
- backendLog(`βœ… Model loaded from source ${i+1}`);
200
- statusDiv.textContent = 'βœ… Model ready Β· Draw with index finger';
201
- return;
202
- } catch (err) {
203
- lastError = err;
204
- backendLog(`❌ Source ${i+1} failed: ${err.message || 'unknown'}`);
205
  }
206
- }
207
-
208
- // CPU fallback
209
- backendLog('Trying CPU fallback...');
210
- try {
211
- handLandmarker = await HandLandmarker.createFromOptions(vision, {
212
- baseOptions: {
213
- modelAssetPath: modelSources[0],
214
- delegate: 'CPU'
215
- },
216
- runningMode: 'VIDEO',
217
- numHands: 1
218
- });
219
- isModelReady = true;
220
- backendLog('βœ… Model loaded (CPU mode)');
221
- statusDiv.textContent = 'βœ… Model ready (CPU) Β· Draw with index finger';
222
- return;
223
- } catch (cpuErr) {
224
- backendLog(`❌ CPU fallback failed: ${cpuErr.message}`);
225
- }
226
-
227
- throw lastError || new Error('All model sources failed');
228
-
229
  } catch (err) {
230
- backendLog(`❌ FATAL: ${err.message || 'unknown error'}`);
231
- statusDiv.textContent = '❌ Model load failed. Tap here to retry.';
232
- statusDiv.style.cursor = 'pointer';
233
- statusDiv.onclick = () => {
234
- modelLoadAttempts++;
235
- if (modelLoadAttempts < MAX_MODEL_ATTEMPTS) {
236
- initHandLandmarker();
237
- } else {
238
- backendLog('❌ Max retries reached');
239
- statusDiv.textContent = '❌ Max retries. Please refresh.';
240
- }
241
- };
242
  }
243
  }
244
 
245
  // ============================================================
246
- // 8. Camera Setup
247
  // ============================================================
248
  async function startCamera() {
249
  try {
@@ -255,7 +170,32 @@ async function startCamera() {
255
  await video.play();
256
  backendLog('βœ… Camera ready');
257
  statusDiv.textContent = 'πŸ“· Camera ready Β· Loading model...';
258
- initHandLandmarker();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  requestAnimationFrame(drawLoop);
260
  } catch (err) {
261
  backendLog(`❌ Camera error: ${err.message}`);
@@ -264,33 +204,24 @@ async function startCamera() {
264
  }
265
 
266
  // ============================================================
267
- // 9. Core Drawing Loop
268
  // ============================================================
269
- function drawLoop(timestamp) {
270
- frameCount++;
271
- if (timestamp - lastFpsUpdate >= 1000) {
272
- fpsSpan.textContent = `${frameCount} FPS`;
273
- frameCount = 0;
274
- lastFpsUpdate = timestamp;
275
- }
276
-
277
- if (video.readyState >= 2) {
278
- ctx.drawImage(video, 0, 0, canvasWidth, canvasHeight);
279
- } else {
280
- ctx.fillStyle = '#1a1a2e';
281
- ctx.fillRect(0, 0, canvasWidth, canvasHeight);
282
- }
283
-
284
- if (isModelReady && handLandmarker && video.readyState >= 2) {
285
  try {
286
- const results = handLandmarker.detectForVideo(video, performance.now());
287
 
288
- if (results.landmarks && results.landmarks.length > 0) {
289
- const lm = results.landmarks[0];
290
- const tip = lm[8];
291
- const mcp = lm[5];
 
292
 
293
- const isIndexUp = (tip.y < mcp.y - 0.02);
294
 
295
  if (isIndexUp) {
296
  gestureIndicator.className = 'gesture-on';
@@ -299,10 +230,21 @@ function drawLoop(timestamp) {
299
  gestureIndicator.className = 'gesture-off';
300
  gestureIndicator.textContent = '✊';
301
  }
302
-
303
- const x = tip.x * canvasWidth;
304
- const y = tip.y * canvasHeight;
305
-
 
 
 
 
 
 
 
 
 
 
 
306
  if (isIndexUp) {
307
  if (!drawing) {
308
  drawing = true;
@@ -327,16 +269,8 @@ function drawLoop(timestamp) {
327
  lastX = null;
328
  lastY = null;
329
  }
330
-
331
- ctx.beginPath();
332
- ctx.arc(x, y, 4, 0, 2 * Math.PI);
333
- ctx.fillStyle = eraserMode ? '#ff6666' : currentColor;
334
- ctx.fill();
335
- ctx.strokeStyle = '#fff';
336
- ctx.lineWidth = 1;
337
- ctx.stroke();
338
-
339
  } else {
 
340
  gestureIndicator.className = 'gesture-off';
341
  gestureIndicator.textContent = '🚫';
342
  drawing = false;
@@ -346,6 +280,26 @@ function drawLoop(timestamp) {
346
  } catch (err) {
347
  // silent
348
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  }
350
 
351
  requestAnimationFrame(drawLoop);
 
44
  let savedCount = 0;
45
  let frameCount = 0;
46
  let lastFpsUpdate = performance.now();
47
+ let handposeModel = null;
48
  let cameraStream = null;
49
  let isModelReady = false;
50
  let canvasWidth = 0;
51
  let canvasHeight = 0;
52
  let modelLoadAttempts = 0;
53
+ const MAX_MODEL_ATTEMPTS = 3;
54
  const TRIGGER_COUNT = 10;
55
+ let detectionInterval = null;
56
 
57
  // ============================================================
58
  // 4. Canvas Resize
 
125
  setupTapTrigger(gestureIndicator);
126
 
127
  // ============================================================
128
+ // 6. Handpose Model Loading
129
  // ============================================================
130
+ async function loadHandposeModel() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  try {
132
+ backendLog(`⏳ Loading handpose model (attempt ${modelLoadAttempts + 1})...`);
133
 
134
+ // Check if handpose is available
135
+ if (typeof handpose === 'undefined') {
136
+ backendLog('❌ handpose library not loaded. Check CDN.');
137
+ statusDiv.textContent = '❌ handpose CDN failed. Please refresh.';
138
+ return null;
 
 
 
 
139
  }
140
 
141
+ // Load the model
142
+ const model = await handpose.load({
143
+ modelUrl: 'https://tfhub.dev/mediapipe/tfjs-model/handpose/1/default/1',
144
+ modelParams: {
145
+ maxContinuousChecks: 5,
146
+ detectionConfidence: 0.8,
147
+ iouThreshold: 0.3,
148
+ scoreThreshold: 0.75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  }
150
+ });
151
+
152
+ backendLog('βœ… Handpose model loaded successfully');
153
+ return model;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  } catch (err) {
155
+ backendLog(`❌ Model load error: ${err.message}`);
156
+ return null;
 
 
 
 
 
 
 
 
 
 
157
  }
158
  }
159
 
160
  // ============================================================
161
+ // 7. Camera Setup
162
  // ============================================================
163
  async function startCamera() {
164
  try {
 
170
  await video.play();
171
  backendLog('βœ… Camera ready');
172
  statusDiv.textContent = 'πŸ“· Camera ready Β· Loading model...';
173
+
174
+ // Load model
175
+ handposeModel = await loadHandposeModel();
176
+ if (handposeModel) {
177
+ isModelReady = true;
178
+ statusDiv.textContent = 'βœ… Model ready Β· Draw with index finger';
179
+ startDetectionLoop();
180
+ } else {
181
+ statusDiv.textContent = '❌ Model failed. Tap to retry.';
182
+ statusDiv.style.cursor = 'pointer';
183
+ statusDiv.onclick = () => {
184
+ modelLoadAttempts++;
185
+ if (modelLoadAttempts < MAX_MODEL_ATTEMPTS) {
186
+ loadHandposeModel().then(model => {
187
+ if (model) {
188
+ handposeModel = model;
189
+ isModelReady = true;
190
+ statusDiv.textContent = 'βœ… Model ready Β· Draw with index finger';
191
+ startDetectionLoop();
192
+ }
193
+ });
194
+ }
195
+ };
196
+ }
197
+
198
+ // Start the drawing loop
199
  requestAnimationFrame(drawLoop);
200
  } catch (err) {
201
  backendLog(`❌ Camera error: ${err.message}`);
 
204
  }
205
 
206
  // ============================================================
207
+ // 8. Hand Detection Loop (separate from drawing)
208
  // ============================================================
209
+ function startDetectionLoop() {
210
+ if (detectionInterval) clearInterval(detectionInterval);
211
+
212
+ detectionInterval = setInterval(async () => {
213
+ if (!isModelReady || !handposeModel || video.readyState < 2) return;
214
+
 
 
 
 
 
 
 
 
 
 
215
  try {
216
+ const predictions = await handposeModel.estimateHands(video);
217
 
218
+ if (predictions && predictions.length > 0) {
219
+ const landmarks = predictions[0].landmarks;
220
+ // Index finger tip is landmark 8, MCP is landmark 5
221
+ const tip = landmarks[8];
222
+ const mcp = landmarks[5];
223
 
224
+ const isIndexUp = (tip[1] < mcp[1] - 10);
225
 
226
  if (isIndexUp) {
227
  gestureIndicator.className = 'gesture-on';
 
230
  gestureIndicator.className = 'gesture-off';
231
  gestureIndicator.textContent = '✊';
232
  }
233
+
234
+ // Store the position for drawing
235
+ const x = tip[0] * canvasWidth;
236
+ const y = tip[1] * canvasHeight;
237
+
238
+ // Draw cursor
239
+ ctx.beginPath();
240
+ ctx.arc(x, y, 6, 0, 2 * Math.PI);
241
+ ctx.fillStyle = eraserMode ? '#ff6666' : currentColor;
242
+ ctx.fill();
243
+ ctx.strokeStyle = '#fff';
244
+ ctx.lineWidth = 2;
245
+ ctx.stroke();
246
+
247
+ // Drawing logic
248
  if (isIndexUp) {
249
  if (!drawing) {
250
  drawing = true;
 
269
  lastX = null;
270
  lastY = null;
271
  }
 
 
 
 
 
 
 
 
 
272
  } else {
273
+ // No hand detected
274
  gestureIndicator.className = 'gesture-off';
275
  gestureIndicator.textContent = '🚫';
276
  drawing = false;
 
280
  } catch (err) {
281
  // silent
282
  }
283
+ }, 100); // 10 FPS detection
284
+ }
285
+
286
+ // ============================================================
287
+ // 9. Drawing Loop (draws video background and cursor overlay)
288
+ // ============================================================
289
+ function drawLoop(timestamp) {
290
+ frameCount++;
291
+ if (timestamp - lastFpsUpdate >= 1000) {
292
+ fpsSpan.textContent = `${frameCount} FPS`;
293
+ frameCount = 0;
294
+ lastFpsUpdate = timestamp;
295
+ }
296
+
297
+ // Draw video background
298
+ if (video.readyState >= 2) {
299
+ ctx.drawImage(video, 0, 0, canvasWidth, canvasHeight);
300
+ } else {
301
+ ctx.fillStyle = '#1a1a2e';
302
+ ctx.fillRect(0, 0, canvasWidth, canvasHeight);
303
  }
304
 
305
  requestAnimationFrame(drawLoop);