zforkash commited on
Commit
3e30e95
·
verified ·
1 Parent(s): d310413
Files changed (1) hide show
  1. app.py +678 -673
app.py CHANGED
@@ -1,4 +1,3 @@
1
-
2
  import streamlit as st
3
  import cv2
4
  import mediapipe as mp
@@ -6,6 +5,681 @@ import numpy as np
6
  import time
7
  from sklearn.ensemble import IsolationForest
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  # Streamlit configuration
10
  st.set_page_config(page_title="Workout Tracker", page_icon="💪", layout="centered")
11
 
@@ -45,683 +719,14 @@ workout_option = st.radio("Select Your Workout:", ["Bicep Curl", "Lateral Raise"
45
  if st.button("Start Workout"):
46
  if workout_option == "Bicep Curl":
47
  st.write("Launching Bicep Curl Tracker...")
48
- # Bicep workout code
49
- import cv2
50
- import mediapipe as mp
51
- import numpy as np
52
- import time
53
- from sklearn.ensemble import IsolationForest
54
-
55
- # Mediapipe utilities
56
- mp_drawing = mp.solutions.drawing_utils
57
- mp_pose = mp.solutions.pose
58
-
59
-
60
- # Function to calculate angles between three points
61
- def calculate_angle(a, b, c):
62
- a = np.array(a)
63
- b = np.array(b)
64
- c = np.array(c)
65
-
66
- radians = np.arctan2(c[1] - b[1], c[0] - b[0]) - np.arctan2(a[1] - b[1], a[0] - b[0])
67
- angle = np.abs(np.degrees(radians))
68
- if angle > 180.0:
69
- angle = 360 - angle
70
- return angle
71
-
72
-
73
- # Function to draw text with a background
74
- def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10):
75
- text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
76
- text_x, text_y = position
77
- box_coords = (
78
- (text_x - padding, text_y - padding),
79
- (text_x + text_size[0] + padding, text_y + text_size[1] + padding),
80
- )
81
- cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED)
82
- cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness)
83
-
84
-
85
- # Real-time feedback for single rep
86
- def analyze_single_rep(rep, rep_data):
87
- """Provide actionable feedback for a single rep."""
88
- feedback = []
89
- avg_rom = np.mean([r["ROM"] for r in rep_data])
90
- avg_tempo = np.mean([r["Tempo"] for r in rep_data])
91
- avg_smoothness = np.mean([r["Smoothness"] for r in rep_data])
92
-
93
- if rep["ROM"] < avg_rom * 0.8:
94
- feedback.append("Extend arm more")
95
- if rep["Tempo"] < avg_tempo * 0.8:
96
- feedback.append("Slow down")
97
- if rep["Smoothness"] > avg_smoothness * 1.2:
98
- feedback.append("Move smoothly")
99
-
100
- return " | ".join(feedback) if feedback else "Good rep!"
101
-
102
-
103
- # Post-workout feedback function with Isolation Forest
104
- def analyze_workout_with_isolation_forest(rep_data):
105
- if not rep_data:
106
- print("No reps completed.")
107
- return
108
-
109
- print("\n--- Post-Workout Summary ---")
110
-
111
- # Convert rep_data to a feature matrix
112
- features = np.array([[rep["ROM"], rep["Tempo"], rep["Smoothness"]] for rep in rep_data])
113
-
114
- # Train Isolation Forest
115
- model = IsolationForest(contamination=0.2, random_state=42)
116
- predictions = model.fit_predict(features)
117
-
118
- # Analyze reps
119
- for i, (rep, prediction) in enumerate(zip(rep_data, predictions), 1):
120
- status = "Good" if prediction == 1 else "Anomalous"
121
- reason = []
122
- if prediction == -1: # If anomalous
123
- if rep["ROM"] < np.mean(features[:, 0]) - np.std(features[:, 0]):
124
- reason.append("Low ROM")
125
- if rep["Tempo"] < np.mean(features[:, 1]) - np.std(features[:, 1]):
126
- reason.append("Too Fast")
127
- if rep["Smoothness"] > np.mean(features[:, 2]) + np.std(features[:, 2]):
128
- reason.append("Jerky Movement")
129
- reason_str = ", ".join(reason) if reason else "None"
130
- print(f"Rep {i}: {status} | ROM: {rep['ROM']:.2f}, Tempo: {rep['Tempo']:.2f}s, Smoothness: {rep['Smoothness']:.2f} | Reason: {reason_str}")
131
-
132
-
133
- # Main workout tracking function
134
- def main():
135
- cap = cv2.VideoCapture(0)
136
- counter = 0 # Rep counter
137
- stage = None # Movement stage
138
- max_reps = 10
139
- rep_data = [] # Store metrics for each rep
140
- feedback = "" # Real-time feedback for the video feed
141
- workout_start_time = None # Timer start
142
-
143
- with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
144
- while cap.isOpened():
145
- ret, frame = cap.read()
146
- if not ret:
147
- print("Failed to grab frame.")
148
- break
149
-
150
- # Initialize workout start time
151
- if workout_start_time is None:
152
- workout_start_time = time.time()
153
-
154
- # Timer
155
- elapsed_time = time.time() - workout_start_time
156
- timer_text = f"Timer: {int(elapsed_time)}s"
157
-
158
- # Convert frame to RGB
159
- image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
160
- image.flags.writeable = False
161
- results = pose.process(image)
162
-
163
- # Convert back to BGR
164
- image.flags.writeable = True
165
- image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
166
-
167
- # Check if pose landmarks are detected
168
- if results.pose_landmarks:
169
- landmarks = results.pose_landmarks.landmark
170
-
171
- # Extract key joints
172
- shoulder = [
173
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
174
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y
175
- ]
176
- elbow = [
177
- landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,
178
- landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y
179
- ]
180
- wrist = [
181
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
182
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y
183
- ]
184
-
185
- # Check visibility of key joints
186
- visibility_threshold = 0.5
187
- if (landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].visibility < visibility_threshold or
188
- landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].visibility < visibility_threshold or
189
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].visibility < visibility_threshold):
190
- draw_text_with_background(image, "Ensure all key joints are visible!", (50, 150),
191
- cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 255, 255), 5, (0, 0, 255))
192
- cv2.imshow('Workout Feedback', image)
193
- continue # Skip processing if joints are not visible
194
-
195
- # Calculate the angle
196
- angle = calculate_angle(shoulder, elbow, wrist)
197
-
198
- # Stage logic for counting reps
199
- if angle > 160 and stage != "down":
200
- stage = "down"
201
- start_time = time.time() # Start timing for the rep
202
- start_angle = angle # Record the starting angle
203
-
204
- # Stop the program if it's the 10th rep down stage
205
- if counter == max_reps:
206
- print("Workout complete at rep 10 (down stage)!")
207
- break
208
- elif angle < 40 and stage == "down":
209
- stage = "up"
210
- counter += 1
211
- end_time = time.time() # End timing for the rep
212
- end_angle = angle # Record the ending angle
213
-
214
- # Calculate rep metrics
215
- rom = start_angle - end_angle # Range of Motion
216
- tempo = end_time - start_time # Duration of the rep
217
- smoothness = np.std([start_angle, end_angle]) # Dummy smoothness metric
218
- rep_data.append({"ROM": rom, "Tempo": tempo, "Smoothness": smoothness})
219
-
220
- # Analyze the rep using Isolation Forest
221
- feedback = analyze_single_rep(rep_data[-1], rep_data)
222
-
223
- # Wireframe color based on form
224
- wireframe_color = (0, 255, 0) if stage == "up" or stage == "down" else (0, 0, 255)
225
-
226
- # Draw wireframe
227
- mp_drawing.draw_landmarks(
228
- image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
229
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=5, circle_radius=4),
230
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=5, circle_radius=4)
231
- )
232
-
233
- # Display reps, stage, timer, and feedback
234
- draw_text_with_background(image, f"Reps: {counter}", (50, 150),
235
- cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 255, 255), 5, (0, 0, 0))
236
- draw_text_with_background(image, f"Stage: {stage if stage else 'N/A'}", (50, 300),
237
- cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 255, 255), 5, (0, 0, 0))
238
- draw_text_with_background(image, timer_text, (1000, 50), # Timer in the top-right corner
239
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3, (0, 0, 0))
240
- draw_text_with_background(image, feedback, (50, 450),
241
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3, (0, 0, 0))
242
-
243
- # Show video feed
244
- cv2.imshow('Workout Feedback', image)
245
-
246
- # Break if 'q' is pressed
247
- if cv2.waitKey(10) & 0xFF == ord('q'):
248
- break
249
-
250
- cap.release()
251
- cv2.destroyAllWindows()
252
-
253
- # Post-workout analysis
254
- analyze_workout_with_isolation_forest(rep_data)
255
-
256
-
257
- if __name__ == "__main__":
258
- main()
259
-
260
  elif workout_option == "Lateral Raise":
261
  st.write("Launching Lateral Raise Tracker...")
262
- # Lateral raise workout code
263
- import cv2
264
- import mediapipe as mp
265
- import numpy as np
266
- import time
267
- from sklearn.ensemble import IsolationForest
268
-
269
- # Mediapipe utilities
270
- mp_drawing = mp.solutions.drawing_utils
271
- mp_pose = mp.solutions.pose
272
-
273
-
274
- # Function to calculate lateral raise angle
275
- def calculate_angle_for_lateral_raise(shoulder, wrist):
276
- """
277
- Calculate the angle of the arm relative to the horizontal plane
278
- passing through the shoulder.
279
- """
280
- horizontal_reference = np.array([1, 0]) # Horizontal vector
281
- arm_vector = np.array([wrist[0] - shoulder[0], wrist[1] - shoulder[1]])
282
- dot_product = np.dot(horizontal_reference, arm_vector)
283
- magnitude_reference = np.linalg.norm(horizontal_reference)
284
- magnitude_arm = np.linalg.norm(arm_vector)
285
- if magnitude_arm == 0 or magnitude_reference == 0:
286
- return 0
287
- cos_angle = dot_product / (magnitude_reference * magnitude_arm)
288
- angle = np.arccos(np.clip(cos_angle, -1.0, 1.0))
289
- return np.degrees(angle)
290
-
291
-
292
- # Function to draw text with a background
293
- def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10):
294
- text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
295
- text_x, text_y = position
296
- box_coords = (
297
- (text_x - padding, text_y - padding),
298
- (text_x + text_size[0] + padding, text_y + text_size[1] + padding),
299
- )
300
- cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED)
301
- cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness)
302
-
303
-
304
- # Function to check if all required joints are visible
305
- def are_key_joints_visible(landmarks, visibility_threshold=0.5):
306
- """
307
- Ensure that all required joints are visible based on their visibility scores.
308
- """
309
- required_joints = [
310
- mp_pose.PoseLandmark.LEFT_SHOULDER.value,
311
- mp_pose.PoseLandmark.RIGHT_SHOULDER.value,
312
- mp_pose.PoseLandmark.LEFT_WRIST.value,
313
- mp_pose.PoseLandmark.RIGHT_WRIST.value,
314
- ]
315
- for joint in required_joints:
316
- if landmarks[joint].visibility < visibility_threshold:
317
- return False
318
- return True
319
-
320
-
321
- # Real-time feedback for single rep
322
- def analyze_single_rep(rep, rep_data):
323
- """Provide actionable feedback for a single rep."""
324
- feedback = []
325
-
326
- # Calculate averages from previous reps
327
- avg_rom = np.mean([r["ROM"] for r in rep_data]) if rep_data else 0
328
- avg_tempo = np.mean([r["Tempo"] for r in rep_data]) if rep_data else 0
329
-
330
- # Dynamic tempo thresholds
331
- lower_tempo_threshold = 2.0 # Minimum grace threshold for faster tempo
332
- upper_tempo_threshold = 9.0 # Maximum grace threshold for slower tempo
333
-
334
- # Adjust thresholds after a few reps
335
- if len(rep_data) > 3:
336
- lower_tempo_threshold = max(2.0, avg_tempo * 0.7)
337
- upper_tempo_threshold = min(9.0, avg_tempo * 1.3)
338
-
339
- # Feedback for ROM
340
- if rep["ROM"] < 30: # Minimum ROM threshold
341
- feedback.append("Lift arm higher")
342
- elif rep_data and rep["ROM"] < avg_rom * 0.8:
343
- feedback.append("Increase ROM")
344
-
345
- # Feedback for Tempo
346
- if rep["Tempo"] < lower_tempo_threshold: # Tempo too fast
347
- feedback.append("Slow down")
348
- elif rep["Tempo"] > upper_tempo_threshold: # Tempo too slow
349
- feedback.append("Speed up")
350
-
351
- return feedback
352
-
353
-
354
- # Post-workout feedback function
355
- def analyze_workout_with_isolation_forest(rep_data):
356
- if not rep_data:
357
- print("No reps completed.")
358
- return
359
-
360
- print("\n--- Post-Workout Summary ---")
361
-
362
- # Filter valid reps for recalculating thresholds
363
- valid_reps = [rep for rep in rep_data if rep["ROM"] > 20] # Ignore very low ROM reps
364
-
365
- if not valid_reps:
366
- print("No valid reps to analyze.")
367
- return
368
-
369
- features = np.array([[rep["ROM"], rep["Tempo"]] for rep in valid_reps])
370
-
371
- avg_rom = np.mean(features[:, 0])
372
- avg_tempo = np.mean(features[:, 1])
373
- std_rom = np.std(features[:, 0])
374
- std_tempo = np.std(features[:, 1])
375
-
376
- # Adjusted bounds for anomalies
377
- rom_lower_bound = max(20, avg_rom - std_rom * 2)
378
- tempo_lower_bound = max(1.0, avg_tempo - std_tempo * 2)
379
- tempo_upper_bound = min(10.0, avg_tempo + std_tempo * 2)
380
-
381
- print(f"ROM Lower Bound: {rom_lower_bound}")
382
- print(f"Tempo Bounds: {tempo_lower_bound}-{tempo_upper_bound}")
383
-
384
- # Anomaly detection
385
- for i, rep in enumerate(valid_reps, 1):
386
- feedback = []
387
- if rep["ROM"] < rom_lower_bound:
388
- feedback.append("Low ROM")
389
- if rep["Tempo"] < tempo_lower_bound:
390
- feedback.append("Too Fast")
391
- elif rep["Tempo"] > tempo_upper_bound:
392
- feedback.append("Too Slow")
393
-
394
- if feedback:
395
- print(f"Rep {i}: Anomalous | Feedback: {', '.join(feedback[:1])}")
396
-
397
- # Use Isolation Forest for secondary anomaly detection
398
- model = IsolationForest(contamination=0.1, random_state=42) # Reduced contamination
399
- predictions = model.fit_predict(features)
400
-
401
- for i, prediction in enumerate(predictions, 1):
402
- if prediction == -1: # Outlier
403
- print(f"Rep {i}: Isolation Forest flagged this rep as anomalous.")
404
-
405
-
406
- # Main workout tracking function
407
- def main():
408
- cap = cv2.VideoCapture(0)
409
- counter = 0 # Rep counter
410
- stage = None # Movement stage
411
- feedback = [] # Real-time feedback for the video feed
412
- rep_data = [] # Store metrics for each rep
413
- angles_during_rep = [] # Track angles during a single rep
414
- workout_start_time = None # Timer start
415
-
416
- with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
417
- while cap.isOpened():
418
- ret, frame = cap.read()
419
- if not ret:
420
- print("Failed to grab frame.")
421
- break
422
-
423
- # Initialize workout start time
424
- if workout_start_time is None:
425
- workout_start_time = time.time()
426
-
427
- # Timer
428
- elapsed_time = time.time() - workout_start_time
429
- timer_text = f"Timer: {int(elapsed_time)}s"
430
-
431
- # Convert the image to RGB
432
- image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
433
- image.flags.writeable = False
434
- results = pose.process(image)
435
-
436
- # Convert back to BGR
437
- image.flags.writeable = True
438
- image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
439
-
440
- # Check if pose landmarks are detected
441
- if results.pose_landmarks:
442
- landmarks = results.pose_landmarks.landmark
443
-
444
- # Check if key joints are visible
445
- if not are_key_joints_visible(landmarks):
446
- draw_text_with_background(
447
- image, "Ensure all joints are visible", (50, 50),
448
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255)
449
- )
450
- cv2.imshow("Lateral Raise Tracker", image)
451
- continue
452
-
453
- # Extract key joints
454
- left_shoulder = [
455
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
456
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y,
457
- ]
458
- left_wrist = [
459
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
460
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y,
461
- ]
462
-
463
- # Calculate angle for lateral raise
464
- angle = calculate_angle_for_lateral_raise(left_shoulder, left_wrist)
465
-
466
- # Track angles during a rep
467
- if stage == "up" or stage == "down":
468
- angles_during_rep.append(angle)
469
-
470
- # Stage logic for counting reps
471
- if angle < 20 and stage != "down":
472
- stage = "down"
473
- if counter == 10: # Stop on the down stage of the 10th rep
474
- print("Workout complete! 10 reps reached.")
475
- break
476
-
477
- # Calculate ROM for the completed rep
478
- if len(angles_during_rep) > 1:
479
- rom = max(angles_during_rep) - min(angles_during_rep)
480
- else:
481
- rom = 0.0
482
-
483
- tempo = elapsed_time
484
- print(f"Rep {counter + 1}: ROM={rom:.2f}, Tempo={tempo:.2f}s")
485
-
486
- # Record metrics for the rep
487
- rep_data.append({
488
- "ROM": rom,
489
- "Tempo": tempo,
490
- })
491
-
492
- # Reset angles and timer for the next rep
493
- angles_during_rep = []
494
- workout_start_time = time.time() # Reset timer
495
-
496
- if 70 <= angle <= 110 and stage == "down":
497
- stage = "up"
498
- counter += 1
499
-
500
- # Analyze feedback
501
- feedback = analyze_single_rep(rep_data[-1], rep_data)
502
-
503
- # Determine wireframe color
504
- wireframe_color = (0, 255, 0) if not feedback else (0, 0, 255)
505
-
506
- # Display feedback
507
- draw_text_with_background(image, f"Reps: {counter}", (50, 50),
508
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
509
- draw_text_with_background(image, " | ".join(feedback), (50, 120),
510
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
511
- draw_text_with_background(image, timer_text, (50, 190),
512
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
513
-
514
- # Render detections with wireframe color
515
- mp_drawing.draw_landmarks(
516
- image,
517
- results.pose_landmarks,
518
- mp_pose.POSE_CONNECTIONS,
519
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
520
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
521
- )
522
-
523
- # Display the image
524
- cv2.imshow("Lateral Raise Tracker", image)
525
-
526
- if cv2.waitKey(10) & 0xFF == ord("q"):
527
- break
528
-
529
- cap.release()
530
- cv2.destroyAllWindows()
531
-
532
- # Post-workout analysis
533
- analyze_workout_with_isolation_forest(rep_data)
534
-
535
-
536
- if __name__ == "__main__":
537
- main()
538
-
539
  elif workout_option == "Shoulder Press":
540
  st.write("Launching Shoulder Press Tracker...")
541
- # Shoulder press workout code
542
- import cv2
543
- import mediapipe as mp
544
- import numpy as np
545
- import time
546
-
547
- # Mediapipe utilities
548
- mp_drawing = mp.solutions.drawing_utils
549
- mp_pose = mp.solutions.pose
550
-
551
- # Function to calculate angles
552
- def calculate_angle(point_a, point_b, point_c):
553
- vector_ab = np.array([point_a[0] - point_b[0], point_a[1] - point_b[1]])
554
- vector_cb = np.array([point_c[0] - point_b[0], point_c[1] - point_b[1]])
555
- dot_product = np.dot(vector_ab, vector_cb)
556
- magnitude_ab = np.linalg.norm(vector_ab)
557
- magnitude_cb = np.linalg.norm(vector_cb)
558
- if magnitude_ab == 0 or magnitude_cb == 0:
559
- return 0
560
- cos_angle = dot_product / (magnitude_ab * magnitude_cb)
561
- angle = np.arccos(np.clip(cos_angle, -1.0, 1.0))
562
- return np.degrees(angle)
563
-
564
-
565
- # Function to check if all required joints are visible
566
- def are_key_joints_visible(landmarks, visibility_threshold=0.5):
567
- required_joints = [
568
- mp_pose.PoseLandmark.LEFT_SHOULDER.value,
569
- mp_pose.PoseLandmark.RIGHT_SHOULDER.value,
570
- mp_pose.PoseLandmark.LEFT_ELBOW.value,
571
- mp_pose.PoseLandmark.RIGHT_ELBOW.value,
572
- mp_pose.PoseLandmark.LEFT_WRIST.value,
573
- mp_pose.PoseLandmark.RIGHT_WRIST.value,
574
- ]
575
- for joint in required_joints:
576
- if landmarks[joint].visibility < visibility_threshold:
577
- return False
578
- return True
579
-
580
-
581
- # Function to draw text with a background
582
- def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10):
583
- text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
584
- text_x, text_y = position
585
- box_coords = (
586
- (text_x - padding, text_y - padding),
587
- (text_x + text_size[0] + padding, text_y + text_size[1] + padding),
588
- )
589
- cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED)
590
- cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness)
591
-
592
-
593
- # Main workout tracking function
594
- def main():
595
- cap = cv2.VideoCapture(0)
596
- counter = 0
597
- stage = None
598
- feedback = ""
599
- workout_start_time = None
600
- rep_start_time = None
601
-
602
- with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
603
- while cap.isOpened():
604
- ret, frame = cap.read()
605
- if not ret:
606
- print("Failed to grab frame.")
607
- break
608
-
609
- # Initialize workout start time
610
- if workout_start_time is None:
611
- workout_start_time = time.time()
612
-
613
- # Timer
614
- elapsed_time = time.time() - workout_start_time
615
- timer_text = f"Timer: {int(elapsed_time)}s"
616
-
617
- # Convert the image to RGB
618
- image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
619
- image.flags.writeable = False
620
- results = pose.process(image)
621
-
622
- # Convert back to BGR
623
- image.flags.writeable = True
624
- image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
625
-
626
- # Check if pose landmarks are detected
627
- if results.pose_landmarks:
628
- landmarks = results.pose_landmarks.landmark
629
-
630
- # Check if key joints are visible
631
- if not are_key_joints_visible(landmarks):
632
- feedback = "Ensure all joints are visible"
633
- draw_text_with_background(
634
- image, feedback, (50, 50),
635
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255)
636
- )
637
- cv2.imshow("Shoulder Press Tracker", image)
638
- continue
639
-
640
- # Extract key joints for both arms
641
- left_shoulder = [
642
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
643
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y,
644
- ]
645
- left_elbow = [
646
- landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,
647
- landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y,
648
- ]
649
- left_wrist = [
650
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
651
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y,
652
- ]
653
-
654
- right_shoulder = [
655
- landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].x,
656
- landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].y,
657
- ]
658
- right_elbow = [
659
- landmarks[mp_pose.PoseLandmark.RIGHT_ELBOW.value].x,
660
- landmarks[mp_pose.PoseLandmark.RIGHT_ELBOW.value].y,
661
- ]
662
- right_wrist = [
663
- landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].x,
664
- landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].y,
665
- ]
666
-
667
- # Calculate angles
668
- left_elbow_angle = calculate_angle(left_shoulder, left_elbow, left_wrist)
669
- right_elbow_angle = calculate_angle(right_shoulder, right_elbow, right_wrist)
670
-
671
- # Check starting and ending positions
672
- if 80 <= left_elbow_angle <= 100 and 80 <= right_elbow_angle <= 100 and stage != "down":
673
- stage = "down"
674
- if counter == 10:
675
- feedback = "Workout complete! 10 reps done."
676
- draw_text_with_background(image, feedback, (50, 120),
677
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255))
678
- cv2.imshow("Shoulder Press Tracker", image)
679
- break
680
- if rep_start_time is not None:
681
- tempo = time.time() - rep_start_time
682
- feedback = f"Rep {counter} completed! Tempo: {tempo:.2f}s"
683
- rep_start_time = None
684
- elif left_elbow_angle > 160 and right_elbow_angle > 160 and stage == "down":
685
- stage = "up"
686
- counter += 1
687
- rep_start_time = time.time()
688
-
689
- # Wireframe color
690
- wireframe_color = (0, 255, 0) if "completed" in feedback or "Good" in feedback else (0, 0, 255)
691
-
692
- # Display feedback
693
- draw_text_with_background(image, f"Reps: {counter}", (50, 50),
694
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
695
- draw_text_with_background(image, feedback, (50, 120),
696
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
697
- draw_text_with_background(image, timer_text, (50, 190),
698
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
699
-
700
- # Render detections with wireframe color
701
- mp_drawing.draw_landmarks(
702
- image,
703
- results.pose_landmarks,
704
- mp_pose.POSE_CONNECTIONS,
705
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
706
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
707
- )
708
-
709
- # Display the image
710
- cv2.imshow("Shoulder Press Tracker", image)
711
-
712
- if cv2.waitKey(10) & 0xFF == ord("q"):
713
- break
714
-
715
- cap.release()
716
- cv2.destroyAllWindows()
717
-
718
-
719
- if __name__ == "__main__":
720
- main()
721
-
722
 
723
  # Footer
724
  st.markdown("---")
725
  st.markdown("**Note**: Close the workout window or press 'q' in the camera feed to stop the workout.")
726
-
727
-
 
 
1
  import streamlit as st
2
  import cv2
3
  import mediapipe as mp
 
5
  import time
6
  from sklearn.ensemble import IsolationForest
7
 
8
+ # Encapsulated workout functions
9
+
10
+ def bicep_curl():
11
+ import cv2
12
+ import mediapipe as mp
13
+ import numpy as np
14
+ import time
15
+ from sklearn.ensemble import IsolationForest
16
+
17
+ # Mediapipe utilities
18
+ mp_drawing = mp.solutions.drawing_utils
19
+ mp_pose = mp.solutions.pose
20
+
21
+
22
+ # Function to calculate angles between three points
23
+ def calculate_angle(a, b, c):
24
+ a = np.array(a)
25
+ b = np.array(b)
26
+ c = np.array(c)
27
+
28
+ radians = np.arctan2(c[1] - b[1], c[0] - b[0]) - np.arctan2(a[1] - b[1], a[0] - b[0])
29
+ angle = np.abs(np.degrees(radians))
30
+ if angle > 180.0:
31
+ angle = 360 - angle
32
+ return angle
33
+
34
+
35
+ # Function to draw text with a background
36
+ def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10):
37
+ text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
38
+ text_x, text_y = position
39
+ box_coords = (
40
+ (text_x - padding, text_y - padding),
41
+ (text_x + text_size[0] + padding, text_y + text_size[1] + padding),
42
+ )
43
+ cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED)
44
+ cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness)
45
+
46
+
47
+ # Real-time feedback for single rep
48
+ def analyze_single_rep(rep, rep_data):
49
+ """Provide actionable feedback for a single rep."""
50
+ feedback = []
51
+ avg_rom = np.mean([r["ROM"] for r in rep_data])
52
+ avg_tempo = np.mean([r["Tempo"] for r in rep_data])
53
+ avg_smoothness = np.mean([r["Smoothness"] for r in rep_data])
54
+
55
+ if rep["ROM"] < avg_rom * 0.8:
56
+ feedback.append("Extend arm more")
57
+ if rep["Tempo"] < avg_tempo * 0.8:
58
+ feedback.append("Slow down")
59
+ if rep["Smoothness"] > avg_smoothness * 1.2:
60
+ feedback.append("Move smoothly")
61
+
62
+ return " | ".join(feedback) if feedback else "Good rep!"
63
+
64
+
65
+ # Post-workout feedback function with Isolation Forest
66
+ def analyze_workout_with_isolation_forest(rep_data):
67
+ if not rep_data:
68
+ print("No reps completed.")
69
+ return
70
+
71
+ print("\n--- Post-Workout Summary ---")
72
+
73
+ # Convert rep_data to a feature matrix
74
+ features = np.array([[rep["ROM"], rep["Tempo"], rep["Smoothness"]] for rep in rep_data])
75
+
76
+ # Train Isolation Forest
77
+ model = IsolationForest(contamination=0.2, random_state=42)
78
+ predictions = model.fit_predict(features)
79
+
80
+ # Analyze reps
81
+ for i, (rep, prediction) in enumerate(zip(rep_data, predictions), 1):
82
+ status = "Good" if prediction == 1 else "Anomalous"
83
+ reason = []
84
+ if prediction == -1: # If anomalous
85
+ if rep["ROM"] < np.mean(features[:, 0]) - np.std(features[:, 0]):
86
+ reason.append("Low ROM")
87
+ if rep["Tempo"] < np.mean(features[:, 1]) - np.std(features[:, 1]):
88
+ reason.append("Too Fast")
89
+ if rep["Smoothness"] > np.mean(features[:, 2]) + np.std(features[:, 2]):
90
+ reason.append("Jerky Movement")
91
+ reason_str = ", ".join(reason) if reason else "None"
92
+ print(f"Rep {i}: {status} | ROM: {rep['ROM']:.2f}, Tempo: {rep['Tempo']:.2f}s, Smoothness: {rep['Smoothness']:.2f} | Reason: {reason_str}")
93
+
94
+
95
+ # Main workout tracking function
96
+ def main():
97
+ cap = cv2.VideoCapture(0)
98
+ counter = 0 # Rep counter
99
+ stage = None # Movement stage
100
+ max_reps = 10
101
+ rep_data = [] # Store metrics for each rep
102
+ feedback = "" # Real-time feedback for the video feed
103
+ workout_start_time = None # Timer start
104
+
105
+ with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
106
+ while cap.isOpened():
107
+ ret, frame = cap.read()
108
+ if not ret:
109
+ print("Failed to grab frame.")
110
+ break
111
+
112
+ # Initialize workout start time
113
+ if workout_start_time is None:
114
+ workout_start_time = time.time()
115
+
116
+ # Timer
117
+ elapsed_time = time.time() - workout_start_time
118
+ timer_text = f"Timer: {int(elapsed_time)}s"
119
+
120
+ # Convert frame to RGB
121
+ image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
122
+ image.flags.writeable = False
123
+ results = pose.process(image)
124
+
125
+ # Convert back to BGR
126
+ image.flags.writeable = True
127
+ image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
128
+
129
+ # Check if pose landmarks are detected
130
+ if results.pose_landmarks:
131
+ landmarks = results.pose_landmarks.landmark
132
+
133
+ # Extract key joints
134
+ shoulder = [
135
+ landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
136
+ landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y
137
+ ]
138
+ elbow = [
139
+ landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,
140
+ landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y
141
+ ]
142
+ wrist = [
143
+ landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
144
+ landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y
145
+ ]
146
+
147
+ # Check visibility of key joints
148
+ visibility_threshold = 0.5
149
+ if (landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].visibility < visibility_threshold or
150
+ landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].visibility < visibility_threshold or
151
+ landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].visibility < visibility_threshold):
152
+ draw_text_with_background(image, "Ensure all key joints are visible!", (50, 150),
153
+ cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 255, 255), 5, (0, 0, 255))
154
+ cv2.imshow('Workout Feedback', image)
155
+ continue # Skip processing if joints are not visible
156
+
157
+ # Calculate the angle
158
+ angle = calculate_angle(shoulder, elbow, wrist)
159
+
160
+ # Stage logic for counting reps
161
+ if angle > 160 and stage != "down":
162
+ stage = "down"
163
+ start_time = time.time() # Start timing for the rep
164
+ start_angle = angle # Record the starting angle
165
+
166
+ # Stop the program if it's the 10th rep down stage
167
+ if counter == max_reps:
168
+ print("Workout complete at rep 10 (down stage)!")
169
+ break
170
+ elif angle < 40 and stage == "down":
171
+ stage = "up"
172
+ counter += 1
173
+ end_time = time.time() # End timing for the rep
174
+ end_angle = angle # Record the ending angle
175
+
176
+ # Calculate rep metrics
177
+ rom = start_angle - end_angle # Range of Motion
178
+ tempo = end_time - start_time # Duration of the rep
179
+ smoothness = np.std([start_angle, end_angle]) # Dummy smoothness metric
180
+ rep_data.append({"ROM": rom, "Tempo": tempo, "Smoothness": smoothness})
181
+
182
+ # Analyze the rep using Isolation Forest
183
+ feedback = analyze_single_rep(rep_data[-1], rep_data)
184
+
185
+ # Wireframe color based on form
186
+ wireframe_color = (0, 255, 0) if stage == "up" or stage == "down" else (0, 0, 255)
187
+
188
+ # Draw wireframe
189
+ mp_drawing.draw_landmarks(
190
+ image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
191
+ mp_drawing.DrawingSpec(color=wireframe_color, thickness=5, circle_radius=4),
192
+ mp_drawing.DrawingSpec(color=wireframe_color, thickness=5, circle_radius=4)
193
+ )
194
+
195
+ # Display reps, stage, timer, and feedback
196
+ draw_text_with_background(image, f"Reps: {counter}", (50, 150),
197
+ cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 255, 255), 5, (0, 0, 0))
198
+ draw_text_with_background(image, f"Stage: {stage if stage else 'N/A'}", (50, 300),
199
+ cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 255, 255), 5, (0, 0, 0))
200
+ draw_text_with_background(image, timer_text, (1000, 50), # Timer in the top-right corner
201
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3, (0, 0, 0))
202
+ draw_text_with_background(image, feedback, (50, 450),
203
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3, (0, 0, 0))
204
+
205
+ # Show video feed
206
+ cv2.imshow('Workout Feedback', image)
207
+
208
+ # Break if 'q' is pressed
209
+ if cv2.waitKey(10) & 0xFF == ord('q'):
210
+ break
211
+
212
+ cap.release()
213
+ cv2.destroyAllWindows()
214
+
215
+ # Post-workout analysis
216
+ analyze_workout_with_isolation_forest(rep_data)
217
+
218
+
219
+ if __name__ == "__main__":
220
+ main()
221
+
222
+
223
+ def lateral_raise():
224
+ import cv2
225
+ import mediapipe as mp
226
+ import numpy as np
227
+ import time
228
+ from sklearn.ensemble import IsolationForest
229
+
230
+ # Mediapipe utilities
231
+ mp_drawing = mp.solutions.drawing_utils
232
+ mp_pose = mp.solutions.pose
233
+
234
+
235
+ # Function to calculate lateral raise angle
236
+ def calculate_angle_for_lateral_raise(shoulder, wrist):
237
+ """
238
+ Calculate the angle of the arm relative to the horizontal plane
239
+ passing through the shoulder.
240
+ """
241
+ horizontal_reference = np.array([1, 0]) # Horizontal vector
242
+ arm_vector = np.array([wrist[0] - shoulder[0], wrist[1] - shoulder[1]])
243
+ dot_product = np.dot(horizontal_reference, arm_vector)
244
+ magnitude_reference = np.linalg.norm(horizontal_reference)
245
+ magnitude_arm = np.linalg.norm(arm_vector)
246
+ if magnitude_arm == 0 or magnitude_reference == 0:
247
+ return 0
248
+ cos_angle = dot_product / (magnitude_reference * magnitude_arm)
249
+ angle = np.arccos(np.clip(cos_angle, -1.0, 1.0))
250
+ return np.degrees(angle)
251
+
252
+
253
+ # Function to draw text with a background
254
+ def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10):
255
+ text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
256
+ text_x, text_y = position
257
+ box_coords = (
258
+ (text_x - padding, text_y - padding),
259
+ (text_x + text_size[0] + padding, text_y + text_size[1] + padding),
260
+ )
261
+ cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED)
262
+ cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness)
263
+
264
+
265
+ # Function to check if all required joints are visible
266
+ def are_key_joints_visible(landmarks, visibility_threshold=0.5):
267
+ """
268
+ Ensure that all required joints are visible based on their visibility scores.
269
+ """
270
+ required_joints = [
271
+ mp_pose.PoseLandmark.LEFT_SHOULDER.value,
272
+ mp_pose.PoseLandmark.RIGHT_SHOULDER.value,
273
+ mp_pose.PoseLandmark.LEFT_WRIST.value,
274
+ mp_pose.PoseLandmark.RIGHT_WRIST.value,
275
+ ]
276
+ for joint in required_joints:
277
+ if landmarks[joint].visibility < visibility_threshold:
278
+ return False
279
+ return True
280
+
281
+
282
+ # Real-time feedback for single rep
283
+ def analyze_single_rep(rep, rep_data):
284
+ """Provide actionable feedback for a single rep."""
285
+ feedback = []
286
+
287
+ # Calculate averages from previous reps
288
+ avg_rom = np.mean([r["ROM"] for r in rep_data]) if rep_data else 0
289
+ avg_tempo = np.mean([r["Tempo"] for r in rep_data]) if rep_data else 0
290
+
291
+ # Dynamic tempo thresholds
292
+ lower_tempo_threshold = 2.0 # Minimum grace threshold for faster tempo
293
+ upper_tempo_threshold = 9.0 # Maximum grace threshold for slower tempo
294
+
295
+ # Adjust thresholds after a few reps
296
+ if len(rep_data) > 3:
297
+ lower_tempo_threshold = max(2.0, avg_tempo * 0.7)
298
+ upper_tempo_threshold = min(9.0, avg_tempo * 1.3)
299
+
300
+ # Feedback for ROM
301
+ if rep["ROM"] < 30: # Minimum ROM threshold
302
+ feedback.append("Lift arm higher")
303
+ elif rep_data and rep["ROM"] < avg_rom * 0.8:
304
+ feedback.append("Increase ROM")
305
+
306
+ # Feedback for Tempo
307
+ if rep["Tempo"] < lower_tempo_threshold: # Tempo too fast
308
+ feedback.append("Slow down")
309
+ elif rep["Tempo"] > upper_tempo_threshold: # Tempo too slow
310
+ feedback.append("Speed up")
311
+
312
+ return feedback
313
+
314
+
315
+ # Post-workout feedback function
316
+ def analyze_workout_with_isolation_forest(rep_data):
317
+ if not rep_data:
318
+ print("No reps completed.")
319
+ return
320
+
321
+ print("\n--- Post-Workout Summary ---")
322
+
323
+ # Filter valid reps for recalculating thresholds
324
+ valid_reps = [rep for rep in rep_data if rep["ROM"] > 20] # Ignore very low ROM reps
325
+
326
+ if not valid_reps:
327
+ print("No valid reps to analyze.")
328
+ return
329
+
330
+ features = np.array([[rep["ROM"], rep["Tempo"]] for rep in valid_reps])
331
+
332
+ avg_rom = np.mean(features[:, 0])
333
+ avg_tempo = np.mean(features[:, 1])
334
+ std_rom = np.std(features[:, 0])
335
+ std_tempo = np.std(features[:, 1])
336
+
337
+ # Adjusted bounds for anomalies
338
+ rom_lower_bound = max(20, avg_rom - std_rom * 2)
339
+ tempo_lower_bound = max(1.0, avg_tempo - std_tempo * 2)
340
+ tempo_upper_bound = min(10.0, avg_tempo + std_tempo * 2)
341
+
342
+ print(f"ROM Lower Bound: {rom_lower_bound}")
343
+ print(f"Tempo Bounds: {tempo_lower_bound}-{tempo_upper_bound}")
344
+
345
+ # Anomaly detection
346
+ for i, rep in enumerate(valid_reps, 1):
347
+ feedback = []
348
+ if rep["ROM"] < rom_lower_bound:
349
+ feedback.append("Low ROM")
350
+ if rep["Tempo"] < tempo_lower_bound:
351
+ feedback.append("Too Fast")
352
+ elif rep["Tempo"] > tempo_upper_bound:
353
+ feedback.append("Too Slow")
354
+
355
+ if feedback:
356
+ print(f"Rep {i}: Anomalous | Feedback: {', '.join(feedback[:1])}")
357
+
358
+ # Use Isolation Forest for secondary anomaly detection
359
+ model = IsolationForest(contamination=0.1, random_state=42) # Reduced contamination
360
+ predictions = model.fit_predict(features)
361
+
362
+ for i, prediction in enumerate(predictions, 1):
363
+ if prediction == -1: # Outlier
364
+ print(f"Rep {i}: Isolation Forest flagged this rep as anomalous.")
365
+
366
+
367
+ # Main workout tracking function
368
+ def main():
369
+ cap = cv2.VideoCapture(0)
370
+ counter = 0 # Rep counter
371
+ stage = None # Movement stage
372
+ feedback = [] # Real-time feedback for the video feed
373
+ rep_data = [] # Store metrics for each rep
374
+ angles_during_rep = [] # Track angles during a single rep
375
+ workout_start_time = None # Timer start
376
+
377
+ with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
378
+ while cap.isOpened():
379
+ ret, frame = cap.read()
380
+ if not ret:
381
+ print("Failed to grab frame.")
382
+ break
383
+
384
+ # Initialize workout start time
385
+ if workout_start_time is None:
386
+ workout_start_time = time.time()
387
+
388
+ # Timer
389
+ elapsed_time = time.time() - workout_start_time
390
+ timer_text = f"Timer: {int(elapsed_time)}s"
391
+
392
+ # Convert the image to RGB
393
+ image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
394
+ image.flags.writeable = False
395
+ results = pose.process(image)
396
+
397
+ # Convert back to BGR
398
+ image.flags.writeable = True
399
+ image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
400
+
401
+ # Check if pose landmarks are detected
402
+ if results.pose_landmarks:
403
+ landmarks = results.pose_landmarks.landmark
404
+
405
+ # Check if key joints are visible
406
+ if not are_key_joints_visible(landmarks):
407
+ draw_text_with_background(
408
+ image, "Ensure all joints are visible", (50, 50),
409
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255)
410
+ )
411
+ cv2.imshow("Lateral Raise Tracker", image)
412
+ continue
413
+
414
+ # Extract key joints
415
+ left_shoulder = [
416
+ landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
417
+ landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y,
418
+ ]
419
+ left_wrist = [
420
+ landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
421
+ landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y,
422
+ ]
423
+
424
+ # Calculate angle for lateral raise
425
+ angle = calculate_angle_for_lateral_raise(left_shoulder, left_wrist)
426
+
427
+ # Track angles during a rep
428
+ if stage == "up" or stage == "down":
429
+ angles_during_rep.append(angle)
430
+
431
+ # Stage logic for counting reps
432
+ if angle < 20 and stage != "down":
433
+ stage = "down"
434
+ if counter == 10: # Stop on the down stage of the 10th rep
435
+ print("Workout complete! 10 reps reached.")
436
+ break
437
+
438
+ # Calculate ROM for the completed rep
439
+ if len(angles_during_rep) > 1:
440
+ rom = max(angles_during_rep) - min(angles_during_rep)
441
+ else:
442
+ rom = 0.0
443
+
444
+ tempo = elapsed_time
445
+ print(f"Rep {counter + 1}: ROM={rom:.2f}, Tempo={tempo:.2f}s")
446
+
447
+ # Record metrics for the rep
448
+ rep_data.append({
449
+ "ROM": rom,
450
+ "Tempo": tempo,
451
+ })
452
+
453
+ # Reset angles and timer for the next rep
454
+ angles_during_rep = []
455
+ workout_start_time = time.time() # Reset timer
456
+
457
+ if 70 <= angle <= 110 and stage == "down":
458
+ stage = "up"
459
+ counter += 1
460
+
461
+ # Analyze feedback
462
+ feedback = analyze_single_rep(rep_data[-1], rep_data)
463
+
464
+ # Determine wireframe color
465
+ wireframe_color = (0, 255, 0) if not feedback else (0, 0, 255)
466
+
467
+ # Display feedback
468
+ draw_text_with_background(image, f"Reps: {counter}", (50, 50),
469
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
470
+ draw_text_with_background(image, " | ".join(feedback), (50, 120),
471
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
472
+ draw_text_with_background(image, timer_text, (50, 190),
473
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
474
+
475
+ # Render detections with wireframe color
476
+ mp_drawing.draw_landmarks(
477
+ image,
478
+ results.pose_landmarks,
479
+ mp_pose.POSE_CONNECTIONS,
480
+ mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
481
+ mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
482
+ )
483
+
484
+ # Display the image
485
+ cv2.imshow("Lateral Raise Tracker", image)
486
+
487
+ if cv2.waitKey(10) & 0xFF == ord("q"):
488
+ break
489
+
490
+ cap.release()
491
+ cv2.destroyAllWindows()
492
+
493
+ # Post-workout analysis
494
+ analyze_workout_with_isolation_forest(rep_data)
495
+
496
+
497
+ if __name__ == "__main__":
498
+ main()
499
+
500
+
501
+ def shoulder_press():
502
+ import cv2
503
+ import mediapipe as mp
504
+ import numpy as np
505
+ import time
506
+
507
+ # Mediapipe utilities
508
+ mp_drawing = mp.solutions.drawing_utils
509
+ mp_pose = mp.solutions.pose
510
+
511
+ # Function to calculate angles
512
+ def calculate_angle(point_a, point_b, point_c):
513
+ vector_ab = np.array([point_a[0] - point_b[0], point_a[1] - point_b[1]])
514
+ vector_cb = np.array([point_c[0] - point_b[0], point_c[1] - point_b[1]])
515
+ dot_product = np.dot(vector_ab, vector_cb)
516
+ magnitude_ab = np.linalg.norm(vector_ab)
517
+ magnitude_cb = np.linalg.norm(vector_cb)
518
+ if magnitude_ab == 0 or magnitude_cb == 0:
519
+ return 0
520
+ cos_angle = dot_product / (magnitude_ab * magnitude_cb)
521
+ angle = np.arccos(np.clip(cos_angle, -1.0, 1.0))
522
+ return np.degrees(angle)
523
+
524
+
525
+ # Function to check if all required joints are visible
526
+ def are_key_joints_visible(landmarks, visibility_threshold=0.5):
527
+ required_joints = [
528
+ mp_pose.PoseLandmark.LEFT_SHOULDER.value,
529
+ mp_pose.PoseLandmark.RIGHT_SHOULDER.value,
530
+ mp_pose.PoseLandmark.LEFT_ELBOW.value,
531
+ mp_pose.PoseLandmark.RIGHT_ELBOW.value,
532
+ mp_pose.PoseLandmark.LEFT_WRIST.value,
533
+ mp_pose.PoseLandmark.RIGHT_WRIST.value,
534
+ ]
535
+ for joint in required_joints:
536
+ if landmarks[joint].visibility < visibility_threshold:
537
+ return False
538
+ return True
539
+
540
+
541
+ # Function to draw text with a background
542
+ def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10):
543
+ text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
544
+ text_x, text_y = position
545
+ box_coords = (
546
+ (text_x - padding, text_y - padding),
547
+ (text_x + text_size[0] + padding, text_y + text_size[1] + padding),
548
+ )
549
+ cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED)
550
+ cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness)
551
+
552
+
553
+ # Main workout tracking function
554
+ def main():
555
+ cap = cv2.VideoCapture(0)
556
+ counter = 0
557
+ stage = None
558
+ feedback = ""
559
+ workout_start_time = None
560
+ rep_start_time = None
561
+
562
+ with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
563
+ while cap.isOpened():
564
+ ret, frame = cap.read()
565
+ if not ret:
566
+ print("Failed to grab frame.")
567
+ break
568
+
569
+ # Initialize workout start time
570
+ if workout_start_time is None:
571
+ workout_start_time = time.time()
572
+
573
+ # Timer
574
+ elapsed_time = time.time() - workout_start_time
575
+ timer_text = f"Timer: {int(elapsed_time)}s"
576
+
577
+ # Convert the image to RGB
578
+ image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
579
+ image.flags.writeable = False
580
+ results = pose.process(image)
581
+
582
+ # Convert back to BGR
583
+ image.flags.writeable = True
584
+ image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
585
+
586
+ # Check if pose landmarks are detected
587
+ if results.pose_landmarks:
588
+ landmarks = results.pose_landmarks.landmark
589
+
590
+ # Check if key joints are visible
591
+ if not are_key_joints_visible(landmarks):
592
+ feedback = "Ensure all joints are visible"
593
+ draw_text_with_background(
594
+ image, feedback, (50, 50),
595
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255)
596
+ )
597
+ cv2.imshow("Shoulder Press Tracker", image)
598
+ continue
599
+
600
+ # Extract key joints for both arms
601
+ left_shoulder = [
602
+ landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
603
+ landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y,
604
+ ]
605
+ left_elbow = [
606
+ landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,
607
+ landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y,
608
+ ]
609
+ left_wrist = [
610
+ landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
611
+ landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y,
612
+ ]
613
+
614
+ right_shoulder = [
615
+ landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].x,
616
+ landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].y,
617
+ ]
618
+ right_elbow = [
619
+ landmarks[mp_pose.PoseLandmark.RIGHT_ELBOW.value].x,
620
+ landmarks[mp_pose.PoseLandmark.RIGHT_ELBOW.value].y,
621
+ ]
622
+ right_wrist = [
623
+ landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].x,
624
+ landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].y,
625
+ ]
626
+
627
+ # Calculate angles
628
+ left_elbow_angle = calculate_angle(left_shoulder, left_elbow, left_wrist)
629
+ right_elbow_angle = calculate_angle(right_shoulder, right_elbow, right_wrist)
630
+
631
+ # Check starting and ending positions
632
+ if 80 <= left_elbow_angle <= 100 and 80 <= right_elbow_angle <= 100 and stage != "down":
633
+ stage = "down"
634
+ if counter == 10:
635
+ feedback = "Workout complete! 10 reps done."
636
+ draw_text_with_background(image, feedback, (50, 120),
637
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255))
638
+ cv2.imshow("Shoulder Press Tracker", image)
639
+ break
640
+ if rep_start_time is not None:
641
+ tempo = time.time() - rep_start_time
642
+ feedback = f"Rep {counter} completed! Tempo: {tempo:.2f}s"
643
+ rep_start_time = None
644
+ elif left_elbow_angle > 160 and right_elbow_angle > 160 and stage == "down":
645
+ stage = "up"
646
+ counter += 1
647
+ rep_start_time = time.time()
648
+
649
+ # Wireframe color
650
+ wireframe_color = (0, 255, 0) if "completed" in feedback or "Good" in feedback else (0, 0, 255)
651
+
652
+ # Display feedback
653
+ draw_text_with_background(image, f"Reps: {counter}", (50, 50),
654
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
655
+ draw_text_with_background(image, feedback, (50, 120),
656
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
657
+ draw_text_with_background(image, timer_text, (50, 190),
658
+ cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
659
+
660
+ # Render detections with wireframe color
661
+ mp_drawing.draw_landmarks(
662
+ image,
663
+ results.pose_landmarks,
664
+ mp_pose.POSE_CONNECTIONS,
665
+ mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
666
+ mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
667
+ )
668
+
669
+ # Display the image
670
+ cv2.imshow("Shoulder Press Tracker", image)
671
+
672
+ if cv2.waitKey(10) & 0xFF == ord("q"):
673
+ break
674
+
675
+ cap.release()
676
+ cv2.destroyAllWindows()
677
+
678
+
679
+ if __name__ == "__main__":
680
+ main()
681
+
682
+
683
  # Streamlit configuration
684
  st.set_page_config(page_title="Workout Tracker", page_icon="💪", layout="centered")
685
 
 
719
  if st.button("Start Workout"):
720
  if workout_option == "Bicep Curl":
721
  st.write("Launching Bicep Curl Tracker...")
722
+ bicep_curl()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
723
  elif workout_option == "Lateral Raise":
724
  st.write("Launching Lateral Raise Tracker...")
725
+ lateral_raise()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
726
  elif workout_option == "Shoulder Press":
727
  st.write("Launching Shoulder Press Tracker...")
728
+ shoulder_press()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
729
 
730
  # Footer
731
  st.markdown("---")
732
  st.markdown("**Note**: Close the workout window or press 'q' in the camera feed to stop the workout.")