pjxcharya commited on
Commit
c75e3f3
·
verified ·
1 Parent(s): 0bc51f1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +83 -84
app.py CHANGED
@@ -14,9 +14,9 @@ import logging
14
  from flask_cors import CORS
15
 
16
  # Set up logging
17
- logging.basicConfig(level=logging.DEBUG,
18
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
19
- handlers=[logging.StreamHandler()])
20
  logger = logging.getLogger(__name__)
21
 
22
  # Import attempt with error handling
@@ -41,7 +41,7 @@ try:
41
  logger.info("Successfully initialized workout logger")
42
  except ImportError:
43
  logger.warning("WorkoutLogger import failed, creating dummy class")
44
-
45
  class DummyWorkoutLogger:
46
  def __init__(self):
47
  pass
@@ -55,7 +55,7 @@ except ImportError:
55
  return {}
56
  def get_user_stats(self, *args, **kwargs):
57
  return {'total_workouts': 0, 'total_exercises': 0, 'streak_days': 0}
58
-
59
  workout_logger = DummyWorkoutLogger()
60
 
61
  logger.info("Setting up Flask application")
@@ -67,7 +67,7 @@ socketio = SocketIO(app, cors_allowed_origins="*")
67
  pose_estimator_api = None
68
  active_exercise_sessions = {}
69
  # Max number of concurrent sessions to avoid memory issues
70
- MAX_SESSIONS = 100
71
 
72
  def get_pose_estimator_api_instance():
73
  global pose_estimator_api
@@ -104,45 +104,45 @@ def release_camera():
104
  def generate_frames():
105
  global output_frame, lock, exercise_running, current_exercise, current_exercise_data
106
  global exercise_counter, exercise_goal, sets_completed, sets_goal
107
-
108
  pose_estimator = PoseEstimator()
109
-
110
  while True:
111
  if camera is None:
112
  continue
113
-
114
  success, frame = camera.read()
115
  if not success:
116
  continue
117
-
118
  # Only process frames if an exercise is running
119
  if exercise_running and current_exercise:
120
  # Process with pose estimation
121
  results = pose_estimator.estimate_pose(frame, current_exercise_data['type'])
122
-
123
  if results.pose_landmarks:
124
  # Track exercise based on type
125
  if current_exercise_data['type'] == "squat":
126
  counter, angle, stage = current_exercise.track_squat(results.pose_landmarks.landmark, frame)
127
  layout_indicators(frame, current_exercise_data['type'], (counter, angle, stage))
128
  exercise_counter = counter
129
-
130
  elif current_exercise_data['type'] == "push_up":
131
  counter, angle, stage = current_exercise.track_push_up(results.pose_landmarks.landmark, frame)
132
  layout_indicators(frame, current_exercise_data['type'], (counter, angle, stage))
133
  exercise_counter = counter
134
-
135
  elif current_exercise_data['type'] == "hammer_curl":
136
  (counter_right, angle_right, counter_left, angle_left,
137
- warning_message_right, warning_message_left, progress_right,
138
  progress_left, stage_right, stage_left) = current_exercise.track_hammer_curl(
139
  results.pose_landmarks.landmark, frame)
140
- layout_indicators(frame, current_exercise_data['type'],
141
  (counter_right, angle_right, counter_left, angle_left,
142
- warning_message_right, warning_message_left,
143
  progress_right, progress_left, stage_right, stage_left))
144
  exercise_counter = max(counter_right, counter_left)
145
-
146
  # Display exercise information
147
  exercise_info = get_exercise_info(current_exercise_data['type'])
148
  draw_text_with_background(frame, f"Exercise: {exercise_info.get('name', 'N/A')}", (40, 50),
@@ -153,7 +153,7 @@ def generate_frames():
153
  cv2.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255), (118, 29, 14), 1)
154
  draw_text_with_background(frame, f"Current Set: {sets_completed + 1}", (40, 140),
155
  cv2.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255), (118, 29, 14), 1)
156
-
157
  # Check if rep goal is reached for current set
158
  if exercise_counter >= exercise_goal:
159
  sets_completed += 1
@@ -164,14 +164,14 @@ def generate_frames():
164
  elif current_exercise_data['type'] == "hammer_curl":
165
  current_exercise.counter_right = 0
166
  current_exercise.counter_left = 0
167
-
168
  # Check if all sets are completed
169
  if sets_completed >= sets_goal:
170
  exercise_running = False
171
  draw_text_with_background(frame, "WORKOUT COMPLETE!", (frame.shape[1]//2 - 150, frame.shape[0]//2),
172
  cv2.FONT_HERSHEY_DUPLEX, 1.2, (255, 255, 255), (0, 200, 0), 2)
173
  else:
174
- draw_text_with_background(frame, f"SET {sets_completed} COMPLETE! Rest for 30 sec",
175
  (frame.shape[1]//2 - 200, frame.shape[0]//2),
176
  cv2.FONT_HERSHEY_DUPLEX, 1.0, (255, 255, 255), (0, 0, 200), 2)
177
  # We could add rest timer functionality here
@@ -179,11 +179,11 @@ def generate_frames():
179
  # Display welcome message if no exercise is running
180
  cv2.putText(frame, "Select an exercise to begin", (frame.shape[1]//2 - 150, frame.shape[0]//2),
181
  cv2.FONT_HERSHEY_DUPLEX, 0.8, (255, 255, 255), 1)
182
-
183
  # Encode the frame in JPEG format
184
  with lock:
185
  output_frame = frame.copy()
186
-
187
  # Yield the frame in byte format
188
  ret, buffer = cv2.imencode('.jpg', output_frame)
189
  frame = buffer.tobytes()
@@ -210,7 +210,7 @@ def dashboard():
210
  weekly_stats = workout_logger.get_weekly_stats()
211
  exercise_distribution = workout_logger.get_exercise_distribution()
212
  user_stats = workout_logger.get_user_stats()
213
-
214
  # Format workouts for display
215
  formatted_workouts = []
216
  for workout in recent_workouts:
@@ -221,10 +221,10 @@ def dashboard():
221
  'reps': workout['reps'],
222
  'duration': f"{workout['duration_seconds'] // 60}:{workout['duration_seconds'] % 60:02d}"
223
  })
224
-
225
  # Calculate total workouts this week
226
  weekly_workout_count = sum(day['workout_count'] for day in weekly_stats.values())
227
-
228
  return render_template('dashboard.html',
229
  recent_workouts=formatted_workouts,
230
  weekly_workouts=weekly_workout_count,
@@ -248,20 +248,20 @@ def start_exercise():
248
  global exercise_running, current_exercise, current_exercise_data
249
  global exercise_counter, exercise_goal, sets_completed, sets_goal
250
  global workout_start_time
251
-
252
  data = request.json
253
  exercise_type = data.get('exercise_type')
254
  sets_goal = int(data.get('sets', 3))
255
  exercise_goal = int(data.get('reps', 10))
256
-
257
  # Initialize camera if not already done
258
  initialize_camera()
259
-
260
  # Reset counters
261
  exercise_counter = 0
262
  sets_completed = 0
263
  workout_start_time = time.time()
264
-
265
  # Initialize the appropriate exercise class
266
  if exercise_type == "squat":
267
  current_exercise = Squat()
@@ -271,17 +271,17 @@ def start_exercise():
271
  current_exercise = HammerCurl()
272
  else:
273
  return jsonify({'success': False, 'error': 'Invalid exercise type'})
274
-
275
  # Store exercise data
276
  current_exercise_data = {
277
  'type': exercise_type,
278
  'sets': sets_goal,
279
  'reps': exercise_goal
280
  }
281
-
282
  # Start the exercise
283
  exercise_running = True
284
-
285
  return jsonify({'success': True})
286
 
287
  @app.route('/stop_exercise', methods=['POST'])
@@ -289,11 +289,11 @@ def stop_exercise():
289
  """Stop the current exercise and log the workout"""
290
  global exercise_running, current_exercise_data, workout_start_time
291
  global exercise_counter, exercise_goal, sets_completed, sets_goal
292
-
293
  if exercise_running and current_exercise_data:
294
  # Calculate duration
295
  duration = int(time.time() - workout_start_time) if workout_start_time else 0
296
-
297
  # Log the workout
298
  workout_logger.log_workout(
299
  exercise_type=current_exercise_data['type'],
@@ -309,7 +309,7 @@ def stop_exercise():
309
  def get_status():
310
  """Return current exercise status"""
311
  global exercise_counter, sets_completed, exercise_goal, sets_goal, exercise_running
312
-
313
  return jsonify({
314
  'exercise_running': exercise_running,
315
  'current_reps': exercise_counter,
@@ -325,7 +325,7 @@ def profile():
325
 
326
  @app.route('/healthz')
327
  def health_check():
328
- logger.info("Health check endpoint called successfully.")
329
  return "OK", 200
330
 
331
  @app.route('/api/analyze_frame', methods=['POST'])
@@ -336,7 +336,7 @@ def analyze_frame():
336
  if not data or 'image' not in data:
337
  logger.warning("API /api/analyze_frame: No image provided or invalid JSON.")
338
  return jsonify({'error': 'No image provided in JSON payload (expected base64 string under "image" key)'}), 400
339
-
340
  # Use 'squat' as a default if not provided. This primarily affects server-side drawing, not the landmarks themselves.
341
  exercise_type_for_api = data.get('exercise_type', 'squat')
342
  if exercise_type_for_api not in ["squat", "push_up", "hammer_curl"]: # Validate against known types if necessary
@@ -344,7 +344,7 @@ def analyze_frame():
344
  exercise_type_for_api = "squat"
345
 
346
  image_data = data['image']
347
-
348
  # Add padding if missing for base64 decoding
349
  missing_padding = len(image_data) % 4
350
  if missing_padding:
@@ -361,10 +361,10 @@ def analyze_frame():
361
  return jsonify({'error': f'Invalid base64 image data: {str(e)}'}), 400
362
 
363
  estimator = get_pose_estimator_api_instance()
364
-
365
  # Process with pose estimation
366
- results = estimator.estimate_pose(frame, exercise_type_for_api)
367
-
368
  landmarks_list = []
369
  if results.pose_landmarks:
370
  for i, landmark in enumerate(results.pose_landmarks.landmark):
@@ -406,7 +406,7 @@ def track_exercise_stream():
406
 
407
  if not isinstance(frame_width, int) or not isinstance(frame_height, int) or frame_width <= 0 or frame_height <= 0:
408
  return jsonify({'error': 'Invalid frame_width or frame_height'}), 400
409
-
410
  # Manage session limit
411
  if len(active_exercise_sessions) >= MAX_SESSIONS and session_id not in active_exercise_sessions:
412
  logger.warning(f"Max sessions ({MAX_SESSIONS}) reached. Rejecting new session {session_id}.")
@@ -426,7 +426,7 @@ def track_exercise_stream():
426
  else:
427
  logger.warning(f"Invalid exercise type: {exercise_type} for session {session_id}")
428
  return jsonify({'error': 'Invalid exercise_type'}), 400
429
-
430
  exercise_session = active_exercise_sessions[session_id]
431
 
432
  # Decode image (similar to /api/analyze_frame)
@@ -445,18 +445,18 @@ def track_exercise_stream():
445
  pose_estimator = get_pose_estimator_api_instance() # Reusing the existing estimator instance getter
446
  # The 'squat' here is just a default for PoseEstimator's internal drawing logic, which we don't use for API response.
447
  # The actual exercise type for tracking is handled by `exercise_session` object.
448
- results = pose_estimator.estimate_pose(frame_for_estimation, 'squat')
449
 
450
  if not results.pose_landmarks:
451
  logger.info(f"Session {session_id}: No landmarks detected.")
452
  # Return current state even if no new landmarks, or specific message
453
  return jsonify({
454
- 'success': True,
455
  'landmarks_detected': False,
456
  'message': 'No landmarks detected in this frame.',
457
  # Optionally, could return last known state from exercise_session if needed
458
  })
459
-
460
  exercise_data = None
461
  if exercise_type == 'squat':
462
  exercise_data = exercise_session.track_squat(results.pose_landmarks.landmark, frame_width, frame_height)
@@ -464,7 +464,7 @@ def track_exercise_stream():
464
  exercise_data = exercise_session.track_push_up(results.pose_landmarks.landmark, frame_width, frame_height)
465
  elif exercise_type == 'hammer_curl':
466
  exercise_data = exercise_session.track_hammer_curl(results.pose_landmarks.landmark, frame_width, frame_height)
467
-
468
  if exercise_data:
469
  logger.debug(f"Session {session_id}: Exercise data: {exercise_data}")
470
  return jsonify({'success': True, 'landmarks_detected': True, 'data': exercise_data})
@@ -486,7 +486,7 @@ def end_exercise_session():
486
  data = request.json
487
  if not data:
488
  return jsonify({'error': 'No JSON data provided'}), 400
489
-
490
  session_id = data.get('session_id')
491
  if not session_id:
492
  return jsonify({'error': 'Missing session_id'}), 400
@@ -547,7 +547,7 @@ def handle_start_exercise_session(data):
547
  logger.warning(f"Invalid exercise type: {exercise_type} for session {request.sid}")
548
  socketio.emit('session_error', {'error': 'Invalid exercise_type'}, room=request.sid)
549
  return
550
-
551
  logger.info(f"Successfully created exercise session for {request.sid}, type: {exercise_type}")
552
  socketio.emit('session_started', {'session_id': request.sid, 'exercise_type': exercise_type}, room=request.sid)
553
 
@@ -579,43 +579,42 @@ def handle_process_frame(data):
579
  return
580
 
581
  try:
582
- missing_padding = len(image_data_base64) % 4
583
- if missing_padding:
584
- image_data_base64 += '=' * (4 - missing_padding)
585
-
586
- image_bytes = base64.b64decode(image_data_base64)
587
- pil_image = Image.open(io.BytesIO(image_bytes))
588
- frame_for_estimation = cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR)
589
- except Exception as e:
590
- logger.error(f"Error decoding base64 image for {request.sid}: {e}")
591
- socketio.emit('frame_error', {'error': f'Invalid base64 image data: {str(e)}'}, room=request.sid)
592
- return
 
 
593
 
594
- pose_estimator = get_pose_estimator_api_instance()
595
- # The 'squat' parameter to estimate_pose is a default for drawing, actual logic is in exercise_session
596
- results = pose_estimator.estimate_pose(frame_for_estimation, session_exercise_type)
597
-
598
- exercise_data_result = None
599
- if results.pose_landmarks:
600
- if session_exercise_type == 'squat':
601
- exercise_data_result = exercise_session.track_squat(results.pose_landmarks.landmark, frame_width, frame_height)
602
- elif session_exercise_type == 'push_up':
603
- exercise_data_result = exercise_session.track_push_up(results.pose_landmarks.landmark, frame_width, frame_height)
604
- elif session_exercise_type == 'hammer_curl':
605
- exercise_data_result = exercise_session.track_hammer_curl(results.pose_landmarks.landmark, frame_width, frame_height)
606
-
607
- if exercise_data_result:
608
- # logger.debug(f"Exercise data for {request.sid}: {exercise_data_result}")
609
- socketio.emit('exercise_update', {'success': True, 'landmarks_detected': True, 'data': exercise_data_result}, room=request.sid)
 
 
610
  else:
611
- # This case should ideally not be reached if landmarks are detected and exercise type is valid
612
- logger.error(f"Could not get exercise data for {request.sid}, type {session_exercise_type}, despite landmarks.")
613
- socketio.emit('exercise_update', {'success': False, 'landmarks_detected': True, 'message': 'Error processing landmarks.'}, room=request.sid)
614
- else:
615
- # logger.info(f"No landmarks detected for {request.sid}.")
616
- socketio.emit('exercise_update', {'success': True, 'landmarks_detected': False, 'message': 'No landmarks detected.'}, room=request.sid)
617
 
618
- except Exception as e:
619
  logger.error(f"Error processing frame for {request.sid}: {e}")
620
  traceback.print_exc()
621
  socketio.emit('frame_error', {'error': f'Internal server error during frame processing: {str(e)}'}, room=request.sid)
@@ -629,4 +628,4 @@ if __name__ == '__main__':
629
  socketio.run(app, debug=True, host='0.0.0.0', port=5000)
630
  except Exception as e:
631
  logger.error(f"Failed to start application: {e}")
632
- traceback.print_exc()
 
14
  from flask_cors import CORS
15
 
16
  # Set up logging
17
+ logging.basicConfig(level=logging.DEBUG,
18
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
19
+ handlers=[logging.StreamHandler()])
20
  logger = logging.getLogger(__name__)
21
 
22
  # Import attempt with error handling
 
41
  logger.info("Successfully initialized workout logger")
42
  except ImportError:
43
  logger.warning("WorkoutLogger import failed, creating dummy class")
44
+
45
  class DummyWorkoutLogger:
46
  def __init__(self):
47
  pass
 
55
  return {}
56
  def get_user_stats(self, *args, **kwargs):
57
  return {'total_workouts': 0, 'total_exercises': 0, 'streak_days': 0}
58
+
59
  workout_logger = DummyWorkoutLogger()
60
 
61
  logger.info("Setting up Flask application")
 
67
  pose_estimator_api = None
68
  active_exercise_sessions = {}
69
  # Max number of concurrent sessions to avoid memory issues
70
+ MAX_SESSIONS = 100
71
 
72
  def get_pose_estimator_api_instance():
73
  global pose_estimator_api
 
104
  def generate_frames():
105
  global output_frame, lock, exercise_running, current_exercise, current_exercise_data
106
  global exercise_counter, exercise_goal, sets_completed, sets_goal
107
+
108
  pose_estimator = PoseEstimator()
109
+
110
  while True:
111
  if camera is None:
112
  continue
113
+
114
  success, frame = camera.read()
115
  if not success:
116
  continue
117
+
118
  # Only process frames if an exercise is running
119
  if exercise_running and current_exercise:
120
  # Process with pose estimation
121
  results = pose_estimator.estimate_pose(frame, current_exercise_data['type'])
122
+
123
  if results.pose_landmarks:
124
  # Track exercise based on type
125
  if current_exercise_data['type'] == "squat":
126
  counter, angle, stage = current_exercise.track_squat(results.pose_landmarks.landmark, frame)
127
  layout_indicators(frame, current_exercise_data['type'], (counter, angle, stage))
128
  exercise_counter = counter
129
+
130
  elif current_exercise_data['type'] == "push_up":
131
  counter, angle, stage = current_exercise.track_push_up(results.pose_landmarks.landmark, frame)
132
  layout_indicators(frame, current_exercise_data['type'], (counter, angle, stage))
133
  exercise_counter = counter
134
+
135
  elif current_exercise_data['type'] == "hammer_curl":
136
  (counter_right, angle_right, counter_left, angle_left,
137
+ warning_message_right, warning_message_left, progress_right,
138
  progress_left, stage_right, stage_left) = current_exercise.track_hammer_curl(
139
  results.pose_landmarks.landmark, frame)
140
+ layout_indicators(frame, current_exercise_data['type'],
141
  (counter_right, angle_right, counter_left, angle_left,
142
+ warning_message_right, warning_message_left,
143
  progress_right, progress_left, stage_right, stage_left))
144
  exercise_counter = max(counter_right, counter_left)
145
+
146
  # Display exercise information
147
  exercise_info = get_exercise_info(current_exercise_data['type'])
148
  draw_text_with_background(frame, f"Exercise: {exercise_info.get('name', 'N/A')}", (40, 50),
 
153
  cv2.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255), (118, 29, 14), 1)
154
  draw_text_with_background(frame, f"Current Set: {sets_completed + 1}", (40, 140),
155
  cv2.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255), (118, 29, 14), 1)
156
+
157
  # Check if rep goal is reached for current set
158
  if exercise_counter >= exercise_goal:
159
  sets_completed += 1
 
164
  elif current_exercise_data['type'] == "hammer_curl":
165
  current_exercise.counter_right = 0
166
  current_exercise.counter_left = 0
167
+
168
  # Check if all sets are completed
169
  if sets_completed >= sets_goal:
170
  exercise_running = False
171
  draw_text_with_background(frame, "WORKOUT COMPLETE!", (frame.shape[1]//2 - 150, frame.shape[0]//2),
172
  cv2.FONT_HERSHEY_DUPLEX, 1.2, (255, 255, 255), (0, 200, 0), 2)
173
  else:
174
+ draw_text_with_background(frame, f"SET {sets_completed} COMPLETE! Rest for 30 sec",
175
  (frame.shape[1]//2 - 200, frame.shape[0]//2),
176
  cv2.FONT_HERSHEY_DUPLEX, 1.0, (255, 255, 255), (0, 0, 200), 2)
177
  # We could add rest timer functionality here
 
179
  # Display welcome message if no exercise is running
180
  cv2.putText(frame, "Select an exercise to begin", (frame.shape[1]//2 - 150, frame.shape[0]//2),
181
  cv2.FONT_HERSHEY_DUPLEX, 0.8, (255, 255, 255), 1)
182
+
183
  # Encode the frame in JPEG format
184
  with lock:
185
  output_frame = frame.copy()
186
+
187
  # Yield the frame in byte format
188
  ret, buffer = cv2.imencode('.jpg', output_frame)
189
  frame = buffer.tobytes()
 
210
  weekly_stats = workout_logger.get_weekly_stats()
211
  exercise_distribution = workout_logger.get_exercise_distribution()
212
  user_stats = workout_logger.get_user_stats()
213
+
214
  # Format workouts for display
215
  formatted_workouts = []
216
  for workout in recent_workouts:
 
221
  'reps': workout['reps'],
222
  'duration': f"{workout['duration_seconds'] // 60}:{workout['duration_seconds'] % 60:02d}"
223
  })
224
+
225
  # Calculate total workouts this week
226
  weekly_workout_count = sum(day['workout_count'] for day in weekly_stats.values())
227
+
228
  return render_template('dashboard.html',
229
  recent_workouts=formatted_workouts,
230
  weekly_workouts=weekly_workout_count,
 
248
  global exercise_running, current_exercise, current_exercise_data
249
  global exercise_counter, exercise_goal, sets_completed, sets_goal
250
  global workout_start_time
251
+
252
  data = request.json
253
  exercise_type = data.get('exercise_type')
254
  sets_goal = int(data.get('sets', 3))
255
  exercise_goal = int(data.get('reps', 10))
256
+
257
  # Initialize camera if not already done
258
  initialize_camera()
259
+
260
  # Reset counters
261
  exercise_counter = 0
262
  sets_completed = 0
263
  workout_start_time = time.time()
264
+
265
  # Initialize the appropriate exercise class
266
  if exercise_type == "squat":
267
  current_exercise = Squat()
 
271
  current_exercise = HammerCurl()
272
  else:
273
  return jsonify({'success': False, 'error': 'Invalid exercise type'})
274
+
275
  # Store exercise data
276
  current_exercise_data = {
277
  'type': exercise_type,
278
  'sets': sets_goal,
279
  'reps': exercise_goal
280
  }
281
+
282
  # Start the exercise
283
  exercise_running = True
284
+
285
  return jsonify({'success': True})
286
 
287
  @app.route('/stop_exercise', methods=['POST'])
 
289
  """Stop the current exercise and log the workout"""
290
  global exercise_running, current_exercise_data, workout_start_time
291
  global exercise_counter, exercise_goal, sets_completed, sets_goal
292
+
293
  if exercise_running and current_exercise_data:
294
  # Calculate duration
295
  duration = int(time.time() - workout_start_time) if workout_start_time else 0
296
+
297
  # Log the workout
298
  workout_logger.log_workout(
299
  exercise_type=current_exercise_data['type'],
 
309
  def get_status():
310
  """Return current exercise status"""
311
  global exercise_counter, sets_completed, exercise_goal, sets_goal, exercise_running
312
+
313
  return jsonify({
314
  'exercise_running': exercise_running,
315
  'current_reps': exercise_counter,
 
325
 
326
  @app.route('/healthz')
327
  def health_check():
328
+ logger.info("HEALTH CHECK ENDPOINT CALLED AND RESPONDING OK")
329
  return "OK", 200
330
 
331
  @app.route('/api/analyze_frame', methods=['POST'])
 
336
  if not data or 'image' not in data:
337
  logger.warning("API /api/analyze_frame: No image provided or invalid JSON.")
338
  return jsonify({'error': 'No image provided in JSON payload (expected base64 string under "image" key)'}), 400
339
+
340
  # Use 'squat' as a default if not provided. This primarily affects server-side drawing, not the landmarks themselves.
341
  exercise_type_for_api = data.get('exercise_type', 'squat')
342
  if exercise_type_for_api not in ["squat", "push_up", "hammer_curl"]: # Validate against known types if necessary
 
344
  exercise_type_for_api = "squat"
345
 
346
  image_data = data['image']
347
+
348
  # Add padding if missing for base64 decoding
349
  missing_padding = len(image_data) % 4
350
  if missing_padding:
 
361
  return jsonify({'error': f'Invalid base64 image data: {str(e)}'}), 400
362
 
363
  estimator = get_pose_estimator_api_instance()
364
+
365
  # Process with pose estimation
366
+ results = estimator.estimate_pose(frame, exercise_type_for_api)
367
+
368
  landmarks_list = []
369
  if results.pose_landmarks:
370
  for i, landmark in enumerate(results.pose_landmarks.landmark):
 
406
 
407
  if not isinstance(frame_width, int) or not isinstance(frame_height, int) or frame_width <= 0 or frame_height <= 0:
408
  return jsonify({'error': 'Invalid frame_width or frame_height'}), 400
409
+
410
  # Manage session limit
411
  if len(active_exercise_sessions) >= MAX_SESSIONS and session_id not in active_exercise_sessions:
412
  logger.warning(f"Max sessions ({MAX_SESSIONS}) reached. Rejecting new session {session_id}.")
 
426
  else:
427
  logger.warning(f"Invalid exercise type: {exercise_type} for session {session_id}")
428
  return jsonify({'error': 'Invalid exercise_type'}), 400
429
+
430
  exercise_session = active_exercise_sessions[session_id]
431
 
432
  # Decode image (similar to /api/analyze_frame)
 
445
  pose_estimator = get_pose_estimator_api_instance() # Reusing the existing estimator instance getter
446
  # The 'squat' here is just a default for PoseEstimator's internal drawing logic, which we don't use for API response.
447
  # The actual exercise type for tracking is handled by `exercise_session` object.
448
+ results = pose_estimator.estimate_pose(frame_for_estimation, 'squat')
449
 
450
  if not results.pose_landmarks:
451
  logger.info(f"Session {session_id}: No landmarks detected.")
452
  # Return current state even if no new landmarks, or specific message
453
  return jsonify({
454
+ 'success': True,
455
  'landmarks_detected': False,
456
  'message': 'No landmarks detected in this frame.',
457
  # Optionally, could return last known state from exercise_session if needed
458
  })
459
+
460
  exercise_data = None
461
  if exercise_type == 'squat':
462
  exercise_data = exercise_session.track_squat(results.pose_landmarks.landmark, frame_width, frame_height)
 
464
  exercise_data = exercise_session.track_push_up(results.pose_landmarks.landmark, frame_width, frame_height)
465
  elif exercise_type == 'hammer_curl':
466
  exercise_data = exercise_session.track_hammer_curl(results.pose_landmarks.landmark, frame_width, frame_height)
467
+
468
  if exercise_data:
469
  logger.debug(f"Session {session_id}: Exercise data: {exercise_data}")
470
  return jsonify({'success': True, 'landmarks_detected': True, 'data': exercise_data})
 
486
  data = request.json
487
  if not data:
488
  return jsonify({'error': 'No JSON data provided'}), 400
489
+
490
  session_id = data.get('session_id')
491
  if not session_id:
492
  return jsonify({'error': 'Missing session_id'}), 400
 
547
  logger.warning(f"Invalid exercise type: {exercise_type} for session {request.sid}")
548
  socketio.emit('session_error', {'error': 'Invalid exercise_type'}, room=request.sid)
549
  return
550
+
551
  logger.info(f"Successfully created exercise session for {request.sid}, type: {exercise_type}")
552
  socketio.emit('session_started', {'session_id': request.sid, 'exercise_type': exercise_type}, room=request.sid)
553
 
 
579
  return
580
 
581
  try:
582
+ # This try-except block is for image decoding
583
+ try:
584
+ missing_padding = len(image_data_base64) % 4
585
+ if missing_padding:
586
+ image_data_base64 += '=' * (4 - missing_padding)
587
+
588
+ image_bytes = base64.b64decode(image_data_base64)
589
+ pil_image = Image.open(io.BytesIO(image_bytes))
590
+ frame_for_estimation = cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR)
591
+ except Exception as e:
592
+ logger.error(f"Error decoding base64 image for {request.sid}: {e}")
593
+ socketio.emit('frame_error', {'error': f'Invalid base64 image data: {str(e)}'}, room=request.sid)
594
+ return # Important to return here if decoding fails
595
 
596
+ # This is the main processing logic that should be wrapped in the outer try-except
597
+ pose_estimator = get_pose_estimator_api_instance()
598
+ results = pose_estimator.estimate_pose(frame_for_estimation, session_exercise_type)
599
+
600
+ exercise_data_result = None
601
+ if results.pose_landmarks:
602
+ if session_exercise_type == 'squat':
603
+ exercise_data_result = exercise_session.track_squat(results.pose_landmarks.landmark, frame_width, frame_height)
604
+ elif session_exercise_type == 'push_up':
605
+ exercise_data_result = exercise_session.track_push_up(results.pose_landmarks.landmark, frame_width, frame_height)
606
+ elif session_exercise_type == 'hammer_curl':
607
+ exercise_data_result = exercise_session.track_hammer_curl(results.pose_landmarks.landmark, frame_width, frame_height)
608
+
609
+ if exercise_data_result:
610
+ socketio.emit('exercise_update', {'success': True, 'landmarks_detected': True, 'data': exercise_data_result}, room=request.sid)
611
+ else:
612
+ logger.error(f"Could not get exercise_data for {request.sid}, type {session_exercise_type}, despite landmarks.")
613
+ socketio.emit('exercise_update', {'success': False, 'landmarks_detected': True, 'message': 'Error processing landmarks.'}, room=request.sid)
614
  else:
615
+ socketio.emit('exercise_update', {'success': True, 'landmarks_detected': False, 'message': 'No landmarks detected.'}, room=request.sid)
 
 
 
 
 
616
 
617
+ except Exception as e: # This is the general exception handler for the processing logic
618
  logger.error(f"Error processing frame for {request.sid}: {e}")
619
  traceback.print_exc()
620
  socketio.emit('frame_error', {'error': f'Internal server error during frame processing: {str(e)}'}, room=request.sid)
 
628
  socketio.run(app, debug=True, host='0.0.0.0', port=5000)
629
  except Exception as e:
630
  logger.error(f"Failed to start application: {e}")
631
+ traceback.print_exc()