AI-Coach / push_up /evaluator.py
anhlehong
feat:frontend-using-reflex
0f26f4e
import numpy as np
from push_up.similarity import segment_reps, smooth_series, get_golden_template, align_and_score, check_is_valid_exercise, compute_pose_similarity
from push_up.rules import PushUpRuleEngine
def _normalize_sig(sigs):
"""
Chuẩn hóa sig (elbow_angle, depth*100) về khoảng [0,1] theo min-max
trong phạm vi 1 rep. Giúp DTW không bị ảnh hưởng bởi scale camera.
"""
arr = np.array(sigs)
mins = arr.min(axis=0)
maxs = arr.max(axis=0)
ranges = maxs - mins
ranges[ranges == 0] = 1 # tránh chia 0
return ((arr - mins) / ranges).tolist()
class PushUpEvaluator:
def __init__(self):
pass
def evaluate(self, ex_data, st_data):
# 1. Expert setup
ex_depths = smooth_series([d["depth_sig"] * 100 for d in ex_data])
ex_elbows = smooth_series([d["elbow_angle"] for d in ex_data])
ex_reps = segment_reps(ex_depths, ex_elbows)
if not ex_reps:
return {"error": "Chuyên gia chưa thực hiện động tác chuẩn."}
golden_rep = get_golden_template(ex_data, ex_reps)
ex_start, ex_end = golden_rep
ex_template_sigs = [ex_data[i]["sig"] for i in range(ex_start, ex_end)]
ex_template_embs = [ex_data[i]["pose_embedding"] for i in range(ex_start, ex_end)]
# Tính baseline angles của mentor từ golden rep
mentor_frames = [ex_data[i] for i in range(ex_start, ex_end)]
mentor_min_hip = np.min([f["hip_angle"] for f in mentor_frames])
mentor_max_head_drop = np.max([f.get("head_drop_norm", 0) for f in mentor_frames])
mentor_context = {
"mentor_head_angle": np.mean([f["head_angle"] for f in mentor_frames]),
"mentor_head_std": np.std([f["head_angle"] for f in mentor_frames]),
"mentor_min_head_angle": np.min([f["head_angle"] for f in mentor_frames]),
"mentor_body_line_angle": np.mean([f["body_line_angle"] for f in mentor_frames]),
"mentor_hip_angle": np.mean([f["hip_angle"] for f in mentor_frames]),
"mentor_min_elbow": np.min([f["elbow_angle"] for f in mentor_frames]),
"mentor_min_hip": mentor_min_hip,
"mentor_max_head_drop": mentor_max_head_drop,
"mentor_depth_sig_mean": np.mean([f["depth_sig"] for f in mentor_frames])
}
# Chuẩn hóa sig của template
ex_template_sigs_norm = _normalize_sig(ex_template_sigs)
# Pre-check
if not check_is_valid_exercise(st_data, ex_template_embs):
return {"error": "❌ Hệ thống nhận thấy tư thế trong video hoàn toàn KHÔNG PHẢI là động tác Hít đất. Vui lòng tải lên video đúng bài tập!"}
# 2. Student setup
st_depths = smooth_series([d["depth_sig"] * 100 for d in st_data])
st_elbows = smooth_series([d["elbow_angle"] for d in st_data])
st_reps = segment_reps(st_depths, st_elbows)
# Khởi tạo Rule Engine VỚI mentor context
rule_engine = PushUpRuleEngine(mentor_context=mentor_context)
rep_results = []
for i, (s_start, s_end) in enumerate(st_reps):
st_rep_sigs = [st_data[j]["sig"] for j in range(s_start, s_end)]
st_rep_embs = [st_data[j]["pose_embedding"] for j in range(s_start, s_end)]
# Chuẩn hóa sig trước khi DTW để loại bỏ ảnh hưởng scale camera
st_rep_sigs_norm = _normalize_sig(st_rep_sigs)
dtw_score, path = align_and_score(
st_rep_sigs_norm, ex_template_sigs_norm, st_rep_embs, ex_template_embs)
w_score, w_pair = 1.0, (0, 0)
for s_idx, e_idx in path:
sim = compute_pose_similarity(
st_data[s_start + s_idx]["pose_embedding"], ex_template_embs[e_idx])
if sim < w_score:
w_score, w_pair = sim, (s_start + s_idx, ex_start + e_idx)
st_rep_frames = [dict(d) for d in st_data[s_start:s_end]]
errors = rule_engine.evaluate_rep(st_rep_frames)
rule_score = rule_engine.calculate_score(errors)
# Rule Engine đã được calibrate với mentor context → tin cậy hơn DTW
# DTW bị ảnh hưởng bởi tỷ lệ cơ thể/góc camera khác nhau
final_score = 0.6 * rule_score + 0.4 * dtw_score
rep_results.append({
"rep_num": i+1,
"score": final_score,
"dtw_score": dtw_score,
"rule_score": rule_score,
"errors": errors,
"w_pair": w_pair,
"range": (s_start, s_end),
"kinematics": {
"min_hip": min(f["hip_angle"] for f in st_rep_frames),
"mean_hip": np.mean([f["hip_angle"] for f in st_rep_frames]),
"min_elbow": min(f["elbow_angle"] for f in st_rep_frames),
"mean_body": np.mean([f["body_line_angle"] for f in st_rep_frames])
}
})
overall_score = 0.0
if rep_results:
overall_score = np.mean([r["score"] for r in rep_results])
if len(st_reps) < len(ex_reps):
penalty = (len(ex_reps) - len(st_reps)) * 0.05
overall_score = max(0.0, overall_score - penalty)
return {
"error": None,
"st_reps_count": len(st_reps),
"ex_reps_count": len(ex_reps),
"rep_results": rep_results,
"overall_score": overall_score,
"mentor_context": mentor_context
}