akarsh999 commited on
Commit
0fe4c49
Β·
verified Β·
1 Parent(s): 29fa7a0

Upload 9 files

Browse files
Files changed (5) hide show
  1. README.md +49 -15
  2. app.py +280 -270
  3. athletic_performance.py +573 -0
  4. deploy_hf.py +17 -4
  5. requirements.txt +2 -1
README.md CHANGED
@@ -18,10 +18,12 @@ A powerful web application that analyzes athletic jump performance from videos u
18
 
19
  - **πŸŽ₯ YouTube Integration**: Analyze videos directly from YouTube URLs
20
  - **πŸ“ File Upload**: Support for MP4, AVI, MOV, and other video formats
21
- - **πŸ“Š Detailed Analytics**: Get jump height, flight time, normalized rise, and performance insights
 
22
  - **🎯 Real-time Processing**: Fast analysis using Google's MediaPipe pose estimation
23
- - **πŸ“± Modern Interface**: Beautiful, responsive Gradio interface
24
- - **πŸ”¬ Scientific Accuracy**: Precise biomechanical analysis
 
25
 
26
  ## πŸš€ Live Demo
27
 
@@ -52,7 +54,12 @@ Try the live demo on Hugging Face Spaces: [Athletic Ability Analysis](https://hu
52
  2. **Create a new Space** on [Hugging Face Spaces](https://huggingface.co/spaces)
53
  3. **Connect your GitHub repo** to the Space
54
  4. **Set the Space type** to "Gradio"
55
- 5. **Wait for automatic deployment**
 
 
 
 
 
56
 
57
  ### Manual Deployment
58
 
@@ -66,19 +73,37 @@ Try the live demo on Hugging Face Spaces: [Athletic Ability Analysis](https://hu
66
 
67
  3. **Upload files** to your Space:
68
  - `app.py` (main application)
 
69
  - `requirements.txt` (dependencies)
70
  - `README.md` (this file)
71
 
72
- 4. **Space will automatically deploy** using Gradio
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
  ## πŸ“ Project Structure
75
 
76
  ```
77
  athletic-ability-analysis/
78
- β”œβ”€β”€ app.py # Main Gradio application
79
- β”œβ”€β”€ requirements.txt # Python dependencies
80
- β”œβ”€β”€ README.md # This file (with HF Spaces header)
81
- └── .gitignore # Git ignore file
 
 
 
82
  ```
83
 
84
  ## 🎯 Usage
@@ -86,12 +111,21 @@ athletic-ability-analysis/
86
  ### Web Interface
87
 
88
  1. **Visit your Hugging Face Space URL**
89
- 2. **Enter your height** in centimeters for accurate calculations
90
- 3. **Choose input method**:
91
- - **YouTube**: Paste a YouTube URL containing a jump video
92
- - **File Upload**: Upload a video file from your device
93
- 4. **Click "Analyze"** and wait for processing
94
- 5. **View detailed results** including metrics and performance insights
 
 
 
 
 
 
 
 
 
95
 
96
  ### Supported Video Formats
97
 
 
18
 
19
  - **πŸŽ₯ YouTube Integration**: Analyze videos directly from YouTube URLs
20
  - **πŸ“ File Upload**: Support for MP4, AVI, MOV, and other video formats
21
+ - **πŸ“Š Comprehensive Biomechanical Analysis**: Jump height, flight time, peak power, force development, and more
22
+ - **πŸ€– AI Sports Coach**: Get personalized sport recommendations and technique improvements
23
  - **🎯 Real-time Processing**: Fast analysis using Google's MediaPipe pose estimation
24
+ - **πŸ“± Modern Interface**: Beautiful, responsive Gradio interface with multiple analysis modes
25
+ - **πŸ”¬ Scientific Accuracy**: Professional-grade biomechanical analysis
26
+ - **⚑ Advanced Metrics**: Peak power output, rate of force development, impulse, and ground contact time
27
 
28
  ## πŸš€ Live Demo
29
 
 
54
  2. **Create a new Space** on [Hugging Face Spaces](https://huggingface.co/spaces)
55
  3. **Connect your GitHub repo** to the Space
56
  4. **Set the Space type** to "Gradio"
57
+ 5. **⚠️ IMPORTANT: Set up API Key Environment Variable**:
58
+ - Go to your Space's "Settings" tab
59
+ - Add a new "Secret" with name: `GEMINI_API_KEY`
60
+ - Add your Gemini API key as the value
61
+ - This keeps your API key secure and private
62
+ 6. **Wait for automatic deployment**
63
 
64
  ### Manual Deployment
65
 
 
73
 
74
  3. **Upload files** to your Space:
75
  - `app.py` (main application)
76
+ - `athletic_performance.py` (analysis module)
77
  - `requirements.txt` (dependencies)
78
  - `README.md` (this file)
79
 
80
+ 4. **πŸ” Set up Secure API Key**:
81
+ - In your Space settings, add environment variable: `GEMINI_API_KEY`
82
+ - Get your free API key from [Google AI Studio](https://aistudio.google.com/app/apikey)
83
+ - **NEVER commit API keys to your repository!**
84
+
85
+ 5. **Space will automatically deploy** using Gradio
86
+
87
+ ### πŸ” API Key Security
88
+
89
+ For the AI Sports Coach feature, you need a Google Gemini API key:
90
+
91
+ - **πŸ†“ Free**: Get your key at [Google AI Studio](https://aistudio.google.com/app/apikey)
92
+ - **πŸ”’ Secure**: Set as environment variable `GEMINI_API_KEY` in HF Spaces
93
+ - **🚫 Never**: Commit API keys to code repositories
94
+ - **βœ… Best Practice**: Use HF Spaces secrets for deployment
95
 
96
  ## πŸ“ Project Structure
97
 
98
  ```
99
  athletic-ability-analysis/
100
+ β”œβ”€β”€ app.py # Main Gradio application & UI
101
+ β”œβ”€β”€ athletic_performance.py # Core analysis & AI integration
102
+ β”œβ”€β”€ requirements.txt # Python dependencies
103
+ β”œβ”€β”€ README.md # This file (with HF Spaces header)
104
+ β”œβ”€β”€ deploy_hf.py # Deployment helper script
105
+ β”œβ”€β”€ test_deployment.py # Dependency testing
106
+ └── .gitignore # Git ignore file
107
  ```
108
 
109
  ## 🎯 Usage
 
111
  ### Web Interface
112
 
113
  1. **Visit your Hugging Face Space URL**
114
+ 2. **Enter your height and weight** for accurate biomechanical calculations
115
+ 3. **Choose your analysis type**:
116
+
117
+ **πŸ“Š Standard Analysis:**
118
+ - YouTube or File Upload tabs
119
+ - Get comprehensive biomechanical metrics
120
+
121
+ **πŸ€– AI Sports Coach:**
122
+ - Select your gender
123
+ - Provide video (YouTube URL or upload)
124
+ - Get personalized sport recommendations
125
+ - Receive jump technique improvement suggestions
126
+
127
+ 4. **Click analyze** and wait for processing
128
+ 5. **View comprehensive results** with detailed insights
129
 
130
  ### Supported Video Formats
131
 
app.py CHANGED
@@ -1,328 +1,281 @@
1
- import cv2
2
- import numpy as np
3
- import mediapipe as mp
4
- from collections import deque
5
- from pathlib import Path
6
- import json
7
- import tempfile
8
- import os
9
- import yt_dlp
10
  import gradio as gr
11
  import pandas as pd
 
 
12
 
13
- LHIP, RHIP = 23, 24
14
- POSE_CONNECTIONS = mp.solutions.pose.POSE_CONNECTIONS
15
-
16
- def smooth_moving_avg(series, k=5):
17
- """Simple causal moving average; ignores None values."""
18
- out = []
19
- q = deque()
20
- s = 0.0
21
- cnt = 0
22
- for v in series:
23
- if v is not None:
24
- q.append(v); s += v; cnt += 1
25
- else:
26
- q.append(None)
27
- if len(q) > k:
28
- old = q.popleft()
29
- if old is not None:
30
- s -= old; cnt -= 1
31
- out.append((s / max(cnt, 1)) if cnt > 0 else None)
32
- return out
33
-
34
- def estimate_jump_metrics(hip_y_series, fps):
35
- """Return jump_height_norm (0..1), flight_time_s using hip trajectory."""
36
- # Remove None
37
- hip = [h for h in hip_y_series if h is not None]
38
- if len(hip) < 3:
39
- return None, None
40
-
41
- # Smooth
42
- hip = smooth_moving_avg(hip, k=5)
43
-
44
- # Jump height (normalized): deepest crouch (max y) to apex (min y)
45
- min_y = min(hip) # apex (body highest)
46
- max_y = max(hip) # deepest crouch (body lowest)
47
- jump_height_norm = max(0.0, (max_y - min_y))
48
-
49
- # Flight time heuristic using vertical velocity pattern
50
- hip_arr = np.array(hip, dtype=float)
51
- vel = np.diff(hip_arr)
52
- if vel.size == 0:
53
- flight_time_s = 0.0
54
- else:
55
- takeoff_idx = int(np.argmin(vel)) # most negative velocity
56
- landing_idx = int(np.argmax(vel)) # most positive velocity
57
- flight_frames = max(0, landing_idx - takeoff_idx)
58
- flight_time_s = flight_frames / float(fps or 30.0)
59
-
60
- return jump_height_norm, flight_time_s
61
-
62
- def download_youtube_video(youtube_url, output_path):
63
- """Download YouTube video to specified path."""
64
- ydl_opts = {
65
- 'format': 'best[height<=720]', # Limit quality for faster processing
66
- 'outtmpl': output_path,
67
- 'quiet': True,
68
- 'no_warnings': True,
69
- }
70
 
71
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
72
- ydl.download([youtube_url])
73
- return output_path
74
-
75
- def process_video_analysis(video_path, user_height_cm, progress_callback=None):
76
- """Core video analysis function with progress tracking."""
77
- cap = cv2.VideoCapture(video_path)
78
- if not cap.isOpened():
79
- raise Exception(f"Could not open video: {video_path}")
80
-
81
- w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
82
- h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
83
- fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
84
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
85
-
86
- mp_pose = mp.solutions.pose
87
- pose = mp_pose.Pose(static_image_mode=False, model_complexity=1, enable_segmentation=False)
88
-
89
- hip_y_series = []
90
- frame_idx = 0
91
-
92
- print(f"Processing video: {Path(video_path).name}")
93
- print(f"Video dimensions: {w}x{h}, FPS: {fps}, Total frames: {total_frames}")
94
-
95
- ok, frame = cap.read()
96
- while ok:
97
- rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
98
- res = pose.process(rgb)
99
-
100
- if res.pose_landmarks:
101
- lms = res.pose_landmarks.landmark
102
- mid_hip_y = (lms[LHIP].y + lms[RHIP].y) / 2.0
103
- hip_y_series.append(float(mid_hip_y))
104
- else:
105
- hip_y_series.append(None)
106
-
107
- frame_idx += 1
108
-
109
- # Update progress
110
- if progress_callback and total_frames > 0:
111
- progress = min(frame_idx / total_frames, 1.0)
112
- progress_callback(progress, f"Processing frame {frame_idx}/{total_frames}")
113
-
114
- ok, frame = cap.read()
115
-
116
- cap.release()
117
- print(f"Completed processing {frame_idx} frames")
118
 
119
- jump_norm, flight_time_s = estimate_jump_metrics(hip_y_series, fps)
 
 
 
120
 
121
- if jump_norm is None:
122
- return None
 
 
123
 
124
- jump_height_cm = jump_norm * user_height_cm
 
 
 
125
 
126
- return {
127
- "video": Path(video_path).name,
128
- "frames": len(hip_y_series),
129
- "fps": fps,
130
- "jump_height_cm": jump_height_cm,
131
- "normalized_rise": jump_norm,
132
- "flight_time_s": flight_time_s
133
- }
134
 
135
- def analyze_jump_from_youtube(youtube_url, user_height_cm, progress=gr.Progress()):
136
- """Main analysis function for Gradio interface."""
137
 
138
- # Validate inputs
139
- if not youtube_url or not youtube_url.strip():
140
- return "❌ Please provide a YouTube URL", None, None
 
141
 
142
- if not user_height_cm or user_height_cm <= 0:
143
- return "❌ Please provide a valid height in centimeters", None, None
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
- try:
146
- progress(0.1, desc="Validating YouTube URL...")
147
-
148
- # Validate YouTube URL
149
- youtube_url = youtube_url.strip()
150
- if not any(domain in youtube_url for domain in ['youtube.com', 'youtu.be']):
151
- return "❌ Please provide a valid YouTube URL", None, None
152
-
153
- # Create temporary directory for processing
154
- with tempfile.TemporaryDirectory() as temp_dir:
155
- progress(0.2, desc="Downloading video from YouTube...")
156
-
157
- # Download video
158
- video_filename = os.path.join(temp_dir, 'video.%(ext)s')
159
- try:
160
- download_youtube_video(youtube_url, video_filename)
161
- # Find the actual downloaded file
162
- video_files = [f for f in os.listdir(temp_dir) if f.startswith('video.')]
163
- if not video_files:
164
- return "❌ Failed to download YouTube video. Please check the URL and try again.", None, None
165
- video_path = os.path.join(temp_dir, video_files[0])
166
- except Exception as e:
167
- return f"❌ Failed to download YouTube video: {str(e)}", None, None
168
 
169
- progress(0.3, desc="Starting video analysis...")
170
-
171
- # Process the video with progress tracking
172
- def update_progress(prog, desc):
173
- progress(0.3 + (prog * 0.6), desc=desc)
174
-
175
- result = process_video_analysis(video_path, user_height_cm, update_progress)
176
-
177
- progress(0.9, desc="Generating results...")
178
-
179
- if result is None:
180
- return "⚠️ Could not analyze jump. Make sure the video shows a person clearly performing a vertical jump.", None, None
181
-
182
- # Format results for display
183
- results_text = f"""
184
- ## πŸŽ‰ Jump Analysis Results
 
 
 
 
 
185
 
186
- ### πŸ“Š Performance Metrics
187
  - **Jump Height**: {result['jump_height_cm']:.2f} cm
188
  - **Flight Time**: {result['flight_time_s']:.3f} seconds
189
  - **Normalized Rise**: {result['normalized_rise']:.3f} ({result['normalized_rise']*100:.1f}%)
190
 
 
 
 
 
 
 
 
 
 
 
191
  ### πŸ“Ή Video Information
192
  - **Total Frames**: {result['frames']}
193
  - **Frame Rate**: {result['fps']:.2f} FPS
194
  - **Video File**: {result['video']}
 
195
 
196
  ### πŸ“ˆ Performance Insights
197
  """
198
-
199
- # Add performance insights
200
- if result['jump_height_cm'] > 60:
201
- results_text += "πŸ”₯ **Excellent jump height!** This is above average performance.\n"
202
- elif result['jump_height_cm'] > 40:
203
- results_text += "πŸ‘ **Good jump height!** Solid athletic performance.\n"
204
- elif result['jump_height_cm'] > 25:
205
- results_text += "πŸ“ˆ **Moderate jump height.** Room for improvement with training.\n"
206
- else:
207
- results_text += "🎯 **Starting point identified.** Focus on technique and strength training.\n"
208
-
209
- if result['flight_time_s'] > 0.5:
210
- results_text += "⏱️ **Great flight time!** Shows good explosive power.\n"
211
- elif result['flight_time_s'] > 0.3:
212
- results_text += "⏱️ **Decent flight time.** Good coordination.\n"
213
-
214
- # Create a results dataframe for the table
215
- results_df = pd.DataFrame([
216
- ["Jump Height", f"{result['jump_height_cm']:.2f} cm"],
217
- ["Flight Time", f"{result['flight_time_s']:.3f} seconds"],
218
- ["Normalized Rise", f"{result['normalized_rise']*100:.1f}%"],
219
- ["Video Frames", f"{result['frames']}"],
220
- ["Frame Rate", f"{result['fps']:.2f} FPS"],
221
- ], columns=["Metric", "Value"])
222
-
223
- progress(1.0, desc="Analysis complete!")
224
-
225
- return results_text, results_df, "βœ… Analysis completed successfully!"
226
-
227
- except Exception as e:
228
- return f"❌ Error during analysis: {str(e)}", None, None
229
 
230
- def analyze_jump_from_file(video_file, user_height_cm, progress=gr.Progress()):
231
- """Analysis function for uploaded video files."""
232
 
233
  # Validate inputs
234
- if video_file is None:
235
- return "❌ Please upload a video file", None, None
 
 
 
236
 
237
  if not user_height_cm or user_height_cm <= 0:
238
- return "❌ Please provide a valid height in centimeters", None, None
 
 
 
 
 
 
 
 
 
 
 
239
 
240
  try:
241
- progress(0.1, desc="Processing uploaded video...")
 
242
 
243
- # Process the video with progress tracking
244
- def update_progress(prog, desc):
245
- progress(0.1 + (prog * 0.8), desc=desc)
246
 
247
- result = process_video_analysis(video_file.name, user_height_cm, update_progress)
 
 
 
 
248
 
249
- progress(0.9, desc="Generating results...")
 
 
250
 
251
  if result is None:
252
- return "⚠️ Could not analyze jump. Make sure the video shows a person clearly performing a vertical jump.", None, None
253
 
254
- # Format results (same as YouTube function)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  results_text = f"""
256
- ## πŸŽ‰ Jump Analysis Results
257
 
258
- ### πŸ“Š Performance Metrics
259
  - **Jump Height**: {result['jump_height_cm']:.2f} cm
260
- - **Flight Time**: {result['flight_time_s']:.3f} seconds
261
- - **Normalized Rise**: {result['normalized_rise']:.3f} ({result['normalized_rise']*100:.1f}%)
 
 
 
262
 
263
- ### πŸ“Ή Video Information
264
- - **Total Frames**: {result['frames']}
265
- - **Frame Rate**: {result['fps']:.2f} FPS
266
- - **Video File**: {result['video']}
267
 
268
- ### πŸ“ˆ Performance Insights
 
 
 
269
  """
270
 
271
- # Add performance insights
272
- if result['jump_height_cm'] > 60:
273
- results_text += "πŸ”₯ **Excellent jump height!** This is above average performance.\n"
274
- elif result['jump_height_cm'] > 40:
275
- results_text += "πŸ‘ **Good jump height!** Solid athletic performance.\n"
276
- elif result['jump_height_cm'] > 25:
277
- results_text += "πŸ“ˆ **Moderate jump height.** Room for improvement with training.\n"
278
- else:
279
- results_text += "🎯 **Starting point identified.** Focus on technique and strength training.\n"
280
-
281
- if result['flight_time_s'] > 0.5:
282
- results_text += "⏱️ **Great flight time!** Shows good explosive power.\n"
283
- elif result['flight_time_s'] > 0.3:
284
- results_text += "⏱️ **Decent flight time.** Good coordination.\n"
285
-
286
- # Create a results dataframe for the table
287
- results_df = pd.DataFrame([
288
  ["Jump Height", f"{result['jump_height_cm']:.2f} cm"],
 
289
  ["Flight Time", f"{result['flight_time_s']:.3f} seconds"],
290
- ["Normalized Rise", f"{result['normalized_rise']*100:.1f}%"],
291
- ["Video Frames", f"{result['frames']}"],
292
- ["Frame Rate", f"{result['fps']:.2f} FPS"],
 
 
293
  ], columns=["Metric", "Value"])
294
 
295
- progress(1.0, desc="Analysis complete!")
296
 
297
- return results_text, results_df, "βœ… Analysis completed successfully!"
298
 
299
  except Exception as e:
300
- return f"❌ Error during analysis: {str(e)}", None, None
301
 
302
  # Create Gradio interface
303
  def create_interface():
304
  with gr.Blocks(title="πŸƒβ€β™‚οΈ Athletic Ability Analysis") as app:
305
  gr.Markdown("""
306
- # πŸƒβ€β™‚οΈ Athletic Ability Analysis
 
 
 
307
 
308
- Analyze jumping performance from videos using computer vision and pose estimation.
309
- Upload a video or provide a YouTube URL to get detailed metrics about jump height, flight time, and athletic performance.
 
 
310
 
311
  ## πŸ“‹ Instructions
312
- 1. Enter your height in centimeters
313
- 2. Choose either YouTube URL or file upload
314
- 3. Wait for the analysis to complete
315
- 4. View your detailed jump performance results
316
  """)
317
 
318
  with gr.Row():
319
- user_height = gr.Number(
320
- label="Your Height (cm)",
321
- value=175,
322
- minimum=100,
323
- maximum=250
324
- )
325
- gr.Markdown("πŸ’‘ *Enter your height in centimeters for accurate jump height calculation*")
 
 
 
 
 
 
 
 
326
 
327
  with gr.Tabs():
328
  # YouTube URL Tab
@@ -342,6 +295,53 @@ def create_interface():
342
  file_types=[".mp4", ".avi", ".mov", ".mkv", ".webm"]
343
  )
344
  file_btn = gr.Button("πŸš€ Analyze Uploaded Video", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
 
346
  # Results section
347
  gr.Markdown("## πŸ“Š Analysis Results")
@@ -375,31 +375,41 @@ def create_interface():
375
 
376
  1. **Pose Detection**: Uses Google's MediaPipe to detect human pose landmarks
377
  2. **Hip Tracking**: Tracks the midpoint between left and right hip joints
378
- 3. **Jump Analysis**: Calculates metrics based on hip trajectory:
379
- - Jump height relative to your body size
380
- - Flight time during the airborne phase
381
- - Normalized rise showing jump efficiency
 
 
 
 
382
  """)
383
 
384
  # Event handlers
385
  youtube_btn.click(
386
  fn=analyze_jump_from_youtube,
387
- inputs=[youtube_url, user_height],
388
  outputs=[results_text, results_table, status_message]
389
  )
390
 
391
  file_btn.click(
392
  fn=analyze_jump_from_file,
393
- inputs=[video_file, user_height],
 
 
 
 
 
 
394
  outputs=[results_text, results_table, status_message]
395
  )
396
 
397
  # Example section
398
  gr.Examples(
399
  examples=[
400
- ["https://www.youtube.com/watch?v=dQw4w9WgXcQ", 175], # This is just a placeholder
401
  ],
402
- inputs=[youtube_url, user_height],
403
  label="πŸ“š Example (Replace with actual jump video URLs)"
404
  )
405
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
  import pandas as pd
3
+ import os
4
+ from athletic_performance import analyze_youtube_video, analyze_video_file, get_performance_insights, get_ai_sports_coaching_analysis
5
 
6
+ def analyze_jump_from_youtube(youtube_url, user_height_cm, user_weight_kg, progress=gr.Progress()):
7
+ """Main analysis function for Gradio interface."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ # Create progress callback for the athletic_performance module
10
+ def progress_callback(prog, desc):
11
+ progress(prog, desc=desc)
12
+
13
+ # Call the core analysis function
14
+ result = analyze_youtube_video(youtube_url, user_height_cm, user_weight_kg, progress_callback)
15
+
16
+ # Handle errors
17
+ if "error" in result:
18
+ return f"❌ {result['error']}", None, None
19
+
20
+ if result is None:
21
+ return "⚠️ Could not analyze jump. Make sure the video shows a person clearly performing a vertical jump.", None, None
22
+
23
+ # Format results for display
24
+ results_text = f"""
25
+ ## πŸŽ‰ Comprehensive Jump Analysis Results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ ### πŸ“Š Core Performance Metrics
28
+ - **Jump Height**: {result['jump_height_cm']:.2f} cm
29
+ - **Flight Time**: {result['flight_time_s']:.3f} seconds
30
+ - **Normalized Rise**: {result['normalized_rise']:.3f} ({result['normalized_rise']*100:.1f}%)
31
 
32
+ ### ⚑ Power & Force Metrics
33
+ - **Peak Power Output**: {result.get('peak_power_watts', 0):.0f} watts
34
+ - **Peak Force**: {result.get('peak_force_n', 0):.0f} N
35
+ - **Impulse**: {result.get('impulse_ns', 0):.2f} Nβ‹…s
36
 
37
+ ### πŸš€ Explosiveness Metrics
38
+ - **Rate of Force Development**: {result.get('rate_of_force_development', 0):.2f}
39
+ - **Takeoff Phase Duration**: {result.get('takeoff_phase_duration_s', 0):.3f} seconds
40
+ - **Ground Contact Time**: {result.get('ground_contact_time_s', 0):.3f} seconds
41
 
42
+ ### πŸ“Ή Video Information
43
+ - **Total Frames**: {result['frames']}
44
+ - **Frame Rate**: {result['fps']:.2f} FPS
45
+ - **Video File**: {result['video']}
46
+ - **Subject Weight**: {result.get('user_weight_kg', 'N/A')} kg
 
 
 
47
 
48
+ ### πŸ“ˆ Performance Insights
49
+ """
50
 
51
+ # Add performance insights using the new function
52
+ insights = get_performance_insights(result)
53
+ for insight in insights:
54
+ results_text += f"{insight}\n"
55
 
56
+ # Create a comprehensive results dataframe for the table
57
+ results_df = pd.DataFrame([
58
+ ["Jump Height", f"{result['jump_height_cm']:.2f} cm"],
59
+ ["Flight Time", f"{result['flight_time_s']:.3f} seconds"],
60
+ ["Peak Power", f"{result.get('peak_power_watts', 0):.0f} watts"],
61
+ ["Peak Force", f"{result.get('peak_force_n', 0):.0f} N"],
62
+ ["Rate of Force Development", f"{result.get('rate_of_force_development', 0):.2f}"],
63
+ ["Ground Contact Time", f"{result.get('ground_contact_time_s', 0):.3f} seconds"],
64
+ ["Impulse", f"{result.get('impulse_ns', 0):.2f} Nβ‹…s"],
65
+ ["Takeoff Duration", f"{result.get('takeoff_phase_duration_s', 0):.3f} seconds"],
66
+ ["Normalized Rise", f"{result['normalized_rise']*100:.1f}%"],
67
+ ["Video Frames", f"{result['frames']}"],
68
+ ["Frame Rate", f"{result['fps']:.2f} FPS"],
69
+ ], columns=["Metric", "Value"])
70
 
71
+ return results_text, results_df, "βœ… Analysis completed successfully!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
+ def analyze_jump_from_file(video_file, user_height_cm, user_weight_kg, progress=gr.Progress()):
74
+ """Analysis function for uploaded video files."""
75
+
76
+ # Create progress callback for the athletic_performance module
77
+ def progress_callback(prog, desc):
78
+ progress(prog, desc=desc)
79
+
80
+ # Call the core analysis function
81
+ video_path = video_file.name if video_file else None
82
+ result = analyze_video_file(video_path, user_height_cm, user_weight_kg, progress_callback)
83
+
84
+ # Handle errors
85
+ if "error" in result:
86
+ return f"❌ {result['error']}", None, None
87
+
88
+ if result is None:
89
+ return "⚠️ Could not analyze jump. Make sure the video shows a person clearly performing a vertical jump.", None, None
90
+
91
+ # Format results (same as YouTube function)
92
+ results_text = f"""
93
+ ## πŸŽ‰ Comprehensive Jump Analysis Results
94
 
95
+ ### πŸ“Š Core Performance Metrics
96
  - **Jump Height**: {result['jump_height_cm']:.2f} cm
97
  - **Flight Time**: {result['flight_time_s']:.3f} seconds
98
  - **Normalized Rise**: {result['normalized_rise']:.3f} ({result['normalized_rise']*100:.1f}%)
99
 
100
+ ### ⚑ Power & Force Metrics
101
+ - **Peak Power Output**: {result.get('peak_power_watts', 0):.0f} watts
102
+ - **Peak Force**: {result.get('peak_force_n', 0):.0f} N
103
+ - **Impulse**: {result.get('impulse_ns', 0):.2f} Nβ‹…s
104
+
105
+ ### πŸš€ Explosiveness Metrics
106
+ - **Rate of Force Development**: {result.get('rate_of_force_development', 0):.2f}
107
+ - **Takeoff Phase Duration**: {result.get('takeoff_phase_duration_s', 0):.3f} seconds
108
+ - **Ground Contact Time**: {result.get('ground_contact_time_s', 0):.3f} seconds
109
+
110
  ### πŸ“Ή Video Information
111
  - **Total Frames**: {result['frames']}
112
  - **Frame Rate**: {result['fps']:.2f} FPS
113
  - **Video File**: {result['video']}
114
+ - **Subject Weight**: {result.get('user_weight_kg', 'N/A')} kg
115
 
116
  ### πŸ“ˆ Performance Insights
117
  """
118
+
119
+ # Add performance insights using the new function
120
+ insights = get_performance_insights(result)
121
+ for insight in insights:
122
+ results_text += f"{insight}\n"
123
+
124
+ # Create a comprehensive results dataframe for the table
125
+ results_df = pd.DataFrame([
126
+ ["Jump Height", f"{result['jump_height_cm']:.2f} cm"],
127
+ ["Flight Time", f"{result['flight_time_s']:.3f} seconds"],
128
+ ["Peak Power", f"{result.get('peak_power_watts', 0):.0f} watts"],
129
+ ["Peak Force", f"{result.get('peak_force_n', 0):.0f} N"],
130
+ ["Rate of Force Development", f"{result.get('rate_of_force_development', 0):.2f}"],
131
+ ["Ground Contact Time", f"{result.get('ground_contact_time_s', 0):.3f} seconds"],
132
+ ["Impulse", f"{result.get('impulse_ns', 0):.2f} Nβ‹…s"],
133
+ ["Takeoff Duration", f"{result.get('takeoff_phase_duration_s', 0):.3f} seconds"],
134
+ ["Normalized Rise", f"{result['normalized_rise']*100:.1f}%"],
135
+ ["Video Frames", f"{result['frames']}"],
136
+ ["Frame Rate", f"{result['fps']:.2f} FPS"],
137
+ ], columns=["Metric", "Value"])
138
+
139
+ return results_text, results_df, "βœ… Analysis completed successfully!"
 
 
 
 
 
 
 
 
 
140
 
141
+ def get_ai_coaching_recommendations(youtube_url, video_file, user_height_cm, user_weight_kg, gender, gemini_api_key, progress=gr.Progress()):
142
+ """Get AI-powered sports coaching recommendations."""
143
 
144
  # Validate inputs
145
+ if not gemini_api_key or not gemini_api_key.strip():
146
+ return "❌ Please provide your Gemini API key", None, None
147
+
148
+ if not gender:
149
+ return "❌ Please select your gender", None, None
150
 
151
  if not user_height_cm or user_height_cm <= 0:
152
+ return "❌ Please provide a valid height", None, None
153
+
154
+ # Determine which video source to use
155
+ video_source = None
156
+ if youtube_url and youtube_url.strip():
157
+ video_source = "youtube"
158
+ progress(0.1, desc="Analyzing YouTube video...")
159
+ elif video_file:
160
+ video_source = "file"
161
+ progress(0.1, desc="Analyzing uploaded video...")
162
+ else:
163
+ return "❌ Please provide either a YouTube URL or upload a video file", None, None
164
 
165
  try:
166
+ # First, get the jump analysis
167
+ progress(0.2, desc="Performing biomechanical analysis...")
168
 
169
+ def progress_callback(prog, desc):
170
+ progress(0.2 + (prog * 0.5), desc=desc)
 
171
 
172
+ if video_source == "youtube":
173
+ result = analyze_youtube_video(youtube_url, user_height_cm, user_weight_kg, progress_callback)
174
+ else:
175
+ video_path = video_file.name if video_file else None
176
+ result = analyze_video_file(video_path, user_height_cm, user_weight_kg, progress_callback)
177
 
178
+ # Handle analysis errors
179
+ if "error" in result:
180
+ return f"❌ Video analysis failed: {result['error']}", None, None
181
 
182
  if result is None:
183
+ return "❌ Could not analyze jump. Please ensure the video shows a clear vertical jump.", None, None
184
 
185
+ progress(0.7, desc="Getting AI coaching analysis...")
186
+
187
+ # Get AI coaching analysis
188
+ ai_result = get_ai_sports_coaching_analysis(
189
+ jump_height_cm=result['jump_height_cm'],
190
+ user_height_cm=user_height_cm,
191
+ gender=gender,
192
+ peak_power_watts=result.get('peak_power_watts'),
193
+ flight_time_s=result.get('flight_time_s'),
194
+ rfd=result.get('rate_of_force_development'),
195
+ api_key=gemini_api_key.strip()
196
+ )
197
+
198
+ progress(0.9, desc="Formatting results...")
199
+
200
+ if "error" in ai_result:
201
+ return f"❌ AI analysis failed: {ai_result['error']}", None, None
202
+
203
+ # Format the comprehensive results
204
  results_text = f"""
205
+ # πŸ€– AI Sports Coaching Analysis
206
 
207
+ ## πŸ“Š Performance Summary
208
  - **Jump Height**: {result['jump_height_cm']:.2f} cm
209
+ - **Relative Jump**: {(result['jump_height_cm']/user_height_cm*100):.1f}% of body height
210
+ - **Flight Time**: {result['flight_time_s']:.3f} seconds
211
+ - **Peak Power**: {result.get('peak_power_watts', 0):.0f} watts
212
+ - **Gender**: {gender}
213
+ - **Height**: {user_height_cm} cm
214
 
215
+ ## πŸ† AI Expert Coaching Analysis
 
 
 
216
 
217
+ {ai_result['analysis']}
218
+
219
+ ---
220
+ *Analysis powered by Google Gemini AI*
221
  """
222
 
223
+ # Create summary dataframe
224
+ summary_df = pd.DataFrame([
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  ["Jump Height", f"{result['jump_height_cm']:.2f} cm"],
226
+ ["Relative Jump Height", f"{(result['jump_height_cm']/user_height_cm*100):.1f}%"],
227
  ["Flight Time", f"{result['flight_time_s']:.3f} seconds"],
228
+ ["Peak Power", f"{result.get('peak_power_watts', 0):.0f} watts"],
229
+ ["Rate of Force Development", f"{result.get('rate_of_force_development', 0):.2f}"],
230
+ ["Gender", gender],
231
+ ["Height", f"{user_height_cm} cm"],
232
+ ["Weight", f"{user_weight_kg} kg"],
233
  ], columns=["Metric", "Value"])
234
 
235
+ progress(1.0, desc="AI coaching analysis complete!")
236
 
237
+ return results_text, summary_df, "βœ… AI coaching analysis completed!"
238
 
239
  except Exception as e:
240
+ return f"❌ Unexpected error: {str(e)}", None, None
241
 
242
  # Create Gradio interface
243
  def create_interface():
244
  with gr.Blocks(title="πŸƒβ€β™‚οΈ Athletic Ability Analysis") as app:
245
  gr.Markdown("""
246
+ # πŸƒβ€β™‚οΈ Athletic Ability Analysis & AI Sports Coach
247
+
248
+ Analyze jumping performance from videos using computer vision and get AI-powered sports coaching recommendations.
249
+ Upload a video or provide a YouTube URL to get detailed metrics and personalized coaching insights.
250
 
251
+ ## πŸš€ Features
252
+ - **πŸ“Š Biomechanical Analysis**: Comprehensive jump metrics (height, power, force, RFD)
253
+ - **πŸ€– AI Sports Coach**: Personalized sport recommendations and technique improvements
254
+ - **🎯 Performance Insights**: Professional-grade analysis and training suggestions
255
 
256
  ## πŸ“‹ Instructions
257
+ 1. Enter your height in centimeters and weight in kilograms
258
+ 2. Choose your analysis type: Basic metrics, or AI coaching with sports recommendations
259
+ 3. Provide a video (YouTube URL or file upload)
260
+ 4. Get comprehensive results and actionable insights
261
  """)
262
 
263
  with gr.Row():
264
+ with gr.Column():
265
+ user_height = gr.Number(
266
+ label="Your Height (cm)",
267
+ value=175,
268
+ minimum=100,
269
+ maximum=250
270
+ )
271
+ with gr.Column():
272
+ user_weight = gr.Number(
273
+ label="Your Weight (kg)",
274
+ value=75,
275
+ minimum=30,
276
+ maximum=200
277
+ )
278
+ gr.Markdown("πŸ’‘ *Enter your height and weight for accurate biomechanical calculations*")
279
 
280
  with gr.Tabs():
281
  # YouTube URL Tab
 
295
  file_types=[".mp4", ".avi", ".mov", ".mkv", ".webm"]
296
  )
297
  file_btn = gr.Button("πŸš€ Analyze Uploaded Video", variant="primary")
298
+
299
+ # AI Coaching Tab
300
+ with gr.TabItem("πŸ€– AI Sports Coach"):
301
+ gr.Markdown("""
302
+ ## πŸ€– AI-Powered Sports Coaching Analysis
303
+
304
+ Get personalized sports recommendations and jump technique improvement suggestions from our AI sports coach powered by Google Gemini.
305
+
306
+ **What you'll get:**
307
+ - πŸ† **Recommended Sports** (top 3 that match your athletic profile)
308
+ - 🎯 **Jump Technique Analysis** with specific improvement suggestions
309
+ - πŸ“Š **Personalized Training Recommendations**
310
+ """)
311
+
312
+ with gr.Row():
313
+ with gr.Column():
314
+ ai_gender = gr.Radio(
315
+ choices=["Male", "Female"],
316
+ label="Gender",
317
+ value="Male"
318
+ )
319
+ # Check if API key is available in environment
320
+ default_api_key = os.getenv("GEMINI_API_KEY", "")
321
+ ai_gemini_key = gr.Textbox(
322
+ label="Gemini API Key",
323
+ placeholder="Enter your Google Gemini API key" if not default_api_key else "API key loaded from environment",
324
+ type="password",
325
+ value=default_api_key
326
+ )
327
+ gr.Markdown("""
328
+ πŸ’‘ **Get your free API key**: [Google AI Studio](https://aistudio.google.com/app/apikey)
329
+
330
+ πŸ“± **Privacy**: Your API key is only used for this analysis and not stored.
331
+ """)
332
+
333
+ with gr.Column():
334
+ ai_youtube_url = gr.Textbox(
335
+ label="YouTube URL (Optional)",
336
+ placeholder="https://youtube.com/watch?v=..."
337
+ )
338
+ ai_video_file = gr.File(
339
+ label="Upload Video File (Optional)",
340
+ file_types=[".mp4", ".avi", ".mov", ".mkv", ".webm"]
341
+ )
342
+ gr.Markdown("*Provide either a YouTube URL or upload a video file*")
343
+
344
+ ai_coaching_btn = gr.Button("πŸ€– Get AI Coaching Analysis", variant="primary", size="lg")
345
 
346
  # Results section
347
  gr.Markdown("## πŸ“Š Analysis Results")
 
375
 
376
  1. **Pose Detection**: Uses Google's MediaPipe to detect human pose landmarks
377
  2. **Hip Tracking**: Tracks the midpoint between left and right hip joints
378
+ 3. **Biomechanical Analysis**: Calculates comprehensive metrics based on hip trajectory:
379
+ - **Jump Height**: Relative to your body size
380
+ - **Flight Time**: Duration in the air
381
+ - **Peak Power Output**: Maximum power generated during takeoff
382
+ - **Rate of Force Development (RFD)**: Speed of force generation
383
+ - **Ground Contact Time**: Efficiency in stretch-shortening cycle
384
+ - **Impulse & Peak Force**: Force characteristics during takeoff
385
+ - **Takeoff Phase Duration**: Time from crouch to launch
386
  """)
387
 
388
  # Event handlers
389
  youtube_btn.click(
390
  fn=analyze_jump_from_youtube,
391
+ inputs=[youtube_url, user_height, user_weight],
392
  outputs=[results_text, results_table, status_message]
393
  )
394
 
395
  file_btn.click(
396
  fn=analyze_jump_from_file,
397
+ inputs=[video_file, user_height, user_weight],
398
+ outputs=[results_text, results_table, status_message]
399
+ )
400
+
401
+ ai_coaching_btn.click(
402
+ fn=get_ai_coaching_recommendations,
403
+ inputs=[ai_youtube_url, ai_video_file, user_height, user_weight, ai_gender, ai_gemini_key],
404
  outputs=[results_text, results_table, status_message]
405
  )
406
 
407
  # Example section
408
  gr.Examples(
409
  examples=[
410
+ ["https://www.youtube.com/watch?v=dQw4w9WgXcQ", 175, 75], # This is just a placeholder
411
  ],
412
+ inputs=[youtube_url, user_height, user_weight],
413
  label="πŸ“š Example (Replace with actual jump video URLs)"
414
  )
415
 
athletic_performance.py ADDED
@@ -0,0 +1,573 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import mediapipe as mp
4
+ from collections import deque
5
+ from pathlib import Path
6
+ import tempfile
7
+ import os
8
+ import yt_dlp
9
+ import json
10
+ import requests
11
+
12
+ # MediaPipe pose landmarks
13
+ LHIP, RHIP = 23, 24
14
+ POSE_CONNECTIONS = mp.solutions.pose.POSE_CONNECTIONS
15
+
16
+
17
+ def smooth_moving_avg(series, k=5):
18
+ """Simple causal moving average; ignores None values."""
19
+ out = []
20
+ q = deque()
21
+ s = 0.0
22
+ cnt = 0
23
+ for v in series:
24
+ if v is not None:
25
+ q.append(v)
26
+ s += v
27
+ cnt += 1
28
+ else:
29
+ q.append(None)
30
+ if len(q) > k:
31
+ old = q.popleft()
32
+ if old is not None:
33
+ s -= old
34
+ cnt -= 1
35
+ out.append((s / max(cnt, 1)) if cnt > 0 else None)
36
+ return out
37
+
38
+
39
+ def calculate_peak_power_output(jump_height_m, body_mass_kg, flight_time_s):
40
+ """Calculate peak power output using biomechanical models."""
41
+ if jump_height_m <= 0 or flight_time_s <= 0:
42
+ return None
43
+
44
+ # Using the equation: Power = (body_mass * gravity * jump_height) / flight_time
45
+ # This is a simplified model - in reality, peak power occurs during takeoff phase
46
+ gravity = 9.81 # m/sΒ²
47
+
48
+ # Average power during flight
49
+ avg_power = (body_mass_kg * gravity * jump_height_m) / (flight_time_s / 2)
50
+
51
+ # Peak power is typically 2-3x average power during explosive movements
52
+ peak_power = avg_power * 2.5
53
+
54
+ return peak_power # Watts
55
+
56
+
57
+ def calculate_rate_of_force_development(hip_y_series, fps, takeoff_start_idx, takeoff_end_idx):
58
+ """Calculate Rate of Force Development from hip trajectory."""
59
+ if takeoff_end_idx <= takeoff_start_idx or len(hip_y_series) <= takeoff_end_idx:
60
+ return None
61
+
62
+ # Extract takeoff phase
63
+ takeoff_phase = hip_y_series[takeoff_start_idx:takeoff_end_idx + 1]
64
+
65
+ # Calculate velocity and acceleration
66
+ dt = 1.0 / fps
67
+ velocities = np.diff(takeoff_phase) / dt
68
+ accelerations = np.diff(velocities) / dt
69
+
70
+ if len(accelerations) == 0:
71
+ return None
72
+
73
+ # RFD is the maximum rate of change of force (approximated by acceleration)
74
+ # Convert to relative units (normalized by body position change)
75
+ max_acceleration = np.max(np.abs(accelerations))
76
+
77
+ # Normalize to get RFD index (higher values indicate faster force development)
78
+ rfd_index = max_acceleration * fps # per second
79
+
80
+ return rfd_index
81
+
82
+
83
+ def detect_ground_contact_phases(hip_y_series, fps, threshold_velocity=0.01):
84
+ """Detect ground contact and flight phases from hip trajectory."""
85
+ if len(hip_y_series) < 5:
86
+ return [], []
87
+
88
+ # Calculate vertical velocity
89
+ velocities = np.diff(hip_y_series)
90
+
91
+ # Smooth velocities
92
+ velocities = smooth_moving_avg(list(velocities), k=3)
93
+
94
+ # Find phases where velocity is near zero (ground contact)
95
+ ground_contact_frames = []
96
+ flight_frames = []
97
+
98
+ for i, vel in enumerate(velocities):
99
+ if vel is not None and abs(vel) < threshold_velocity:
100
+ ground_contact_frames.append(i)
101
+ elif vel is not None:
102
+ flight_frames.append(i)
103
+
104
+ # Calculate ground contact time
105
+ if ground_contact_frames:
106
+ total_contact_frames = len(ground_contact_frames)
107
+ ground_contact_time = total_contact_frames / fps
108
+ else:
109
+ ground_contact_time = 0.0
110
+
111
+ return ground_contact_frames, ground_contact_time
112
+
113
+
114
+ def calculate_impulse_and_force(hip_y_series, fps, body_mass_kg, takeoff_start_idx, takeoff_end_idx):
115
+ """Calculate impulse and force characteristics during takeoff."""
116
+ if takeoff_end_idx <= takeoff_start_idx or len(hip_y_series) <= takeoff_end_idx:
117
+ return None, None
118
+
119
+ # Extract takeoff phase
120
+ takeoff_phase = hip_y_series[takeoff_start_idx:takeoff_end_idx + 1]
121
+ dt = 1.0 / fps
122
+
123
+ # Calculate velocity and acceleration
124
+ velocities = np.diff(takeoff_phase) / dt
125
+ accelerations = np.diff(velocities) / dt
126
+
127
+ if len(accelerations) == 0:
128
+ return None, None
129
+
130
+ # Estimate force (F = ma, where a includes gravity)
131
+ gravity = 9.81
132
+ forces = body_mass_kg * (np.array(accelerations) + gravity)
133
+
134
+ # Calculate impulse (area under force-time curve)
135
+ impulse = np.trapz(forces, dx=dt)
136
+
137
+ # Peak force
138
+ peak_force = np.max(forces) if len(forces) > 0 else None
139
+
140
+ return impulse, peak_force
141
+
142
+
143
+ def estimate_jump_metrics(hip_y_series, fps, body_mass_kg=75.0):
144
+ """Return comprehensive jump metrics including advanced biomechanical parameters."""
145
+ # Remove None values
146
+ hip_clean = [(i, h) for i, h in enumerate(hip_y_series) if h is not None]
147
+ if len(hip_clean) < 5:
148
+ return None
149
+
150
+ indices, hip_values = zip(*hip_clean)
151
+ hip = list(hip_values)
152
+
153
+ # Smooth the data
154
+ hip_smooth = smooth_moving_avg(hip, k=5)
155
+
156
+ # Basic jump metrics
157
+ min_y = min(hip_smooth) # apex (body highest)
158
+ max_y = max(hip_smooth) # deepest crouch (body lowest)
159
+ jump_height_norm = max(0.0, (max_y - min_y))
160
+
161
+ # Find key phase indices
162
+ hip_arr = np.array(hip_smooth, dtype=float)
163
+ vel = np.diff(hip_arr)
164
+
165
+ if vel.size == 0:
166
+ return None
167
+
168
+ # Identify takeoff and landing phases
169
+ takeoff_idx = int(np.argmin(vel)) # most negative velocity (takeoff)
170
+ landing_idx = int(np.argmax(vel)) # most positive velocity (landing)
171
+
172
+ # Flight time
173
+ flight_frames = max(0, landing_idx - takeoff_idx)
174
+ flight_time_s = flight_frames / float(fps or 30.0)
175
+
176
+ # Find takeoff phase (from crouch to takeoff)
177
+ crouch_idx = int(np.argmax(hip_smooth)) # deepest crouch
178
+ takeoff_start_idx = max(0, crouch_idx - 10) # start of takeoff phase
179
+ takeoff_end_idx = takeoff_idx
180
+
181
+ # Advanced metrics
182
+ jump_height_m = jump_height_norm * 2.0 # Rough conversion to meters
183
+
184
+ # Peak Power Output
185
+ peak_power = calculate_peak_power_output(jump_height_m, body_mass_kg, flight_time_s)
186
+
187
+ # Rate of Force Development
188
+ rfd = calculate_rate_of_force_development(hip_smooth, fps, takeoff_start_idx, takeoff_end_idx)
189
+
190
+ # Ground Contact Time
191
+ ground_contact_frames, ground_contact_time = detect_ground_contact_phases(hip_smooth, fps)
192
+
193
+ # Impulse and Force
194
+ impulse, peak_force = calculate_impulse_and_force(hip_smooth, fps, body_mass_kg, takeoff_start_idx, takeoff_end_idx)
195
+
196
+ return {
197
+ 'jump_height_norm': jump_height_norm,
198
+ 'flight_time_s': flight_time_s,
199
+ 'peak_power_watts': peak_power,
200
+ 'rate_of_force_development': rfd,
201
+ 'ground_contact_time_s': ground_contact_time,
202
+ 'impulse_ns': impulse,
203
+ 'peak_force_n': peak_force,
204
+ 'takeoff_phase_duration_s': (takeoff_end_idx - takeoff_start_idx) / fps if takeoff_end_idx > takeoff_start_idx else 0
205
+ }
206
+
207
+
208
+ def download_youtube_video(youtube_url, output_path):
209
+ """Download YouTube video to specified path."""
210
+ ydl_opts = {
211
+ 'format': 'best[height<=720]', # Limit quality for faster processing
212
+ 'outtmpl': output_path,
213
+ 'quiet': True,
214
+ 'no_warnings': True,
215
+ }
216
+
217
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
218
+ ydl.download([youtube_url])
219
+ return output_path
220
+
221
+
222
+ def process_video_analysis(video_path, user_height_cm, user_weight_kg=75.0, progress_callback=None):
223
+ """Core video analysis function with progress tracking.
224
+
225
+ Args:
226
+ video_path (str): Path to the video file
227
+ user_height_cm (float): User's height in centimeters
228
+ user_weight_kg (float): User's weight in kilograms (default: 75kg)
229
+ progress_callback (callable, optional): Function to call with progress updates
230
+ Signature: progress_callback(progress_float, description_string)
231
+
232
+ Returns:
233
+ dict: Analysis results containing jump metrics and video info
234
+ None: If analysis failed
235
+ """
236
+ cap = cv2.VideoCapture(video_path)
237
+ if not cap.isOpened():
238
+ raise Exception(f"Could not open video: {video_path}")
239
+
240
+ w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
241
+ h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
242
+ fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
243
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
244
+
245
+ mp_pose = mp.solutions.pose
246
+ pose = mp_pose.Pose(static_image_mode=False, model_complexity=1, enable_segmentation=False)
247
+
248
+ hip_y_series = []
249
+ frame_idx = 0
250
+
251
+ print(f"Processing video: {Path(video_path).name}")
252
+ print(f"Video dimensions: {w}x{h}, FPS: {fps}, Total frames: {total_frames}")
253
+
254
+ ok, frame = cap.read()
255
+ while ok:
256
+ rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
257
+ res = pose.process(rgb)
258
+
259
+ if res.pose_landmarks:
260
+ lms = res.pose_landmarks.landmark
261
+ mid_hip_y = (lms[LHIP].y + lms[RHIP].y) / 2.0
262
+ hip_y_series.append(float(mid_hip_y))
263
+ else:
264
+ hip_y_series.append(None)
265
+
266
+ frame_idx += 1
267
+
268
+ # Update progress
269
+ if progress_callback and total_frames > 0:
270
+ progress = min(frame_idx / total_frames, 1.0)
271
+ progress_callback(progress, f"Processing frame {frame_idx}/{total_frames}")
272
+
273
+ ok, frame = cap.read()
274
+
275
+ cap.release()
276
+ print(f"Completed processing {frame_idx} frames")
277
+
278
+ # Calculate comprehensive jump metrics
279
+ metrics = estimate_jump_metrics(hip_y_series, fps, user_weight_kg)
280
+
281
+ if metrics is None:
282
+ return None
283
+
284
+ # Convert normalized jump height to actual height in cm
285
+ jump_height_cm = metrics['jump_height_norm'] * user_height_cm
286
+
287
+ return {
288
+ "video": Path(video_path).name,
289
+ "frames": len(hip_y_series),
290
+ "fps": fps,
291
+ "jump_height_cm": jump_height_cm,
292
+ "normalized_rise": metrics['jump_height_norm'],
293
+ "flight_time_s": metrics['flight_time_s'],
294
+ "peak_power_watts": metrics['peak_power_watts'],
295
+ "rate_of_force_development": metrics['rate_of_force_development'],
296
+ "ground_contact_time_s": metrics['ground_contact_time_s'],
297
+ "impulse_ns": metrics['impulse_ns'],
298
+ "peak_force_n": metrics['peak_force_n'],
299
+ "takeoff_phase_duration_s": metrics['takeoff_phase_duration_s'],
300
+ "user_weight_kg": user_weight_kg
301
+ }
302
+
303
+
304
+ def analyze_youtube_video(youtube_url, user_height_cm, user_weight_kg=75.0, progress_callback=None):
305
+ """Analyze jump from YouTube video.
306
+
307
+ Args:
308
+ youtube_url (str): YouTube video URL
309
+ user_height_cm (float): User's height in centimeters
310
+ user_weight_kg (float): User's weight in kilograms (default: 75kg)
311
+ progress_callback (callable, optional): Function to call with progress updates
312
+
313
+ Returns:
314
+ dict: Analysis results or error information
315
+ """
316
+ # Validate inputs
317
+ if not youtube_url or not youtube_url.strip():
318
+ return {"error": "Please provide a YouTube URL"}
319
+
320
+ if not user_height_cm or user_height_cm <= 0:
321
+ return {"error": "Please provide a valid height in centimeters"}
322
+
323
+ try:
324
+ if progress_callback:
325
+ progress_callback(0.1, "Validating YouTube URL...")
326
+
327
+ # Validate YouTube URL
328
+ youtube_url = youtube_url.strip()
329
+ if not any(domain in youtube_url for domain in ['youtube.com', 'youtu.be']):
330
+ return {"error": "Please provide a valid YouTube URL"}
331
+
332
+ # Create temporary directory for processing
333
+ with tempfile.TemporaryDirectory() as temp_dir:
334
+ if progress_callback:
335
+ progress_callback(0.2, "Downloading video from YouTube...")
336
+
337
+ # Download video
338
+ video_filename = os.path.join(temp_dir, 'video.%(ext)s')
339
+ try:
340
+ download_youtube_video(youtube_url, video_filename)
341
+ # Find the actual downloaded file
342
+ video_files = [f for f in os.listdir(temp_dir) if f.startswith('video.')]
343
+ if not video_files:
344
+ return {"error": "Failed to download YouTube video. Please check the URL and try again."}
345
+ video_path = os.path.join(temp_dir, video_files[0])
346
+ except Exception as e:
347
+ return {"error": f"Failed to download YouTube video: {str(e)}"}
348
+
349
+ if progress_callback:
350
+ progress_callback(0.3, "Starting video analysis...")
351
+
352
+ # Process the video with progress tracking
353
+ def update_progress(prog, desc):
354
+ if progress_callback:
355
+ progress_callback(0.3 + (prog * 0.6), desc)
356
+
357
+ result = process_video_analysis(video_path, user_height_cm, user_weight_kg, update_progress)
358
+
359
+ if progress_callback:
360
+ progress_callback(0.9, "Analysis complete!")
361
+
362
+ return result
363
+
364
+ except Exception as e:
365
+ return {"error": f"Error during analysis: {str(e)}"}
366
+
367
+
368
+ def analyze_video_file(video_path, user_height_cm, user_weight_kg=75.0, progress_callback=None):
369
+ """Analyze jump from video file.
370
+
371
+ Args:
372
+ video_path (str): Path to video file
373
+ user_height_cm (float): User's height in centimeters
374
+ user_weight_kg (float): User's weight in kilograms (default: 75kg)
375
+ progress_callback (callable, optional): Function to call with progress updates
376
+
377
+ Returns:
378
+ dict: Analysis results or error information
379
+ """
380
+ # Validate inputs
381
+ if not video_path:
382
+ return {"error": "Please provide a video file"}
383
+
384
+ if not user_height_cm or user_height_cm <= 0:
385
+ return {"error": "Please provide a valid height in centimeters"}
386
+
387
+ try:
388
+ if progress_callback:
389
+ progress_callback(0.1, "Processing video file...")
390
+
391
+ # Process the video with progress tracking
392
+ def update_progress(prog, desc):
393
+ if progress_callback:
394
+ progress_callback(0.1 + (prog * 0.8), desc)
395
+
396
+ result = process_video_analysis(video_path, user_height_cm, user_weight_kg, update_progress)
397
+
398
+ if progress_callback:
399
+ progress_callback(1.0, "Analysis complete!")
400
+
401
+ return result
402
+
403
+ except Exception as e:
404
+ return {"error": f"Error during analysis: {str(e)}"}
405
+
406
+
407
+ def get_performance_insights(result_dict):
408
+ """Generate performance insights based on comprehensive jump metrics.
409
+
410
+ Args:
411
+ result_dict (dict): Dictionary containing all jump analysis results
412
+
413
+ Returns:
414
+ list: List of insight strings
415
+ """
416
+ insights = []
417
+
418
+ jump_height_cm = result_dict.get('jump_height_cm', 0)
419
+ flight_time_s = result_dict.get('flight_time_s', 0)
420
+ peak_power_watts = result_dict.get('peak_power_watts')
421
+ rfd = result_dict.get('rate_of_force_development')
422
+ ground_contact_time = result_dict.get('ground_contact_time_s')
423
+ peak_force = result_dict.get('peak_force_n')
424
+
425
+ # Jump height insights
426
+ if jump_height_cm > 60:
427
+ insights.append("πŸ”₯ **Excellent jump height!** This is above average performance.")
428
+ elif jump_height_cm > 40:
429
+ insights.append("πŸ‘ **Good jump height!** Solid athletic performance.")
430
+ elif jump_height_cm > 25:
431
+ insights.append("πŸ“ˆ **Moderate jump height.** Room for improvement with training.")
432
+ else:
433
+ insights.append("🎯 **Starting point identified.** Focus on technique and strength training.")
434
+
435
+ # Flight time insights
436
+ if flight_time_s > 0.5:
437
+ insights.append("⏱️ **Great flight time!** Shows good explosive power.")
438
+ elif flight_time_s > 0.3:
439
+ insights.append("⏱️ **Decent flight time.** Good coordination.")
440
+
441
+ # Peak power insights
442
+ if peak_power_watts and peak_power_watts > 3000:
443
+ insights.append("⚑ **Outstanding power output!** Elite-level explosive strength.")
444
+ elif peak_power_watts and peak_power_watts > 2000:
445
+ insights.append("πŸ’ͺ **High power output!** Strong explosive capabilities.")
446
+ elif peak_power_watts and peak_power_watts > 1000:
447
+ insights.append("πŸ‹οΈ **Moderate power output.** Good base strength to build on.")
448
+
449
+ # Rate of Force Development insights
450
+ if rfd and rfd > 15:
451
+ insights.append("πŸš€ **Excellent RFD!** Very quick force generation - great for sprinting and jumping.")
452
+ elif rfd and rfd > 10:
453
+ insights.append("⚑ **Good RFD!** Solid ability to generate force quickly.")
454
+ elif rfd and rfd > 5:
455
+ insights.append("πŸ“Š **Moderate RFD.** Work on plyometric training to improve explosiveness.")
456
+
457
+ # Ground contact time insights
458
+ if ground_contact_time and ground_contact_time < 0.2:
459
+ insights.append("🦘 **Excellent ground contact time!** Very efficient stretch-shortening cycle.")
460
+ elif ground_contact_time and ground_contact_time < 0.3:
461
+ insights.append("πŸ‘Ÿ **Good ground contact efficiency.** Solid reactive strength.")
462
+
463
+ # Peak force insights
464
+ if peak_force and peak_force > 2000:
465
+ insights.append("πŸ‹οΈβ€β™‚οΈ **High peak force production!** Strong neuromuscular system.")
466
+ elif peak_force and peak_force > 1500:
467
+ insights.append("πŸ’ͺ **Good force production.** Solid strength foundation.")
468
+
469
+ return insights
470
+
471
+
472
+ def get_ai_sports_coaching_analysis(jump_height_cm, user_height_cm, gender, peak_power_watts=None,
473
+ flight_time_s=None, rfd=None, api_key=None):
474
+ """Get AI-powered sports coaching analysis using Google Gemini API.
475
+
476
+ Args:
477
+ jump_height_cm (float): Jump height in centimeters
478
+ user_height_cm (float): User's height in centimeters
479
+ gender (str): User's gender ('Male' or 'Female')
480
+ peak_power_watts (float, optional): Peak power output in watts
481
+ flight_time_s (float, optional): Flight time in seconds
482
+ rfd (float, optional): Rate of force development
483
+ api_key (str): Google Gemini API key
484
+
485
+ Returns:
486
+ dict: AI analysis with sports recommendations and improvement suggestions
487
+ """
488
+ if not api_key:
489
+ return {"error": "Gemini API key is required"}
490
+
491
+ # Calculate relative jump height
492
+ relative_jump_height = (jump_height_cm / user_height_cm) * 100 if user_height_cm > 0 else 0
493
+
494
+ # Prepare the analysis prompt
495
+ prompt = f"""Consider yourself an expert sports coach and biomechanics analyst. Based on the following athletic performance data, provide your professional analysis:
496
+
497
+ ATHLETE PROFILE:
498
+ - Gender: {gender}
499
+ - Height: {user_height_cm} cm
500
+ - Jump Height: {jump_height_cm} cm
501
+ - Relative Jump Height: {relative_jump_height:.1f}% of body height
502
+ - Flight Time: {flight_time_s:.3f} seconds (if available: {flight_time_s is not None})
503
+ - Peak Power Output: {peak_power_watts:.0f} watts (if available: {peak_power_watts is not None})
504
+ - Rate of Force Development: {rfd:.2f} (if available: {rfd is not None})
505
+
506
+ ANALYSIS REQUIRED:
507
+ Please provide a comprehensive analysis with exactly these two sections:
508
+
509
+ 1. RECOMMENDED SPORTS (Maximum 3 sports):
510
+ Based on the athletic profile, suggest the top 3 sports where this athlete would likely excel. Consider their explosive power, jump ability, and biomechanical characteristics. Be specific about why each sport matches their strengths.
511
+
512
+ 2. JUMP TECHNIQUE IMPROVEMENT:
513
+ Analyze potential areas for improvement in their jumping technique. Consider possible errors in their jump execution, training recommendations, and specific drills that could enhance their performance. Focus on actionable advice.
514
+
515
+ Please format your response clearly with these two distinct sections and provide practical, evidence-based recommendations."""
516
+
517
+ # Prepare the API request
518
+ url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
519
+
520
+ headers = {
521
+ 'Content-Type': 'application/json',
522
+ 'X-goog-api-key': api_key
523
+ }
524
+
525
+ data = {
526
+ "contents": [
527
+ {
528
+ "parts": [
529
+ {
530
+ "text": prompt
531
+ }
532
+ ]
533
+ }
534
+ ],
535
+ "generationConfig": {
536
+ "temperature": 0.7,
537
+ "topK": 40,
538
+ "topP": 0.95,
539
+ "maxOutputTokens": 1024
540
+ }
541
+ }
542
+
543
+ try:
544
+ response = requests.post(url, headers=headers, json=data, timeout=30)
545
+ response.raise_for_status()
546
+
547
+ result = response.json()
548
+
549
+ if 'candidates' in result and len(result['candidates']) > 0:
550
+ ai_analysis = result['candidates'][0]['content']['parts'][0]['text']
551
+
552
+ return {
553
+ "success": True,
554
+ "analysis": ai_analysis,
555
+ "athlete_profile": {
556
+ "gender": gender,
557
+ "height_cm": user_height_cm,
558
+ "jump_height_cm": jump_height_cm,
559
+ "relative_jump_height": relative_jump_height,
560
+ "flight_time_s": flight_time_s,
561
+ "peak_power_watts": peak_power_watts,
562
+ "rfd": rfd
563
+ }
564
+ }
565
+ else:
566
+ return {"error": "No response generated from AI"}
567
+
568
+ except requests.exceptions.RequestException as e:
569
+ return {"error": f"API request failed: {str(e)}"}
570
+ except json.JSONDecodeError as e:
571
+ return {"error": f"Failed to parse API response: {str(e)}"}
572
+ except Exception as e:
573
+ return {"error": f"Unexpected error: {str(e)}"}
deploy_hf.py CHANGED
@@ -29,6 +29,13 @@ def main():
29
 
30
  print("βœ… Found app.py")
31
 
 
 
 
 
 
 
 
32
  # Check if requirements.txt exists
33
  if not os.path.exists("requirements.txt"):
34
  print("❌ Error: requirements.txt not found.")
@@ -66,9 +73,10 @@ def main():
66
 
67
  print("\nπŸ“‹ Deployment Checklist:")
68
  print("1. βœ… app.py - Main Gradio application")
69
- print("2. βœ… requirements.txt - Python dependencies")
70
- print("3. βœ… README.md - With HF Spaces header")
71
- print("4. βœ… Dependencies tested")
 
72
 
73
  print("\nπŸš€ Ready for Hugging Face Spaces deployment!")
74
  print("\nπŸ“– Deployment Instructions:")
@@ -77,9 +85,14 @@ def main():
77
  print("3. Choose 'Gradio' as the SDK")
78
  print("4. Upload these files:")
79
  print(" - app.py")
 
80
  print(" - requirements.txt")
81
  print(" - README.md")
82
- print("5. Wait for automatic deployment")
 
 
 
 
83
  print("\nπŸ”— Your Space will be available at:")
84
  print(" https://huggingface.co/spaces/YOUR_USERNAME/athletic-ability-analysis")
85
 
 
29
 
30
  print("βœ… Found app.py")
31
 
32
+ # Check if athletic_performance.py exists
33
+ if not os.path.exists("athletic_performance.py"):
34
+ print("❌ Error: athletic_performance.py not found.")
35
+ sys.exit(1)
36
+
37
+ print("βœ… Found athletic_performance.py")
38
+
39
  # Check if requirements.txt exists
40
  if not os.path.exists("requirements.txt"):
41
  print("❌ Error: requirements.txt not found.")
 
73
 
74
  print("\nπŸ“‹ Deployment Checklist:")
75
  print("1. βœ… app.py - Main Gradio application")
76
+ print("2. βœ… athletic_performance.py - Core analysis module")
77
+ print("3. βœ… requirements.txt - Python dependencies")
78
+ print("4. βœ… README.md - With HF Spaces header")
79
+ print("5. βœ… Dependencies tested")
80
 
81
  print("\nπŸš€ Ready for Hugging Face Spaces deployment!")
82
  print("\nπŸ“– Deployment Instructions:")
 
85
  print("3. Choose 'Gradio' as the SDK")
86
  print("4. Upload these files:")
87
  print(" - app.py")
88
+ print(" - athletic_performance.py")
89
  print(" - requirements.txt")
90
  print(" - README.md")
91
+ print("5. πŸ” IMPORTANT - Set up API Key Security:")
92
+ print(" - Go to Space Settings")
93
+ print(" - Add Secret: GEMINI_API_KEY = your_api_key")
94
+ print(" - NEVER commit API keys to your repository!")
95
+ print("6. Wait for automatic deployment")
96
  print("\nπŸ”— Your Space will be available at:")
97
  print(" https://huggingface.co/spaces/YOUR_USERNAME/athletic-ability-analysis")
98
 
requirements.txt CHANGED
@@ -3,4 +3,5 @@ opencv-python-headless>=4.8.0
3
  numpy>=1.21.0
4
  mediapipe>=0.10.14
5
  yt-dlp>=2023.7.6
6
- pandas>=2.0.0
 
 
3
  numpy>=1.21.0
4
  mediapipe>=0.10.14
5
  yt-dlp>=2023.7.6
6
+ pandas>=2.0.0
7
+ requests>=2.28.0