File size: 9,841 Bytes
1fe2781
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
"""Synthetic test suite for EyeGuard 20-20-20. No webcam required."""
import time, sys
sys.path.insert(0, '/app')
from eye_guard_2020 import EyeGuard2020, Status, update_eye_state
from eye_guard_2020 import REST_DURATION_SECONDS, SCREEN_TIME_ALERT_SECONDS, MIN_CONSECUTIVE_CLOSED_FRAMES


def run_state_machine(guard, ear_l, ear_r, now_override=None):
    now = now_override if now_override is not None else time.time()
    state = guard.state
    ear_left = guard._smooth_ear(state.ear_history_left, ear_l)
    ear_right = guard._smooth_ear(state.ear_history_right, ear_r)
    state.left_eye = update_eye_state(state.left_eye, ear_left, guard.ear_open, guard.ear_closed)
    state.right_eye = update_eye_state(state.right_eye, ear_right, guard.ear_open, guard.ear_closed)

    screen_elapsed = now - state.screen_timer_start
    both_eyes_closed = state.left_eye.is_closed and state.right_eye.is_closed
    one_eye_closed = (state.left_eye.is_closed and not state.right_eye.is_closed) or \
                     (not state.left_eye.is_closed and state.right_eye.is_closed)

    if state.status == Status.RESTING and one_eye_closed:
        state.spoofing_detected_at = now
        state.spoofing_count += 1
        state.status = Status.SPOOFING
        state.failed_rests += 1
        state.rest_start_time = None
        return

    if state.status == Status.ALERT and one_eye_closed:
        state.spoofing_detected_at = now
        state.spoofing_count += 1

    if state.status == Status.SCREENING:
        if screen_elapsed >= SCREEN_TIME_ALERT_SECONDS:
            state.status = Status.ALERT
    elif state.status == Status.ALERT:
        if both_eyes_closed and state.left_eye.closed_frames >= MIN_CONSECUTIVE_CLOSED_FRAMES:
            state.status = Status.RESTING
            state.rest_start_time = now
    elif state.status == Status.RESTING:
        if not both_eyes_closed:
            state.failed_rests += 1
            state.status = Status.ALERT
            state.rest_start_time = None
        else:
            rest_elapsed = now - state.rest_start_time
            if rest_elapsed >= REST_DURATION_SECONDS:
                state.status = Status.REST_COMPLETE
                state.rest_completed_time = now
                state.total_rests_completed += 1
                state.total_rest_seconds += rest_elapsed
                state.screen_timer_start = now
    elif state.status == Status.REST_COMPLETE:
        if now - state.rest_completed_time > 3.0:
            state.status = Status.SCREENING
    elif state.status == Status.SPOOFING:
        if now - state.spoofing_detected_at > 3.0:
            state.status = Status.ALERT
    elif state.status == Status.NO_FACE:
        if screen_elapsed >= SCREEN_TIME_ALERT_SECONDS:
            state.status = Status.ALERT
        else:
            state.status = Status.SCREENING


def print_frame(i, guard, desc):
    le = "C" if guard.state.left_eye.is_closed else "O"
    re = "C" if guard.state.right_eye.is_closed else "O"
    print(f"  Frame {i:3d}: Eyes=({le},{re}) Status={guard.state.status.name:<12} | {desc}")


def test_normal_rest():
    print("\n" + "=" * 60)
    print("TEST 1: Normal rest cycle (both eyes closed)")
    print("=" * 60)
    guard = EyeGuard2020()
    guard.ear_open = 0.20
    guard.ear_closed = 0.15
    t0 = 1000.0
    guard.state.screen_timer_start = t0 - SCREEN_TIME_ALERT_SECONDS - 1
    guard.state.status = Status.SCREENING

    for i in range(5):
        run_state_machine(guard, 0.25, 0.25, t0 + i * 0.033)
        print_frame(i, guard, "eyes open")
    assert guard.state.status == Status.ALERT, f"Expected ALERT, got {guard.state.status.name}"
    print("  -> ALERT triggered correctly")

    for i in range(5, 30):
        run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033)
    assert guard.state.status == Status.RESTING
    print("  -> RESTING entered correctly")

    for i in range(30, 650):
        run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033)
        if i == 35:
            print_frame(i, guard, "resting...")

    assert guard.state.status == Status.REST_COMPLETE
    assert guard.state.total_rests_completed >= 1
    print(f"  -> REST_COMPLETE! rests={guard.state.total_rests_completed}")
    print("  PASS")


def test_spoofing():
    print("\n" + "=" * 60)
    print("TEST 2: Spoofing detection (one eye closed)")
    print("=" * 60)
    guard = EyeGuard2020()
    guard.ear_open = 0.20
    guard.ear_closed = 0.15
    t0 = 2000.0
    guard.state.screen_timer_start = t0 - SCREEN_TIME_ALERT_SECONDS - 1
    guard.state.status = Status.ALERT

    for i in range(20):
        run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033)
        if i < 3:
            print_frame(i, guard, "entering rest")
    assert guard.state.status == Status.RESTING
    print("  -> RESTING entered")

    for i in range(20, 50):
        run_state_machine(guard, 0.25, 0.10, t0 + i * 0.033)
        if i < 23:
            print_frame(i, guard, "SPOOFING: left eye open!")

    assert guard.state.status == Status.SPOOFING
    assert guard.state.spoofing_count >= 1
    print(f"  -> SPOOFING detected! count={guard.state.spoofing_count}")

    for i in range(50, 200):
        run_state_machine(guard, 0.25, 0.25, t0 + i * 0.033)
        if i == 150:
            print_frame(i, guard, "after spoof penalty")
    assert guard.state.status == Status.ALERT
    print("  -> Back to ALERT after penalty")
    print("  PASS")


def test_blink_filter():
    print("\n" + "=" * 60)
    print("TEST 3: Blink filtering (brief closures ignored)")
    print("=" * 60)
    guard = EyeGuard2020()
    guard.ear_open = 0.20
    guard.ear_closed = 0.15
    t0 = 3000.0
    guard.state.screen_timer_start = t0 - SCREEN_TIME_ALERT_SECONDS - 1
    guard.state.status = Status.ALERT

    for i in range(5):
        run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033)
        print_frame(i, guard, "blink!")
    for i in range(5, 20):
        run_state_machine(guard, 0.25, 0.25, t0 + i * 0.033)
        if i < 7:
            print_frame(i, guard, "eyes open again")

    assert guard.state.status == Status.ALERT, f"Expected ALERT, got {guard.state.status.name}"
    print("  -> Blink correctly filtered, still in ALERT")

    for i in range(20, 650):
        run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033)
        if i == 30:
            print_frame(i, guard, "proper rest...")

    assert guard.state.status == Status.REST_COMPLETE
    assert guard.state.total_rests_completed >= 1
    print(f"  -> Rest completed after blink! rests={guard.state.total_rests_completed}")
    print("  PASS")


def test_interruption():
    print("\n" + "=" * 60)
    print("TEST 4: Rest interruption (early opening)")
    print("=" * 60)
    guard = EyeGuard2020()
    guard.ear_open = 0.20
    guard.ear_closed = 0.15
    t0 = 4000.0
    guard.state.screen_timer_start = t0 - SCREEN_TIME_ALERT_SECONDS - 1
    guard.state.status = Status.ALERT

    for i in range(30):
        run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033)
        if i < 3:
            print_frame(i, guard, "starting rest")
    assert guard.state.status == Status.RESTING
    print("  -> RESTING entered")

    for i in range(30, 350):
        run_state_machine(guard, 0.25, 0.25, t0 + i * 0.033)
        if i == 35:
            print_frame(i, guard, "REST INTERRUPTED!")

    assert guard.state.status == Status.ALERT
    assert guard.state.failed_rests >= 1
    print(f"  -> Rest failed (interrupted). failed={guard.state.failed_rests}")

    guard.state.status = Status.ALERT
    for i in range(350, 1000):
        run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033)
        if i == 360:
            print_frame(i, guard, "retry rest...")

    assert guard.state.status == Status.REST_COMPLETE
    assert guard.state.total_rests_completed >= 1
    print(f"  -> Retry succeeded! rests={guard.state.total_rests_completed}")
    print("  PASS")


def test_no_face():
    print("\n" + "=" * 60)
    print("TEST 5: No face visible during rest")
    print("=" * 60)
    guard = EyeGuard2020()
    guard.ear_open = 0.20
    guard.ear_closed = 0.15
    t0 = 5000.0
    guard.state.screen_timer_start = t0 - SCREEN_TIME_ALERT_SECONDS - 1
    guard.state.status = Status.ALERT

    for i in range(20):
        run_state_machine(guard, 0.10, 0.10, t0 + i * 0.033)
        if i < 3:
            print_frame(i, guard, "entering rest")
    assert guard.state.status == Status.RESTING
    print("  -> RESTING entered")

    guard.state.status = Status.NO_FACE
    guard.state.failed_rests += 1
    guard.state.rest_start_time = None

    assert guard.state.status == Status.NO_FACE
    print("  -> NO_FACE detected, rest failed")
    assert guard.state.failed_rests >= 1
    print(f"  -> failed={guard.state.failed_rests}")
    print("  PASS")


def test_one_eye_alert():
    print("\n" + "=" * 60)
    print("TEST 6: One-eye trick during ALERT phase")
    print("=" * 60)
    guard = EyeGuard2020()
    guard.ear_open = 0.20
    guard.ear_closed = 0.15
    t0 = 6000.0
    guard.state.screen_timer_start = t0 - SCREEN_TIME_ALERT_SECONDS - 1
    guard.state.status = Status.ALERT

    for i in range(50):
        run_state_machine(guard, 0.10, 0.25, t0 + i * 0.033)
        if i < 3:
            print_frame(i, guard, "one-eye trick (left closed)")

    assert guard.state.spoofing_count >= 1
    print(f"  -> Spoofing detected during ALERT! count={guard.state.spoofing_count}")
    print(f"  -> Final status: {guard.state.status.name}")
    print("  PASS")


if __name__ == "__main__":
    print("=" * 60)
    print("EyeGuard 20-20-20 Synthetic Test Suite")
    print("=" * 60)
    test_normal_rest()
    test_spoofing()
    test_blink_filter()
    test_interruption()
    test_no_face()
    test_one_eye_alert()
    print("\n" + "=" * 60)
    print("ALL TESTS PASSED!")
    print("=" * 60)