4485cbf702
Split monolithic files into focused modules: - app/core: settings, logging, lifecycle - app/signaling: websocket server, ICE parser, message models - app/webrtc: peer session, video receiver, frame source - app/vision: pose landmarker wrapper, model config, pose types - app/exercises/dead_bug: detector, metrics, rules, state machine, types - app/rendering: skeleton renderer, status overlay, window display - app/audio: rep announcer - app/diagnostics: perf timer, crash handler - configs: environment-based settings - tests: unit tests for rules, state machine, ICE parser - run.py: entry point
93 lines
3.1 KiB
Python
93 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
import cv2
|
|
import numpy as np
|
|
|
|
from app.exercises.dead_bug.types import Point
|
|
|
|
|
|
def angle(a: Point, b: Point, c: Point) -> float:
|
|
ba = np.array([a.x - b.x, a.y - b.y], dtype=np.float32)
|
|
bc = np.array([c.x - b.x, c.y - b.y], dtype=np.float32)
|
|
denom = float(np.linalg.norm(ba) * np.linalg.norm(bc))
|
|
if denom == 0:
|
|
return 0.0
|
|
cos_value = float(np.dot(ba, bc) / denom)
|
|
return float(np.degrees(np.arccos(np.clip(cos_value, -1.0, 1.0))))
|
|
|
|
|
|
def distance(a: Point, b: Point) -> float:
|
|
return float(np.hypot(a.x - b.x, a.y - b.y))
|
|
|
|
|
|
def calculate_metrics(
|
|
lm: list[Point],
|
|
*,
|
|
left_shoulder: int,
|
|
right_shoulder: int,
|
|
left_elbow: int,
|
|
right_elbow: int,
|
|
left_wrist: int,
|
|
right_wrist: int,
|
|
left_hip: int,
|
|
right_hip: int,
|
|
left_knee: int,
|
|
right_knee: int,
|
|
left_ankle: int,
|
|
right_ankle: int,
|
|
visibility_threshold: float = 0.45,
|
|
) -> dict:
|
|
left_elbow_angle = angle(lm[left_shoulder], lm[left_elbow], lm[left_wrist])
|
|
right_elbow_angle = angle(lm[right_shoulder], lm[right_elbow], lm[right_wrist])
|
|
left_knee_angle = angle(lm[left_hip], lm[left_knee], lm[left_ankle])
|
|
right_knee_angle = angle(lm[right_hip], lm[right_knee], lm[right_ankle])
|
|
|
|
shoulder_width = distance(lm[left_shoulder], lm[right_shoulder])
|
|
hip_width = distance(lm[left_hip], lm[right_hip])
|
|
scale = max(shoulder_width, hip_width, 0.08)
|
|
|
|
left_arm_extended = (
|
|
left_elbow_angle >= 145
|
|
and distance(lm[left_shoulder], lm[left_wrist]) >= scale * 1.15
|
|
and lm[left_wrist].y <= lm[left_shoulder].y + scale * 0.35
|
|
)
|
|
right_arm_extended = (
|
|
right_elbow_angle >= 145
|
|
and distance(lm[right_shoulder], lm[right_wrist]) >= scale * 1.15
|
|
and lm[right_wrist].y <= lm[right_shoulder].y + scale * 0.35
|
|
)
|
|
|
|
left_leg_extended = (
|
|
left_knee_angle >= 150
|
|
and distance(lm[left_hip], lm[left_ankle]) >= scale * 1.55
|
|
and lm[left_ankle].y >= lm[left_knee].y - scale * 0.2
|
|
)
|
|
right_leg_extended = (
|
|
right_knee_angle >= 150
|
|
and distance(lm[right_hip], lm[right_ankle]) >= scale * 1.55
|
|
and lm[right_ankle].y >= lm[right_knee].y - scale * 0.2
|
|
)
|
|
|
|
feedback: list[str] = []
|
|
if left_arm_extended and left_elbow_angle < 160:
|
|
feedback.append("Straighten left arm")
|
|
if right_arm_extended and right_elbow_angle < 160:
|
|
feedback.append("Straighten right arm")
|
|
if left_leg_extended and left_knee_angle < 165:
|
|
feedback.append("Straighten left leg")
|
|
if right_leg_extended and right_knee_angle < 165:
|
|
feedback.append("Straighten right leg")
|
|
|
|
return {
|
|
"left_arm_extended": left_arm_extended,
|
|
"right_arm_extended": right_arm_extended,
|
|
"left_leg_extended": left_leg_extended,
|
|
"right_leg_extended": right_leg_extended,
|
|
"left_elbow_angle": left_elbow_angle,
|
|
"right_elbow_angle": right_elbow_angle,
|
|
"left_knee_angle": left_knee_angle,
|
|
"right_knee_angle": right_knee_angle,
|
|
"scale": scale,
|
|
"feedback": feedback,
|
|
}
|