kennn14 commited on
Commit
6cb6e7b
Β·
verified Β·
1 Parent(s): d526b08

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +62 -57
app.py CHANGED
@@ -385,7 +385,7 @@ def health():
385
  async def predict_alphabet(file: UploadFile = File(...)):
386
  data = await file.read()
387
  mp_img = bytes_to_mp_image(data)
388
- result = hand_detector_alpha.detect_for_video(mp_img, 0)
389
  hand_lms = result.hand_landmarks[0] if result.hand_landmarks else None
390
 
391
  if hand_lms is None:
@@ -414,7 +414,8 @@ async def predict_alphabet_batch(files: List[UploadFile] = File(...)):
414
  for i, file in enumerate(files):
415
  data = await file.read()
416
  mp_img = bytes_to_mp_image(data)
417
- result = hand_detector_alpha.detect_for_video(mp_img, i * 33)
 
418
  hand_lms = result.hand_landmarks[0] if result.hand_landmarks else None
419
  if hand_lms is None:
420
  continue
@@ -436,31 +437,16 @@ async def predict_alphabet_batch(files: List[UploadFile] = File(...)):
436
 
437
  @app.post("/predict/auto", response_model=AutoResponse)
438
  async def predict_auto(files: List[UploadFile] = File(...)):
439
- """
440
- Accepts 60 JPEG frames.
441
- Processes all frames (no skipping).
442
-
443
- Decision logic:
444
- 1. If hand presence < 1/3 of processed frames β†’ not detected
445
- 2. If 2 hands detected in >= 1/3 of processed frames β†’ skip CNN, go straight to LSTM
446
- 3. If wrist movement >= threshold β†’ skip CNN, go straight to LSTM
447
- 4. If CNN vote_ratio >= 0.65 β†’ return letter
448
- 5. Else β†’ run LSTM β†’ return phrase
449
- """
450
  if not files:
451
  raise HTTPException(status_code=400, detail="No frames provided")
452
 
453
- frame_data = []
454
- for file in files:
455
- data = await file.read()
456
- frame_data.append(data)
457
 
458
  votes = {}
459
  total_processed_frames = len(frame_data)
460
  total_hand_frames = 0
461
  two_hand_frames = 0
462
-
463
- # Movement tracking β€” store wrist position of each processed frame
464
  wrist_positions = []
465
 
466
  coord_buffer = []
@@ -468,40 +454,54 @@ async def predict_auto(files: List[UploadFile] = File(...)):
468
  last_left_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
469
  last_right_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
470
 
 
 
 
471
  for i, data in enumerate(frame_data):
 
 
472
  timestamp_ms = int(i * (1000 / 30))
 
473
  mp_img = bytes_to_mp_image(data)
474
 
475
- # CNN β€” single hand for alphabet
476
  cnn_result = hand_detector_alpha.detect_for_video(mp_img, timestamp_ms)
477
- hand_lms = cnn_result.hand_landmarks[0] \
478
- if cnn_result.hand_landmarks else None
479
 
480
  if hand_lms is not None:
481
  total_hand_frames += 1
482
- # Track wrist position for movement detection
 
483
  wrist_positions.append((hand_lms[WRIST].x, hand_lms[WRIST].y))
484
 
485
  skel = render_hand_skeleton(hand_lms)
486
  tensor = infer_transform(skel).unsqueeze(0).to(device)
 
487
  with torch.no_grad():
488
  probs = torch.softmax(alphabet_model(tensor), dim=1)[0]
 
489
  conf, idx = probs.max(dim=0)
490
- if conf.item() >= 0.6: # only count confident predictions
 
491
  letter = ALPHA_CLASSES[idx.item()]
492
  votes[letter] = votes.get(letter, 0) + 1
493
 
494
- # LSTM β€” pose + both hands
495
  pose_result = pose_detector.detect_for_video(mp_img, timestamp_ms)
496
- pose_lms = pose_result.pose_landmarks[0] \
497
- if pose_result.pose_landmarks else None
498
  if pose_lms:
499
  last_pose_vec = normalize_pose(pose_lms)
500
 
 
501
  lstm_result = hand_detector_phrase.detect_for_video(mp_img, timestamp_ms)
502
- left_lms, right_lms = get_hands_by_side(lstm_result)
503
- if left_lms is not None: last_left_hand_vec = normalize_hand(left_lms)
504
- if right_lms is not None: last_right_hand_vec = normalize_hand(right_lms)
 
 
 
 
505
 
506
  if left_lms is not None and right_lms is not None:
507
  two_hand_frames += 1
@@ -509,43 +509,40 @@ async def predict_auto(files: List[UploadFile] = File(...)):
509
  vec = np.concatenate([last_pose_vec, last_left_hand_vec, last_right_hand_vec])
510
  coord_buffer.append(vec)
511
 
512
- # ── Step 1: Minimum hand presence check ──────────────────
 
 
513
  min_required = total_processed_frames * MIN_HAND_PRESENCE_RATIO
 
514
  if total_hand_frames < min_required:
515
- print(f"[auto] Hand presence too low: {total_hand_frames}/{total_processed_frames} "
516
- f"(need >= {min_required:.1f}) β€” skipping")
517
  return AutoResponse(
518
  result="", result_type="none",
519
  confidence=0.0, vote_ratio=0.0, detected=False
520
  )
521
 
522
- # ── Step 2: Two hands detected β†’ LSTM directly ───────────
523
- two_hand_threshold = total_processed_frames * TWO_HAND_RATIO
524
- if two_hand_frames >= two_hand_threshold:
525
- print(f"[auto] Two hands in {two_hand_frames}/{total_processed_frames} frames β†’ LSTM")
526
  return run_lstm(coord_buffer, vote_ratio=0.0)
527
 
528
- # ── Step 3: Obvious movement detected β†’ LSTM directly ────
529
- # Compare wrist position from midpoint to end of sequence.
530
- # Ignores initial arm raising to position the hand.
531
- if len(wrist_positions) >= 4:
532
- mid_idx = len(wrist_positions) // 2
533
- mid_pos = wrist_positions[mid_idx]
534
- end_pos = wrist_positions[-1]
535
- dx = end_pos[0] - mid_pos[0]
536
- dy = end_pos[1] - mid_pos[1]
537
- movement = (dx**2 + dy**2) ** 0.5
538
 
539
- print(f"[auto] Wrist movement (mid→end): {movement:.4f}")
 
 
 
 
540
 
541
- if movement >= MOVEMENT_THRESHOLD:
542
- print(f"[auto] Significant movement ({movement:.4f}) β†’ LSTM")
543
- return run_lstm(coord_buffer, vote_ratio=0.0)
544
-
545
- # ── Step 4: CNN vote decision ─────────────────────────────
546
- vote_ratio = 0.0
547
- best_letter = ""
548
 
 
 
 
549
  if not votes:
550
  return AutoResponse(
551
  result="", result_type="none",
@@ -555,7 +552,13 @@ async def predict_auto(files: List[UploadFile] = File(...)):
555
  best_letter = max(votes, key=votes.get)
556
  vote_ratio = votes[best_letter] / sum(votes.values())
557
 
558
- if vote_ratio >= 0.65 and best_letter:
 
 
 
 
 
 
559
  return AutoResponse(
560
  result=best_letter,
561
  result_type="letter",
@@ -564,7 +567,9 @@ async def predict_auto(files: List[UploadFile] = File(...)):
564
  detected=True
565
  )
566
 
567
- # ── Step 5: LSTM ─────────────────────────────────────────
 
 
568
  return run_lstm(coord_buffer, vote_ratio)
569
 
570
 
 
385
  async def predict_alphabet(file: UploadFile = File(...)):
386
  data = await file.read()
387
  mp_img = bytes_to_mp_image(data)
388
+ result = hand_detector_alpha.detect_for_video(mp_img, int(0))
389
  hand_lms = result.hand_landmarks[0] if result.hand_landmarks else None
390
 
391
  if hand_lms is None:
 
414
  for i, file in enumerate(files):
415
  data = await file.read()
416
  mp_img = bytes_to_mp_image(data)
417
+ timestamp_ms = int(i * (1000 / 30))
418
+ result = hand_detector_alpha.detect_for_video(mp_img, timestamp_ms)
419
  hand_lms = result.hand_landmarks[0] if result.hand_landmarks else None
420
  if hand_lms is None:
421
  continue
 
437
 
438
  @app.post("/predict/auto", response_model=AutoResponse)
439
  async def predict_auto(files: List[UploadFile] = File(...)):
440
+
 
 
 
 
 
 
 
 
 
 
441
  if not files:
442
  raise HTTPException(status_code=400, detail="No frames provided")
443
 
444
+ frame_data = [await f.read() for f in files]
 
 
 
445
 
446
  votes = {}
447
  total_processed_frames = len(frame_data)
448
  total_hand_frames = 0
449
  two_hand_frames = 0
 
 
450
  wrist_positions = []
451
 
452
  coord_buffer = []
 
454
  last_left_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
455
  last_right_hand_vec = np.zeros(HAND_SIZE, dtype=np.float32)
456
 
457
+ # ===============================
458
+ # Frame Processing Loop
459
+ # ===============================
460
  for i, data in enumerate(frame_data):
461
+
462
+ # βœ… FIXED timestamp
463
  timestamp_ms = int(i * (1000 / 30))
464
+
465
  mp_img = bytes_to_mp_image(data)
466
 
467
+ # ── CNN (alphabet) ──
468
  cnn_result = hand_detector_alpha.detect_for_video(mp_img, timestamp_ms)
469
+ hand_lms = cnn_result.hand_landmarks[0] if cnn_result.hand_landmarks else None
 
470
 
471
  if hand_lms is not None:
472
  total_hand_frames += 1
473
+
474
+ # Track wrist position
475
  wrist_positions.append((hand_lms[WRIST].x, hand_lms[WRIST].y))
476
 
477
  skel = render_hand_skeleton(hand_lms)
478
  tensor = infer_transform(skel).unsqueeze(0).to(device)
479
+
480
  with torch.no_grad():
481
  probs = torch.softmax(alphabet_model(tensor), dim=1)[0]
482
+
483
  conf, idx = probs.max(dim=0)
484
+
485
+ if conf.item() >= 0.6:
486
  letter = ALPHA_CLASSES[idx.item()]
487
  votes[letter] = votes.get(letter, 0) + 1
488
 
489
+ # ── Pose ──
490
  pose_result = pose_detector.detect_for_video(mp_img, timestamp_ms)
491
+ pose_lms = pose_result.pose_landmarks[0] if pose_result.pose_landmarks else None
492
+
493
  if pose_lms:
494
  last_pose_vec = normalize_pose(pose_lms)
495
 
496
+ # ── Hands (LSTM) ──
497
  lstm_result = hand_detector_phrase.detect_for_video(mp_img, timestamp_ms)
498
+ left_lms, right_lms = get_hands_by_side(lstm_result)
499
+
500
+ if left_lms is not None:
501
+ last_left_hand_vec = normalize_hand(left_lms)
502
+
503
+ if right_lms is not None:
504
+ last_right_hand_vec = normalize_hand(right_lms)
505
 
506
  if left_lms is not None and right_lms is not None:
507
  two_hand_frames += 1
 
509
  vec = np.concatenate([last_pose_vec, last_left_hand_vec, last_right_hand_vec])
510
  coord_buffer.append(vec)
511
 
512
+ # ===============================
513
+ # Step 1: Hand Presence Check
514
+ # ===============================
515
  min_required = total_processed_frames * MIN_HAND_PRESENCE_RATIO
516
+
517
  if total_hand_frames < min_required:
 
 
518
  return AutoResponse(
519
  result="", result_type="none",
520
  confidence=0.0, vote_ratio=0.0, detected=False
521
  )
522
 
523
+ # ===============================
524
+ # Step 2: Two Hands β†’ LSTM
525
+ # ===============================
526
+ if two_hand_frames >= total_processed_frames * TWO_HAND_RATIO:
527
  return run_lstm(coord_buffer, vote_ratio=0.0)
528
 
529
+ # ===============================
530
+ # Step 3: Movement Detection (FIXED)
531
+ # ===============================
532
+ movement = 0.0
 
 
 
 
 
 
533
 
534
+ if len(wrist_positions) >= 2:
535
+ for j in range(1, len(wrist_positions)):
536
+ dx = wrist_positions[j][0] - wrist_positions[j-1][0]
537
+ dy = wrist_positions[j][1] - wrist_positions[j-1][1]
538
+ movement += (dx**2 + dy**2) ** 0.5
539
 
540
+ # βœ… Normalize (important)
541
+ movement /= len(wrist_positions)
 
 
 
 
 
542
 
543
+ # ===============================
544
+ # Step 4: CNN Voting
545
+ # ===============================
546
  if not votes:
547
  return AutoResponse(
548
  result="", result_type="none",
 
552
  best_letter = max(votes, key=votes.get)
553
  vote_ratio = votes[best_letter] / sum(votes.values())
554
 
555
+ # ===============================
556
+ # Step 5: Smart Decision Logic
557
+ # ===============================
558
+ if movement >= MOVEMENT_THRESHOLD and vote_ratio < 0.75:
559
+ return run_lstm(coord_buffer, vote_ratio)
560
+
561
+ if vote_ratio >= 0.65:
562
  return AutoResponse(
563
  result=best_letter,
564
  result_type="letter",
 
567
  detected=True
568
  )
569
 
570
+ # ===============================
571
+ # Step 6: Default β†’ LSTM
572
+ # ===============================
573
  return run_lstm(coord_buffer, vote_ratio)
574
 
575