106 lines
3.9 KiB
Go
106 lines
3.9 KiB
Go
package fall
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
|
|
"silverpose/v2/internal/pose"
|
|
)
|
|
|
|
func TestEngineConfirmsPersistentHorizontalPoseAtV1Delay(t *testing.T) {
|
|
engine, err := NewEngine(EngineConfig{
|
|
KeypointConfidenceThreshold: 0.4,
|
|
SuspectWindowSeconds: 0.5,
|
|
ConfirmWindowSeconds: 1.8,
|
|
RecoveryWindowSeconds: 2.0,
|
|
CooldownSeconds: 10.0,
|
|
RequireRapidDrop: false,
|
|
RequireLowerBody: false,
|
|
HorizontalAngleThresholdDegrees: 45.0,
|
|
ConfigVersion: "cfg-v1-contract",
|
|
SessionID: "regression",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewEngine returned an error: %v", err)
|
|
}
|
|
|
|
engine.Process(Frame{Timestamp: 0.0, Width: 180, Height: 180, Poses: []pose.PersonPose{testPose(false)}})
|
|
engine.Process(Frame{Timestamp: 0.1, Width: 180, Height: 180, Poses: []pose.PersonPose{testPose(true)}})
|
|
result := engine.Process(Frame{Timestamp: 1.91, Width: 180, Height: 180, Poses: []pose.PersonPose{testPose(true)}})
|
|
|
|
if len(result.Events) != 1 {
|
|
t.Fatalf("events = %+v, want one confirmation", result.Events)
|
|
}
|
|
event := result.Events[0]
|
|
if event.TrackID != "P-0001" || math.Abs(event.LatencySeconds-1.81) > 1e-9 || event.ConfigVersion != "cfg-v1-contract" {
|
|
t.Fatalf("event = %+v", event)
|
|
}
|
|
if got := engine.StateOf("P-0001"); got != Confirmed {
|
|
t.Fatalf("state = %s, want CONFIRMED", got)
|
|
}
|
|
}
|
|
|
|
func TestEngineDoesNotConfirmWhenTrackIsMissing(t *testing.T) {
|
|
engine, err := NewEngine(testConfig())
|
|
if err != nil {
|
|
t.Fatalf("NewEngine returned an error: %v", err)
|
|
}
|
|
|
|
engine.Process(Frame{Timestamp: 0.0, Width: 180, Height: 180, Poses: []pose.PersonPose{testPose(false)}})
|
|
engine.Process(Frame{Timestamp: 0.1, Width: 180, Height: 180, Poses: []pose.PersonPose{testPose(true)}})
|
|
engine.Process(Frame{Timestamp: 0.2, Width: 180, Height: 180})
|
|
result := engine.Process(Frame{Timestamp: 1.9, Width: 180, Height: 180, Poses: []pose.PersonPose{testPose(true)}})
|
|
|
|
if len(result.Events) != 0 {
|
|
t.Fatalf("events = %+v, want none after a missing track", result.Events)
|
|
}
|
|
}
|
|
|
|
func TestEngineDoesNotAlarmForUprightSequence(t *testing.T) {
|
|
engine, err := NewEngine(testConfig())
|
|
if err != nil {
|
|
t.Fatalf("NewEngine returned an error: %v", err)
|
|
}
|
|
|
|
for _, timestamp := range []float64{0.0, 0.6, 1.2, 1.8, 2.4} {
|
|
result := engine.Process(Frame{Timestamp: timestamp, Width: 180, Height: 180, Poses: []pose.PersonPose{testPose(false)}})
|
|
if len(result.Events) != 0 {
|
|
t.Fatalf("timestamp %v emitted %+v for upright pose", timestamp, result.Events)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testConfig() EngineConfig {
|
|
return EngineConfig{
|
|
KeypointConfidenceThreshold: 0.4,
|
|
SuspectWindowSeconds: 0.5,
|
|
ConfirmWindowSeconds: 1.8,
|
|
RecoveryWindowSeconds: 2.0,
|
|
CooldownSeconds: 10.0,
|
|
RequireRapidDrop: false,
|
|
RequireLowerBody: false,
|
|
HorizontalAngleThresholdDegrees: 45.0,
|
|
ConfigVersion: "cfg-v1-contract",
|
|
SessionID: "regression",
|
|
}
|
|
}
|
|
|
|
func testPose(horizontal bool) pose.PersonPose {
|
|
person := pose.PersonPose{Box: pose.Box{Left: 20, Top: 20, Right: 160, Bottom: 160}, Confidence: 0.9}
|
|
for index := range person.Keypoints {
|
|
person.Keypoints[index] = pose.Keypoint{X: float32(index), Y: float32(index), Confidence: 0.9}
|
|
}
|
|
if horizontal {
|
|
person.Keypoints[5] = pose.Keypoint{X: 20, Y: 80, Confidence: 0.9}
|
|
person.Keypoints[6] = pose.Keypoint{X: 30, Y: 80, Confidence: 0.9}
|
|
person.Keypoints[11] = pose.Keypoint{X: 70, Y: 100, Confidence: 0.9}
|
|
person.Keypoints[12] = pose.Keypoint{X: 80, Y: 100, Confidence: 0.9}
|
|
} else {
|
|
person.Keypoints[5] = pose.Keypoint{X: 30, Y: 10, Confidence: 0.9}
|
|
person.Keypoints[6] = pose.Keypoint{X: 40, Y: 10, Confidence: 0.9}
|
|
person.Keypoints[11] = pose.Keypoint{X: 30, Y: 30, Confidence: 0.9}
|
|
person.Keypoints[12] = pose.Keypoint{X: 40, Y: 30, Confidence: 0.9}
|
|
}
|
|
return person
|
|
}
|