pjxcharya commited on
Commit
d3a82c6
·
verified ·
1 Parent(s): edf4fe6

Upload 67 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +3 -0
  2. LICENSE +21 -0
  3. README.md +83 -10
  4. app.py +314 -0
  5. create_static_folders.py +63 -0
  6. db/__pycache__/workout_logger.cpython-311.pyc +0 -0
  7. db/__pycache__/workout_logger.cpython-312.pyc +0 -0
  8. db/__pycache__/workout_logger.cpython-39.pyc +0 -0
  9. db/workout_logger.py +0 -0
  10. exercises/__pycache__/hammer_curl.cpython-311.pyc +0 -0
  11. exercises/__pycache__/hammer_curl.cpython-312.pyc +0 -0
  12. exercises/__pycache__/hammer_curl.cpython-39.pyc +0 -0
  13. exercises/__pycache__/push_up.cpython-311.pyc +0 -0
  14. exercises/__pycache__/push_up.cpython-312.pyc +0 -0
  15. exercises/__pycache__/push_up.cpython-39.pyc +0 -0
  16. exercises/__pycache__/squat.cpython-311.pyc +0 -0
  17. exercises/__pycache__/squat.cpython-312.pyc +0 -0
  18. exercises/__pycache__/squat.cpython-39.pyc +0 -0
  19. exercises/hammer_curl.py +114 -0
  20. exercises/push_up.py +77 -0
  21. exercises/squat.py +62 -0
  22. feedback/__pycache__/indicators.cpython-311.pyc +0 -0
  23. feedback/__pycache__/indicators.cpython-312.pyc +0 -0
  24. feedback/__pycache__/indicators.cpython-39.pyc +0 -0
  25. feedback/__pycache__/information.cpython-311.pyc +0 -0
  26. feedback/__pycache__/information.cpython-312.pyc +0 -0
  27. feedback/__pycache__/information.cpython-39.pyc +0 -0
  28. feedback/__pycache__/layout.cpython-311.pyc +0 -0
  29. feedback/__pycache__/layout.cpython-312.pyc +0 -0
  30. feedback/__pycache__/layout.cpython-39.pyc +0 -0
  31. feedback/indicators.py +50 -0
  32. feedback/information.py +41 -0
  33. feedback/layout.py +16 -0
  34. main.py +82 -0
  35. output/images/Screenshot 2024-09-08 030742.png +3 -0
  36. output/images/Screenshot 2024-09-08 030816.png +3 -0
  37. output/images/Screenshot 2024-09-08 030836.png +3 -0
  38. pose_estimation/__pycache__/angle_calculation.cpython-311.pyc +0 -0
  39. pose_estimation/__pycache__/angle_calculation.cpython-312.pyc +0 -0
  40. pose_estimation/__pycache__/angle_calculation.cpython-39.pyc +0 -0
  41. pose_estimation/__pycache__/estimation.cpython-311.pyc +0 -0
  42. pose_estimation/__pycache__/estimation.cpython-312.pyc +0 -0
  43. pose_estimation/__pycache__/estimation.cpython-39.pyc +0 -0
  44. pose_estimation/angle_calculation.py +23 -0
  45. pose_estimation/estimation.py +81 -0
  46. requirements.txt +0 -0
  47. static/css/dashboard.css +138 -0
  48. static/css/style.css +210 -0
  49. static/images/README.txt +7 -0
  50. static/images/hammer_curl.png +0 -0
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ output/images/Screenshot[[:space:]]2024-09-08[[:space:]]030742.png filter=lfs diff=lfs merge=lfs -text
37
+ output/images/Screenshot[[:space:]]2024-09-08[[:space:]]030816.png filter=lfs diff=lfs merge=lfs -text
38
+ output/images/Screenshot[[:space:]]2024-09-08[[:space:]]030836.png filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Yakup Zengin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,10 +1,83 @@
1
- ---
2
- title: LiveTrain
3
- emoji: 🔥
4
- colorFrom: blue
5
- colorTo: yellow
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fitness Trainer with Pose Estimation
2
+
3
+ An AI-powered web application that tracks exercises using computer vision and provides real-time feedback.
4
+
5
+ ## Features
6
+
7
+ - Real-time pose estimation using MediaPipe
8
+ - Multiple exercise types: Squats, Push-ups, and Hammer Curls
9
+ - Customizable sets and repetitions
10
+ - Exercise form feedback
11
+ - Progress tracking
12
+ - Web interface for easy access
13
+
14
+ ## Installation
15
+
16
+ 1. Clone the repository:
17
+
18
+ ```
19
+ git clone https://github.com/yourusername/fitness-trainer-pose-estimation.git
20
+ cd fitness-trainer-pose-estimation
21
+ ```
22
+
23
+ 2. Install dependencies:
24
+
25
+ ```
26
+ pip install -r requirements.txt
27
+ ```
28
+
29
+ 3. Set up the static folder structure:
30
+
31
+ ```
32
+ mkdir -p static/images
33
+ ```
34
+
35
+ 4. Add exercise images to the static/images folder:
36
+ - squat.png
37
+ - push_up.png
38
+ - hammer_curl.png
39
+
40
+ ## Usage
41
+
42
+ 1. Start the Flask server:
43
+
44
+ ```
45
+ python app.py
46
+ ```
47
+
48
+ 2. Open a web browser and navigate to:
49
+
50
+ ```
51
+ http://127.0.0.1:5000
52
+ ```
53
+
54
+ 3. Select an exercise type, set your desired number of repetitions and sets, then click "Start Workout"
55
+
56
+ 4. Position yourself in front of your camera so that your full body is visible
57
+
58
+ 5. Follow the on-screen guidance to perform the exercise correctly
59
+
60
+ ## Project Structure
61
+
62
+ - `app.py` - Main Flask application
63
+ - `templates/` - HTML templates
64
+ - `static/` - CSS, JavaScript, and images
65
+ - `pose_estimation/` - Pose estimation modules
66
+ - `exercises/` - Exercise tracking classes
67
+ - `feedback/` - User feedback modules
68
+ - `utils/` - Helper functions and utilities
69
+
70
+ ## Technologies Used
71
+
72
+ - Flask - Web framework
73
+ - OpenCV - Computer vision
74
+ - MediaPipe - Pose estimation
75
+ - HTML/CSS/JavaScript - Frontend
76
+
77
+ ## Contributing
78
+
79
+ Contributions are welcome! Please feel free to submit a Pull Request.
80
+
81
+ ## License
82
+
83
+ This project is licensed under the MIT License - see the LICENSE file for details.
app.py ADDED
@@ -0,0 +1,314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, Response, request, jsonify, session, redirect, url_for
2
+ import cv2
3
+ import threading
4
+ import time
5
+ import sys
6
+ import traceback
7
+ import logging
8
+ from flask_cors import CORS
9
+
10
+ # Set up logging
11
+ logging.basicConfig(level=logging.DEBUG,
12
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
13
+ handlers=[logging.StreamHandler()])
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # Import attempt with error handling
17
+ try:
18
+ from pose_estimation.estimation import PoseEstimator
19
+ from exercises.squat import Squat
20
+ from exercises.hammer_curl import HammerCurl
21
+ from exercises.push_up import PushUp
22
+ from feedback.information import get_exercise_info
23
+ from feedback.layout import layout_indicators
24
+ from utils.draw_text_with_background import draw_text_with_background
25
+ logger.info("Successfully imported pose estimation modules")
26
+ except ImportError as e:
27
+ logger.error(f"Failed to import required modules: {e}")
28
+ traceback.print_exc()
29
+ sys.exit(1)
30
+
31
+ # Try to import WorkoutLogger with fallback
32
+ try:
33
+ from db.workout_logger import WorkoutLogger
34
+ workout_logger = WorkoutLogger()
35
+ logger.info("Successfully initialized workout logger")
36
+ except ImportError:
37
+ logger.warning("WorkoutLogger import failed, creating dummy class")
38
+
39
+ class DummyWorkoutLogger:
40
+ def __init__(self):
41
+ pass
42
+ def log_workout(self, *args, **kwargs):
43
+ return {}
44
+ def get_recent_workouts(self, *args, **kwargs):
45
+ return []
46
+ def get_weekly_stats(self, *args, **kwargs):
47
+ return {}
48
+ def get_exercise_distribution(self, *args, **kwargs):
49
+ return {}
50
+ def get_user_stats(self, *args, **kwargs):
51
+ return {'total_workouts': 0, 'total_exercises': 0, 'streak_days': 0}
52
+
53
+ workout_logger = DummyWorkoutLogger()
54
+
55
+ logger.info("Setting up Flask application")
56
+ app = Flask(__name__)
57
+ app.secret_key = 'fitness_trainer_secret_key' # Required for sessions
58
+ CORS(app)
59
+ # Global variables
60
+ camera = None
61
+ output_frame = None
62
+ lock = threading.Lock()
63
+ exercise_running = False
64
+ current_exercise = None
65
+ current_exercise_data = None
66
+ exercise_counter = 0
67
+ exercise_goal = 0
68
+ sets_completed = 0
69
+ sets_goal = 0
70
+ workout_start_time = None
71
+
72
+ def initialize_camera():
73
+ global camera
74
+ if camera is None:
75
+ camera = cv2.VideoCapture(0)
76
+ return camera
77
+
78
+ def release_camera():
79
+ global camera
80
+ if camera is not None:
81
+ camera.release()
82
+ camera = None
83
+
84
+ def generate_frames():
85
+ global output_frame, lock, exercise_running, current_exercise, current_exercise_data
86
+ global exercise_counter, exercise_goal, sets_completed, sets_goal
87
+
88
+ pose_estimator = PoseEstimator()
89
+
90
+ while True:
91
+ if camera is None:
92
+ continue
93
+
94
+ success, frame = camera.read()
95
+ if not success:
96
+ continue
97
+
98
+ # Only process frames if an exercise is running
99
+ if exercise_running and current_exercise:
100
+ # Process with pose estimation
101
+ results = pose_estimator.estimate_pose(frame, current_exercise_data['type'])
102
+
103
+ if results.pose_landmarks:
104
+ # Track exercise based on type
105
+ if current_exercise_data['type'] == "squat":
106
+ counter, angle, stage = current_exercise.track_squat(results.pose_landmarks.landmark, frame)
107
+ layout_indicators(frame, current_exercise_data['type'], (counter, angle, stage))
108
+ exercise_counter = counter
109
+
110
+ elif current_exercise_data['type'] == "push_up":
111
+ counter, angle, stage = current_exercise.track_push_up(results.pose_landmarks.landmark, frame)
112
+ layout_indicators(frame, current_exercise_data['type'], (counter, angle, stage))
113
+ exercise_counter = counter
114
+
115
+ elif current_exercise_data['type'] == "hammer_curl":
116
+ (counter_right, angle_right, counter_left, angle_left,
117
+ warning_message_right, warning_message_left, progress_right,
118
+ progress_left, stage_right, stage_left) = current_exercise.track_hammer_curl(
119
+ results.pose_landmarks.landmark, frame)
120
+ layout_indicators(frame, current_exercise_data['type'],
121
+ (counter_right, angle_right, counter_left, angle_left,
122
+ warning_message_right, warning_message_left,
123
+ progress_right, progress_left, stage_right, stage_left))
124
+ exercise_counter = max(counter_right, counter_left)
125
+
126
+ # Display exercise information
127
+ exercise_info = get_exercise_info(current_exercise_data['type'])
128
+ draw_text_with_background(frame, f"Exercise: {exercise_info.get('name', 'N/A')}", (40, 50),
129
+ cv2.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255), (118, 29, 14), 1)
130
+ draw_text_with_background(frame, f"Reps Goal: {exercise_goal}", (40, 80),
131
+ cv2.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255), (118, 29, 14), 1)
132
+ draw_text_with_background(frame, f"Sets Goal: {sets_goal}", (40, 110),
133
+ cv2.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255), (118, 29, 14), 1)
134
+ draw_text_with_background(frame, f"Current Set: {sets_completed + 1}", (40, 140),
135
+ cv2.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255), (118, 29, 14), 1)
136
+
137
+ # Check if rep goal is reached for current set
138
+ if exercise_counter >= exercise_goal:
139
+ sets_completed += 1
140
+ exercise_counter = 0
141
+ # Reset exercise counter in the appropriate exercise object
142
+ if current_exercise_data['type'] == "squat" or current_exercise_data['type'] == "push_up":
143
+ current_exercise.counter = 0
144
+ elif current_exercise_data['type'] == "hammer_curl":
145
+ current_exercise.counter_right = 0
146
+ current_exercise.counter_left = 0
147
+
148
+ # Check if all sets are completed
149
+ if sets_completed >= sets_goal:
150
+ exercise_running = False
151
+ draw_text_with_background(frame, "WORKOUT COMPLETE!", (frame.shape[1]//2 - 150, frame.shape[0]//2),
152
+ cv2.FONT_HERSHEY_DUPLEX, 1.2, (255, 255, 255), (0, 200, 0), 2)
153
+ else:
154
+ draw_text_with_background(frame, f"SET {sets_completed} COMPLETE! Rest for 30 sec",
155
+ (frame.shape[1]//2 - 200, frame.shape[0]//2),
156
+ cv2.FONT_HERSHEY_DUPLEX, 1.0, (255, 255, 255), (0, 0, 200), 2)
157
+ # We could add rest timer functionality here
158
+ else:
159
+ # Display welcome message if no exercise is running
160
+ cv2.putText(frame, "Select an exercise to begin", (frame.shape[1]//2 - 150, frame.shape[0]//2),
161
+ cv2.FONT_HERSHEY_DUPLEX, 0.8, (255, 255, 255), 1)
162
+
163
+ # Encode the frame in JPEG format
164
+ with lock:
165
+ output_frame = frame.copy()
166
+
167
+ # Yield the frame in byte format
168
+ ret, buffer = cv2.imencode('.jpg', output_frame)
169
+ frame = buffer.tobytes()
170
+ yield (b'--frame\r\n'
171
+ b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
172
+
173
+ @app.route('/')
174
+ def index():
175
+ """Home page with exercise selection"""
176
+ logger.info("Rendering index page")
177
+ try:
178
+ return render_template('index.html')
179
+ except Exception as e:
180
+ logger.error(f"Error rendering index: {e}")
181
+ return f"Error rendering template: {str(e)}", 500
182
+
183
+ @app.route('/dashboard')
184
+ def dashboard():
185
+ """Dashboard page with workout statistics"""
186
+ logger.info("Rendering dashboard page")
187
+ try:
188
+ # Get data for the dashboard
189
+ recent_workouts = workout_logger.get_recent_workouts(5)
190
+ weekly_stats = workout_logger.get_weekly_stats()
191
+ exercise_distribution = workout_logger.get_exercise_distribution()
192
+ user_stats = workout_logger.get_user_stats()
193
+
194
+ # Format workouts for display
195
+ formatted_workouts = []
196
+ for workout in recent_workouts:
197
+ formatted_workouts.append({
198
+ 'date': workout['date'],
199
+ 'exercise': workout['exercise_type'].replace('_', ' ').title(),
200
+ 'sets': workout['sets'],
201
+ 'reps': workout['reps'],
202
+ 'duration': f"{workout['duration_seconds'] // 60}:{workout['duration_seconds'] % 60:02d}"
203
+ })
204
+
205
+ # Calculate total workouts this week
206
+ weekly_workout_count = sum(day['workout_count'] for day in weekly_stats.values())
207
+
208
+ return render_template('dashboard.html',
209
+ recent_workouts=formatted_workouts,
210
+ weekly_workouts=weekly_workout_count,
211
+ total_workouts=user_stats['total_workouts'],
212
+ total_exercises=user_stats['total_exercises'],
213
+ streak_days=user_stats['streak_days'])
214
+ except Exception as e:
215
+ logger.error(f"Error in dashboard: {e}")
216
+ traceback.print_exc()
217
+ return f"Error loading dashboard: {str(e)}", 500
218
+
219
+ @app.route('/video_feed')
220
+ def video_feed():
221
+ """Video streaming route"""
222
+ return Response(generate_frames(),
223
+ mimetype='multipart/x-mixed-replace; boundary=frame')
224
+
225
+ @app.route('/start_exercise', methods=['POST'])
226
+ def start_exercise():
227
+ """Start a new exercise based on user selection"""
228
+ global exercise_running, current_exercise, current_exercise_data
229
+ global exercise_counter, exercise_goal, sets_completed, sets_goal
230
+ global workout_start_time
231
+
232
+ data = request.json
233
+ exercise_type = data.get('exercise_type')
234
+ sets_goal = int(data.get('sets', 3))
235
+ exercise_goal = int(data.get('reps', 10))
236
+
237
+ # Initialize camera if not already done
238
+ initialize_camera()
239
+
240
+ # Reset counters
241
+ exercise_counter = 0
242
+ sets_completed = 0
243
+ workout_start_time = time.time()
244
+
245
+ # Initialize the appropriate exercise class
246
+ if exercise_type == "squat":
247
+ current_exercise = Squat()
248
+ elif exercise_type == "push_up":
249
+ current_exercise = PushUp()
250
+ elif exercise_type == "hammer_curl":
251
+ current_exercise = HammerCurl()
252
+ else:
253
+ return jsonify({'success': False, 'error': 'Invalid exercise type'})
254
+
255
+ # Store exercise data
256
+ current_exercise_data = {
257
+ 'type': exercise_type,
258
+ 'sets': sets_goal,
259
+ 'reps': exercise_goal
260
+ }
261
+
262
+ # Start the exercise
263
+ exercise_running = True
264
+
265
+ return jsonify({'success': True})
266
+
267
+ @app.route('/stop_exercise', methods=['POST'])
268
+ def stop_exercise():
269
+ """Stop the current exercise and log the workout"""
270
+ global exercise_running, current_exercise_data, workout_start_time
271
+ global exercise_counter, exercise_goal, sets_completed, sets_goal
272
+
273
+ if exercise_running and current_exercise_data:
274
+ # Calculate duration
275
+ duration = int(time.time() - workout_start_time) if workout_start_time else 0
276
+
277
+ # Log the workout
278
+ workout_logger.log_workout(
279
+ exercise_type=current_exercise_data['type'],
280
+ sets=sets_completed + (1 if exercise_counter > 0 else 0), # Include partial set
281
+ reps=exercise_goal,
282
+ duration_seconds=duration
283
+ )
284
+ release_camera()
285
+ exercise_running = False
286
+ return jsonify({'success': True})
287
+
288
+ @app.route('/get_status', methods=['GET'])
289
+ def get_status():
290
+ """Return current exercise status"""
291
+ global exercise_counter, sets_completed, exercise_goal, sets_goal, exercise_running
292
+
293
+ return jsonify({
294
+ 'exercise_running': exercise_running,
295
+ 'current_reps': exercise_counter,
296
+ 'current_set': sets_completed + 1 if exercise_running else 0,
297
+ 'total_sets': sets_goal,
298
+ 'rep_goal': exercise_goal
299
+ })
300
+
301
+ @app.route('/profile')
302
+ def profile():
303
+ """User profile page - placeholder for future development"""
304
+ return "Profile page - Coming soon!"
305
+
306
+ if __name__ == '__main__':
307
+ try:
308
+ logger.info("Starting the Flask application on http://127.0.0.1:5000")
309
+ print("Starting Fitness Trainer app, please wait...")
310
+ print("Open http://127.0.0.1:5000 in your web browser when the server starts")
311
+ app.run(debug=True)
312
+ except Exception as e:
313
+ logger.error(f"Failed to start application: {e}")
314
+ traceback.print_exc()
create_static_folders.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ from pathlib import Path
4
+
5
+ def create_directory_structure():
6
+ """Create the necessary directory structure for static files."""
7
+ base_dir = Path(__file__).parent
8
+
9
+ # Create static directories
10
+ static_dir = base_dir / 'static'
11
+ css_dir = static_dir / 'css'
12
+ js_dir = static_dir / 'js'
13
+ images_dir = static_dir / 'images'
14
+
15
+ # Create directories if they don't exist
16
+ for directory in [static_dir, css_dir, js_dir, images_dir]:
17
+ directory.mkdir(exist_ok=True)
18
+ print(f"Created directory: {directory}")
19
+
20
+ # Create sample images if they don't exist
21
+ create_placeholder_image(images_dir / 'squat.png', "Squat")
22
+ create_placeholder_image(images_dir / 'push_up.png', "Push Up")
23
+ create_placeholder_image(images_dir / 'hammer_curl.png', "Hammer Curl")
24
+
25
+ print("\nDirectory structure created successfully!")
26
+ print(f"Static files should be placed in: {static_dir}")
27
+ print("Make sure the following files exist:")
28
+ print(f" - {images_dir / 'squat.png'}")
29
+ print(f" - {images_dir / 'push_up.png'}")
30
+ print(f" - {images_dir / 'hammer_curl.png'}")
31
+
32
+ def create_placeholder_image(filepath, text="Exercise"):
33
+ """Create a simple placeholder image using PIL."""
34
+ try:
35
+ from PIL import Image, ImageDraw, ImageFont
36
+
37
+ # Create a blank image with a colored background
38
+ img = Image.new('RGB', (200, 200), color=(73, 109, 137))
39
+ d = ImageDraw.Draw(img)
40
+
41
+ # Try to use a default font
42
+ try:
43
+ font = ImageFont.truetype("arial.ttf", 18)
44
+ except IOError:
45
+ font = ImageFont.load_default()
46
+
47
+ # Draw text in the center of the image
48
+ d.text((100, 100), text, fill=(255, 255, 255), anchor="mm", font=font)
49
+
50
+ # Save the image
51
+ img.save(filepath)
52
+ print(f"Created placeholder image: {filepath}")
53
+
54
+ except ImportError:
55
+ # If PIL is not available, create an empty file
56
+ print("PIL not installed. Installing empty image files.")
57
+ with open(filepath, 'wb') as f:
58
+ f.write(b'')
59
+ print(f"Created empty file: {filepath}")
60
+
61
+ if __name__ == "__main__":
62
+ create_directory_structure()
63
+ print("\nRun 'pip install pillow' if you want to generate proper placeholder images.")
db/__pycache__/workout_logger.cpython-311.pyc ADDED
Binary file (221 Bytes). View file
 
db/__pycache__/workout_logger.cpython-312.pyc ADDED
Binary file (170 Bytes). View file
 
db/__pycache__/workout_logger.cpython-39.pyc ADDED
Binary file (188 Bytes). View file
 
db/workout_logger.py ADDED
File without changes
exercises/__pycache__/hammer_curl.cpython-311.pyc ADDED
Binary file (7.86 kB). View file
 
exercises/__pycache__/hammer_curl.cpython-312.pyc ADDED
Binary file (7.52 kB). View file
 
exercises/__pycache__/hammer_curl.cpython-39.pyc ADDED
Binary file (3.8 kB). View file
 
exercises/__pycache__/push_up.cpython-311.pyc ADDED
Binary file (5.83 kB). View file
 
exercises/__pycache__/push_up.cpython-312.pyc ADDED
Binary file (5.49 kB). View file
 
exercises/__pycache__/push_up.cpython-39.pyc ADDED
Binary file (2.86 kB). View file
 
exercises/__pycache__/squat.cpython-311.pyc ADDED
Binary file (5.22 kB). View file
 
exercises/__pycache__/squat.cpython-312.pyc ADDED
Binary file (4.84 kB). View file
 
exercises/__pycache__/squat.cpython-39.pyc ADDED
Binary file (2.52 kB). View file
 
exercises/hammer_curl.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from pose_estimation.angle_calculation import calculate_angle
4
+ from voice_feedback.feedback import provide_hammer_curl_feedback , speak
5
+
6
+
7
+ class HammerCurl:
8
+ def __init__(self):
9
+ self.counter_right = 0
10
+ self.counter_left = 0
11
+ self.stage_right = None # 'up' or 'down' for right arm
12
+ self.stage_left = None # 'up' or 'down' for left arm
13
+
14
+ self.angle_threshold = 40 # Angle threshold for misalignment
15
+ self.flexion_angle_up = 155 # Flexion angle for 'up' stage
16
+ self.flexion_angle_down = 35 # Flexion angle for 'down' stage
17
+
18
+ self.angle_threshold_up = 155 # Upper threshold for 'up' stage
19
+ self.angle_threshold_down = 47 # Lower threshold for 'down' stage
20
+
21
+ def calculate_shoulder_elbow_hip_angle(self, shoulder, elbow, hip):
22
+ """Calculate the angle between shoulder, elbow, and hip."""
23
+ return calculate_angle(elbow, shoulder, hip)
24
+
25
+ def calculate_shoulder_elbow_wrist(self, shoulder, elbow, wrist):
26
+ """Calculate the angle between shoulder, elbow, and wrist."""
27
+ return calculate_angle(shoulder, elbow, wrist)
28
+
29
+ def track_hammer_curl(self, landmarks, frame):
30
+ # Right arm landmarks (shoulder, elbow, hip, wrist)
31
+ shoulder_right = [int(landmarks[11].x * frame.shape[1]), int(landmarks[11].y * frame.shape[0])]
32
+ elbow_right = [int(landmarks[13].x * frame.shape[1]), int(landmarks[13].y * frame.shape[0])]
33
+ hip_right = [int(landmarks[23].x * frame.shape[1]), int(landmarks[23].y * frame.shape[0])]
34
+ wrist_right = [int(landmarks[15].x * frame.shape[1]), int(landmarks[15].y * frame.shape[0])]
35
+
36
+ # Left arm landmarks (shoulder, elbow, hip, wrist)
37
+ shoulder_left = [int(landmarks[12].x * frame.shape[1]), int(landmarks[12].y * frame.shape[0])]
38
+ elbow_left = [int(landmarks[14].x * frame.shape[1]), int(landmarks[14].y * frame.shape[0])]
39
+ hip_left = [int(landmarks[24].x * frame.shape[1]), int(landmarks[24].y * frame.shape[0])]
40
+ wrist_left = [int(landmarks[16].x * frame.shape[1]), int(landmarks[16].y * frame.shape[0])]
41
+
42
+ # Calculate the angle for counting (elbow flexion angle)
43
+ angle_right_counter = self.calculate_shoulder_elbow_wrist(shoulder_right, elbow_right, wrist_right)
44
+ angle_left_counter = self.calculate_shoulder_elbow_wrist(shoulder_left, elbow_left, wrist_left)
45
+
46
+ # Calculate the angle for the right arm (shoulder, elbow, hip)
47
+ angle_right = self.calculate_shoulder_elbow_hip_angle(shoulder_right, elbow_right, hip_right)
48
+
49
+ # Calculate the angle for the left arm (shoulder, elbow, hip)
50
+ angle_left = self.calculate_shoulder_elbow_hip_angle(shoulder_left, elbow_left, hip_left)
51
+
52
+ # Draw lines with improved style
53
+ self.draw_line_with_style(frame, shoulder_left, elbow_left, (0, 0, 255), 4)
54
+ self.draw_line_with_style(frame, elbow_left, wrist_left, (0, 0, 255), 4)
55
+
56
+ self.draw_line_with_style(frame, shoulder_right, elbow_right, (0, 0, 255), 4)
57
+ self.draw_line_with_style(frame, elbow_right, wrist_right, (0, 0, 255), 4)
58
+
59
+ # Add circles to highlight key points
60
+ self.draw_circle(frame, shoulder_left, (0, 0, 255), 8)
61
+ self.draw_circle(frame, elbow_left, (0, 0, 255), 8)
62
+ self.draw_circle(frame, wrist_left, (0, 0, 255), 8)
63
+
64
+ self.draw_circle(frame, shoulder_right, (0, 0, 255), 8)
65
+ self.draw_circle(frame, elbow_right, (0, 0, 255), 8)
66
+ self.draw_circle(frame, wrist_right, (0, 0, 255), 8)
67
+
68
+ # Convert the angles to integers and update the text positions
69
+ angle_text_position_left = (elbow_left[0] + 10, elbow_left[1] - 10)
70
+ cv2.putText(frame, f'Angle: {int(angle_left_counter)}', angle_text_position_left, cv2.FONT_HERSHEY_SIMPLEX, 0.5,
71
+ (255, 255, 255), 2)
72
+
73
+ angle_text_position_right = (elbow_right[0] + 10, elbow_right[1] - 10)
74
+ cv2.putText(frame, f'Angle: {int(angle_right_counter)}', angle_text_position_right, cv2.FONT_HERSHEY_SIMPLEX,
75
+ 0.5,
76
+ (255, 255, 255), 2)
77
+
78
+ warning_message_right = None
79
+ warning_message_left = None
80
+
81
+ # Check for misalignment based on shoulder-elbow-hip angle
82
+ if abs(angle_right) > self.angle_threshold:
83
+ warning_message_right = f"Right Shoulder-Elbow-Hip Misalignment! Angle: {angle_right:.2f}°"
84
+ if abs(angle_left) > self.angle_threshold:
85
+ warning_message_left = f"Left Shoulder-Elbow-Hip Misalignment! Angle: {angle_left:.2f}°"
86
+
87
+ if angle_right_counter > self.angle_threshold_up:
88
+ self.stage_right = "Flex"
89
+ elif self.angle_threshold_down < angle_right_counter < self.angle_threshold_up and self.stage_right == "Flex":
90
+ self.stage_right = "Up"
91
+ elif angle_right_counter < self.angle_threshold_down and self.stage_right=="Up":
92
+ self.stage_right = "Down"
93
+ self.counter_right +=1
94
+
95
+ if angle_left_counter > self.angle_threshold_up:
96
+ self.stage_left = "Flex"
97
+ elif self.angle_threshold_down < angle_left_counter < self.angle_threshold_up and self.stage_left == "Flex":
98
+ self.stage_left = "Up"
99
+ elif angle_left_counter < self.angle_threshold_down and self.stage_left == "Up":
100
+ self.stage_left = "Down"
101
+ self.counter_left +=1
102
+
103
+ # Progress percentages: 1 for "up", 0 for "down"
104
+ progress_right = 1 if self.stage_right == "up" else 0
105
+ progress_left = 1 if self.stage_left == "up" else 0
106
+
107
+ return self.counter_right, angle_right_counter, self.counter_left, angle_left_counter, warning_message_right, warning_message_left, progress_right, progress_left, self.stage_right, self.stage_left
108
+
109
+ def draw_line_with_style(self, frame, start_point, end_point, color, thickness):
110
+ cv2.line(frame, start_point, end_point, color, thickness, lineType=cv2.LINE_AA)
111
+
112
+ def draw_circle(self, frame, center, color, radius):
113
+ """Draw a circle with specified style."""
114
+ cv2.circle(frame, center, radius, color, -1) # -1 to fill the circle
exercises/push_up.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import time
3
+ from pose_estimation.angle_calculation import calculate_angle
4
+
5
+ class PushUp:
6
+ def __init__(self):
7
+ self.counter = 0
8
+ self.stage = "Initial" # 'up' or 'down'
9
+ self.angle_threshold_up = 150 # Upper threshold for 'up' stage
10
+ self.angle_threshold_down = 70 # Lower threshold for 'down' stage
11
+ self.last_counter_update = time.time() # Track the time of the last counter update
12
+
13
+ def calculate_shoulder_elbow_wrist_angle(self, shoulder, elbow, wrist):
14
+ """Calculate the angle between shoulder, elbow, and wrist."""
15
+ return calculate_angle(shoulder, elbow, wrist)
16
+
17
+ def track_push_up(self, landmarks, frame):
18
+ # Right side landmarks (shoulder, elbow, wrist)
19
+ shoulder_left = [int(landmarks[11].x * frame.shape[1]), int(landmarks[11].y * frame.shape[0])]
20
+ elbow_left = [int(landmarks[13].x * frame.shape[1]), int(landmarks[13].y * frame.shape[0])]
21
+ wrist_left = [int(landmarks[15].x * frame.shape[1]), int(landmarks[15].y * frame.shape[0])]
22
+
23
+ shoulder_right = [int(landmarks[12].x * frame.shape[1]), int(landmarks[12].y * frame.shape[0])]
24
+ elbow_right = [int(landmarks[14].x * frame.shape[1]), int(landmarks[14].y * frame.shape[0])]
25
+ wrist_right = [int(landmarks[16].x * frame.shape[1]), int(landmarks[16].y * frame.shape[0])]
26
+
27
+ # Calculate angles for push-up tracking
28
+ angle_left = self.calculate_shoulder_elbow_wrist_angle(shoulder_left, elbow_left, wrist_left)
29
+ angle_right = self.calculate_shoulder_elbow_wrist_angle(shoulder_right, elbow_right, wrist_right)
30
+
31
+ # Draw lines with improved style
32
+ self.draw_line_with_style(frame, shoulder_left, elbow_left, (0, 0, 255), 2)
33
+ self.draw_line_with_style(frame, elbow_left, wrist_left, (0, 0, 255), 2)
34
+
35
+ self.draw_line_with_style(frame, shoulder_right, elbow_right, (102, 0, 0), 2)
36
+ self.draw_line_with_style(frame, elbow_right, wrist_right, (102, 0, 0), 2)
37
+
38
+ # Draw circles to highlight key points
39
+ self.draw_circle(frame, shoulder_left, (0, 0, 255), 8)
40
+ self.draw_circle(frame, elbow_left, (0, 0, 255), 8)
41
+ self.draw_circle(frame, wrist_left, (0, 0, 255), 8)
42
+
43
+ self.draw_circle(frame, shoulder_right, (102, 0, 0), 8)
44
+ self.draw_circle(frame, elbow_right, (102, 0, 0), 8)
45
+ self.draw_circle(frame, wrist_right, (102, 0, 0), 8)
46
+
47
+ # Update angle text positions and display
48
+ angle_text_position_left = (elbow_left[0] + 10, elbow_left[1] - 10)
49
+ cv2.putText(frame, f'Angle: {int(angle_left)}', angle_text_position_left, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
50
+
51
+ angle_text_position_right = (elbow_right[0] + 10, elbow_right[1] - 10)
52
+ cv2.putText(frame, f'Angle: {int(angle_right)}', angle_text_position_right, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
53
+
54
+ # Get current time
55
+ current_time = time.time()
56
+
57
+ # Update stage and counter
58
+ if angle_left > self.angle_threshold_up:
59
+ self.stage = "Starting position"
60
+ elif self.angle_threshold_down < angle_left < self.angle_threshold_up and self.stage == "Starting position":
61
+ self.stage = "Descent"
62
+ elif angle_left < self.angle_threshold_down and self.stage == "Descent":
63
+ self.stage = "Ascent"
64
+ # Increment counter only if enough time has passed since last update
65
+ if current_time - self.last_counter_update > 1: # 1 second threshold
66
+ self.counter += 1
67
+ self.last_counter_update = current_time
68
+
69
+ return self.counter, angle_left, self.stage
70
+
71
+ def draw_line_with_style(self, frame, start_point, end_point, color, thickness):
72
+ """Draw a line with specified style."""
73
+ cv2.line(frame, start_point, end_point, color, thickness, lineType=cv2.LINE_AA)
74
+
75
+ def draw_circle(self, frame, center, color, radius):
76
+ """Draw a circle with specified style."""
77
+ cv2.circle(frame, center, radius, color, -1) # -1 to fill the circle
exercises/squat.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ from pose_estimation.angle_calculation import calculate_angle
3
+
4
+ class Squat:
5
+ def __init__(self):
6
+ self.counter = 0
7
+ self.stage = None
8
+
9
+ def calculate_angle(self, hip, knee, ankle):
10
+ return calculate_angle(hip, knee, ankle)
11
+
12
+ def track_squat(self, landmarks, frame):
13
+ # Landmark coordinates
14
+ hip = [int(landmarks[23].x * frame.shape[1]), int(landmarks[23].y * frame.shape[0])]
15
+ knee = [int(landmarks[25].x * frame.shape[1]), int(landmarks[25].y * frame.shape[0])]
16
+ shoulder = [int(landmarks[11].x * frame.shape[1]), int(landmarks[11].y * frame.shape[0])]
17
+
18
+ hip_right = [int(landmarks[24].x * frame.shape[1]), int(landmarks[24].y * frame.shape[0])]
19
+ knee_right = [int(landmarks[26].x * frame.shape[1]), int(landmarks[26].y * frame.shape[0])]
20
+ shoulder_right = [int(landmarks[12].x * frame.shape[1]), int(landmarks[12].y * frame.shape[0])]
21
+
22
+ # Calculate angles
23
+ angle = self.calculate_angle(shoulder, hip, knee)
24
+ angle_right = self.calculate_angle(shoulder_right, hip_right, knee_right)
25
+
26
+ # Draw lines and circles to highlight key points
27
+ self.draw_line_with_style(frame, shoulder, hip, (178, 102, 255), 2)
28
+ self.draw_line_with_style(frame, hip, knee, (178, 102, 255), 2)
29
+ self.draw_line_with_style(frame, shoulder_right, hip_right, (51, 153, 255), 2)
30
+ self.draw_line_with_style(frame, hip_right, knee_right, (51, 153, 255), 2)
31
+
32
+ self.draw_circle(frame, shoulder, (178, 102, 255), 8)
33
+ self.draw_circle(frame, hip, (178, 102, 255), 8)
34
+ self.draw_circle(frame, knee, (178, 102, 255), 8)
35
+ self.draw_circle(frame, shoulder_right, (51, 153, 255), 8)
36
+ self.draw_circle(frame, hip_right, (51, 153, 255), 8)
37
+ self.draw_circle(frame, knee_right, (51, 153, 255), 8)
38
+
39
+ # Display angles on screen
40
+ angle_text_position = (knee[0] + 10, knee[1] - 10)
41
+ cv2.putText(frame, f'Angle Left: {int(angle)}', angle_text_position, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
42
+
43
+ angle_text_position_right = (knee_right[0] + 10, knee_right[1] - 10)
44
+ cv2.putText(frame, f'Angle Right: {int(angle_right)}', angle_text_position_right, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
45
+
46
+ # Update exercise stage and counter
47
+ if angle > 170:
48
+ self.stage = "Starting Position"
49
+ elif 90 < angle < 170 and self.stage == "Starting Position":
50
+ self.stage = "Descent"
51
+ elif angle < 90 and self.stage == "Descent":
52
+ self.stage = "Ascent"
53
+ self.counter += 1
54
+ return self.counter, angle, self.stage
55
+
56
+ def draw_line_with_style(self, frame, start_point, end_point, color, thickness):
57
+ """Draw a line with specified style."""
58
+ cv2.line(frame, start_point, end_point, color, thickness, lineType=cv2.LINE_AA)
59
+
60
+ def draw_circle(self, frame, center, color, radius):
61
+ """Draw a circle with specified style."""
62
+ cv2.circle(frame, center, radius, color, -1) # -1 to fill the circle
feedback/__pycache__/indicators.cpython-311.pyc ADDED
Binary file (2.96 kB). View file
 
feedback/__pycache__/indicators.cpython-312.pyc ADDED
Binary file (2.52 kB). View file
 
feedback/__pycache__/indicators.cpython-39.pyc ADDED
Binary file (1.93 kB). View file
 
feedback/__pycache__/information.cpython-311.pyc ADDED
Binary file (1.14 kB). View file
 
feedback/__pycache__/information.cpython-312.pyc ADDED
Binary file (1.04 kB). View file
 
feedback/__pycache__/information.cpython-39.pyc ADDED
Binary file (951 Bytes). View file
 
feedback/__pycache__/layout.cpython-311.pyc ADDED
Binary file (1.17 kB). View file
 
feedback/__pycache__/layout.cpython-312.pyc ADDED
Binary file (992 Bytes). View file
 
feedback/__pycache__/layout.cpython-39.pyc ADDED
Binary file (814 Bytes). View file
 
feedback/indicators.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # feedback/indicators.py
2
+ from utils.drawing_utils import draw_gauge_meter,draw_progress_bar,display_stage,display_counter
3
+
4
+ display_counter_poisiton=(40, 240)
5
+ display_stage_poisiton=(40, 270)
6
+ display_counter_angel_color=(255,255,0)
7
+
8
+
9
+ def draw_squat_indicators(frame, counter, angle, stage):
10
+ # Counter
11
+ display_counter(frame,counter, position=display_counter_poisiton, color=(0, 0, 0),background_color=(192,192,192))
12
+
13
+ # Stage
14
+ display_stage(frame, stage,"Stage", position=display_stage_poisiton, color=(0, 0, 0),background_color=(192,192,192))
15
+
16
+ draw_progress_bar(frame, exercise="squat", value=counter, position=(40, 170), size=(200, 20), color=(163, 245, 184, 1),background_color=(255,255,255))
17
+
18
+ draw_gauge_meter(frame, angle=angle, text="Squat Gauge Meter", position=(135, 415), radius=75, color=(0, 0, 255))
19
+
20
+ def draw_pushup_indicators(frame, counter, angle, stage):
21
+ # Counter
22
+ display_counter(frame,counter, position=display_counter_poisiton, color=(0, 0, 0),background_color=(192,192,192))
23
+
24
+ display_stage(frame, stage,"Stage", position=display_stage_poisiton, color=(0, 0, 0),background_color=(192,192,192))
25
+ draw_progress_bar(frame, exercise="push_up", value=counter, position=(40, 170), size=(200, 20), color=(163, 245, 184, 1),background_color=(255,255,255))
26
+
27
+ text = "Push-u Gauge Meter"
28
+ draw_gauge_meter(frame, angle=angle,text=text, position=(350,80), radius=50, color=(0, 102, 204))
29
+
30
+
31
+ def draw_hammercurl_indicators(frame, counter_right, angle_right, counter_left, angle_left, stage_right, stage_left):
32
+ display_counter_poisiton_left_arm = (40, 300)
33
+
34
+ # Right Arm Indicators
35
+ display_counter(frame, counter_right, position=display_counter_poisiton, color=(0, 0, 0),background_color=(192,192,192))
36
+
37
+ display_stage(frame, stage_right,"Right Stage", position=display_stage_poisiton, color=(0, 0, 0),background_color=(192,192,192))
38
+ display_stage(frame, stage_left,"Left Stage", position=display_counter_poisiton_left_arm, color=(0, 0, 0),background_color=(192,192,192))
39
+
40
+ # Progress Bars
41
+ draw_progress_bar(frame, exercise="hammer_curl", value=(counter_right+counter_left)/2, position=(40, 170), size=(200, 20), color=(163, 245, 184, 1),background_color=(255,255,255))
42
+
43
+ text_right = "Right Gauge Meter"
44
+ text_left = "Left Gauge Meter"
45
+
46
+ # Gauge Meters for Angles
47
+ draw_gauge_meter(frame, angle=angle_right,text=text_right, position=(1200,80), radius=50, color=(0, 102, 204))
48
+ draw_gauge_meter(frame, angle=angle_left,text=text_left, position=(1200,240), radius=50, color=(0, 102, 204))
49
+
50
+
feedback/information.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def get_exercise_info(exercise_type):
2
+ exercises = {
3
+ "hammer_curl": {
4
+ "name": "Hammer Curl",
5
+ "target_muscles": ["Biceps", "Brachialis"],
6
+ "equipment": "Dumbbells",
7
+ "reps": 8,
8
+ "sets": 1,
9
+ "rest_time": "60 seconds",
10
+ "benefits": [
11
+ "Improves bicep and forearm strength",
12
+ "Enhances grip strength"
13
+ ]
14
+ },
15
+ "push_up": {
16
+ "name": "Push-Up",
17
+ "target_muscles": ["Chest", "Triceps", "Shoulders"],
18
+ "equipment": "Bodyweight",
19
+ "reps": 10,
20
+ "sets": 1,
21
+ "rest_time": "45 seconds",
22
+ "benefits": [
23
+ "Builds upper body strength",
24
+ "Improves core stability"
25
+ ]
26
+ },
27
+ "squat": {
28
+ "name": "Squat",
29
+ "target_muscles": ["Quads", "Glutes", "Hamstrings"],
30
+ "equipment": "Bodyweight or Barbell",
31
+ "reps": 2,
32
+ "sets": 3,
33
+ "rest_time": "60 seconds",
34
+ "benefits": [
35
+ "Builds lower body strength",
36
+ "Improves mobility and balance"
37
+ ]
38
+ }
39
+ }
40
+
41
+ return exercises.get(exercise_type, {})
feedback/layout.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # feedback/layout.py
2
+
3
+ from feedback.indicators import draw_squat_indicators, draw_pushup_indicators, draw_hammercurl_indicators
4
+
5
+ def layout_indicators(frame, exercise_type, exercise_data):
6
+ if exercise_type == "squat":
7
+ counter, angle, stage = exercise_data
8
+ draw_squat_indicators(frame, counter, angle, stage)
9
+ elif exercise_type == "push_up":
10
+ counter, angle, stage = exercise_data
11
+ draw_pushup_indicators(frame, counter, angle, stage)
12
+ elif exercise_type == "hammer_curl":
13
+ (counter_right, angle_right, counter_left, angle_left,
14
+ warning_message_right, warning_message_left, progress_right, progress_left,stage_right,stage_left) = exercise_data
15
+ draw_hammercurl_indicators(frame, counter_right, angle_right, counter_left, angle_left, stage_right,stage_left)
16
+
main.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ from pose_estimation.estimation import PoseEstimator
3
+ from exercises.squat import Squat
4
+ from exercises.hammer_curl import HammerCurl
5
+ from exercises.push_up import PushUp
6
+ from feedback.layout import layout_indicators
7
+ from feedback.information import get_exercise_info
8
+ from utils.draw_text_with_background import draw_text_with_background
9
+
10
+ def main():
11
+ video_path = r"C:\Users\yakupzengin\Fitness-Trainer\data\squat.mp4"
12
+ video_path = r"C:\Users\yakupzengin\Fitness-Trainer\data\push_up.mp4"
13
+ video_path = r"C:\Users\yakupzengin\Fitness-Trainer\data\dumbel-workout.mp4"
14
+
15
+ exercise_type = "hammer_curl" # Egzersiz türünü belirleyin ("hammer_curl", "squat", "push_up")
16
+
17
+ cap = cv2.VideoCapture(0)
18
+ pose_estimator = PoseEstimator()
19
+
20
+ if exercise_type == "hammer_curl":
21
+ exercise = HammerCurl()
22
+ elif exercise_type == "squat":
23
+ exercise = Squat()
24
+ elif exercise_type == "push_up":
25
+ exercise = PushUp()
26
+ else:
27
+ print("Invalid exercise type.")
28
+ return
29
+
30
+ exercise_info = get_exercise_info(exercise_type)
31
+
32
+ fourcc = cv2.VideoWriter_fourcc(*'XVID')
33
+ output_file = r"C:\Users\yakupzengin\Fitness-Trainer\output\push-up.avi"
34
+ fps = cap.get(cv2.CAP_PROP_FPS)
35
+ frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
36
+ frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
37
+ out = cv2.VideoWriter(output_file, fourcc, fps, (frame_width, frame_height))
38
+
39
+ while cap.isOpened():
40
+ ret, frame = cap.read()
41
+ if not ret:
42
+ break
43
+
44
+ results = pose_estimator.estimate_pose(frame, exercise_type)
45
+ if results.pose_landmarks:
46
+ if exercise_type == "squat":
47
+ counter, angle, stage = exercise.track_squat(results.pose_landmarks.landmark, frame)
48
+ layout_indicators(frame, exercise_type, (counter, angle, stage))
49
+ elif exercise_type == "hammer_curl":
50
+ (counter_right, angle_right, counter_left, angle_left,
51
+ warning_message_right, warning_message_left, progress_right, progress_left, stage_right, stage_left) = exercise.track_hammer_curl(
52
+ results.pose_landmarks.landmark, frame)
53
+ layout_indicators(frame, exercise_type,
54
+ (counter_right, angle_right, counter_left, angle_left,
55
+ warning_message_right, warning_message_left, progress_right, progress_left, stage_right, stage_left))
56
+ elif exercise_type == "push_up":
57
+ counter, angle, stage = exercise.track_push_up(results.pose_landmarks.landmark, frame)
58
+ layout_indicators(frame, exercise_type, (counter, angle, stage))
59
+
60
+ draw_text_with_background(frame, f"Exercise: {exercise_info.get('name', 'N/A')}", (40, 50),
61
+ cv2.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255,), (118, 29, 14, 0.79), 1)
62
+ draw_text_with_background(frame, f"Reps: {exercise_info.get('reps', 0)}", (40, 80),
63
+ cv2.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255,), (118, 29, 14, 0.79), 1)
64
+ draw_text_with_background(frame, f"Sets: {exercise_info.get('sets', 0)}", (40, 110),
65
+ cv2.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255,), (118, 29, 14, 0.79),1 )
66
+
67
+ out.write(frame)
68
+
69
+ cv2.namedWindow(f"{exercise_type.replace('_', ' ').title()} Tracker", cv2.WINDOW_NORMAL)
70
+ cv2.resizeWindow(f"{exercise_type.replace('_', ' ').title()} Tracker", 1920, 1080)
71
+ cv2.imshow(f"{exercise_type.replace('_', ' ').title()} Tracker", frame)
72
+
73
+ if cv2.waitKey(10) & 0xFF == ord('q'):
74
+ break
75
+
76
+ cap.release()
77
+ out.release()
78
+ cv2.destroyAllWindows()
79
+
80
+
81
+ if __name__ == '__main__':
82
+ main()
output/images/Screenshot 2024-09-08 030742.png ADDED

Git LFS Details

  • SHA256: 926c32240058e63f8e15d3624043a9a5d0fe8ded3a5e320b343f25711e6a6aa8
  • Pointer size: 132 Bytes
  • Size of remote file: 1.15 MB
output/images/Screenshot 2024-09-08 030816.png ADDED

Git LFS Details

  • SHA256: adde99a2b45c93652ff22466fafd611db1d4670615f17ebdbb3fc4e732bec601
  • Pointer size: 132 Bytes
  • Size of remote file: 1.09 MB
output/images/Screenshot 2024-09-08 030836.png ADDED

Git LFS Details

  • SHA256: c1848c95377dbb47b481205b0e9bda4a992d9e0bfaadba8bee0d241fcd62dbe5
  • Pointer size: 132 Bytes
  • Size of remote file: 2.45 MB
pose_estimation/__pycache__/angle_calculation.cpython-311.pyc ADDED
Binary file (1.2 kB). View file
 
pose_estimation/__pycache__/angle_calculation.cpython-312.pyc ADDED
Binary file (1.04 kB). View file
 
pose_estimation/__pycache__/angle_calculation.cpython-39.pyc ADDED
Binary file (642 Bytes). View file
 
pose_estimation/__pycache__/estimation.cpython-311.pyc ADDED
Binary file (8.2 kB). View file
 
pose_estimation/__pycache__/estimation.cpython-312.pyc ADDED
Binary file (8.09 kB). View file
 
pose_estimation/__pycache__/estimation.cpython-39.pyc ADDED
Binary file (3.23 kB). View file
 
pose_estimation/angle_calculation.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+
3
+ def calculate_angle(a,b,c):
4
+ # abc npktaları [x,y,z] noktaları
5
+
6
+ ba = [a[0] - b[0], a[1] - b[1]]
7
+ bc = [c[0] - b[0], c[1] - b[1]]
8
+
9
+
10
+ # dot product (iç çarpım) hesapla: ba · bc
11
+ dot_product = ba[0] * bc[0] + ba[1] * bc[1]
12
+
13
+ # Vektörlerin büyüklüklerini hesapla
14
+ magnitude_ba = math.sqrt(ba[0] ** 2 + ba[1] ** 2)
15
+ magnitude_bc = math.sqrt(bc[0] ** 2 + bc[1] ** 2)
16
+
17
+ # Kosinüs açısını hesapla
18
+ cosine_angle = dot_product / (magnitude_ba * magnitude_bc)
19
+
20
+ # Açı hesapla ve dereceye çevir
21
+ angle = math.degrees(math.acos(cosine_angle))
22
+
23
+ return angle
pose_estimation/estimation.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import mediapipe as mp
3
+ from exercises.hammer_curl import HammerCurl
4
+
5
+ class PoseEstimator:
6
+ def __init__(self):
7
+ self.mp_pose = mp.solutions.pose
8
+ self.pose = self.mp_pose.Pose()
9
+ self.mp_drawing = mp.solutions.drawing_utils
10
+
11
+ def estimate_pose(self, frame, exercise_type):
12
+ # BGR to RGB
13
+ rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
14
+
15
+ # Pose estimate
16
+ results = self.pose.process(rgb_frame)
17
+
18
+ # Draw landmarks and specific connections based on exercise type
19
+ if results.pose_landmarks:
20
+ # Draw specific landmarks and connections based on exercise_type
21
+ if exercise_type == "squat":
22
+ self.draw_squat_lines(frame, results.pose_landmarks.landmark)
23
+ elif exercise_type == "push_up":
24
+ self.draw_push_up_lines(frame, results.pose_landmarks.landmark)
25
+ elif exercise_type == "hammer_curl":
26
+ self.draw_hammerl_curl_lines(frame, results.pose_landmarks.landmark)
27
+
28
+ return results
29
+ def draw_hammerl_curl_lines(self, frame, landmarks):
30
+
31
+ shoulder_right = [int(landmarks[11].x * frame.shape[1]), int(landmarks[11].y * frame.shape[0])]
32
+ elbow_right = [int(landmarks[13].x * frame.shape[1]), int(landmarks[13].y * frame.shape[0])]
33
+ hip_right = [int(landmarks[23].x * frame.shape[1]), int(landmarks[23].y * frame.shape[0])]
34
+ wrist_right = [int(landmarks[15].x * frame.shape[1]), int(landmarks[15].y * frame.shape[0])]
35
+
36
+ # Left arm landmarks (shoulder, elbow, hip, wrist)
37
+ shoulder_left = [int(landmarks[12].x * frame.shape[1]), int(landmarks[12].y * frame.shape[0])]
38
+ elbow_left = [int(landmarks[14].x * frame.shape[1]), int(landmarks[14].y * frame.shape[0])]
39
+ hip_left = [int(landmarks[24].x * frame.shape[1]), int(landmarks[24].y * frame.shape[0])]
40
+ wrist_left = [int(landmarks[16].x * frame.shape[1]), int(landmarks[16].y * frame.shape[0])]
41
+
42
+ # Draw lines with improved style
43
+ cv2.line(frame, shoulder_left, elbow_left, (0, 0, 255), 4,2)
44
+ cv2.line(frame, elbow_left, wrist_left, (0, 0, 255), 4,2)
45
+
46
+ cv2.line(frame, shoulder_right, elbow_right, (0, 0, 255), 4,2)
47
+ cv2.line(frame, elbow_right, wrist_right, (0, 0, 255), 4,2)
48
+
49
+
50
+
51
+ def draw_squat_lines(self, frame, landmarks):
52
+ # Squat specific lines (hip, knee, shoulder)
53
+ hip = [int(landmarks[23].x * frame.shape[1]), int(landmarks[23].y * frame.shape[0])]
54
+ knee = [int(landmarks[25].x * frame.shape[1]), int(landmarks[25].y * frame.shape[0])]
55
+ shoulder = [int(landmarks[11].x * frame.shape[1]), int(landmarks[11].y * frame.shape[0])]
56
+
57
+ hip_right = [int(landmarks[24].x * frame.shape[1]), int(landmarks[24].y * frame.shape[0])]
58
+ knee_right = [int(landmarks[26].x * frame.shape[1]), int(landmarks[26].y * frame.shape[0])]
59
+ shoulder_right = [int(landmarks[12].x * frame.shape[1]), int(landmarks[12].y * frame.shape[0])]
60
+
61
+ # Draw lines for squat
62
+ cv2.line(frame, shoulder, hip, (178, 102, 255), 2)
63
+ cv2.line(frame, hip, knee, (178, 102, 255), 2)
64
+ cv2.line(frame, shoulder_right, hip_right, (51, 153, 255), 2)
65
+ cv2.line(frame, hip_right, knee_right, (51, 153, 255), 2)
66
+
67
+ def draw_push_up_lines(self, frame, landmarks):
68
+ # Push-up specific lines (shoulder, elbow, wrist)
69
+ shoulder_left = [int(landmarks[11].x * frame.shape[1]), int(landmarks[11].y * frame.shape[0])]
70
+ elbow_left = [int(landmarks[13].x * frame.shape[1]), int(landmarks[13].y * frame.shape[0])]
71
+ wrist_left = [int(landmarks[15].x * frame.shape[1]), int(landmarks[15].y * frame.shape[0])]
72
+
73
+ shoulder_right = [int(landmarks[12].x * frame.shape[1]), int(landmarks[12].y * frame.shape[0])]
74
+ elbow_right = [int(landmarks[14].x * frame.shape[1]), int(landmarks[14].y * frame.shape[0])]
75
+ wrist_right = [int(landmarks[16].x * frame.shape[1]), int(landmarks[16].y * frame.shape[0])]
76
+
77
+ # Draw lines for push-up
78
+ cv2.line(frame, shoulder_left, elbow_left, (0, 0, 255), 2)
79
+ cv2.line(frame, elbow_left, wrist_left, (0, 0, 255), 2)
80
+ cv2.line(frame, shoulder_right, elbow_right, (102, 0, 0), 2)
81
+ cv2.line(frame, elbow_right, wrist_right, (102, 0, 0), 2)
requirements.txt ADDED
Binary file (1.4 kB). View file
 
static/css/dashboard.css ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .main-nav {
2
+ margin-top: 15px;
3
+ }
4
+
5
+ .main-nav ul {
6
+ display: flex;
7
+ list-style: none;
8
+ justify-content: center;
9
+ gap: 25px;
10
+ }
11
+
12
+ .nav-link {
13
+ text-decoration: none;
14
+ color: #555;
15
+ font-weight: 500;
16
+ padding: 8px 15px;
17
+ border-radius: 5px;
18
+ transition: all 0.3s;
19
+ }
20
+
21
+ .nav-link:hover {
22
+ color: #3498db;
23
+ background-color: rgba(52, 152, 219, 0.1);
24
+ }
25
+
26
+ .nav-link.active {
27
+ color: #3498db;
28
+ border-bottom: 2px solid #3498db;
29
+ }
30
+
31
+ .dashboard-content {
32
+ display: flex;
33
+ flex-direction: column;
34
+ gap: 30px;
35
+ }
36
+
37
+ .stats-summary {
38
+ display: flex;
39
+ flex-wrap: wrap;
40
+ gap: 20px;
41
+ justify-content: space-between;
42
+ }
43
+
44
+ .stat-card {
45
+ flex: 1 1 200px;
46
+ background-color: white;
47
+ border-radius: 10px;
48
+ padding: 20px;
49
+ text-align: center;
50
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
51
+ transition: transform 0.3s;
52
+ }
53
+
54
+ .stat-card:hover {
55
+ transform: translateY(-5px);
56
+ }
57
+
58
+ .stat-card h3 {
59
+ font-size: 1rem;
60
+ color: #777;
61
+ margin-bottom: 10px;
62
+ }
63
+
64
+ .stat-value {
65
+ font-size: 2.5rem;
66
+ font-weight: 700;
67
+ color: #3498db;
68
+ }
69
+
70
+ .charts-container {
71
+ display: flex;
72
+ flex-wrap: wrap;
73
+ gap: 20px;
74
+ }
75
+
76
+ .chart-card {
77
+ flex: 1 1 400px;
78
+ background-color: white;
79
+ border-radius: 10px;
80
+ padding: 20px;
81
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
82
+ }
83
+
84
+ .chart-card h3 {
85
+ font-size: 1.2rem;
86
+ color: #555;
87
+ margin-bottom: 15px;
88
+ text-align: center;
89
+ }
90
+
91
+ .recent-workouts {
92
+ background-color: white;
93
+ border-radius: 10px;
94
+ padding: 20px;
95
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
96
+ }
97
+
98
+ .recent-workouts h2 {
99
+ font-size: 1.5rem;
100
+ color: #3a3a3a;
101
+ margin-bottom: 15px;
102
+ }
103
+
104
+ .workout-table {
105
+ width: 100%;
106
+ border-collapse: collapse;
107
+ }
108
+
109
+ .workout-table th,
110
+ .workout-table td {
111
+ padding: 12px 15px;
112
+ text-align: left;
113
+ border-bottom: 1px solid #eee;
114
+ }
115
+
116
+ .workout-table th {
117
+ background-color: #f9f9f9;
118
+ color: #555;
119
+ font-weight: 500;
120
+ }
121
+
122
+ .workout-table tr:hover {
123
+ background-color: #f5f5f5;
124
+ }
125
+
126
+ @media (max-width: 768px) {
127
+ .stats-summary {
128
+ flex-direction: column;
129
+ }
130
+
131
+ .charts-container {
132
+ flex-direction: column;
133
+ }
134
+
135
+ .workout-table {
136
+ font-size: 0.9rem;
137
+ }
138
+ }
static/css/style.css ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ font-family: 'Roboto', sans-serif;
6
+ }
7
+
8
+ body {
9
+ background-color: #f5f5f5;
10
+ color: #333;
11
+ }
12
+
13
+ .container {
14
+ max-width: 1200px;
15
+ margin: 0 auto;
16
+ padding: 20px;
17
+ }
18
+
19
+ header {
20
+ text-align: center;
21
+ margin-bottom: 30px;
22
+ }
23
+
24
+ header h1 {
25
+ font-size: 2.5rem;
26
+ color: #3a3a3a;
27
+ margin-bottom: 10px;
28
+ }
29
+
30
+ header p {
31
+ font-size: 1.2rem;
32
+ color: #666;
33
+ }
34
+
35
+ .main-content {
36
+ display: flex;
37
+ flex-wrap: wrap;
38
+ gap: 20px;
39
+ }
40
+
41
+ .video-container {
42
+ flex: 1 1 600px;
43
+ border-radius: 10px;
44
+ overflow: hidden;
45
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
46
+ }
47
+
48
+ .video-container img {
49
+ width: 100%;
50
+ height: auto;
51
+ display: block;
52
+ }
53
+
54
+ .controls {
55
+ flex: 1 1 300px;
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: 20px;
59
+ }
60
+
61
+ .controls h2 {
62
+ font-size: 1.5rem;
63
+ margin-bottom: 15px;
64
+ color: #3a3a3a;
65
+ border-bottom: 2px solid #3498db;
66
+ padding-bottom: 5px;
67
+ }
68
+
69
+ .exercise-options {
70
+ display: flex;
71
+ gap: 10px;
72
+ flex-wrap: wrap;
73
+ }
74
+
75
+ .exercise-option {
76
+ flex: 1 1 calc(33% - 10px);
77
+ min-width: 80px;
78
+ background-color: white;
79
+ border-radius: 8px;
80
+ overflow: hidden;
81
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
82
+ transition:
83
+ transform 0.3s,
84
+ box-shadow 0.3s;
85
+ cursor: pointer;
86
+ text-align: center;
87
+ }
88
+
89
+ .exercise-option:hover {
90
+ transform: translateY(-5px);
91
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
92
+ }
93
+
94
+ .exercise-option.selected {
95
+ border: 2px solid #3498db;
96
+ transform: translateY(-5px);
97
+ }
98
+
99
+ .exercise-option img {
100
+ width: 100%;
101
+ height: 100px;
102
+ object-fit: cover;
103
+ }
104
+
105
+ .exercise-option h3 {
106
+ padding: 10px;
107
+ font-size: 1rem;
108
+ }
109
+
110
+ .config-inputs {
111
+ display: flex;
112
+ gap: 15px;
113
+ }
114
+
115
+ .input-group {
116
+ flex: 1;
117
+ }
118
+
119
+ .input-group label {
120
+ display: block;
121
+ margin-bottom: 5px;
122
+ font-weight: 500;
123
+ }
124
+
125
+ .input-group input {
126
+ width: 100%;
127
+ padding: 8px 12px;
128
+ border: 1px solid #ddd;
129
+ border-radius: 5px;
130
+ font-size: 1rem;
131
+ }
132
+
133
+ .action-buttons {
134
+ display: flex;
135
+ gap: 10px;
136
+ }
137
+
138
+ .btn {
139
+ flex: 1;
140
+ padding: 12px 20px;
141
+ border: none;
142
+ border-radius: 5px;
143
+ font-size: 1rem;
144
+ font-weight: 500;
145
+ cursor: pointer;
146
+ transition: background-color 0.3s;
147
+ }
148
+
149
+ .primary-btn {
150
+ background-color: #3498db;
151
+ color: white;
152
+ }
153
+
154
+ .primary-btn:hover {
155
+ background-color: #2980b9;
156
+ }
157
+
158
+ .secondary-btn {
159
+ background-color: #e74c3c;
160
+ color: white;
161
+ }
162
+
163
+ .secondary-btn:hover {
164
+ background-color: #c0392b;
165
+ }
166
+
167
+ .btn:disabled {
168
+ background-color: #ccc;
169
+ cursor: not-allowed;
170
+ }
171
+
172
+ .workout-status {
173
+ background-color: white;
174
+ border-radius: 8px;
175
+ padding: 15px;
176
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
177
+ }
178
+
179
+ .status-display {
180
+ display: flex;
181
+ flex-direction: column;
182
+ gap: 10px;
183
+ }
184
+
185
+ .status-item {
186
+ display: flex;
187
+ justify-content: space-between;
188
+ padding: 5px 0;
189
+ border-bottom: 1px solid #eee;
190
+ }
191
+
192
+ .status-label {
193
+ font-weight: 500;
194
+ color: #555;
195
+ }
196
+
197
+ .status-value {
198
+ font-weight: 700;
199
+ color: #3498db;
200
+ }
201
+
202
+ @media (max-width: 768px) {
203
+ .main-content {
204
+ flex-direction: column;
205
+ }
206
+
207
+ .config-inputs {
208
+ flex-direction: column;
209
+ }
210
+ }
static/images/README.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ This directory should contain the following images:
2
+ - squat.png
3
+ - push_up.png
4
+ - hammer_curl.png
5
+
6
+ These images will be used as icons on the exercise selection page.
7
+ You can use any appropriate images for these exercises.
static/images/hammer_curl.png ADDED