| 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) |
|
|
| |
| 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 |
|
|