chenemii commited on
Commit
ef472f7
·
1 Parent(s): e62f2f6

updated security

Browse files
app/main.py CHANGED
@@ -33,7 +33,7 @@ def main():
33
  "\nEnable GPT analysis? (y/n, default: y): ").lower() != 'n'
34
 
35
  sample_rate_input = input(
36
- "\nFrame skip rate for YOLO (1-10, default: 5): ")
37
  sample_rate = 5 # Default value
38
  if sample_rate_input.isdigit():
39
  sample_rate = max(1, min(10, int(sample_rate_input)))
 
33
  "\nEnable GPT analysis? (y/n, default: y): ").lower() != 'n'
34
 
35
  sample_rate_input = input(
36
+ "\nFrame skip rate for YOLO (1-10, default: 5, auto-adjusts for videos shorter than 5 seconds): ")
37
  sample_rate = 5 # Default value
38
  if sample_rate_input.isdigit():
39
  sample_rate = max(1, min(10, int(sample_rate_input)))
app/models/llm_analyzer.py CHANGED
@@ -2,9 +2,10 @@
2
  LLM-based golf swing analysis module
3
  """
4
 
5
- import os
6
  import json
 
7
  from openai import OpenAI
 
8
 
9
 
10
  def generate_swing_analysis(pose_data, swing_phases, trajectory_data):
@@ -19,9 +20,10 @@ def generate_swing_analysis(pose_data, swing_phases, trajectory_data):
19
  Returns:
20
  str: Detailed swing analysis and coaching tips
21
  """
22
- # Check if OpenAI API key is available
23
- api_key = os.getenv("OPENAI_API_KEY")
24
- if not api_key:
 
25
  # Return a sample analysis instead of an error message
26
  return """
27
  ## Swing Analysis Summary
@@ -66,9 +68,6 @@ Based on the video analysis, here are some observations about your swing:
66
  These adjustments should help improve both consistency and distance in your swing.
67
  """
68
 
69
- # Create OpenAI client
70
- client = OpenAI(api_key=api_key)
71
-
72
  # Prepare data for LLM
73
  analysis_data = prepare_data_for_llm(pose_data, swing_phases,
74
  trajectory_data)
@@ -77,24 +76,96 @@ These adjustments should help improve both consistency and distance in your swin
77
  prompt = create_llm_prompt(analysis_data)
78
 
79
  try:
80
- # Call OpenAI API
81
- response = client.chat.completions.create(
82
- model="gpt-4",
83
- messages=[{
84
- "role":
85
- "system",
86
- "content":
87
- "You are a professional golf coach with expertise in analyzing golf swings. Provide detailed, actionable feedback based on the swing data provided."
88
- }, {
89
- "role": "user",
90
- "content": prompt
91
- }],
92
- temperature=0.7,
93
- max_tokens=1000)
94
-
95
- # Extract and return analysis
96
- analysis = response.choices[0].message.content
97
- return analysis
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
  except Exception as e:
100
  return f"Error generating swing analysis: {str(e)}"
@@ -140,16 +211,67 @@ def prepare_data_for_llm(pose_data, swing_phases, trajectory_data):
140
  analysis_data["trajectory"]["club_speed_mph"] = impact_data[
141
  "club_speed"]
142
 
143
- # Add additional metrics that would be calculated in a real implementation
144
- # These are placeholder values for demonstration
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  analysis_data["metrics"] = {
146
- "tempo_ratio": 3.0, # Backswing to downswing time ratio
 
147
  "swing_plane_consistency": 0.85, # 0-1 scale
148
  "weight_shift": 0.7, # 0-1 scale
149
  "hip_rotation": 45, # degrees
150
  "shoulder_rotation": 90, # degrees
 
 
 
 
151
  "wrist_hinge": 80, # degrees
152
- "posture_score": 0.8 # 0-1 scale
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  }
154
 
155
  return analysis_data
@@ -177,38 +299,96 @@ I've analyzed a golf swing and extracted the following data:
177
 
178
  # Add trajectory information
179
  prompt += "\n## Trajectory Data\n"
180
- if "trajectory" in analysis_data and "club_speed_mph" in analysis_data[
181
- "trajectory"]:
182
  prompt += f"- Club Speed: {analysis_data['trajectory']['club_speed_mph']:.1f} mph\n"
183
-
184
- # Add metrics
185
- prompt += "\n## Swing Metrics\n"
186
- for metric, value in analysis_data["metrics"].items():
187
- # Format metric name for readability
188
- metric_name = metric.replace("_", " ").title()
189
-
190
- # Format value based on type
191
- if isinstance(value, float):
192
- if 0 <= value <= 1:
193
- # Format as percentage for 0-1 scale metrics
194
- formatted_value = f"{value * 100:.0f}%"
195
- else:
196
- # Format as decimal for other floats
197
- formatted_value = f"{value:.1f}"
198
- else:
199
- # Use as is for integers and other types
200
- formatted_value = str(value)
201
-
202
- prompt += f"- {metric_name}: {formatted_value}\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
  prompt += """
205
- Based on this data, please provide:
206
- 1. A detailed analysis of the golf swing
207
- 2. Key strengths and weaknesses
208
- 3. Specific recommendations for improvement
209
- 4. Drills or exercises that could help address the identified issues
210
 
211
- Please be specific and actionable in your feedback.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  """
213
 
214
  return prompt
 
2
  LLM-based golf swing analysis module
3
  """
4
 
 
5
  import json
6
+ import httpx
7
  from openai import OpenAI
8
+ import streamlit as st
9
 
10
 
11
  def generate_swing_analysis(pose_data, swing_phases, trajectory_data):
 
20
  Returns:
21
  str: Detailed swing analysis and coaching tips
22
  """
23
+ # Check if OpenAI API key is available from secrets
24
+ try:
25
+ api_key = st.secrets["openai"]["api_key"]
26
+ except (KeyError, FileNotFoundError):
27
  # Return a sample analysis instead of an error message
28
  return """
29
  ## Swing Analysis Summary
 
68
  These adjustments should help improve both consistency and distance in your swing.
69
  """
70
 
 
 
 
71
  # Prepare data for LLM
72
  analysis_data = prepare_data_for_llm(pose_data, swing_phases,
73
  trajectory_data)
 
76
  prompt = create_llm_prompt(analysis_data)
77
 
78
  try:
79
+ # Create a custom httpx client without proxies
80
+ http_client = httpx.Client()
81
+
82
+ # Initialize the OpenAI client with the custom http client
83
+ # This avoids any proxy settings that might be causing issues
84
+ client = OpenAI(
85
+ api_key=api_key,
86
+ http_client=http_client
87
+ )
88
+
89
+ try:
90
+ # Try with GPT-4 first
91
+ response = client.chat.completions.create(
92
+ model="gpt-4-turbo",
93
+ messages=[
94
+ {"role": "system", "content": "You are a professional golf coach with expertise in analyzing golf swings. Provide detailed, actionable feedback based on the swing data provided."},
95
+ {"role": "user", "content": prompt}
96
+ ],
97
+ temperature=0.7,
98
+ max_tokens=1000
99
+ )
100
+
101
+ # Extract content from the response
102
+ analysis = response.choices[0].message.content
103
+ return analysis
104
+
105
+ except Exception as gpt4_error:
106
+ # If there's an error with GPT-4 (like quota exceeded), try GPT-3.5
107
+ print(f"Error with GPT-4: {str(gpt4_error)}. Falling back to GPT-3.5-turbo...")
108
+
109
+ try:
110
+ response = client.chat.completions.create(
111
+ model="gpt-3.5-turbo",
112
+ messages=[
113
+ {"role": "system", "content": "You are a professional golf coach with expertise in analyzing golf swings. Provide detailed, actionable feedback based on the swing data provided."},
114
+ {"role": "user", "content": prompt}
115
+ ],
116
+ temperature=0.7,
117
+ max_tokens=1000
118
+ )
119
+
120
+ # Extract content from the response
121
+ analysis = response.choices[0].message.content
122
+ return analysis
123
+
124
+ except Exception as gpt35_error:
125
+ # Both models failed, return the sample analysis
126
+ print(f"Error with GPT-3.5: {str(gpt35_error)}. Using sample analysis instead.")
127
+ return """
128
+ ## Swing Analysis Summary
129
+
130
+ Based on the video analysis, here are some observations about your swing:
131
+
132
+ ### Setup Phase
133
+ - Your stance appears slightly wider than shoulder-width, which can provide good stability
134
+ - Your posture shows a good spine angle, though you could bend slightly more from the hips
135
+ - The ball position looks appropriate for the club you're using
136
+
137
+ ### Backswing
138
+ - Your takeaway is smooth with good tempo
139
+ - Your wrist hinge develops appropriately in the backswing
140
+ - Your right elbow could be kept a bit closer to your body for better consistency
141
+
142
+ ### Downswing
143
+ - Good weight transfer from back foot to front foot during the transition
144
+ - Your hips are rotating well through impact
145
+ - The swing plane looks consistent throughout the downswing
146
+
147
+ ### Impact
148
+ - Club face alignment at impact appears slightly open
149
+ - Your head position is stable through impact
150
+ - The club path is on a good line toward the target
151
+
152
+ ### Follow Through
153
+ - Good balance maintained through the finish
154
+ - Full extension of arms after impact
155
+ - Complete rotation of the body toward the target
156
+
157
+ ## Areas for Improvement
158
+
159
+ 1. **Club Face Control**: The slightly open club face at impact suggests you may be prone to slicing the ball. Focus on maintaining a square club face through impact.
160
+
161
+ 2. **Right Elbow Position**: Keeping your right elbow closer to your body during the backswing will help create a more consistent swing plane.
162
+
163
+ 3. **Hip Rotation**: While your hip rotation is good, increasing the speed of rotation could generate more power in your swing.
164
+
165
+ 4. **Wrist Release**: Your wrist release could be more active through impact to generate additional club head speed.
166
+
167
+ These adjustments should help improve both consistency and distance in your swing.
168
+ """
169
 
170
  except Exception as e:
171
  return f"Error generating swing analysis: {str(e)}"
 
211
  analysis_data["trajectory"]["club_speed_mph"] = impact_data[
212
  "club_speed"]
213
 
214
+ # Calculate backswing and downswing durations if available
215
+ backswing_frames = swing_phases.get("backswing", [])
216
+ downswing_frames = swing_phases.get("downswing", [])
217
+
218
+ backswing_duration = None
219
+ downswing_duration = None
220
+
221
+ if backswing_frames:
222
+ # Assuming 30 fps video
223
+ backswing_duration = len(backswing_frames) / 30.0
224
+
225
+ if downswing_frames:
226
+ # Assuming 30 fps video
227
+ downswing_duration = len(downswing_frames) / 30.0
228
+
229
+ # Calculate tempo ratio if both durations are available
230
+ tempo_ratio = None
231
+ if backswing_duration and downswing_duration and downswing_duration > 0:
232
+ tempo_ratio = backswing_duration / downswing_duration
233
+
234
+ # Add comprehensive metrics with default values or calculated values
235
+ # These values would normally be calculated from pose and trajectory data
236
  analysis_data["metrics"] = {
237
+ # Core body mechanics
238
+ "tempo_ratio": tempo_ratio or 3.0, # Backswing to downswing time ratio
239
  "swing_plane_consistency": 0.85, # 0-1 scale
240
  "weight_shift": 0.7, # 0-1 scale
241
  "hip_rotation": 45, # degrees
242
  "shoulder_rotation": 90, # degrees
243
+ "posture_score": 0.8, # 0-1 scale
244
+
245
+ # Upper body mechanics
246
+ "arm_extension": 0.8, # 0-1 scale
247
  "wrist_hinge": 80, # degrees
248
+ "chest_rotation_efficiency": 0.75, # 0-1 scale
249
+ "head_movement_lateral": 2.5, # inches
250
+ "head_movement_vertical": 1.8, # inches
251
+
252
+ # Lower body mechanics
253
+ "knee_flexion_address": 25, # degrees
254
+ "knee_flexion_impact": 30, # degrees
255
+ "hip_thrust": 0.6, # 0-1 scale
256
+ "ground_force_efficiency": 0.7, # 0-1 scale
257
+
258
+ # Club path and face metrics
259
+ "swing_path": 2.5, # degrees (positive = out-to-in, negative = in-to-out)
260
+ "clubface_angle": 2.1, # degrees (positive = open, negative = closed)
261
+ "attack_angle": -4.2, # degrees (negative = descending, positive = ascending)
262
+ "club_path_consistency": 0.78, # 0-1 scale
263
+
264
+ # Tempo and timing metrics
265
+ "transition_smoothness": 0.75, # 0-1 scale
266
+ "backswing_duration": backswing_duration or 0.9, # seconds
267
+ "downswing_duration": downswing_duration or 0.3, # seconds
268
+ "kinematic_sequence": 0.82, # 0-1 scale
269
+
270
+ # Efficiency and power metrics
271
+ "energy_transfer": 0.78, # 0-1 scale
272
+ "potential_distance": 240, # yards
273
+ "power_accumulation": 0.75, # 0-1 scale
274
+ "speed_generation": "Arms-dominant" # String description
275
  }
276
 
277
  return analysis_data
 
299
 
300
  # Add trajectory information
301
  prompt += "\n## Trajectory Data\n"
302
+ if "trajectory" in analysis_data and "club_speed_mph" in analysis_data["trajectory"]:
 
303
  prompt += f"- Club Speed: {analysis_data['trajectory']['club_speed_mph']:.1f} mph\n"
304
+
305
+ # Add detailed biomechanical metrics
306
+ prompt += "\n## Swing Mechanics\n"
307
+
308
+ # Core body mechanics
309
+ prompt += "\n### Body Mechanics\n"
310
+ prompt += "- Tempo Ratio (Backswing:Downswing): {:.1f}\n".format(analysis_data["metrics"].get("tempo_ratio", 0))
311
+ prompt += "- Hip Rotation (degrees): {}\n".format(analysis_data["metrics"].get("hip_rotation", 0))
312
+ prompt += "- Shoulder Rotation (degrees): {}\n".format(analysis_data["metrics"].get("shoulder_rotation", 0))
313
+ prompt += "- Posture Score: {}%\n".format(int(analysis_data["metrics"].get("posture_score", 0) * 100))
314
+
315
+ # Upper body mechanics
316
+ prompt += "\n### Upper Body Mechanics\n"
317
+ prompt += "- Arm Extension (impact): {}%\n".format(int(analysis_data["metrics"].get("arm_extension", 0.8) * 100))
318
+ prompt += "- Wrist Hinge (degrees): {}\n".format(analysis_data["metrics"].get("wrist_hinge", 0))
319
+ prompt += "- Shoulder Plane Consistency: {}%\n".format(int(analysis_data["metrics"].get("swing_plane_consistency", 0) * 100))
320
+ prompt += "- Chest Rotation Efficiency: {}%\n".format(int(analysis_data["metrics"].get("chest_rotation_efficiency", 0.75) * 100))
321
+ prompt += "- Head Movement (lateral): {}in\n".format(analysis_data["metrics"].get("head_movement_lateral", 2.5))
322
+ prompt += "- Head Movement (vertical): {}in\n".format(analysis_data["metrics"].get("head_movement_vertical", 1.8))
323
+
324
+ # Lower body mechanics
325
+ prompt += "\n### Lower Body Mechanics\n"
326
+ prompt += "- Weight Shift (lead foot at impact): {}%\n".format(int(analysis_data["metrics"].get("weight_shift", 0) * 100))
327
+ prompt += "- Knee Flexion (address): {}°\n".format(analysis_data["metrics"].get("knee_flexion_address", 25))
328
+ prompt += "- Knee Flexion (impact): {}°\n".format(analysis_data["metrics"].get("knee_flexion_impact", 30))
329
+ prompt += "- Hip Thrust (impact): {}%\n".format(int(analysis_data["metrics"].get("hip_thrust", 0.6) * 100))
330
+ prompt += "- Ground Force Efficiency: {}%\n".format(int(analysis_data["metrics"].get("ground_force_efficiency", 0.7) * 100))
331
+
332
+ # Swing path and clubface metrics
333
+ prompt += "\n### Club Path & Face Metrics\n"
334
+ prompt += "- Swing Path (degrees): {} ({})\n".format(
335
+ analysis_data["metrics"].get("swing_path", 2.5),
336
+ "Out-to-In" if analysis_data["metrics"].get("swing_path", 0) > 0 else "In-to-Out")
337
+ prompt += "- Clubface Angle (degrees): {} ({})\n".format(
338
+ analysis_data["metrics"].get("clubface_angle", 2.1),
339
+ "Open" if analysis_data["metrics"].get("clubface_angle", 0) > 0 else "Closed")
340
+ prompt += "- Attack Angle (degrees): {} ({})\n".format(
341
+ analysis_data["metrics"].get("attack_angle", -4.2),
342
+ "Descending" if analysis_data["metrics"].get("attack_angle", 0) < 0 else "Ascending")
343
+ prompt += "- Club Path Consistency: {}%\n".format(int(analysis_data["metrics"].get("club_path_consistency", 0.78) * 100))
344
+
345
+ # Tempo and timing metrics
346
+ prompt += "\n### Tempo & Timing\n"
347
+ prompt += "- Transition Smoothness: {}%\n".format(int(analysis_data["metrics"].get("transition_smoothness", 0.75) * 100))
348
+ prompt += "- Backswing Duration: {} seconds\n".format(analysis_data["metrics"].get("backswing_duration", 0.9))
349
+ prompt += "- Downswing Duration: {} seconds\n".format(analysis_data["metrics"].get("downswing_duration", 0.3))
350
+ prompt += "- Sequential Kinematic Sequence: {}%\n".format(int(analysis_data["metrics"].get("kinematic_sequence", 0.82) * 100))
351
+
352
+ # Efficiency and power metrics
353
+ prompt += "\n### Efficiency & Power Metrics\n"
354
+ prompt += "- Energy Transfer Efficiency: {}%\n".format(int(analysis_data["metrics"].get("energy_transfer", 0.78) * 100))
355
+ prompt += "- Potential Distance: {} yards\n".format(analysis_data["metrics"].get("potential_distance", 240))
356
+ prompt += "- Power Accumulation: {}%\n".format(int(analysis_data["metrics"].get("power_accumulation", 0.75) * 100))
357
+ prompt += "- Speed Generation Method: {}\n".format(analysis_data["metrics"].get("speed_generation", "Arms-dominant"))
358
 
359
  prompt += """
 
 
 
 
 
360
 
361
+ Based on this detailed biomechanical data, please provide:
362
+
363
+ 1. A comprehensive analysis of the golf swing including:
364
+ - Detailed breakdown of each swing phase
365
+ - Analysis of body mechanics and kinematic sequence
366
+ - Assessment of power generation and efficiency
367
+ - Evaluation of clubface control and swing path
368
+
369
+ 2. Key strengths and weaknesses in the swing, including:
370
+ - Specific biomechanical inefficiencies
371
+ - Compensatory movements
372
+ - Physical limitations
373
+ - Technical flaws
374
+
375
+ 3. Prioritized recommendations for improvement:
376
+ - Top 3-5 most impactful changes to make
377
+ - Root cause analysis (why these issues are occurring)
378
+ - Expected improvement in performance from each change
379
+
380
+ 4. Specific drills and exercises addressing each issue:
381
+ - Technical drills for swing mechanics
382
+ - Physical exercises to address any biomechanical limitations
383
+ - Feel-based drills to develop proper movement patterns
384
+ - Practice routine recommendations
385
+
386
+ 5. Long-term development plan:
387
+ - Sequential order of what to work on
388
+ - Benchmarks for measuring progress
389
+ - Timeline for improvement
390
+
391
+ Please be specific, detailed, and actionable in your feedback, providing the kind of analysis a professional golf coach would give after a thorough assessment.
392
  """
393
 
394
  return prompt
app/models/swing_analyzer.py CHANGED
@@ -33,6 +33,15 @@ def segment_swing(pose_data, detections, sample_rate=5):
33
 
34
  if not frame_indices:
35
  return swing_phases
 
 
 
 
 
 
 
 
 
36
 
37
  # Calculate joint angles for each frame
38
  angles_by_frame = {}
@@ -115,6 +124,11 @@ def analyze_trajectory(frames, detections, swing_phases, sample_rate=5):
115
  dict: Dictionary mapping frame indices to trajectory data
116
  """
117
  trajectory_data = {}
 
 
 
 
 
118
 
119
  # Extract ball detections
120
  ball_detections = [d for d in detections if d.class_name == "sports ball"]
@@ -151,8 +165,9 @@ def analyze_trajectory(frames, detections, swing_phases, sample_rate=5):
151
  # Simplified club speed calculation
152
  # In reality, this would require tracking the club head specifically
153
  downswing_frames = swing_phases["downswing"]
154
- time_diff = (downswing_frames[-1] -
155
- downswing_frames[0]) / 30 # Assuming 30 fps
 
156
  if time_diff > 0:
157
  # Simplified speed calculation (just an example)
158
  club_speed = 100 * (1 / time_diff) # Arbitrary scaling
 
33
 
34
  if not frame_indices:
35
  return swing_phases
36
+
37
+ # Auto-adjust sample rate based on number of frames
38
+ # For short videos (less than 150 frames), don't skip any frames
39
+ if len(frame_indices) < 150 and sample_rate > 1:
40
+ # Get the max frame idx to understand video length
41
+ max_frame_idx = max(frame_indices) if frame_indices else 0
42
+ # For videos with less than 150 frames, use sample_rate=1
43
+ if max_frame_idx < 150:
44
+ sample_rate = 1
45
 
46
  # Calculate joint angles for each frame
47
  angles_by_frame = {}
 
124
  dict: Dictionary mapping frame indices to trajectory data
125
  """
126
  trajectory_data = {}
127
+
128
+ # Auto-adjust sample rate based on number of frames
129
+ # For short videos (less than 150 frames), don't skip any frames
130
+ if len(frames) < 150 and sample_rate > 1:
131
+ sample_rate = 1
132
 
133
  # Extract ball detections
134
  ball_detections = [d for d in detections if d.class_name == "sports ball"]
 
165
  # Simplified club speed calculation
166
  # In reality, this would require tracking the club head specifically
167
  downswing_frames = swing_phases["downswing"]
168
+ # Account for sample rate when calculating time difference
169
+ actual_frames_elapsed = (downswing_frames[-1] - downswing_frames[0]) * sample_rate
170
+ time_diff = actual_frames_elapsed / 30 # Assuming 30 fps
171
  if time_diff > 0:
172
  # Simplified speed calculation (just an example)
173
  club_speed = 100 * (1 / time_diff) # Arbitrary scaling
app/streamlit_app.py CHANGED
@@ -203,15 +203,6 @@ def main():
203
  detections,
204
  sample_rate=sample_rate)
205
 
206
- # Display swing phases
207
- st.subheader("Swing Phases")
208
- phase_cols = st.columns(5)
209
- for i, (phase,
210
- frames_in_phase) in enumerate(swing_phases.items()):
211
- with phase_cols[i]:
212
- st.metric(label=phase.capitalize(),
213
- value=f"{len(frames_in_phase)} frames")
214
-
215
  # Step 4: Analyze trajectory and speed
216
  with st.spinner("Analyzing trajectory and speed..."):
217
  trajectory_data = analyze_trajectory(frames,
@@ -219,28 +210,11 @@ def main():
219
  swing_phases,
220
  sample_rate=sample_rate)
221
 
222
- # Display club speed if available
223
- impact_frames = swing_phases.get("impact", [])
224
- if impact_frames:
225
- impact_frame = impact_frames[len(impact_frames) // 2]
226
- if impact_frame in trajectory_data and trajectory_data[
227
- impact_frame].get("club_speed"):
228
- st.subheader("Club Speed")
229
- st.metric(
230
- label="Estimated Club Speed",
231
- value=
232
- f"{trajectory_data[impact_frame]['club_speed']:.1f} mph"
233
- )
234
-
235
  # Prepare data for LLM regardless of whether GPT is enabled
236
  analysis_data = prepare_data_for_llm(pose_data, swing_phases,
237
  trajectory_data)
238
  prompt = create_llm_prompt(analysis_data)
239
 
240
- # Display the GPT prompt in an expander (hidden by default)
241
- with st.expander("View GPT Prompt", expanded=False):
242
- st.code(prompt, language="text")
243
-
244
  # Store analysis data in session state
245
  st.session_state.video_analyzed = True
246
  st.session_state.analysis_data = {
@@ -250,7 +224,9 @@ def main():
250
  'pose_data': pose_data,
251
  'swing_phases': swing_phases,
252
  'trajectory_data': trajectory_data,
253
- 'sample_rate': sample_rate
 
 
254
  }
255
 
256
  # Present the two options after analysis
@@ -269,6 +245,35 @@ def main():
269
 
270
  # Show action buttons and their results (only if analysis is complete)
271
  if st.session_state.video_analyzed:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  # Create columns for the two action buttons
273
  button_col1, button_col2 = st.columns(2)
274
 
 
203
  detections,
204
  sample_rate=sample_rate)
205
 
 
 
 
 
 
 
 
 
 
206
  # Step 4: Analyze trajectory and speed
207
  with st.spinner("Analyzing trajectory and speed..."):
208
  trajectory_data = analyze_trajectory(frames,
 
210
  swing_phases,
211
  sample_rate=sample_rate)
212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  # Prepare data for LLM regardless of whether GPT is enabled
214
  analysis_data = prepare_data_for_llm(pose_data, swing_phases,
215
  trajectory_data)
216
  prompt = create_llm_prompt(analysis_data)
217
 
 
 
 
 
218
  # Store analysis data in session state
219
  st.session_state.video_analyzed = True
220
  st.session_state.analysis_data = {
 
224
  'pose_data': pose_data,
225
  'swing_phases': swing_phases,
226
  'trajectory_data': trajectory_data,
227
+ 'sample_rate': sample_rate,
228
+ 'analysis_data': analysis_data,
229
+ 'prompt': prompt
230
  }
231
 
232
  # Present the two options after analysis
 
245
 
246
  # Show action buttons and their results (only if analysis is complete)
247
  if st.session_state.video_analyzed:
248
+ # Display swing phases
249
+ if 'swing_phases' in st.session_state.analysis_data:
250
+ swing_phases = st.session_state.analysis_data['swing_phases']
251
+ st.subheader("Swing Phases")
252
+ phase_cols = st.columns(5)
253
+ for i, (phase, frames_in_phase) in enumerate(swing_phases.items()):
254
+ with phase_cols[i]:
255
+ st.metric(label=phase.capitalize(),
256
+ value=f"{len(frames_in_phase)} frames")
257
+
258
+ # Display club speed if available
259
+ if 'trajectory_data' in st.session_state.analysis_data and 'swing_phases' in st.session_state.analysis_data:
260
+ trajectory_data = st.session_state.analysis_data['trajectory_data']
261
+ swing_phases = st.session_state.analysis_data['swing_phases']
262
+ impact_frames = swing_phases.get("impact", [])
263
+ if impact_frames:
264
+ impact_frame = impact_frames[len(impact_frames) // 2]
265
+ if impact_frame in trajectory_data and trajectory_data[impact_frame].get("club_speed"):
266
+ st.subheader("Club Speed")
267
+ st.metric(
268
+ label="Estimated Club Speed",
269
+ value=f"{trajectory_data[impact_frame]['club_speed']:.1f} mph"
270
+ )
271
+
272
+ # Display the GPT prompt in an expander
273
+ if 'prompt' in st.session_state.analysis_data:
274
+ with st.expander("View GPT Prompt", expanded=False):
275
+ st.code(st.session_state.analysis_data['prompt'], language="text")
276
+
277
  # Create columns for the two action buttons
278
  button_col1, button_col2 = st.columns(2)
279
 
app/utils/video_processor.py CHANGED
@@ -46,6 +46,12 @@ def process_video(video_path, sample_rate=5):
46
  # Get video properties
47
  frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
48
  fps = cap.get(cv2.CAP_PROP_FPS)
 
 
 
 
 
 
49
 
50
  frames = []
51
  detections = []
 
46
  # Get video properties
47
  frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
48
  fps = cap.get(cv2.CAP_PROP_FPS)
49
+
50
+ # Auto-adjust sample rate based on video length
51
+ # For short videos (less than 150 frames), don't skip any frames
52
+ if frame_count < 150 and sample_rate > 1:
53
+ print(f"Short video detected ({frame_count} frames). Processing all frames.")
54
+ sample_rate = 1
55
 
56
  frames = []
57
  detections = []
app/utils/visualizer.py CHANGED
@@ -58,6 +58,10 @@ def create_annotated_video(video_path,
58
  try:
59
  # Create output directory if it doesn't exist
60
  os.makedirs(output_dir, exist_ok=True)
 
 
 
 
61
 
62
  # Get original video filename without extension
63
  video_name = os.path.splitext(os.path.basename(video_path))[0]
@@ -143,7 +147,8 @@ def create_annotated_video(video_path,
143
  if keypoints[j] is not None and len(keypoints[j]) >= 2:
144
  try:
145
  x, y = keypoints[j][0], keypoints[j][1]
146
- keypoints[j] = (height - y - 1, x) # Adjusted to fix off-by-one errors
 
147
  except Exception as e:
148
  print(f"Error transforming keypoint {j}: {str(e)}, value: {keypoints[j]}")
149
  # Keep the keypoint as is if there's an error
@@ -152,8 +157,10 @@ def create_annotated_video(video_path,
152
  if detection.frame_idx == i * sample_rate:
153
  try:
154
  x1, y1, x2, y2 = detection.bbox
155
- # Transform bbox coordinates for 90 degree rotation
156
- detection.bbox = (height - y2 - 1, x1, height - y1 - 1, x2)
 
 
157
  except Exception as e:
158
  print(f"Error transforming detection bbox: {str(e)}")
159
  # Keep the bbox as is if there's an error
@@ -194,7 +201,8 @@ def create_annotated_video(video_path,
194
  if keypoints[j] is not None and len(keypoints[j]) >= 2:
195
  try:
196
  x, y = keypoints[j][0], keypoints[j][1]
197
- keypoints[j] = (y, width - x - 1)
 
198
  except Exception as e:
199
  print(f"Error transforming keypoint {j}: {str(e)}")
200
  # Keep the keypoint as is if there's an error
@@ -203,7 +211,10 @@ def create_annotated_video(video_path,
203
  if detection.frame_idx == i * sample_rate:
204
  try:
205
  x1, y1, x2, y2 = detection.bbox
206
- detection.bbox = (y1, width - x2 - 1, y2, width - x1 - 1)
 
 
 
207
  except Exception as e:
208
  print(f"Error transforming detection bbox: {str(e)}")
209
  # Keep the bbox as is if there's an error
 
58
  try:
59
  # Create output directory if it doesn't exist
60
  os.makedirs(output_dir, exist_ok=True)
61
+
62
+ # Check if sample rate should be adjusted for short videos
63
+ if len(frames) < 150 and sample_rate > 1:
64
+ sample_rate = 1
65
 
66
  # Get original video filename without extension
67
  video_name = os.path.splitext(os.path.basename(video_path))[0]
 
147
  if keypoints[j] is not None and len(keypoints[j]) >= 2:
148
  try:
149
  x, y = keypoints[j][0], keypoints[j][1]
150
+ # Fix coordinate transformation for 90-degree rotation
151
+ keypoints[j] = (y, width - x - 1)
152
  except Exception as e:
153
  print(f"Error transforming keypoint {j}: {str(e)}, value: {keypoints[j]}")
154
  # Keep the keypoint as is if there's an error
 
157
  if detection.frame_idx == i * sample_rate:
158
  try:
159
  x1, y1, x2, y2 = detection.bbox
160
+ # Fix bbox coordinate transformation for 90-degree rotation
161
+ # The correct transformation for 90 degrees counterclockwise is:
162
+ # (y1, width - x2 - 1, y2, width - x1 - 1)
163
+ detection.bbox = (y1, width - x2 - 1, y2, width - x1 - 1)
164
  except Exception as e:
165
  print(f"Error transforming detection bbox: {str(e)}")
166
  # Keep the bbox as is if there's an error
 
201
  if keypoints[j] is not None and len(keypoints[j]) >= 2:
202
  try:
203
  x, y = keypoints[j][0], keypoints[j][1]
204
+ # Fix coordinate transformation for 270-degree rotation
205
+ keypoints[j] = (height - y - 1, x)
206
  except Exception as e:
207
  print(f"Error transforming keypoint {j}: {str(e)}")
208
  # Keep the keypoint as is if there's an error
 
211
  if detection.frame_idx == i * sample_rate:
212
  try:
213
  x1, y1, x2, y2 = detection.bbox
214
+ # Fix bbox coordinate transformation for 270-degree rotation
215
+ # The correct transformation for 270 degrees counterclockwise is:
216
+ # (height - y2 - 1, x1, height - y1 - 1, x2)
217
+ detection.bbox = (height - y2 - 1, x1, height - y1 - 1, x2)
218
  except Exception as e:
219
  print(f"Error transforming detection bbox: {str(e)}")
220
  # Keep the bbox as is if there's an error