Spaces:
Build error
Build error
| import json | |
| import numpy as np | |
| import signspeak.asl.asl_detector as asl_detector_module | |
| from signspeak.asl.asl_detector import ASLDetector | |
| def test_load_labels_prefers_prediction_index_map(tmp_path): | |
| model_dir = tmp_path / "asl" | |
| model_dir.mkdir() | |
| (model_dir / "sign_to_prediction_index_map.json").write_text( | |
| json.dumps({"love": 2, "hello": 0, "thanks": 1}), | |
| encoding="utf-8", | |
| ) | |
| detector = ASLDetector(model_dir=model_dir) | |
| assert detector.labels == ["hello", "thanks", "love"] | |
| class FakeSignatureInterpreter: | |
| def get_signature_list(self): | |
| return {"serving_default": {"inputs": ["inputs"], "outputs": ["outputs"]}} | |
| def get_signature_runner(self, signature_name): | |
| assert signature_name == "serving_default" | |
| def predict(**kwargs): | |
| assert kwargs["inputs"].shape == (543, 3) | |
| assert np.isnan(kwargs["inputs"][0][0]) | |
| return {"outputs": np.asarray([[0.1, 0.8, 0.1]], dtype=np.float32)} | |
| return predict | |
| def test_predict_uses_tflite_signature_runner(tmp_path): | |
| detector = ASLDetector(model_dir=tmp_path) | |
| keypoints = np.zeros((543, 3), dtype=np.float32) | |
| keypoints[0][0] = np.nan | |
| output = detector._predict(FakeSignatureInterpreter(), keypoints) | |
| assert output.shape == (1, 3) | |
| assert float(output[0][1]) == np.float32(0.8) | |
| class FakeLandmarkResult: | |
| keypoints = np.zeros((30, 543, 3), dtype=np.float32) | |
| status = "ok" | |
| detector = "fake" | |
| error = None | |
| class FakeLandmarksDetector: | |
| def __init__(self, missing_value=0.0): | |
| pass | |
| def detect_sequence(self, frames): | |
| return FakeLandmarkResult() | |
| def close(self): | |
| pass | |
| def test_low_confidence_prediction_is_not_accepted(monkeypatch, tmp_path): | |
| model_dir = tmp_path / "asl" | |
| model_dir.mkdir() | |
| (model_dir / "model.tflite").write_bytes(b"demo") | |
| (model_dir / "sign_to_prediction_index_map.json").write_text( | |
| json.dumps({"where": 0, "hello": 1}), | |
| encoding="utf-8", | |
| ) | |
| monkeypatch.setattr(asl_detector_module, "LandmarksDetector", FakeLandmarksDetector) | |
| monkeypatch.setenv("ASL_CONFIDENCE_THRESHOLD", "0.70") | |
| monkeypatch.setattr(ASLDetector, "_load_interpreter", lambda self: object()) | |
| monkeypatch.setattr( | |
| ASLDetector, | |
| "_predict", | |
| lambda self, interpreter, keypoints: np.asarray([[0.667, 0.333]], dtype=np.float32), | |
| ) | |
| result = ASLDetector(model_dir=model_dir).predict_from_frames([np.zeros((2, 2, 3), dtype=np.uint8)] * 30) | |
| assert result["status"] == "low_confidence" | |
| assert result["top_prediction"] == "where" | |
| assert result["gloss_sequence"] == [] | |
| def test_sequence_prediction_collapses_accepted_windows(monkeypatch, tmp_path): | |
| model_dir = tmp_path / "asl" | |
| model_dir.mkdir() | |
| (model_dir / "model.tflite").write_bytes(b"demo") | |
| (model_dir / "sign_to_prediction_index_map.json").write_text( | |
| json.dumps({"hello": 0, "water": 1}), | |
| encoding="utf-8", | |
| ) | |
| class SequenceLandmarkResult: | |
| keypoints = np.zeros((60, 543, 3), dtype=np.float32) | |
| status = "ok" | |
| detector = "fake" | |
| error = None | |
| class SequenceLandmarksDetector: | |
| def __init__(self, missing_value=0.0): | |
| pass | |
| def detect_sequence(self, frames): | |
| return SequenceLandmarkResult() | |
| def close(self): | |
| pass | |
| calls = iter( | |
| [ | |
| np.asarray([[0.9, 0.1]], dtype=np.float32), | |
| np.asarray([[0.86, 0.14]], dtype=np.float32), | |
| np.asarray([[0.1, 0.9]], dtype=np.float32), | |
| ] | |
| ) | |
| monkeypatch.setattr(asl_detector_module, "LandmarksDetector", SequenceLandmarksDetector) | |
| monkeypatch.setenv("ASL_CONFIDENCE_THRESHOLD", "0.70") | |
| monkeypatch.setenv("ASL_SEQUENCE_WINDOW", "30") | |
| monkeypatch.setenv("ASL_SEQUENCE_STRIDE", "15") | |
| monkeypatch.setattr(ASLDetector, "_load_interpreter", lambda self: object()) | |
| monkeypatch.setattr(ASLDetector, "_predict", lambda self, interpreter, keypoints: next(calls)) | |
| result = ASLDetector(model_dir=model_dir).predict_sequence_from_frames( | |
| [np.zeros((2, 2, 3), dtype=np.uint8)] * 60 | |
| ) | |
| assert result["status"] == "ok" | |
| assert result["gloss_sequence"] == ["hello", "water"] | |
| assert result["windows_used"] == 3 | |
| assert result["recognition_mode"] == "sliding_window_sequence" | |