pjxcharya commited on
Commit
6ad4f49
Β·
verified Β·
1 Parent(s): 3cc5b48

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +339 -211
app.py CHANGED
@@ -3,7 +3,7 @@ import cv2
3
  import numpy as np
4
  import mediapipe as mp
5
  import time
6
- import traceback # For detailed error logging
7
 
8
  # Import your exercise classes
9
  from exercises.hammer_curl import HammerCurl
@@ -22,19 +22,131 @@ exercise_trackers = {
22
  "Push Up": PushUp(),
23
  "Squat": Squat()
24
  }
25
- current_exercise_tracker = None
26
  selected_exercise_name = "Hammer Curl" # Default exercise
27
 
28
- # Target and progress tracking
29
- target_reps = 10 # Default target reps
30
- target_sets = 3 # Default target sets
31
  current_set_count = 1
32
  workout_complete_message = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
- def process_frame(video_frame_np, exercise_name_choice, target_reps_input, target_sets_input):
36
- global current_exercise_tracker, selected_exercise_name
37
- global target_reps, target_sets, current_set_count, workout_complete_message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  default_h, default_w = 480, 640
40
  if video_frame_np is not None:
@@ -43,250 +155,266 @@ def process_frame(video_frame_np, exercise_name_choice, target_reps_input, targe
43
  else:
44
  blank_frame = np.zeros((default_h, default_w, 3), dtype=np.uint8)
45
  cv2.putText(blank_frame, "No Camera Input", (50, default_h // 2), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
46
- return blank_frame, f"0/{target_reps}", f"{current_set_count}/{target_sets}", "No frame", "No camera", "Error: No Frame"
47
-
48
- try:
49
- new_target_reps_val = target_reps
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  try:
51
- new_target_reps_val = int(target_reps_input)
52
- if new_target_reps_val > 0 and target_reps != new_target_reps_val:
53
- target_reps = new_target_reps_val
54
- if current_exercise_tracker: current_exercise_tracker.reset_reps()
55
- current_set_count = 1
56
- workout_complete_message = ""
57
- except ValueError: pass
58
-
59
- new_target_sets_val = target_sets
60
- try:
61
- new_target_sets_val = int(target_sets_input)
62
- if new_target_sets_val > 0 and target_sets != new_target_sets_val:
63
- target_sets = new_target_sets_val
64
- if current_exercise_tracker: current_exercise_tracker.reset_reps()
65
- current_set_count = 1
66
- workout_complete_message = ""
67
- except ValueError: pass
68
-
69
- if selected_exercise_name != exercise_name_choice:
70
- selected_exercise_name = exercise_name_choice
71
- if selected_exercise_name in exercise_trackers:
72
- if selected_exercise_name == "Hammer Curl": exercise_trackers[selected_exercise_name] = HammerCurl()
73
- elif selected_exercise_name == "Push Up": exercise_trackers[selected_exercise_name] = PushUp()
74
- elif selected_exercise_name == "Squat": exercise_trackers[selected_exercise_name] = Squat()
75
- current_set_count = 1
76
- workout_complete_message = ""
77
- else: current_exercise_tracker = None
78
- current_exercise_tracker = exercise_trackers.get(selected_exercise_name)
79
-
80
- image_rgb = cv2.cvtColor(video_frame_np, cv2.COLOR_BGR2RGB)
81
- image_rgb.flags.writeable = False
82
- results = pose.process(image_rgb)
83
- image_rgb.flags.writeable = True
84
-
85
- reps_display = f"0/{target_reps}"
86
- sets_display = f"{current_set_count}/{target_sets}"
87
- angle_display = "N/A"
88
- feedback_display = "Initializing..."
89
- temp_workout_message = workout_complete_message
90
-
91
- if results.pose_landmarks and current_exercise_tracker and not workout_complete_message:
92
- landmarks_mp = results.pose_landmarks.landmark
93
- frame_height, frame_width, _ = annotated_image.shape
94
- actual_reps_this_set = 0
95
-
96
- try:
97
- if selected_exercise_name == "Hammer Curl":
98
- r_count, r_angle, l_count, l_angle, warn_r, warn_l, _, _, r_stage, l_stage = current_exercise_tracker.track_hammer_curl(landmarks_mp, annotated_image)
99
- actual_reps_this_set = r_count
100
- reps_display = f"R: {r_count}, L: {l_count} (Target: {target_reps} for R)"
101
- angle_display = f"R Ang: {int(r_angle)}, L Ang: {int(l_angle)}"
102
- feedback_list = []
103
- if warn_r: feedback_list.append(f"R: {warn_r}")
104
- if warn_l: feedback_list.append(f"L: {warn_l}")
105
- feedback_display = " | ".join(feedback_list) if feedback_list else "Good form!"
106
-
107
- elif selected_exercise_name == "Push Up":
108
- exercise_data = current_exercise_tracker.track_push_up(landmarks_mp, frame_width, frame_height)
109
- actual_reps_this_set = exercise_data.get("counter", 0)
110
- angle_display = f"L: {int(exercise_data.get('angle_left',0))}, R: {int(exercise_data.get('angle_right',0))}"
111
- feedback_display = str(exercise_data.get("feedback", "No feedback"))
112
- if 'get_drawing_annotations' in dir(current_exercise_tracker):
113
- annotations_to_draw = current_exercise_tracker.get_drawing_annotations(landmarks_mp, frame_width, frame_height, exercise_data)
114
- for ann in annotations_to_draw:
115
- if ann["type"] == "line": cv2.line(annotated_image, tuple(ann["start_point"]), tuple(ann["end_point"]), ann["color_bgr"], ann["thickness"])
116
- elif ann["type"] == "circle": cv2.circle(annotated_image, tuple(ann["center_point"]), ann["radius"], ann["color_bgr"], -1 if ann.get("filled", False) else ann["thickness"])
117
- elif ann["type"] == "text": cv2.putText(annotated_image, ann["text_content"], tuple(ann["position"]), cv2.FONT_HERSHEY_SIMPLEX, ann["font_scale"], ann["color_bgr"], ann["thickness"])
118
-
119
- elif selected_exercise_name == "Squat":
120
- exercise_data = current_exercise_tracker.track_squat(landmarks_mp, frame_width, frame_height)
121
- actual_reps_this_set = exercise_data.get("counter", 0)
122
- angle_display = f"L: {int(exercise_data.get('angle_left',0))}, R: {int(exercise_data.get('angle_right',0))}"
123
- feedback_display = str(exercise_data.get("feedback", "No feedback"))
124
- if 'get_drawing_annotations' in dir(current_exercise_tracker):
125
- annotations_to_draw = current_exercise_tracker.get_drawing_annotations(landmarks_mp, frame_width, frame_height, exercise_data)
126
- for ann in annotations_to_draw:
127
- if ann["type"] == "line": cv2.line(annotated_image, tuple(ann["start_point"]), tuple(ann["end_point"]), ann["color_bgr"], ann["thickness"])
128
- elif ann["type"] == "circle": cv2.circle(annotated_image, tuple(ann["center_point"]), ann["radius"], ann["color_bgr"], -1 if ann.get("filled", False) else ann["thickness"])
129
- elif ann["type"] == "text": cv2.putText(annotated_image, ann["text_content"], tuple(ann["position"]), cv2.FONT_HERSHEY_SIMPLEX, ann["font_scale"], ann["color_bgr"], ann["thickness"])
130
-
131
- if selected_exercise_name != "Hammer Curl":
132
- reps_display = f"{actual_reps_this_set}/{target_reps}"
133
-
134
- if actual_reps_this_set >= target_reps:
135
- if current_set_count < target_sets:
136
- current_set_count += 1
137
- current_exercise_tracker.reset_reps()
138
- feedback_display = f"Set {current_set_count-1} complete! Starting set {current_set_count}."
139
- if selected_exercise_name == "Hammer Curl": reps_display = f"R: 0, L: 0 (Target: {target_reps} for R)"
140
- else: reps_display = f"0/{target_reps}"
141
- elif current_set_count >= target_sets:
142
- feedback_display = "Workout Complete!"
143
- workout_complete_message = "Workout Complete! Change targets or exercise to restart."
144
- if selected_exercise_name == "Hammer Curl": reps_display = f"R: {target_reps}, L: {target_reps} (Target: {target_reps} for R)"
145
- else: reps_display = f"{target_reps}/{target_reps}"
146
- temp_workout_message = workout_complete_message
147
-
148
- except Exception as e_exercise:
149
- print(f"PROCESS_FRAME: Error during exercise '{selected_exercise_name}' logic: {e_exercise}")
150
- print(traceback.format_exc())
151
- cv2.putText(annotated_image, f"Error in {selected_exercise_name}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
152
- feedback_display = f"Error in {selected_exercise_name} processing."
153
-
154
- elif workout_complete_message:
155
  feedback_display = workout_complete_message
 
156
  reps_display = f"{target_reps}/{target_reps}" if selected_exercise_name != "Hammer Curl" else f"R: {target_reps}, L: {target_reps} (Target: {target_reps} for R)"
157
  sets_display = f"{target_sets}/{target_sets}"
158
- if results.pose_landmarks:
159
- mp_drawing.draw_landmarks(annotated_image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style())
160
-
161
- else:
162
- feedback_display = "No person detected or exercise not selected properly."
 
163
  if results and results.pose_landmarks :
164
  mp_drawing.draw_landmarks(annotated_image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style())
165
- else:
166
- cv2.putText(annotated_image, "No person detected", (50, default_h // 2), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255),2)
167
-
168
- sets_display = f"{current_set_count}/{target_sets}"
169
 
170
  if not isinstance(annotated_image, np.ndarray) or annotated_image.ndim != 3 or annotated_image.shape[2] != 3:
171
  annotated_image = np.zeros((default_h, default_w, 3), dtype=np.uint8)
172
  cv2.putText(annotated_image, "Display Error", (50, default_h // 2), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255),2)
173
-
174
- return annotated_image, reps_display, sets_display, angle_display, feedback_display, temp_workout_message
175
 
176
  except Exception as e_main:
177
  print(f"PROCESS_FRAME: CRITICAL error in process_frame: {e_main}")
178
  print(traceback.format_exc())
179
  error_frame = np.zeros((default_h, default_w, 3), dtype=np.uint8)
180
- cv2.putText(error_frame, f"Error: {e_main}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
181
- return error_frame, "Error", "Error", "Error", "Critical Error", "Critical Error"
182
 
183
- def trigger_reset_workout():
184
- global current_set_count, workout_complete_message, selected_exercise_name, target_reps, target_sets, current_exercise_tracker
185
-
186
- current_set_count = 1
187
- workout_complete_message = ""
188
-
189
- if current_exercise_tracker:
190
- current_exercise_tracker.reset_reps()
191
-
192
- reset_reps_display = f"0/{target_reps}"
193
- if selected_exercise_name == "Hammer Curl":
194
- reset_reps_display = f"R: 0, L: 0 (Target: {target_reps} for R)"
195
-
196
- reset_sets_display = f"1/{target_sets}"
197
- reset_angle_display = "N/A"
198
- reset_feedback_display = "Workout Reset. Ready to start."
199
- reset_workout_status = ""
200
-
201
- return reset_reps_display, reset_sets_display, reset_angle_display, reset_feedback_display, reset_workout_status
202
-
203
- # --- Custom CSS for gradient background and styling ---
204
- # Note: Applying gradient to the root/body might be overridden by Gradio's specific block styling.
205
- # It's often better to target .gradio-container or specific blocks if possible.
206
- # However, for broad effect, body is a start. More specific selectors might be needed for full coverage.
207
- # --- Custom CSS for gradient background and styling ---
208
  custom_css = """
209
- body {
210
- background: linear-gradient(to bottom right, #301934, #8A2BE0) !important; /* Dark Violet to a brighter Violet */
211
- }
212
- .gradio-container {
213
- background: linear-gradient(to bottom right, #301934, #8A2BE0) !important; /* Ensure container also gets it */
214
  }
215
- label, .gr-checkbox-label span { /* Target labels and checkbox labels */
216
- color: #E8E8E8 !important; /* Slightly brighter light gray for labels */
 
217
  font-weight: bold !important;
218
  }
219
- h1 {
220
  color: #FFFFFF !important;
221
  text-align: center !important;
 
222
  }
223
- .prose { /* Markdown text */
224
- color: #F0F0F0 !important;
 
 
 
225
  }
226
- /* General text within blocks that isn't a label or title */
227
- .gr-block .gr-box > div > p, .gr-block .gr-box > div > span {
228
- color: #E0E0E0 !important;
 
 
 
229
  }
230
- /* You might need to adjust button font styling with more specific selectors if this doesn't work */
231
- button.gr-button-primary {
232
- font-family: 'Exo 2', sans-serif !important;
233
- /* The theme's primary_hue should handle button background and text color for contrast */
234
  }
235
  """
236
 
237
  # --- Gradio Theme ---
238
- # Using a base theme to set font and then overriding some colors.
239
- # For button color, if primary_hue doesn't give desired button color, specific CSS might be needed.
240
  theme = gr.themes.Base(
241
  font=[gr.themes.GoogleFont("Exo 2"), "ui-sans-serif", "system-ui", "sans-serif"],
242
- primary_hue=gr.themes.colors.amber, # For buttons - gives a yellowish/golden hue
243
- secondary_hue=gr.themes.colors.blue,
244
- neutral_hue=gr.themes.colors.gray
245
  ).set(
246
- body_text_color="#E0E0E0", # Light gray for general text (this should also affect input text)
247
- input_background_fill="#4A2A6C", # Darker violet for input backgrounds
248
- input_border_color="#6A3AA2",
249
- # button_primary_text_color="#111111", # Often better to let theme handle this or use CSS
250
- # Ensure other text elements have good contrast automatically or via custom_css
 
 
 
 
 
 
 
 
 
 
251
  )
252
 
253
 
254
  # --- Gradio Interface ---
255
- exercise_choices = ["Hammer Curl", "Push Up", "Squat"]
256
 
257
- # Pass the theme and custom_css to gr.Blocks
258
  with gr.Blocks(theme=theme, css=custom_css) as iface:
259
- gr.Markdown("# Live AI Trainer") # This will be styled by H1 in CSS
260
- gr.Markdown("Select an exercise, set your targets, and get real-time feedback on your form and reps.") # Styled by .prose
261
-
262
- with gr.Row():
263
- with gr.Column(scale=2):
264
- webcam_input = gr.Image(sources=["webcam"], streaming=True, type="numpy", label="Your Webcam")
265
- with gr.Column(scale=1):
266
- gr.Markdown("### Controls") # Markdown default color should be handled by theme or prose
267
- exercise_dropdown = gr.Dropdown(choices=exercise_choices, label="Select Exercise", value="Hammer Curl")
268
- target_reps_number = gr.Number(value=target_reps, label="Target Reps per Set", precision=0, minimum=1)
269
- target_sets_number = gr.Number(value=target_sets, label="Target Sets", precision=0, minimum=1)
270
-
271
- reset_button = gr.Button("Reset Workout") # Will use primary_hue from theme
 
 
 
 
 
 
 
272
 
273
- gr.Markdown("### Progress")
274
- reps_output = gr.Textbox(label="Reps Progress")
275
- sets_output = gr.Textbox(label="Sets Progress")
276
- angle_output = gr.Textbox(label="Angle Details")
277
- feedback_output = gr.Textbox(label="Feedback", lines=3, max_lines=5)
278
- workout_status_output = gr.Textbox(label="Workout Status", lines=2, interactive=False)
279
-
280
- process_frame_inputs = [webcam_input, exercise_dropdown, target_reps_number, target_sets_number]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  process_frame_outputs = [webcam_input, reps_output, sets_output, angle_output, feedback_output, workout_status_output]
282
 
283
- webcam_input.stream(fn=process_frame, inputs=process_frame_inputs, outputs=process_frame_outputs)
284
- exercise_dropdown.change(fn=process_frame, inputs=process_frame_inputs, outputs=process_frame_outputs)
285
- target_reps_number.change(fn=process_frame, inputs=process_frame_inputs, outputs=process_frame_outputs)
286
- target_sets_number.change(fn=process_frame, inputs=process_frame_inputs, outputs=process_frame_outputs)
287
 
288
- reset_button_outputs = [reps_output, sets_output, angle_output, feedback_output, workout_status_output]
289
- reset_button.click(fn=trigger_reset_workout, inputs=None, outputs=reset_button_outputs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
  if __name__ == "__main__":
292
- iface.launch(debug=False, share=False) # share=False is default but good to be explicit for Spaces
 
3
  import numpy as np
4
  import mediapipe as mp
5
  import time
6
+ import traceback
7
 
8
  # Import your exercise classes
9
  from exercises.hammer_curl import HammerCurl
 
22
  "Push Up": PushUp(),
23
  "Squat": Squat()
24
  }
25
+ current_exercise_tracker = None # Will be set when an exercise is selected and workout starts
26
  selected_exercise_name = "Hammer Curl" # Default exercise
27
 
28
+ target_reps = 10
29
+ target_sets = 3
 
30
  current_set_count = 1
31
  workout_complete_message = ""
32
+ workout_active = False # New state variable to control active workout session
33
+
34
+
35
+ def update_targets_and_exercise_display(exercise_name_choice, reps_in, sets_in):
36
+ """
37
+ Handles changes from exercise selection, target reps, or target sets.
38
+ This function is primarily for updating the system state when these controls change,
39
+ and preparing for a new workout session if one isn't active.
40
+ """
41
+ global selected_exercise_name, target_reps, target_sets
42
+ global current_set_count, workout_complete_message, current_exercise_tracker, workout_active
43
+
44
+ # Update selected exercise
45
+ if selected_exercise_name != exercise_name_choice:
46
+ selected_exercise_name = exercise_name_choice
47
+ # Reset tracker and progress as exercise changed
48
+ if selected_exercise_name in exercise_trackers:
49
+ if selected_exercise_name == "Hammer Curl": exercise_trackers[selected_exercise_name] = HammerCurl()
50
+ elif selected_exercise_name == "Push Up": exercise_trackers[selected_exercise_name] = PushUp()
51
+ elif selected_exercise_name == "Squat": exercise_trackers[selected_exercise_name] = Squat()
52
+ current_exercise_tracker = exercise_trackers.get(selected_exercise_name) # Get the new tracker
53
+ current_set_count = 1
54
+ workout_complete_message = ""
55
+ workout_active = False # Changing exercise stops active workout
56
+ print(f"Exercise changed to: {selected_exercise_name}. Workout stopped and progress reset.")
57
+
58
+ # Update target reps
59
+ try:
60
+ new_reps = int(reps_in)
61
+ if new_reps > 0 and target_reps != new_reps:
62
+ target_reps = new_reps
63
+ current_set_count = 1 # Reset progress if targets change
64
+ workout_complete_message = ""
65
+ if current_exercise_tracker: current_exercise_tracker.reset_reps()
66
+ workout_active = False # Changing targets stops active workout
67
+ print(f"Target reps updated to: {target_reps}. Workout stopped and progress reset.")
68
+ except ValueError: pass
69
+
70
+ # Update target sets
71
+ try:
72
+ new_sets = int(sets_in)
73
+ if new_sets > 0 and target_sets != new_sets:
74
+ target_sets = new_sets
75
+ current_set_count = 1 # Reset progress if targets change
76
+ workout_complete_message = ""
77
+ if current_exercise_tracker: current_exercise_tracker.reset_reps()
78
+ workout_active = False # Changing targets stops active workout
79
+ print(f"Target sets updated to: {target_sets}. Workout stopped and progress reset.")
80
+ except ValueError: pass
81
+
82
+ # Determine initial display values
83
+ current_reps_val = 0
84
+ if current_exercise_tracker and hasattr(current_exercise_tracker, 'counter'): # For Pushup/Squat
85
+ current_reps_val = current_exercise_tracker.counter
86
+ elif current_exercise_tracker and hasattr(current_exercise_tracker, 'counter_right'): # For Hammer Curl
87
+ current_reps_val = current_exercise_tracker.counter_right
88
+
89
+ reps_disp = f"{current_reps_val}/{target_reps}"
90
+ if selected_exercise_name == "Hammer Curl":
91
+ r_c = current_exercise_tracker.counter_right if current_exercise_tracker else 0
92
+ l_c = current_exercise_tracker.counter_left if current_exercise_tracker else 0
93
+ reps_disp = f"R: {r_c}, L: {l_c} (Target: {target_reps} for R)"
94
 
95
+ return (selected_exercise_name,
96
+ reps_disp,
97
+ f"{current_set_count}/{target_sets}",
98
+ "N/A", # Angle
99
+ "Select exercise, set targets, then press 'Start Workout'.", # Feedback
100
+ workout_complete_message if workout_complete_message else ("Workout Not Active" if not workout_active else "")) # Workout Status
101
+
102
+
103
+ def trigger_start_workout():
104
+ global current_set_count, workout_complete_message, workout_active, selected_exercise_name, current_exercise_tracker, target_reps, target_sets
105
+
106
+ print("Start Workout button clicked.")
107
+ workout_active = True
108
+ current_set_count = 1
109
+ workout_complete_message = ""
110
+
111
+ current_exercise_tracker = exercise_trackers.get(selected_exercise_name) # Ensure it's the current one
112
+ if current_exercise_tracker:
113
+ current_exercise_tracker.reset_reps()
114
+ print(f"Tracker for {selected_exercise_name} reset.")
115
+ else:
116
+ # This case should ideally not happen if selected_exercise_name is always valid
117
+ print(f"Error: No tracker found for {selected_exercise_name} on start.")
118
+ # Initialize a new one just in case
119
+ if selected_exercise_name == "Hammer Curl": exercise_trackers[selected_exercise_name] = HammerCurl()
120
+ elif selected_exercise_name == "Push Up": exercise_trackers[selected_exercise_name] = PushUp()
121
+ elif selected_exercise_name == "Squat": exercise_trackers[selected_exercise_name] = Squat()
122
+ current_exercise_tracker = exercise_trackers.get(selected_exercise_name)
123
+ if current_exercise_tracker: current_exercise_tracker.reset_reps()
124
 
125
+ reps_disp = f"0/{target_reps}"
126
+ if selected_exercise_name == "Hammer Curl":
127
+ reps_disp = f"R: 0, L: 0 (Target: {target_reps} for R)"
128
+
129
+ return (selected_exercise_name,
130
+ reps_disp,
131
+ f"1/{target_sets}",
132
+ "N/A",
133
+ f"Workout Started: {selected_exercise_name}. Go!",
134
+ "Workout Active")
135
+
136
+ def trigger_stop_workout():
137
+ global workout_active
138
+ print("Stop Workout button clicked.")
139
+ workout_active = False
140
+ # Values to update UI components to reflect stopped state
141
+ # Reps/sets can remain as they were or be explicitly cleared for display
142
+ # For simplicity, let's just change the feedback and status
143
+ return ("Workout Stopped. Press Start to resume or change settings.",
144
+ "Workout Stopped")
145
+
146
+
147
+ def process_frame(video_frame_np): # Removed other inputs as they are handled by global state now
148
+ global current_exercise_tracker, selected_exercise_name, target_reps, target_sets
149
+ global current_set_count, workout_complete_message, workout_active
150
 
151
  default_h, default_w = 480, 640
152
  if video_frame_np is not None:
 
155
  else:
156
  blank_frame = np.zeros((default_h, default_w, 3), dtype=np.uint8)
157
  cv2.putText(blank_frame, "No Camera Input", (50, default_h // 2), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
158
+ # Must return all 6 expected values for process_frame outputs
159
+ return blank_frame, f"0/{target_reps}", f"{current_set_count}/{target_sets}", "No frame", "No camera", "Error"
160
+
161
+ # Initialize display values
162
+ reps_display = f"0/{target_reps}"
163
+ if selected_exercise_name == "Hammer Curl" and current_exercise_tracker:
164
+ reps_display = f"R: {current_exercise_tracker.counter_right}, L: {current_exercise_tracker.counter_left} (Target: {target_reps} for R)"
165
+ elif current_exercise_tracker and hasattr(current_exercise_tracker, 'counter'):
166
+ reps_display = f"{current_exercise_tracker.counter}/{target_reps}"
167
+
168
+ sets_display = f"{current_set_count}/{target_sets}"
169
+ angle_display = "N/A"
170
+ feedback_display = "Waiting for workout to start..."
171
+ current_workout_status = "Workout Not Active"
172
+
173
+ if workout_active and not workout_complete_message and current_exercise_tracker:
174
+ feedback_display = "Processing..." # Default if active
175
+ current_workout_status = "Workout Active"
176
  try:
177
+ image_rgb = cv2.cvtColor(video_frame_np, cv2.COLOR_BGR2RGB)
178
+ image_rgb.flags.writeable = False
179
+ results = pose.process(image_rgb)
180
+ image_rgb.flags.writeable = True
181
+
182
+ if results.pose_landmarks:
183
+ landmarks_mp = results.pose_landmarks.landmark
184
+ frame_height, frame_width, _ = annotated_image.shape
185
+ actual_reps_this_set = 0
186
+
187
+ try:
188
+ if selected_exercise_name == "Hammer Curl":
189
+ r_count, r_angle, l_count, l_angle, warn_r, warn_l, _, _, r_stage, l_stage = current_exercise_tracker.track_hammer_curl(landmarks_mp, annotated_image)
190
+ actual_reps_this_set = r_count
191
+ reps_display = f"R: {r_count}, L: {l_count} (Target: {target_reps} for R)"
192
+ angle_display = f"R Ang: {int(r_angle)}, L Ang: {int(l_angle)}"
193
+ feedback_list = []
194
+ if warn_r: feedback_list.append(f"R: {warn_r}")
195
+ if warn_l: feedback_list.append(f"L: {warn_l}")
196
+ feedback_display = " | ".join(feedback_list) if feedback_list else "Good form!"
197
+
198
+ elif selected_exercise_name == "Push Up":
199
+ exercise_data = current_exercise_tracker.track_push_up(landmarks_mp, frame_width, frame_height)
200
+ actual_reps_this_set = exercise_data.get("counter", 0)
201
+ angle_display = f"L: {int(exercise_data.get('angle_left',0))}, R: {int(exercise_data.get('angle_right',0))}"
202
+ feedback_display = str(exercise_data.get("feedback", "No feedback"))
203
+ if 'get_drawing_annotations' in dir(current_exercise_tracker):
204
+ annotations_to_draw = current_exercise_tracker.get_drawing_annotations(landmarks_mp, frame_width, frame_height, exercise_data)
205
+ for ann in annotations_to_draw:
206
+ if ann["type"] == "line": cv2.line(annotated_image, tuple(ann["start_point"]), tuple(ann["end_point"]), ann["color_bgr"], ann["thickness"])
207
+ elif ann["type"] == "circle": cv2.circle(annotated_image, tuple(ann["center_point"]), ann["radius"], ann["color_bgr"], -1 if ann.get("filled", False) else ann["thickness"])
208
+ elif ann["type"] == "text": cv2.putText(annotated_image, ann["text_content"], tuple(ann["position"]), cv2.FONT_HERSHEY_SIMPLEX, ann["font_scale"], ann["color_bgr"], ann["thickness"])
209
+
210
+ elif selected_exercise_name == "Squat":
211
+ exercise_data = current_exercise_tracker.track_squat(landmarks_mp, frame_width, frame_height)
212
+ actual_reps_this_set = exercise_data.get("counter", 0)
213
+ angle_display = f"L: {int(exercise_data.get('angle_left',0))}, R: {int(exercise_data.get('angle_right',0))}"
214
+ feedback_display = str(exercise_data.get("feedback", "No feedback"))
215
+ if 'get_drawing_annotations' in dir(current_exercise_tracker):
216
+ annotations_to_draw = current_exercise_tracker.get_drawing_annotations(landmarks_mp, frame_width, frame_height, exercise_data)
217
+ for ann in annotations_to_draw:
218
+ if ann["type"] == "line": cv2.line(annotated_image, tuple(ann["start_point"]), tuple(ann["end_point"]), ann["color_bgr"], ann["thickness"])
219
+ elif ann["type"] == "circle": cv2.circle(annotated_image, tuple(ann["center_point"]), ann["radius"], ann["color_bgr"], -1 if ann.get("filled", False) else ann["thickness"])
220
+ elif ann["type"] == "text": cv2.putText(annotated_image, ann["text_content"], tuple(ann["position"]), cv2.FONT_HERSHEY_SIMPLEX, ann["font_scale"], ann["color_bgr"], ann["thickness"])
221
+
222
+ if selected_exercise_name != "Hammer Curl":
223
+ reps_display = f"{actual_reps_this_set}/{target_reps}"
224
+
225
+ if actual_reps_this_set >= target_reps:
226
+ if current_set_count < target_sets:
227
+ current_set_count += 1
228
+ current_exercise_tracker.reset_reps()
229
+ feedback_display = f"Set {current_set_count-1} complete! Starting set {current_set_count}."
230
+ if selected_exercise_name == "Hammer Curl": reps_display = f"R: 0, L: 0 (Target: {target_reps} for R)"
231
+ else: reps_display = f"0/{target_reps}"
232
+ elif current_set_count >= target_sets:
233
+ feedback_display = "Workout Complete!"
234
+ workout_complete_message = "Workout Complete!" # No more auto-restart message
235
+ workout_active = False # Stop workout automatically
236
+ if selected_exercise_name == "Hammer Curl": reps_display = f"R: {target_reps}, L: {target_reps} (Target: {target_reps} for R)"
237
+ else: reps_display = f"{target_reps}/{target_reps}"
238
+ current_workout_status = workout_complete_message if workout_complete_message else "Workout Active"
239
+
240
+
241
+ except Exception as e_exercise:
242
+ print(f"PROCESS_FRAME: Error during exercise '{selected_exercise_name}' logic: {e_exercise}")
243
+ print(traceback.format_exc())
244
+ cv2.putText(annotated_image, f"Error in {selected_exercise_name}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
245
+ feedback_display = f"Error in {selected_exercise_name} processing."
246
+ else: # No landmarks detected
247
+ feedback_display = "No person detected. Adjust position."
248
+ # Keep drawing generic if no landmarks and workout is active
249
+ # mp_drawing.draw_landmarks(annotated_image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style())
250
+
251
+ elif workout_complete_message : # Workout is complete
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  feedback_display = workout_complete_message
253
+ current_workout_status = workout_complete_message
254
  reps_display = f"{target_reps}/{target_reps}" if selected_exercise_name != "Hammer Curl" else f"R: {target_reps}, L: {target_reps} (Target: {target_reps} for R)"
255
  sets_display = f"{target_sets}/{target_sets}"
256
+ if results and results.pose_landmarks: mp_drawing.draw_landmarks(annotated_image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style())
257
+
258
+ elif not workout_active:
259
+ feedback_display = "Workout stopped or not started. Press 'Start Workout'."
260
+ current_workout_status = "Workout Stopped / Not Started"
261
+ # Draw generic landmarks if pose was processed
262
  if results and results.pose_landmarks :
263
  mp_drawing.draw_landmarks(annotated_image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style())
264
+
 
 
 
265
 
266
  if not isinstance(annotated_image, np.ndarray) or annotated_image.ndim != 3 or annotated_image.shape[2] != 3:
267
  annotated_image = np.zeros((default_h, default_w, 3), dtype=np.uint8)
268
  cv2.putText(annotated_image, "Display Error", (50, default_h // 2), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255),2)
269
+
270
+ return annotated_image, reps_display, sets_display, angle_display, feedback_display, current_workout_status
271
 
272
  except Exception as e_main:
273
  print(f"PROCESS_FRAME: CRITICAL error in process_frame: {e_main}")
274
  print(traceback.format_exc())
275
  error_frame = np.zeros((default_h, default_w, 3), dtype=np.uint8)
276
+ cv2.putText(error_frame, "Critical Error", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
277
+ return error_frame, "Error", "Error", "Error", "Critical Error", "Error"
278
 
279
+
280
+ # --- Custom CSS ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  custom_css = """
282
+ body, .gradio-container {
283
+ background: linear-gradient(to bottom right, #2A002A, #5D3FD3) !important; /* Darker Violet to a brighter Violet */
284
+ color: #E0E0E0 !important;
 
 
285
  }
286
+ .gradio-container { font-family: 'Exo 2', sans-serif !important; }
287
+ label, .gr-checkbox-label span {
288
+ color: #D0D0D0 !important;
289
  font-weight: bold !important;
290
  }
291
+ h1, h3 { /* Targeting h1 and h3 for titles */
292
  color: #FFFFFF !important;
293
  text-align: center !important;
294
+ font-family: 'Exo 2', sans-serif !important;
295
  }
296
+ .prose { color: #E8E8E8 !important; text-align: center !important; }
297
+ .gr-button { /* General button styling */
298
+ font-family: 'Exo 2', sans-serif !important;
299
+ border-radius: 8px !important;
300
+ font-weight: bold !important;
301
  }
302
+ /* Specific styling for control panel sections - you might need to inspect element for exact classes */
303
+ .controls-section .gr-panel { /* Assuming you wrap sections in gr.Panel or gr.Group */
304
+ background-color: rgba(0,0,0,0.2) !important;
305
+ border-radius: 10px !important;
306
+ padding: 15px !important;
307
+ margin-bottom: 15px !important;
308
  }
309
+ .status-text { /* Class for status text boxes if needed */
310
+ font-weight: bold !important;
 
 
311
  }
312
  """
313
 
314
  # --- Gradio Theme ---
 
 
315
  theme = gr.themes.Base(
316
  font=[gr.themes.GoogleFont("Exo 2"), "ui-sans-serif", "system-ui", "sans-serif"],
317
+ primary_hue=gr.themes.colors.purple, # Main accent color
318
+ secondary_hue=gr.themes.colors.pink,
319
+ neutral_hue=gr.themes.colors.slate
320
  ).set(
321
+ body_text_color="#E0E0E0",
322
+ input_background_fill="rgba(255,255,255,0.05)", # Slightly transparent white
323
+ input_border_color="rgba(255,255,255,0.2)",
324
+ input_text_color="#FFFFFF",
325
+ button_primary_background_fill=gr.themes.colors.purple[600], # Main button color
326
+ button_primary_background_fill_hover=gr.themes.colors.purple[500],
327
+ button_primary_text_color="#FFFFFF",
328
+ button_secondary_background_fill=gr.themes.colors.pink[600],
329
+ button_secondary_background_fill_hover=gr.themes.colors.pink[500],
330
+ button_secondary_text_color="#FFFFFF",
331
+ block_title_text_color = "#FFFFFF", # For titles of blocks/groups
332
+ block_label_text_color = "#E0E0E0", # For labels of blocks/groups
333
+ border_color_accent = gr.themes.colors.purple[400],
334
+ background_fill_primary = "#1E001E", # Very dark purple for main background if gradient doesn't fully take
335
+ background_fill_secondary = "#2A0A2A", # Slightly lighter for other areas
336
  )
337
 
338
 
339
  # --- Gradio Interface ---
340
+ exercise_choices_list = ["Hammer Curl", "Push Up", "Squat"]
341
 
 
342
  with gr.Blocks(theme=theme, css=custom_css) as iface:
343
+ gr.Markdown("# LIVE TRAINING SESSION")
344
+ gr.Markdown("AI-powered exercise tracking and feedback")
345
+
346
+ # Hidden state for selected exercise name (updated by buttons)
347
+ # This allows process_frame to know the selection without direct input from dropdown
348
+ # However, for simplicity now, we will rely on the global selected_exercise_name
349
+ # exercise_name_state = gr.State(value="Hammer Curl")
350
+
351
+
352
+ with gr.Row(equal_height=False):
353
+ with gr.Column(scale=2): # Video feed
354
+ webcam_input = gr.Image(sources=["webcam"], streaming=True, type="numpy", label="Live Workout Feed")
355
+
356
+ with gr.Column(scale=1): # Controls and Status
357
+ with gr.Group(): # Using Group for card-like effect, can be styled with CSS if needed
358
+ gr.Markdown("### Select Exercise")
359
+ with gr.Row():
360
+ hc_btn = gr.Button("Hammer Curl")
361
+ pu_btn = gr.Button("Push Up")
362
+ sq_btn = gr.Button("Squat")
363
 
364
+ with gr.Group():
365
+ gr.Markdown("### Configure Workout")
366
+ with gr.Row():
367
+ target_sets_number = gr.Number(value=target_sets, label="Sets", precision=0, minimum=1, scale=1)
368
+ target_reps_number = gr.Number(value=target_reps, label="Reps", precision=0, minimum=1, scale=1)
369
+ with gr.Row():
370
+ start_button = gr.Button("Start Workout", variant="primary", scale=1) # Make it stand out
371
+ stop_button = gr.Button("Stop Workout", variant="stop", scale=1) # 'stop' variant for red-ish
372
+
373
+ with gr.Group():
374
+ gr.Markdown("### Current Status")
375
+ current_exercise_display = gr.Textbox(label="Exercise", value=selected_exercise_name, interactive=False)
376
+ sets_output = gr.Textbox(label="Set", interactive=False)
377
+ reps_output = gr.Textbox(label="Repetitions", interactive=False)
378
+ # angle_output = gr.Textbox(label="Angle Details", interactive=False) # Removed from UI as per image
379
+ feedback_output = gr.Textbox(label="Feedback", lines=3, max_lines=5, interactive=False)
380
+ workout_status_output = gr.Textbox(label="Workout Status", interactive=False)
381
+
382
+ # --- Define component interactions ---
383
+
384
+ # Outputs that are updated by multiple actions
385
+ shared_outputs = [current_exercise_display, reps_output, sets_output, feedback_output, workout_status_output] # Removed angle_output from display
386
+ # Outputs from process_frame (includes image + text outputs for status)
387
+ # Note: angle_display is calculated in process_frame but not shown in this UI version
388
  process_frame_outputs = [webcam_input, reps_output, sets_output, angle_output, feedback_output, workout_status_output]
389
 
 
 
 
 
390
 
391
+ # Handler for changing targets or initial setup
392
+ # This function now just returns the values for shared_outputs
393
+ def handle_config_change_and_select(exercise_name, reps, sets):
394
+ sel_ex, r_disp, s_disp, _, f_disp, w_stat = update_targets_and_exercise_display(exercise_name, reps, sets)
395
+ return sel_ex, r_disp, s_disp, f_disp, w_stat # Matches shared_outputs
396
+
397
+ # Exercise selection buttons
398
+ hc_btn.click(lambda r=target_reps_number, s=target_sets_number: handle_config_change_and_select("Hammer Curl", r,s), inputs=[target_reps_number, target_sets_number], outputs=shared_outputs)
399
+ pu_btn.click(lambda r=target_reps_number, s=target_sets_number: handle_config_change_and_select("Push Up",r,s), inputs=[target_reps_number, target_sets_number], outputs=shared_outputs)
400
+ sq_btn.click(lambda r=target_reps_number, s=target_sets_number: handle_config_change_and_select("Squat",r,s), inputs=[target_reps_number, target_sets_number], outputs=shared_outputs)
401
+
402
+ # Target number changes
403
+ target_reps_number.change(lambda ex=selected_exercise_name, r=target_reps_number, s=target_sets_number: handle_config_change_and_select(ex, r, s), inputs=[selected_exercise_name, target_reps_number, target_sets_number], outputs=shared_outputs)
404
+ target_sets_number.change(lambda ex=selected_exercise_name, r=target_reps_number, s=target_sets_number: handle_config_change_and_select(ex, r, s), inputs=[selected_exercise_name, target_reps_number, target_sets_number], outputs=shared_outputs)
405
+
406
+
407
+ # Start and Stop buttons
408
+ start_button.click(trigger_start_workout, inputs=None, outputs=shared_outputs) # Updates text fields
409
+
410
+ # Stop button only needs to update feedback and status_output
411
+ stop_button.click(trigger_stop_workout, inputs=None, outputs=[feedback_output, workout_status_output])
412
+
413
+ # Video stream processing
414
+ # process_frame only takes webcam_input directly. Other states are global.
415
+ # Its outputs now align with process_frame_outputs defined earlier.
416
+ webcam_input.stream(fn=process_frame, inputs=[webcam_input], outputs=process_frame_outputs)
417
+
418
 
419
  if __name__ == "__main__":
420
+ iface.launch(debug=False, share=False)