Spaces:
Sleeping
Sleeping
Commit ·
a024ce8
1
Parent(s): b59ec4f
change test params and files
Browse files
.coverage
CHANGED
|
Binary files a/.coverage and b/.coverage differ
|
|
|
tests/test_cnn_eye_attention_classifier.py
DELETED
|
@@ -1,86 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import sys
|
| 3 |
-
|
| 4 |
-
import numpy as np
|
| 5 |
-
import pytest
|
| 6 |
-
|
| 7 |
-
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 8 |
-
if PROJECT_ROOT not in sys.path:
|
| 9 |
-
sys.path.insert(0, PROJECT_ROOT)
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
def test_load_eye_classifier_geometric_backend():
|
| 13 |
-
from models.cnn.eye_attention.classifier import load_eye_classifier, GeometricOnlyClassifier
|
| 14 |
-
|
| 15 |
-
clf = load_eye_classifier(path="anything.pt", backend="geometric")
|
| 16 |
-
# Assert that the returned object is indeed a GeometricOnlyClassifier
|
| 17 |
-
assert isinstance(clf, GeometricOnlyClassifier)
|
| 18 |
-
assert clf.name == "geometric"
|
| 19 |
-
# Assert that it can successfully process a dummy black image and return a score
|
| 20 |
-
assert clf.predict_score([np.zeros((10, 10, 3), dtype=np.uint8)]) == 1.0
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def test_load_eye_classifier_none_path_falls_back_to_geometric(capsys):
|
| 24 |
-
from models.cnn.eye_attention.classifier import load_eye_classifier, GeometricOnlyClassifier
|
| 25 |
-
# Intentionally request the 'yolo' backend but provide no model path
|
| 26 |
-
clf = load_eye_classifier(path=None, backend="yolo")
|
| 27 |
-
assert isinstance(clf, GeometricOnlyClassifier)
|
| 28 |
-
out = capsys.readouterr().out
|
| 29 |
-
assert "falling back to geometric" in out
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
def test_load_eye_classifier_extension_overrides_backend(monkeypatch, tmp_path):
|
| 33 |
-
"""
|
| 34 |
-
.pth -> cnn, .pt -> yolo(但不真的加载 torch/ultralytics)
|
| 35 |
-
"""
|
| 36 |
-
import models.cnn.eye_attention.classifier as m
|
| 37 |
-
|
| 38 |
-
created = {}
|
| 39 |
-
#Create dummy model classes to mock real deep learning models
|
| 40 |
-
class DummyCNN(m.EyeClassifier):
|
| 41 |
-
@property
|
| 42 |
-
def name(self) -> str:
|
| 43 |
-
return "dummy_cnn"
|
| 44 |
-
|
| 45 |
-
def predict_score(self, crops_bgr):
|
| 46 |
-
return 0.5
|
| 47 |
-
|
| 48 |
-
class DummyYOLO(m.EyeClassifier):
|
| 49 |
-
@property
|
| 50 |
-
def name(self) -> str:
|
| 51 |
-
return "dummy_yolo"
|
| 52 |
-
|
| 53 |
-
def predict_score(self, crops_bgr):
|
| 54 |
-
return 0.7
|
| 55 |
-
# Intercept the original constructors to return our dummy models
|
| 56 |
-
def fake_cnn_ctor(path, device="cpu"):
|
| 57 |
-
created["cnn"] = (path, device)
|
| 58 |
-
return DummyCNN()
|
| 59 |
-
|
| 60 |
-
def fake_yolo_ctor(path, device="cpu"):
|
| 61 |
-
created["yolo"] = (path, device)
|
| 62 |
-
return DummyYOLO()
|
| 63 |
-
|
| 64 |
-
monkeypatch.setattr(m, "EyeCNNClassifier", fake_cnn_ctor)
|
| 65 |
-
monkeypatch.setattr(m, "YOLOv11Classifier", fake_yolo_ctor)
|
| 66 |
-
#Test the '.pth' extension logic
|
| 67 |
-
pth = tmp_path / "eye_model.pth"
|
| 68 |
-
pth.write_bytes(b"not a real checkpoint")
|
| 69 |
-
clf = m.load_eye_classifier(path=str(pth), backend="yolo", device="cpu")
|
| 70 |
-
assert clf.name == "dummy_cnn"
|
| 71 |
-
assert "cnn" in created and created["cnn"][0] == str(pth)
|
| 72 |
-
#Test the '.pt' extension logic
|
| 73 |
-
pt = tmp_path / "eye_model.pt"
|
| 74 |
-
pt.write_bytes(b"not a real yolo model")
|
| 75 |
-
clf2 = m.load_eye_classifier(path=str(pt), backend="cnn", device="cpu")
|
| 76 |
-
assert clf2.name == "dummy_yolo"
|
| 77 |
-
assert "yolo" in created and created["yolo"][0] == str(pt)
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
def test_load_eye_classifier_unknown_backend_raises():
|
| 81 |
-
from models.cnn.eye_attention.classifier import load_eye_classifier
|
| 82 |
-
|
| 83 |
-
with pytest.raises(ValueError):
|
| 84 |
-
# Use an unknown extension to prevent the automatic fallback mechanism
|
| 85 |
-
load_eye_classifier(path="x.unknownext", backend="unknown")
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tests/test_cnn_eye_attention_crop.py
DELETED
|
@@ -1,84 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import sys
|
| 3 |
-
import types
|
| 4 |
-
import importlib
|
| 5 |
-
|
| 6 |
-
import numpy as np
|
| 7 |
-
|
| 8 |
-
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 9 |
-
if PROJECT_ROOT not in sys.path:
|
| 10 |
-
sys.path.insert(0, PROJECT_ROOT)
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
def _install_fake_facemesh_module():
|
| 14 |
-
pkg_models = sys.modules.setdefault("models", types.ModuleType("models"))
|
| 15 |
-
pkg_pretrained = sys.modules.setdefault(
|
| 16 |
-
"models.pretrained", types.ModuleType("models.pretrained")
|
| 17 |
-
)
|
| 18 |
-
pkg_face_mesh = sys.modules.setdefault(
|
| 19 |
-
"models.pretrained.face_mesh", types.ModuleType("models.pretrained.face_mesh")
|
| 20 |
-
)
|
| 21 |
-
mod = types.ModuleType("models.pretrained.face_mesh.face_mesh")
|
| 22 |
-
|
| 23 |
-
class FaceMeshDetector:
|
| 24 |
-
# get landmarks
|
| 25 |
-
LEFT_EYE_INDICES = [0, 1, 2]
|
| 26 |
-
RIGHT_EYE_INDICES = [3, 4, 5]
|
| 27 |
-
|
| 28 |
-
mod.FaceMeshDetector = FaceMeshDetector
|
| 29 |
-
|
| 30 |
-
sys.modules["models.pretrained.face_mesh.face_mesh"] = mod
|
| 31 |
-
pkg_models.pretrained = pkg_pretrained
|
| 32 |
-
pkg_pretrained.face_mesh = pkg_face_mesh
|
| 33 |
-
pkg_face_mesh.face_mesh = mod
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
def test_bbox_from_landmarks_clamps_to_frame():
|
| 37 |
-
_install_fake_facemesh_module()
|
| 38 |
-
crop = importlib.import_module("models.cnn.eye_attention.crop")
|
| 39 |
-
importlib.reload(crop)
|
| 40 |
-
|
| 41 |
-
# normalize the cord
|
| 42 |
-
landmarks = np.array(
|
| 43 |
-
[
|
| 44 |
-
[0.1, 0.1, 0.0],
|
| 45 |
-
[0.2, 0.1, 0.0],
|
| 46 |
-
[0.15, 0.2, 0.0],
|
| 47 |
-
[0.8, 0.1, 0.0],
|
| 48 |
-
[0.9, 0.1, 0.0],
|
| 49 |
-
[0.85, 0.2, 0.0],
|
| 50 |
-
],
|
| 51 |
-
dtype=np.float32,
|
| 52 |
-
)
|
| 53 |
-
|
| 54 |
-
x1, y1, x2, y2 = crop._bbox_from_landmarks(landmarks, [0, 1, 2], 100, 50, expand=0.0)
|
| 55 |
-
assert 0 <= x1 < x2 <= 100
|
| 56 |
-
assert 0 <= y1 < y2 <= 50
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
def test_extract_eye_crops_returns_expected_shapes():
|
| 60 |
-
_install_fake_facemesh_module()
|
| 61 |
-
crop = importlib.import_module("models.cnn.eye_attention.crop")
|
| 62 |
-
importlib.reload(crop)
|
| 63 |
-
|
| 64 |
-
frame = np.zeros((60, 120, 3), dtype=np.uint8)
|
| 65 |
-
landmarks = np.array(
|
| 66 |
-
[
|
| 67 |
-
[0.1, 0.2, 0.0],
|
| 68 |
-
[0.2, 0.2, 0.0],
|
| 69 |
-
[0.15, 0.3, 0.0],
|
| 70 |
-
[0.7, 0.2, 0.0],
|
| 71 |
-
[0.8, 0.2, 0.0],
|
| 72 |
-
[0.75, 0.3, 0.0],
|
| 73 |
-
],
|
| 74 |
-
dtype=np.float32,
|
| 75 |
-
)
|
| 76 |
-
|
| 77 |
-
left, right, left_bbox, right_bbox = crop.extract_eye_crops(
|
| 78 |
-
frame, landmarks, expand=0.0, crop_size=32
|
| 79 |
-
)
|
| 80 |
-
assert left.shape == (32, 32, 3)
|
| 81 |
-
assert right.shape == (32, 32, 3)
|
| 82 |
-
assert len(left_bbox) == 4
|
| 83 |
-
assert len(right_bbox) == 4
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tests/test_pipeline_integration.py
CHANGED
|
@@ -22,7 +22,7 @@ class _DummyDetector:
|
|
| 22 |
|
| 23 |
|
| 24 |
def test_face_mesh_pipeline_no_face_returns_expected_keys():
|
| 25 |
-
pipe = FaceMeshPipeline(detector=_DummyDetector(landmarks=None)
|
| 26 |
frame = np.zeros((480, 640, 3), dtype=np.uint8)
|
| 27 |
out = pipe.process_frame(frame)
|
| 28 |
|
|
@@ -35,7 +35,7 @@ def test_face_mesh_pipeline_no_face_returns_expected_keys():
|
|
| 35 |
|
| 36 |
def test_face_mesh_pipeline_with_fake_landmarks_runs():
|
| 37 |
fake_lm = np.zeros((478, 2), dtype=np.float32)
|
| 38 |
-
pipe = FaceMeshPipeline(detector=_DummyDetector(landmarks=fake_lm)
|
| 39 |
frame = np.zeros((480, 640, 3), dtype=np.uint8)
|
| 40 |
out = pipe.process_frame(frame)
|
| 41 |
|
|
|
|
| 22 |
|
| 23 |
|
| 24 |
def test_face_mesh_pipeline_no_face_returns_expected_keys():
|
| 25 |
+
pipe = FaceMeshPipeline(detector=_DummyDetector(landmarks=None))
|
| 26 |
frame = np.zeros((480, 640, 3), dtype=np.uint8)
|
| 27 |
out = pipe.process_frame(frame)
|
| 28 |
|
|
|
|
| 35 |
|
| 36 |
def test_face_mesh_pipeline_with_fake_landmarks_runs():
|
| 37 |
fake_lm = np.zeros((478, 2), dtype=np.float32)
|
| 38 |
+
pipe = FaceMeshPipeline(detector=_DummyDetector(landmarks=fake_lm))
|
| 39 |
frame = np.zeros((480, 640, 3), dtype=np.uint8)
|
| 40 |
out = pipe.process_frame(frame)
|
| 41 |
|