Spaces:
Sleeping
Sleeping
Merge origin/hugging_face_final into hugging_face_final
Browse filesResolve conflict in main.py: keep refactored drawing imports
(draw_face_mesh, draw_hud from api.drawing) over inline definitions.
- .gitignore +1 -0
- main.py +2 -0
- 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 —
|
| 720 |
-
if (
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
|
|
|
|
|
|
| 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 |
|