Abdelrahman Almatrooshi commited on
Commit
9ab271b
·
2 Parent(s): cf591cfc0db411

Merge origin/hugging_face_final into hugging_face_final

Browse files

Resolve conflict in main.py: keep refactored drawing imports
(draw_face_mesh, draw_hud from api.drawing) over inline definitions.

Files changed (3) hide show
  1. .gitignore +1 -0
  2. main.py +2 -0
  3. src/utils/VideoManagerLocal.js +64 -59
.gitignore CHANGED
@@ -46,3 +46,4 @@ static/
46
  __pycache__/
47
  docs/
48
  docs
 
 
46
  __pycache__/
47
  docs/
48
  docs
49
+ LOCAL_TESTING.md
main.py CHANGED
@@ -201,6 +201,7 @@ class VideoTransformTrack(VideoStreamTrack):
201
  # Draw face mesh + HUD on the video frame
202
  h_f, w_f = img.shape[:2]
203
  lm = out.get("landmarks")
 
204
  if lm is not None:
205
  draw_face_mesh(img, lm, w_f, h_f)
206
  draw_hud(img, out, model_name)
@@ -671,6 +672,7 @@ async def websocket_endpoint(websocket: WebSocket):
671
  "model": model_name,
672
  "fc": frame_count,
673
  "frame_count": frame_count,
 
674
  }
675
  if out is not None:
676
  if out.get("yaw") is not None:
 
201
  # Draw face mesh + HUD on the video frame
202
  h_f, w_f = img.shape[:2]
203
  lm = out.get("landmarks")
204
+ eye_gaze_enabled = _l2cs_boost_enabled or model_name == "l2cs"
205
  if lm is not None:
206
  draw_face_mesh(img, lm, w_f, h_f)
207
  draw_hud(img, out, model_name)
 
672
  "model": model_name,
673
  "fc": frame_count,
674
  "frame_count": frame_count,
675
+ "eye_gaze_enabled": _l2cs_boost_enabled or model_name == "l2cs",
676
  }
677
  if out is not None:
678
  if out.get("yaw") is not None:
src/utils/VideoManagerLocal.js CHANGED
@@ -164,7 +164,7 @@ export class VideoManagerLocal {
164
  this.ws.onclose = null;
165
  try {
166
  this.ws.close();
167
- } catch (_) {}
168
  this.ws = null;
169
  }
170
 
@@ -464,6 +464,7 @@ export class VideoManagerLocal {
464
  on_screen: data.on_screen,
465
  gaze_yaw: data.gaze_yaw,
466
  gaze_pitch: data.gaze_pitch,
 
467
  };
468
  this.drawDetectionResult(detectionData);
469
  break;
@@ -576,15 +577,15 @@ export class VideoManagerLocal {
576
  152, 148, 176, 149, 150, 136, 172, 58, 132,
577
  93, 234, 127, 162, 21, 54, 103, 67, 109, 10,
578
  ];
579
- static LEFT_EYE = [33,7,163,144,145,153,154,155,133,173,157,158,159,160,161,246];
580
- static RIGHT_EYE = [362,382,381,380,374,373,390,249,263,466,388,387,386,385,384,398];
581
- static LEFT_IRIS = [468,469,470,471,472];
582
- static RIGHT_IRIS = [473,474,475,476,477];
583
- static LEFT_EYEBROW = [70,63,105,66,107,55,65,52,53,46];
584
- static RIGHT_EYEBROW = [300,293,334,296,336,285,295,282,283,276];
585
- static NOSE_BRIDGE = [6,197,195,5,4,1,19,94,2];
586
- static LIPS_OUTER = [61,146,91,181,84,17,314,405,321,375,291,409,270,269,267,0,37,39,40,185,61];
587
- static LIPS_INNER = [78,95,88,178,87,14,317,402,318,324,308,415,310,311,312,13,82,81,80,191,78];
588
  static LEFT_EAR_POINTS = [33, 160, 158, 133, 153, 145];
589
  static RIGHT_EAR_POINTS = [362, 385, 387, 263, 373, 380];
590
  // Iris/eye corners for gaze lines
@@ -680,11 +681,12 @@ export class VideoManagerLocal {
680
  outer: VideoManagerLocal.RIGHT_EYE_OUTER,
681
  },
682
  ];
683
- // Get L2CS gaze angles + on_screen state from latest detection data
684
  const detection = this._lastDetection;
685
  const gazeYaw = detection ? detection.gaze_yaw : undefined;
686
  const gazePitch = detection ? detection.gaze_pitch : undefined;
687
  const onScreen = detection ? detection.on_screen : undefined;
 
688
  const hasL2CSGaze = gazeYaw !== undefined && gazePitch !== undefined;
689
  const gazeLineColor = (onScreen === false) ? '#FF0000' : '#00FF00';
690
  const gazeLineLength = 100;
@@ -716,57 +718,60 @@ export class VideoManagerLocal {
716
  ctx.lineWidth = 1;
717
  ctx.stroke();
718
 
719
- // Gaze direction line — use L2CS angles when available, else geometric fallback
720
- if (hasL2CSGaze) {
721
- // L2CS pitch/yaw in radians -> pixel direction vector
722
- // Matches upstream L2CS-Net vis.py draw_gaze formula:
723
- // dx = -length * sin(pitch) * cos(yaw)
724
- // dy = -length * sin(yaw)
725
- const dx = -gazeLineLength * Math.sin(gazePitch) * Math.cos(gazeYaw);
726
- const dy = -gazeLineLength * Math.sin(gazeYaw);
727
- const ex = cx + dx;
728
- const ey = cy + dy;
729
-
730
- // Main gaze line (thick, color-coded)
731
- ctx.beginPath();
732
- ctx.moveTo(cx, cy);
733
- ctx.lineTo(ex, ey);
734
- ctx.strokeStyle = gazeLineColor;
735
- ctx.lineWidth = 3;
736
- ctx.stroke();
737
-
738
- // Arrowhead
739
- const angle = Math.atan2(ey - cy, ex - cx);
740
- const arrowLen = 10;
741
- ctx.beginPath();
742
- ctx.moveTo(ex, ey);
743
- ctx.lineTo(ex - arrowLen * Math.cos(angle - 0.4), ey - arrowLen * Math.sin(angle - 0.4));
744
- ctx.moveTo(ex, ey);
745
- ctx.lineTo(ex - arrowLen * Math.cos(angle + 0.4), ey - arrowLen * Math.sin(angle + 0.4));
746
- ctx.strokeStyle = gazeLineColor;
747
- ctx.lineWidth = 2;
748
- ctx.stroke();
749
- } else {
750
- // Geometric fallback: iris displacement from eye center (scaled up)
751
- const innerPt = _get(inner);
752
- const outerPt = _get(outer);
753
- if (innerPt && outerPt) {
754
- const eyeCx = (innerPt[0] + outerPt[0]) / 2.0 * w;
755
- const eyeCy = (innerPt[1] + outerPt[1]) / 2.0 * h;
756
- const fdx = cx - eyeCx;
757
- const fdy = cy - eyeCy;
758
- const flen = Math.hypot(fdx, fdy);
759
- if (flen > 0.5) {
760
- const scale = gazeLineLength / flen;
761
- ctx.beginPath();
762
- ctx.moveTo(cx, cy);
763
- ctx.lineTo(cx + fdx * scale, cy + fdy * scale);
764
- ctx.strokeStyle = '#00FFFF';
765
- ctx.lineWidth = 2;
766
- ctx.stroke();
 
 
767
  }
768
  }
769
  }
 
770
  }
771
  }
772
 
 
164
  this.ws.onclose = null;
165
  try {
166
  this.ws.close();
167
+ } catch (_) { }
168
  this.ws = null;
169
  }
170
 
 
464
  on_screen: data.on_screen,
465
  gaze_yaw: data.gaze_yaw,
466
  gaze_pitch: data.gaze_pitch,
467
+ eye_gaze_enabled: data.eye_gaze_enabled || false,
468
  };
469
  this.drawDetectionResult(detectionData);
470
  break;
 
577
  152, 148, 176, 149, 150, 136, 172, 58, 132,
578
  93, 234, 127, 162, 21, 54, 103, 67, 109, 10,
579
  ];
580
+ static LEFT_EYE = [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
581
+ static RIGHT_EYE = [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
582
+ static LEFT_IRIS = [468, 469, 470, 471, 472];
583
+ static RIGHT_IRIS = [473, 474, 475, 476, 477];
584
+ static LEFT_EYEBROW = [70, 63, 105, 66, 107, 55, 65, 52, 53, 46];
585
+ static RIGHT_EYEBROW = [300, 293, 334, 296, 336, 285, 295, 282, 283, 276];
586
+ static NOSE_BRIDGE = [6, 197, 195, 5, 4, 1, 19, 94, 2];
587
+ static LIPS_OUTER = [61, 146, 91, 181, 84, 17, 314, 405, 321, 375, 291, 409, 270, 269, 267, 0, 37, 39, 40, 185, 61];
588
+ static LIPS_INNER = [78, 95, 88, 178, 87, 14, 317, 402, 318, 324, 308, 415, 310, 311, 312, 13, 82, 81, 80, 191, 78];
589
  static LEFT_EAR_POINTS = [33, 160, 158, 133, 153, 145];
590
  static RIGHT_EAR_POINTS = [362, 385, 387, 263, 373, 380];
591
  // Iris/eye corners for gaze lines
 
681
  outer: VideoManagerLocal.RIGHT_EYE_OUTER,
682
  },
683
  ];
684
+ // Get L2CS gaze angles + on_screen state + eye gaze toggle from latest detection data
685
  const detection = this._lastDetection;
686
  const gazeYaw = detection ? detection.gaze_yaw : undefined;
687
  const gazePitch = detection ? detection.gaze_pitch : undefined;
688
  const onScreen = detection ? detection.on_screen : undefined;
689
+ const eyeGazeEnabled = detection ? detection.eye_gaze_enabled : false;
690
  const hasL2CSGaze = gazeYaw !== undefined && gazePitch !== undefined;
691
  const gazeLineColor = (onScreen === false) ? '#FF0000' : '#00FF00';
692
  const gazeLineLength = 100;
 
718
  ctx.lineWidth = 1;
719
  ctx.stroke();
720
 
721
+ // Gaze direction line — only draw when eye gaze toggle is ON
722
+ if (eyeGazeEnabled) {
723
+ if (hasL2CSGaze) {
724
+ // L2CS pitch/yaw in radians -> pixel direction vector
725
+ // Matches upstream L2CS-Net vis.py draw_gaze formula:
726
+ // dx = -length * sin(pitch) * cos(yaw)
727
+ // dy = -length * sin(yaw)
728
+ const dx = -gazeLineLength * Math.sin(gazePitch) * Math.cos(gazeYaw);
729
+ const dy = -gazeLineLength * Math.sin(gazeYaw);
730
+ const ex = cx + dx;
731
+ const ey = cy + dy;
732
+
733
+ // Main gaze line (thick, color-coded)
734
+ ctx.beginPath();
735
+ ctx.moveTo(cx, cy);
736
+ ctx.lineTo(ex, ey);
737
+ ctx.strokeStyle = gazeLineColor;
738
+ ctx.lineWidth = 3;
739
+ ctx.stroke();
740
+
741
+ // Arrowhead
742
+ const angle = Math.atan2(ey - cy, ex - cx);
743
+ const arrowLen = 10;
744
+ ctx.beginPath();
745
+ ctx.moveTo(ex, ey);
746
+ ctx.lineTo(ex - arrowLen * Math.cos(angle - 0.4), ey - arrowLen * Math.sin(angle - 0.4));
747
+ ctx.moveTo(ex, ey);
748
+ ctx.lineTo(ex - arrowLen * Math.cos(angle + 0.4), ey - arrowLen * Math.sin(angle + 0.4));
749
+ ctx.strokeStyle = gazeLineColor;
750
+ ctx.lineWidth = 2;
751
+ ctx.stroke();
752
+ } else {
753
+ // Geometric fallback: iris displacement from eye center (scaled up)
754
+ const innerPt = _get(inner);
755
+ const outerPt = _get(outer);
756
+ if (innerPt && outerPt) {
757
+ const eyeCx = (innerPt[0] + outerPt[0]) / 2.0 * w;
758
+ const eyeCy = (innerPt[1] + outerPt[1]) / 2.0 * h;
759
+ const fdx = cx - eyeCx;
760
+ const fdy = cy - eyeCy;
761
+ const flen = Math.hypot(fdx, fdy);
762
+ if (flen > 0.5) {
763
+ const scale = gazeLineLength / flen;
764
+ ctx.beginPath();
765
+ ctx.moveTo(cx, cy);
766
+ ctx.lineTo(cx + fdx * scale, cy + fdy * scale);
767
+ ctx.strokeStyle = '#00FFFF';
768
+ ctx.lineWidth = 2;
769
+ ctx.stroke();
770
+ }
771
  }
772
  }
773
  }
774
+ // When eye gaze is OFF, no gaze lines are drawn
775
  }
776
  }
777