Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions .idea/biome-backend.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file added Final_gym/__init__.py
Empty file.
35 changes: 35 additions & 0 deletions Final_gym/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from scripts.realtime_match import run_live_match

def main():
print("\n=== AI GYM TRAINER ===\n")
print("Select Exercise:")
print("1. Push-up")
print("2. Squat")
print("3. Deadlift")
print("4. Plank")
print("5. Lunge")
print("6. Pull-up\n")

choice = input("Enter number: ")

exercise_map = {
"1": ("refs/ref_pushup.npy", "pushup"),
"2": ("refs/ref_squat.npy", "squat"),
"3": ("refs/ref_deadlift.npy", "deadlift"),
"4": ("refs/ref_plank.npy", "plank"),
"5": ("refs/ref_lunge.npy", "lunge"),
"6": ("refs/ref_pullup.npy", "pullup"),
}

if choice not in exercise_map:
print("Invalid choice!")
return

ref_path, name = exercise_map[choice]
print(f"\nLoading reference → {ref_path}")
print(f"Starting {name} trainer... (press Q to exit)\n")

run_live_match(ref_path, name)

if __name__ == "__main__":
main()
Binary file added Final_gym/refs/ref_lunge.mp4
Binary file not shown.
Binary file added Final_gym/refs/ref_lunge.npy
Binary file not shown.
Binary file added Final_gym/refs/ref_plank.mp4
Binary file not shown.
Binary file added Final_gym/refs/ref_plank.npy
Binary file not shown.
Binary file added Final_gym/refs/ref_pullup.mp4
Binary file not shown.
Binary file added Final_gym/refs/ref_pullup.npy
Binary file not shown.
Binary file added Final_gym/refs/ref_pushup.mp4
Binary file not shown.
Binary file added Final_gym/refs/ref_pushup.npy
Binary file not shown.
Binary file added Final_gym/refs/ref_squat.mp4
Binary file not shown.
Binary file added Final_gym/refs/ref_squat.npy
Binary file not shown.
69 changes: 69 additions & 0 deletions Final_gym/scripts/extract_reference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import os
import sys
# For Root Directory
sys.path.append(os.path.dirname(os.path.dirname(__file__)))


try:
import cv2
import numpy as np
import mediapipe as mp
from utils.pose_features import vectorize, ema
from utils.joint_angles import extract_angle_vector
#For Exceptions
except Exception as e:
print("❌ Import Error:", e)
sys.exit(1)

def extract_reference(video_path, save_path, max_frames=120):
mp_pose = mp.solutions.pose
pose = mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5)

cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print(f"❌ Cannot open video: {video_path}")
return

seq = []
smoothed = None
frame_i = 0

while True:
ret, frame = cap.read()
if not ret or frame_i > max_frames:
break
h, w = frame.shape[:2]
if h > w:
frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE)

frame = cv2.resize(frame, (960, 540))

rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
res = pose.process(rgb)

if res.pose_landmarks:

feat = extract_angle_vector(res.pose_landmarks.landmark)
if feat is not None and not np.any(np.isnan(feat)):
smoothed = ema(smoothed, feat, 0.2)
if smoothed is not None:
seq.append(feat)
frame_i += 1
print(f"Processing frame {frame_i}", end="\r")

cap.release()

if len(seq) < 20:
print("⚠️ Not enough pose frames. Try a clearer slow squat video.")
return

np.save(save_path, np.stack(seq))
print(f"\n✅ Saved REFERENCE MODEL: {save_path} ({len(seq)} frames)")

if __name__ == "__main__":
print("\n=== Reference Pose Extractor ===")
if len(sys.argv) < 3:
print("Usage: python scripts/extract_reference.py refs/ref_squat.mp4 refs/ref_squat.npy")
sys.exit(0)

extract_reference(sys.argv[1], sys.argv[2])
234 changes: 234 additions & 0 deletions Final_gym/scripts/realtime_match.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import cv2
import sys
import numpy as np
from fastdtw import fastdtw
from scipy.spatial.distance import cosine
import mediapipe as mp
from utils.pose_features import ema
from utils.sound import speak
from utils.joint_angles import extract_angle_vector

IDEAL_RANGES = {
'hip': (85, 130),
'knee': (70, 140),
'back': (150, 180),
'ankle': (70, 110)
}

def analyze_pose(angles):
"""Analyze pose angles and return (score, feedback)."""
if angles is None or len(angles) != 4:
return 0, "Invalid pose detected"

hip_angle, knee_angle, back_angle, ankle_angle = angles
feedback = []
scores = []


hip_min, hip_max = IDEAL_RANGES['hip']
if hip_angle < hip_min:
feedback.append("Hips too high")
scores.append(hip_angle / hip_min * 100)
elif hip_angle > hip_max:
feedback.append("Hips too low")
scores.append((180 - hip_angle) / (180 - hip_max) * 100)
else:
scores.append(100)


knee_min, knee_max = IDEAL_RANGES['knee']
if knee_angle < knee_min:
feedback.append("Knees too bent")
scores.append(knee_angle / knee_min * 100)
elif knee_angle > knee_max:
feedback.append("Need to bend knees more")
scores.append((180 - knee_angle) / (180 - knee_max) * 100)
else:
scores.append(100)


back_min, back_max = IDEAL_RANGES['back']
if back_angle < back_min:
feedback.append("Straighten your back")
scores.append(back_angle / back_min * 100)
else:
scores.append(100)

ankle_min, ankle_max = IDEAL_RANGES['ankle']
if ankle_angle < ankle_min:
feedback.append("Ankles too bent")
scores.append(ankle_angle / ankle_min * 100)
elif ankle_angle > ankle_max:
feedback.append("Need more ankle flexion")
scores.append((180 - ankle_angle) / (180 - ankle_max) * 100)
else:
scores.append(100)


weights = np.array([0.35, 0.35, 0.2, 0.1])
final_score = np.average(scores, weights=weights)

if not feedback:
return final_score, "Good form"
return final_score, " | ".join(feedback)

def load_ref(path: str) -> np.ndarray:
"""Load and validate reference data."""
try:
arr = np.load(path, allow_pickle=True)
print("[DEBUG] Initial load shape:", arr.shape, "dtype:", arr.dtype)

if arr.ndim == 1:
arr = arr[None, :]


if arr.dtype == 'O':
valid_frames = [frame for frame in arr if frame is not None]
if not valid_frames:
raise ValueError("No valid frames found in reference data")
arr = np.stack(valid_frames)

arr = arr.astype(np.float32, copy=False)
print("[DEBUG] Value range:", arr.min(), "to", arr.max())
return arr

except Exception as e:
print(f"[ERROR] Failed to load reference data: {e}")
raise

def run_live_match(ref_path, exercise_name="squat", speak_on=True):
print("[DEBUG] Loading reference from:", ref_path)

try:
ref = load_ref(ref_path)
print(f"[INFO] Loaded ref: {ref_path} shape={ref.shape}")
if len(ref) < 20:
raise RuntimeError(f"Reference too short or empty: {ref_path}")
except Exception as e:
print(f"[ERROR] Failed to load reference: {e}")
raise
try:
mp_pose = mp.solutions.pose
pose = mp_pose.Pose(
static_image_mode=False,
model_complexity=1,
min_detection_confidence=0.5,
min_tracking_confidence=0.5
)

except Exception as e:
print(f"[ERROR] Failed to initialize MediaPipe: {e}")
raise

try:
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) # Try DirectShow first
if not cap.isOpened():
cap = cv2.VideoCapture(0) # Fallback to default
if not cap.isOpened():
raise RuntimeError("Could not open camera - please check if it's connected and not in use")

# Try to set reasonable camera parameters
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
cap.set(cv2.CAP_PROP_FPS, 30)


print(f"[DEBUG] Camera resolution: {int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))}x{int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))}")
except Exception as e:
print(f"[ERROR] Camera initialization failed: {e}")
raise

smoothed = None
last_feedback = ""
feedback_cooldown = 0

EXCELLENT_THRESHOLD = 90.0
GOOD_THRESHOLD = 75.0
OKAY_THRESHOLD = 60.0
FEEDBACK_COOLDOWN = 30

try:
while True:
ok, frame = cap.read()
if not ok:
print("[ERROR] Failed to read from camera")
break

h, w = frame.shape[:2]
if h > w:
frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE)
frame = cv2.resize(frame, (960, 540))

rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = pose.process(rgb)

score = 0.0
feedback = "NO BODY DETECTED"
color = (128, 128, 128)

if results.pose_landmarks:

curr_angles = extract_angle_vector(results.pose_landmarks.landmark)
if curr_angles is not None:
smoothed = ema(smoothed, curr_angles, 0.2)
if smoothed is not None:
score, pose_feedback = analyze_pose(smoothed)

if score >= EXCELLENT_THRESHOLD:
feedback = "EXCELLENT FORM ⭐"
color = (0, 255, 0)
elif score >= GOOD_THRESHOLD:
feedback = "GOOD FORM ✅"
color = (0, 255, 255)
elif score >= OKAY_THRESHOLD:
feedback = "OKAY FORM 🔄"
color = (0, 165, 255)
else:
feedback = "FIX FORM ❌"
color = (0, 0, 255)

if score < EXCELLENT_THRESHOLD:
feedback = f"{feedback}\n{pose_feedback}"

if feedback != last_feedback and feedback_cooldown <= 0:
if speak_on:
try:
speak(pose_feedback if score < EXCELLENT_THRESHOLD else "Perfect form")
feedback_cooldown = FEEDBACK_COOLDOWN
except Exception:
pass
last_feedback = feedback

feedback_cooldown = max(0, feedback_cooldown - 1)

cv2.putText(frame, f"{exercise_name.upper()} FORM CHECK", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255,255,255), 2)
cv2.putText(frame, f"Score: {int(score)}%", (10, 70),
cv2.FONT_HERSHEY_SIMPLEX, 1.1, color, 3)

y = 110
for line in feedback.split('\n'):
cv2.putText(frame, line, (10, y),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (230,230,230), 2)
y += 40

if results.pose_landmarks:
mp.solutions.drawing_utils.draw_landmarks(
frame, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)

cv2.imshow("Form Checker", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break

except Exception as e:
print(f"[ERROR] Runtime error: {e}")
finally:
print("[DEBUG] Cleaning up...")
cap.release()
cv2.destroyAllWindows()

if __name__ == "__main__":
print("in if")
ref_path = sys.argv[1] if len(sys.argv) > 1 else "refs/ref_squat.npy"
exercise = sys.argv[2] if len(sys.argv) > 2 else "squat"
run_live_match(ref_path, exercise)
Empty file added Final_gym/utils/__init__.py
Empty file.
Loading