zforkash commited on
Commit
cc46889
·
verified ·
1 Parent(s): 0620345
Files changed (1) hide show
  1. app.py +45 -618
app.py CHANGED
@@ -1,64 +1,33 @@
1
- # Consolidated Streamlit App
2
  import streamlit as st
3
- import subprocess
 
 
 
 
4
 
5
- # Title and introduction
6
- st.title("Workout Tracker")
7
  st.markdown("""
8
  Welcome to the **Workout Tracker App**!
9
  Select your desired workout below, and the app will guide you through the exercise with real-time feedback.
10
  """)
11
 
12
- # Workout options
13
  st.header("Choose Your Workout")
14
  workout_option = st.selectbox(
15
  "Available Workouts:",
16
  ["Bicep Curl", "Lateral Raise", "Shoulder Press"]
17
  )
18
 
19
- # Button to start the workout
20
- if st.button("Start Workout"):
21
- st.write(f"Starting {workout_option}...")
22
-
23
- # Map the workout to the corresponding script
24
- workout_scripts = {
25
- "Bicep Curl": "bicep_curl.py",
26
- "Lateral Raise": "lateral_raise.py",
27
- "Shoulder Press": "shoulder_press.py",
28
- }
29
-
30
- selected_script = workout_scripts.get(workout_option)
31
-
32
- # Run the corresponding script
33
- try:
34
- subprocess.run(["python", selected_script], check=True)
35
- st.success(f"{workout_option} workout completed! Check the feedback on your terminal.")
36
- except subprocess.CalledProcessError as e:
37
- st.error(f"An error occurred while running {workout_option}. Please try again.")
38
- except FileNotFoundError:
39
- st.error(f"Workout script {selected_script} not found! Ensure the file exists in the same directory.")
40
-
41
- # Footer
42
- st.markdown("""
43
- ---
44
- **Note**: Close the workout window or press "q" in the camera feed to stop the workout.
45
- """)
46
-
47
-
48
- # From bicep_with_feedback.py
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)
@@ -70,8 +39,8 @@ def calculate_angle(a, b, c):
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 = (
@@ -82,79 +51,21 @@ def draw_text_with_background(image, text, position, font, font_scale, color, th
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
@@ -171,551 +82,67 @@ def main():
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
-
261
- # From lateral_raise.py
262
- import cv2
263
- import mediapipe as mp
264
- import numpy as np
265
- import time
266
- from sklearn.ensemble import IsolationForest
267
-
268
- # Mediapipe utilities
269
- mp_drawing = mp.solutions.drawing_utils
270
- mp_pose = mp.solutions.pose
271
-
272
-
273
- # Function to calculate lateral raise angle
274
- def calculate_angle_for_lateral_raise(shoulder, wrist):
275
- """
276
- Calculate the angle of the arm relative to the horizontal plane
277
- passing through the shoulder.
278
- """
279
- horizontal_reference = np.array([1, 0]) # Horizontal vector
280
- arm_vector = np.array([wrist[0] - shoulder[0], wrist[1] - shoulder[1]])
281
- dot_product = np.dot(horizontal_reference, arm_vector)
282
- magnitude_reference = np.linalg.norm(horizontal_reference)
283
- magnitude_arm = np.linalg.norm(arm_vector)
284
- if magnitude_arm == 0 or magnitude_reference == 0:
285
- return 0
286
- cos_angle = dot_product / (magnitude_reference * magnitude_arm)
287
- angle = np.arccos(np.clip(cos_angle, -1.0, 1.0))
288
- return np.degrees(angle)
289
-
290
-
291
- # Function to draw text with a background
292
- def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10):
293
- text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
294
- text_x, text_y = position
295
- box_coords = (
296
- (text_x - padding, text_y - padding),
297
- (text_x + text_size[0] + padding, text_y + text_size[1] + padding),
298
- )
299
- cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED)
300
- cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness)
301
-
302
-
303
- # Function to check if all required joints are visible
304
- def are_key_joints_visible(landmarks, visibility_threshold=0.5):
305
- """
306
- Ensure that all required joints are visible based on their visibility scores.
307
- """
308
- required_joints = [
309
- mp_pose.PoseLandmark.LEFT_SHOULDER.value,
310
- mp_pose.PoseLandmark.RIGHT_SHOULDER.value,
311
- mp_pose.PoseLandmark.LEFT_WRIST.value,
312
- mp_pose.PoseLandmark.RIGHT_WRIST.value,
313
- ]
314
- for joint in required_joints:
315
- if landmarks[joint].visibility < visibility_threshold:
316
- return False
317
- return True
318
-
319
-
320
- # Real-time feedback for single rep
321
- def analyze_single_rep(rep, rep_data):
322
- """Provide actionable feedback for a single rep."""
323
- feedback = []
324
-
325
- # Calculate averages from previous reps
326
- avg_rom = np.mean([r["ROM"] for r in rep_data]) if rep_data else 0
327
- avg_tempo = np.mean([r["Tempo"] for r in rep_data]) if rep_data else 0
328
-
329
- # Dynamic tempo thresholds
330
- lower_tempo_threshold = 2.0 # Minimum grace threshold for faster tempo
331
- upper_tempo_threshold = 9.0 # Maximum grace threshold for slower tempo
332
-
333
- # Adjust thresholds after a few reps
334
- if len(rep_data) > 3:
335
- lower_tempo_threshold = max(2.0, avg_tempo * 0.7)
336
- upper_tempo_threshold = min(9.0, avg_tempo * 1.3)
337
-
338
- # Feedback for ROM
339
- if rep["ROM"] < 30: # Minimum ROM threshold
340
- feedback.append("Lift arm higher")
341
- elif rep_data and rep["ROM"] < avg_rom * 0.8:
342
- feedback.append("Increase ROM")
343
-
344
- # Feedback for Tempo
345
- if rep["Tempo"] < lower_tempo_threshold: # Tempo too fast
346
- feedback.append("Slow down")
347
- elif rep["Tempo"] > upper_tempo_threshold: # Tempo too slow
348
- feedback.append("Speed up")
349
-
350
- return feedback
351
-
352
 
353
- # Post-workout feedback function
354
- def analyze_workout_with_isolation_forest(rep_data):
355
- if not rep_data:
356
- print("No reps completed.")
357
- return
358
-
359
- print("\n--- Post-Workout Summary ---")
360
-
361
- # Filter valid reps for recalculating thresholds
362
- valid_reps = [rep for rep in rep_data if rep["ROM"] > 20] # Ignore very low ROM reps
363
-
364
- if not valid_reps:
365
- print("No valid reps to analyze.")
366
- return
367
-
368
- features = np.array([[rep["ROM"], rep["Tempo"]] for rep in valid_reps])
369
-
370
- avg_rom = np.mean(features[:, 0])
371
- avg_tempo = np.mean(features[:, 1])
372
- std_rom = np.std(features[:, 0])
373
- std_tempo = np.std(features[:, 1])
374
-
375
- # Adjusted bounds for anomalies
376
- rom_lower_bound = max(20, avg_rom - std_rom * 2)
377
- tempo_lower_bound = max(1.0, avg_tempo - std_tempo * 2)
378
- tempo_upper_bound = min(10.0, avg_tempo + std_tempo * 2)
379
-
380
- print(f"ROM Lower Bound: {rom_lower_bound}")
381
- print(f"Tempo Bounds: {tempo_lower_bound}-{tempo_upper_bound}")
382
-
383
- # Anomaly detection
384
- for i, rep in enumerate(valid_reps, 1):
385
- feedback = []
386
- if rep["ROM"] < rom_lower_bound:
387
- feedback.append("Low ROM")
388
- if rep["Tempo"] < tempo_lower_bound:
389
- feedback.append("Too Fast")
390
- elif rep["Tempo"] > tempo_upper_bound:
391
- feedback.append("Too Slow")
392
-
393
- if feedback:
394
- print(f"Rep {i}: Anomalous | Feedback: {', '.join(feedback[:1])}")
395
-
396
- # Use Isolation Forest for secondary anomaly detection
397
- model = IsolationForest(contamination=0.1, random_state=42) # Reduced contamination
398
- predictions = model.fit_predict(features)
399
-
400
- for i, prediction in enumerate(predictions, 1):
401
- if prediction == -1: # Outlier
402
- print(f"Rep {i}: Isolation Forest flagged this rep as anomalous.")
403
-
404
-
405
- # Main workout tracking function
406
- def main():
407
- cap = cv2.VideoCapture(0)
408
- counter = 0 # Rep counter
409
- stage = None # Movement stage
410
- feedback = [] # Real-time feedback for the video feed
411
- rep_data = [] # Store metrics for each rep
412
- angles_during_rep = [] # Track angles during a single rep
413
- workout_start_time = None # Timer start
414
-
415
- with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
416
- while cap.isOpened():
417
- ret, frame = cap.read()
418
- if not ret:
419
- print("Failed to grab frame.")
420
- break
421
-
422
- # Initialize workout start time
423
- if workout_start_time is None:
424
- workout_start_time = time.time()
425
-
426
- # Timer
427
- elapsed_time = time.time() - workout_start_time
428
- timer_text = f"Timer: {int(elapsed_time)}s"
429
-
430
- # Convert the image to RGB
431
- image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
432
- image.flags.writeable = False
433
- results = pose.process(image)
434
-
435
- # Convert back to BGR
436
- image.flags.writeable = True
437
- image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
438
-
439
- # Check if pose landmarks are detected
440
- if results.pose_landmarks:
441
- landmarks = results.pose_landmarks.landmark
442
-
443
- # Check if key joints are visible
444
- if not are_key_joints_visible(landmarks):
445
- draw_text_with_background(
446
- image, "Ensure all joints are visible", (50, 50),
447
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255)
448
- )
449
- cv2.imshow("Lateral Raise Tracker", image)
450
- continue
451
-
452
- # Extract key joints
453
- left_shoulder = [
454
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
455
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y,
456
- ]
457
- left_wrist = [
458
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
459
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y,
460
- ]
461
-
462
- # Calculate angle for lateral raise
463
- angle = calculate_angle_for_lateral_raise(left_shoulder, left_wrist)
464
-
465
- # Track angles during a rep
466
- if stage == "up" or stage == "down":
467
- angles_during_rep.append(angle)
468
-
469
- # Stage logic for counting reps
470
- if angle < 20 and stage != "down":
471
- stage = "down"
472
- if counter == 10: # Stop on the down stage of the 10th rep
473
- print("Workout complete! 10 reps reached.")
474
  break
475
-
476
- # Calculate ROM for the completed rep
477
- if len(angles_during_rep) > 1:
478
- rom = max(angles_during_rep) - min(angles_during_rep)
479
  else:
480
- rom = 0.0
481
-
482
- tempo = elapsed_time
483
- print(f"Rep {counter + 1}: ROM={rom:.2f}, Tempo={tempo:.2f}s")
484
-
485
- # Record metrics for the rep
486
- rep_data.append({
487
- "ROM": rom,
488
- "Tempo": tempo,
489
- })
490
-
491
- # Reset angles and timer for the next rep
492
- angles_during_rep = []
493
- workout_start_time = time.time() # Reset timer
494
-
495
- if 70 <= angle <= 110 and stage == "down":
496
- stage = "up"
497
- counter += 1
498
-
499
- # Analyze feedback
500
- feedback = analyze_single_rep(rep_data[-1], rep_data)
501
-
502
- # Determine wireframe color
503
- wireframe_color = (0, 255, 0) if not feedback else (0, 0, 255)
504
 
505
- # Display feedback
506
  draw_text_with_background(image, f"Reps: {counter}", (50, 50),
507
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
508
- draw_text_with_background(image, " | ".join(feedback), (50, 120),
509
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
510
- draw_text_with_background(image, timer_text, (50, 190),
511
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
512
 
513
- # Render detections with wireframe color
514
  mp_drawing.draw_landmarks(
515
- image,
516
- results.pose_landmarks,
517
- mp_pose.POSE_CONNECTIONS,
518
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
519
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
520
- )
521
-
522
- # Display the image
523
- cv2.imshow("Lateral Raise Tracker", image)
524
-
525
- if cv2.waitKey(10) & 0xFF == ord("q"):
526
- break
527
-
528
- cap.release()
529
- cv2.destroyAllWindows()
530
-
531
- # Post-workout analysis
532
- analyze_workout_with_isolation_forest(rep_data)
533
-
534
-
535
- if __name__ == "__main__":
536
- main()
537
-
538
-
539
- # From shoulder_press.py
540
- import cv2
541
- import mediapipe as mp
542
- import numpy as np
543
- import time
544
-
545
- # Mediapipe utilities
546
- mp_drawing = mp.solutions.drawing_utils
547
- mp_pose = mp.solutions.pose
548
-
549
- # Function to calculate angles
550
- def calculate_angle(point_a, point_b, point_c):
551
- vector_ab = np.array([point_a[0] - point_b[0], point_a[1] - point_b[1]])
552
- vector_cb = np.array([point_c[0] - point_b[0], point_c[1] - point_b[1]])
553
- dot_product = np.dot(vector_ab, vector_cb)
554
- magnitude_ab = np.linalg.norm(vector_ab)
555
- magnitude_cb = np.linalg.norm(vector_cb)
556
- if magnitude_ab == 0 or magnitude_cb == 0:
557
- return 0
558
- cos_angle = dot_product / (magnitude_ab * magnitude_cb)
559
- angle = np.arccos(np.clip(cos_angle, -1.0, 1.0))
560
- return np.degrees(angle)
561
-
562
-
563
- # Function to check if all required joints are visible
564
- def are_key_joints_visible(landmarks, visibility_threshold=0.5):
565
- required_joints = [
566
- mp_pose.PoseLandmark.LEFT_SHOULDER.value,
567
- mp_pose.PoseLandmark.RIGHT_SHOULDER.value,
568
- mp_pose.PoseLandmark.LEFT_ELBOW.value,
569
- mp_pose.PoseLandmark.RIGHT_ELBOW.value,
570
- mp_pose.PoseLandmark.LEFT_WRIST.value,
571
- mp_pose.PoseLandmark.RIGHT_WRIST.value,
572
- ]
573
- for joint in required_joints:
574
- if landmarks[joint].visibility < visibility_threshold:
575
- return False
576
- return True
577
-
578
-
579
- # Function to draw text with a background
580
- def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10):
581
- text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
582
- text_x, text_y = position
583
- box_coords = (
584
- (text_x - padding, text_y - padding),
585
- (text_x + text_size[0] + padding, text_y + text_size[1] + padding),
586
- )
587
- cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED)
588
- cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness)
589
-
590
-
591
- # Main workout tracking function
592
- def main():
593
- cap = cv2.VideoCapture(0)
594
- counter = 0
595
- stage = None
596
- feedback = ""
597
- workout_start_time = None
598
- rep_start_time = None
599
-
600
- with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
601
- while cap.isOpened():
602
- ret, frame = cap.read()
603
- if not ret:
604
- print("Failed to grab frame.")
605
- break
606
-
607
- # Initialize workout start time
608
- if workout_start_time is None:
609
- workout_start_time = time.time()
610
-
611
- # Timer
612
- elapsed_time = time.time() - workout_start_time
613
- timer_text = f"Timer: {int(elapsed_time)}s"
614
-
615
- # Convert the image to RGB
616
- image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
617
- image.flags.writeable = False
618
- results = pose.process(image)
619
-
620
- # Convert back to BGR
621
- image.flags.writeable = True
622
- image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
623
-
624
- # Check if pose landmarks are detected
625
- if results.pose_landmarks:
626
- landmarks = results.pose_landmarks.landmark
627
-
628
- # Check if key joints are visible
629
- if not are_key_joints_visible(landmarks):
630
- feedback = "Ensure all joints are visible"
631
- draw_text_with_background(
632
- image, feedback, (50, 50),
633
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255)
634
- )
635
- cv2.imshow("Shoulder Press Tracker", image)
636
- continue
637
-
638
- # Extract key joints for both arms
639
- left_shoulder = [
640
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
641
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y,
642
- ]
643
- left_elbow = [
644
- landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,
645
- landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y,
646
- ]
647
- left_wrist = [
648
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
649
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y,
650
- ]
651
-
652
- right_shoulder = [
653
- landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].x,
654
- landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].y,
655
- ]
656
- right_elbow = [
657
- landmarks[mp_pose.PoseLandmark.RIGHT_ELBOW.value].x,
658
- landmarks[mp_pose.PoseLandmark.RIGHT_ELBOW.value].y,
659
- ]
660
- right_wrist = [
661
- landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].x,
662
- landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].y,
663
- ]
664
-
665
- # Calculate angles
666
- left_elbow_angle = calculate_angle(left_shoulder, left_elbow, left_wrist)
667
- right_elbow_angle = calculate_angle(right_shoulder, right_elbow, right_wrist)
668
-
669
- # Check starting and ending positions
670
- if 80 <= left_elbow_angle <= 100 and 80 <= right_elbow_angle <= 100 and stage != "down":
671
- stage = "down"
672
- if counter == 10:
673
- feedback = "Workout complete! 10 reps done."
674
- draw_text_with_background(image, feedback, (50, 120),
675
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255))
676
- cv2.imshow("Shoulder Press Tracker", image)
677
- break
678
- if rep_start_time is not None:
679
- tempo = time.time() - rep_start_time
680
- feedback = f"Rep {counter} completed! Tempo: {tempo:.2f}s"
681
- rep_start_time = None
682
- elif left_elbow_angle > 160 and right_elbow_angle > 160 and stage == "down":
683
- stage = "up"
684
- counter += 1
685
- rep_start_time = time.time()
686
-
687
- # Wireframe color
688
- wireframe_color = (0, 255, 0) if "completed" in feedback or "Good" in feedback else (0, 0, 255)
689
-
690
- # Display feedback
691
- draw_text_with_background(image, f"Reps: {counter}", (50, 50),
692
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
693
- draw_text_with_background(image, feedback, (50, 120),
694
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
695
- draw_text_with_background(image, timer_text, (50, 190),
696
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
697
-
698
- # Render detections with wireframe color
699
- mp_drawing.draw_landmarks(
700
- image,
701
- results.pose_landmarks,
702
- mp_pose.POSE_CONNECTIONS,
703
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
704
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
705
  )
706
 
707
- # Display the image
708
- cv2.imshow("Shoulder Press Tracker", image)
709
 
 
710
  if cv2.waitKey(10) & 0xFF == ord("q"):
711
  break
712
 
713
- cap.release()
714
- cv2.destroyAllWindows()
715
 
716
 
717
- if __name__ == "__main__":
718
- main()
 
 
719
 
 
 
 
 
 
720
 
721
 
 
1
+ # Streamlit App for Workout Tracker
2
  import streamlit as st
3
+ import cv2
4
+ import mediapipe as mp
5
+ import numpy as np
6
+ import time
7
+ from sklearn.ensemble import IsolationForest
8
 
9
+ # Title and Introduction
10
+ st.title("Muscle Memory")
11
  st.markdown("""
12
  Welcome to the **Workout Tracker App**!
13
  Select your desired workout below, and the app will guide you through the exercise with real-time feedback.
14
  """)
15
 
16
+ # Workout Options
17
  st.header("Choose Your Workout")
18
  workout_option = st.selectbox(
19
  "Available Workouts:",
20
  ["Bicep Curl", "Lateral Raise", "Shoulder Press"]
21
  )
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  # Mediapipe utilities
24
  mp_drawing = mp.solutions.drawing_utils
25
  mp_pose = mp.solutions.pose
26
 
27
 
28
+ # Helper Functions
29
  def calculate_angle(a, b, c):
30
+ """Calculate the angle between three points."""
31
  a = np.array(a)
32
  b = np.array(b)
33
  c = np.array(c)
 
39
  return angle
40
 
41
 
 
42
  def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10):
43
+ """Draw text with a background on an image."""
44
  text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
45
  text_x, text_y = position
46
  box_coords = (
 
51
  cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness)
52
 
53
 
54
+ # Main Function to Track Workout
55
+ def track_workout():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  cap = cv2.VideoCapture(0)
57
  counter = 0 # Rep counter
58
  stage = None # Movement stage
59
+ max_reps = 10 # Max reps
 
60
  feedback = "" # Real-time feedback for the video feed
 
61
 
62
  with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
63
  while cap.isOpened():
64
  ret, frame = cap.read()
65
  if not ret:
66
+ st.error("Failed to access the webcam. Please ensure your webcam is enabled.")
67
  break
68
 
 
 
 
 
 
 
 
 
69
  # Convert frame to RGB
70
  image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
71
  image.flags.writeable = False
 
82
  # Extract key joints
83
  shoulder = [
84
  landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
85
+ landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y,
86
  ]
87
  elbow = [
88
  landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,
89
+ landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y,
90
  ]
91
  wrist = [
92
  landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
93
+ landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y,
94
  ]
95
 
 
 
 
 
 
 
 
 
 
 
96
  # Calculate the angle
97
  angle = calculate_angle(shoulder, elbow, wrist)
98
 
99
  # Stage logic for counting reps
100
  if angle > 160 and stage != "down":
101
  stage = "down"
 
 
 
 
 
 
 
102
  elif angle < 40 and stage == "down":
103
  stage = "up"
104
  counter += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
+ # Feedback
107
+ if counter == max_reps:
108
+ feedback = "Workout Complete!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  break
 
 
 
 
110
  else:
111
+ feedback = f"Rep {counter} completed!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
+ # Draw the feedback on the frame
114
  draw_text_with_background(image, f"Reps: {counter}", (50, 50),
115
+ cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, (0, 0, 0))
116
+ draw_text_with_background(image, feedback, (50, 100),
117
+ cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, (0, 0, 0))
 
 
118
 
119
+ # Draw landmarks
120
  mp_drawing.draw_landmarks(
121
+ image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
122
+ mp_drawing.DrawingSpec(color=(245, 117, 66), thickness=2, circle_radius=2),
123
+ mp_drawing.DrawingSpec(color=(245, 66, 230), thickness=2, circle_radius=2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  )
125
 
126
+ # Display the video feed
127
+ cv2.imshow("Workout Tracker", image)
128
 
129
+ # Break if 'q' is pressed
130
  if cv2.waitKey(10) & 0xFF == ord("q"):
131
  break
132
 
133
+ cap.release()
134
+ cv2.destroyAllWindows()
135
 
136
 
137
+ # Button to Start the Workout
138
+ if st.button("Start Workout"):
139
+ st.write(f"Starting {workout_option} workout...")
140
+ track_workout()
141
 
142
+ # Footer
143
+ st.markdown("""
144
+ ---
145
+ **Note**: Press "q" in the webcam feed to stop the workout. Ensure your webcam is enabled.
146
+ """)
147
 
148