File size: 2,709 Bytes
e47efb2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from collections import deque
import time


NEXT = "NEXT"
PREV = "PREV"
FIRST = "FIRST"
LAST = "LAST"
NONE = "NONE"

VALID_COMMANDS = {NEXT, PREV, FIRST, LAST, NONE}


HISTORY_SIZE = 6
SWIPE_DELTA_THRESHOLD = 0.08
COOLDOWN_SECONDS = 0.6
FINGER_CONFIRM_FRAMES = 2

_x_history = deque(maxlen=HISTORY_SIZE)
_last_command_time = 0.0
_first_hold_count = 0
_last_hold_count = 0


def _in_cooldown(now_ts: float) -> bool:
    return (now_ts - _last_command_time) < COOLDOWN_SECONDS


def _register_command(command: str, now_ts: float) -> str:
    global _last_command_time
    _last_command_time = now_ts
    _x_history.clear()
    return command


def _finger_only_command(fingers: dict) -> str:
    global _first_hold_count, _last_hold_count

    index = fingers.get("index", False)
    middle = fingers.get("middle", False)
    ring = fingers.get("ring", False)
    pinky = fingers.get("pinky", False)

    # Thumb is noisy in webcam/selfie views, so we ignore thumb for FIRST/LAST.
    only_index = index and (not middle) and (not ring) and (not pinky)
    only_pinky = pinky and (not index) and (not middle) and (not ring)

    if only_index:
        _first_hold_count += 1
        _last_hold_count = 0
        if _first_hold_count >= FINGER_CONFIRM_FRAMES:
            _first_hold_count = 0
            return FIRST
        return NONE

    if only_pinky:
        _last_hold_count += 1
        _first_hold_count = 0
        if _last_hold_count >= FINGER_CONFIRM_FRAMES:
            _last_hold_count = 0
            return LAST
        return NONE

    _first_hold_count = 0
    _last_hold_count = 0
    return NONE


def infer_gesture_command(hand_data) -> str:
    """
    Infer command from tracked hand data.
    Command priority:
    1) Finger-only gestures (FIRST/LAST)
    2) Swipe gestures (NEXT/PREV)
    3) NONE
    """
    now_ts = time.time()
    if _in_cooldown(now_ts):
        return NONE

    if not hand_data or not hand_data.get("hand_found", False):
        _x_history.clear()
        return NONE

    fingers = hand_data.get("fingers_extended", {})
    finger_command = _finger_only_command(fingers)
    if finger_command != NONE:
        return _register_command(finger_command, now_ts)

    center_x = hand_data.get("hand_center_x")
    if center_x is None:
        return NONE

    _x_history.append(float(center_x))
    if len(_x_history) < 3:
        return NONE

    delta_x = _x_history[-1] - _x_history[0]
    if delta_x >= SWIPE_DELTA_THRESHOLD:
        return _register_command(NEXT, now_ts)
    if delta_x <= -SWIPE_DELTA_THRESHOLD:
        return _register_command(PREV, now_ts)

    return NONE


def is_valid_command(command: str) -> bool:
    return command in VALID_COMMANDS