TwishaD11 commited on
Commit
53d4cbd
Β·
1 Parent(s): 7ddbcae

Add application file

Browse files
Files changed (5) hide show
  1. .docker-compose.yml +30 -0
  2. .dockerignore +90 -0
  3. Dockerfile +73 -0
  4. app.py +348 -0
  5. requirements.txt +7 -0
.docker-compose.yml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ squat-analyzer:
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile
8
+ ports:
9
+ - "7860:7860"
10
+ volumes:
11
+ # Mount temporary directory for video processing
12
+ - /tmp/squat-analyzer:/tmp/gradio
13
+ environment:
14
+ - GRADIO_SERVER_NAME=0.0.0.0
15
+ - GRADIO_SERVER_PORT=7860
16
+ healthcheck:
17
+ test: ["CMD", "curl", "-f", "http://localhost:7860/"]
18
+ interval: 30s
19
+ timeout: 10s
20
+ retries: 3
21
+ start_period: 30s
22
+ restart: unless-stopped
23
+ deploy:
24
+ resources:
25
+ limits:
26
+ memory: 2G
27
+ cpus: '1.0'
28
+ reservations:
29
+ memory: 1G
30
+ cpus: '0.5'
.dockerignore ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ share/python-wheels/
20
+ *.egg-info/
21
+ .installed.cfg
22
+ *.egg
23
+ MANIFEST
24
+
25
+ # Virtual environments
26
+ .env
27
+ .venv
28
+ env/
29
+ venv/
30
+ ENV/
31
+ env.bak/
32
+ venv.bak/
33
+
34
+ # IDE
35
+ .vscode/
36
+ .idea/
37
+ *.swp
38
+ *.swo
39
+ *~
40
+
41
+ # OS
42
+ .DS_Store
43
+ .DS_Store?
44
+ ._*
45
+ .Spotlight-V100
46
+ .Trashes
47
+ ehthumbs.db
48
+ Thumbs.db
49
+
50
+ # Git
51
+ .git/
52
+ .gitignore
53
+ .gitattributes
54
+
55
+ # Documentation
56
+ README.md
57
+ *.md
58
+ docs/
59
+
60
+ # Test files
61
+ tests/
62
+ test_*.py
63
+ *_test.py
64
+
65
+ # Jupyter notebooks
66
+ *.ipynb
67
+ .ipynb_checkpoints/
68
+
69
+ # Temporary files
70
+ tmp/
71
+ temp/
72
+ *.tmp
73
+ *.temp
74
+
75
+ # Logs
76
+ *.log
77
+ logs/
78
+
79
+ # Media files (examples)
80
+ *.mp4
81
+ *.avi
82
+ *.mov
83
+ *.mkv
84
+ *.wmv
85
+ *.flv
86
+ *.webm
87
+
88
+ # Cache
89
+ .cache/
90
+ *.cache
Dockerfile ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ # Create non-root user for security
4
+ RUN useradd -m -u 1000 user
5
+
6
+ # Install system dependencies needed for OpenCV and MediaPipe
7
+ RUN apt-get update && apt-get install -y \
8
+ libglib2.0-0 \
9
+ libsm6 \
10
+ libxext6 \
11
+ libxrender-dev \
12
+ libgomp1 \
13
+ libgstreamer1.0-0 \
14
+ libgstreamer-plugins-base1.0-0 \
15
+ libgstreamer-plugins-good1.0-0 \
16
+ libgstreamer-plugins-bad1.0-0 \
17
+ gstreamer1.0-plugins-ugly \
18
+ gstreamer1.0-tools \
19
+ ffmpeg \
20
+ libgl1-mesa-glx \
21
+ libglib2.0-0 \
22
+ wget \
23
+ curl \
24
+ && apt-get clean \
25
+ && rm -rf /var/lib/apt/lists/*
26
+
27
+ # Set working directory
28
+ WORKDIR /app
29
+
30
+ # Create necessary directories with proper permissions
31
+ RUN mkdir -p /tmp/matplotlib \
32
+ && mkdir -p /tmp/mediapipe \
33
+ && mkdir -p /tmp/gradio \
34
+ && mkdir -p /home/user/.cache/matplotlib \
35
+ && mkdir -p /home/user/.cache/pip \
36
+ && mkdir -p /home/user/.local/lib/python3.9/site-packages \
37
+ && chown -R user:user /app \
38
+ && chown -R user:user /tmp \
39
+ && chown -R user:user /home/user
40
+
41
+ # Set environment variables for headless operation
42
+ ENV OPENCV_IO_ENABLE_JASPER=1
43
+ ENV OPENCV_IO_ENABLE_OPENEXR=1
44
+ ENV MPLCONFIGDIR=/home/user/.cache/matplotlib
45
+ ENV MEDIAPIPE_CACHE_DIR=/tmp/mediapipe
46
+ ENV GRADIO_TEMP_DIR=/tmp/gradio
47
+ ENV HOME=/home/user
48
+ ENV PATH="/home/user/.local/bin:${PATH}"
49
+ ENV PYTHONPATH="${PYTHONPATH}:/home/user/.local/lib/python3.9/site-packages"
50
+
51
+ # Switch to non-root user
52
+ USER user
53
+
54
+ # Copy and install Python dependencies
55
+ COPY --chown=user:user requirements.txt .
56
+ RUN pip install --no-cache-dir --user --upgrade pip
57
+ RUN pip install --no-cache-dir --user -r requirements.txt
58
+
59
+ # Pre-download MediaPipe models to avoid runtime downloads
60
+ RUN python -c "import mediapipe as mp; mp.solutions.pose.Pose()"
61
+
62
+ # Copy application code
63
+ COPY --chown=user:user app.py .
64
+
65
+ # Expose the port that Gradio uses
66
+ EXPOSE 7860
67
+
68
+ # Health check to ensure the service is running
69
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
70
+ CMD curl -f http://localhost:7860/ || exit 1
71
+
72
+ # Run the application with proper Gradio server settings for Hugging Face
73
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import cv2
3
+ import numpy as np
4
+ import mediapipe as mp
5
+ import tempfile
6
+ import os
7
+ from typing import List, Tuple, Dict
8
+ import math
9
+ from pathlib import Path
10
+
11
+
12
+ class SquatFormAnalyzer:
13
+ def __init__(self):
14
+ self.mp_pose = mp.solutions.pose
15
+ self.pose = self.mp_pose.Pose(
16
+ static_image_mode=False,
17
+ model_complexity=2,
18
+ enable_segmentation=False,
19
+ min_detection_confidence=0.5,
20
+ min_tracking_confidence=0.5
21
+ )
22
+ self.mp_drawing = mp.solutions.drawing_utils
23
+
24
+ def calculate_angle(self, a, b, c):
25
+ """Calculate angle between three points"""
26
+ a = np.array(a)
27
+ b = np.array(b)
28
+ c = np.array(c)
29
+
30
+ radians = np.arctan2(c[1] - b[1], c[0] - b[0]) - np.arctan2(a[1] - b[1], a[0] - b[0])
31
+ angle = np.abs(radians * 180.0 / np.pi)
32
+
33
+ if angle > 180.0:
34
+ angle = 360 - angle
35
+
36
+ return angle
37
+
38
+ def analyze_knee_alignment(self, landmarks, frame_width, frame_height):
39
+ """Check for knees caving inward"""
40
+ try:
41
+ # Get hip, knee, and ankle landmarks
42
+ left_hip = [landmarks[self.mp_pose.PoseLandmark.LEFT_HIP.value].x * frame_width,
43
+ landmarks[self.mp_pose.PoseLandmark.LEFT_HIP.value].y * frame_height]
44
+ right_hip = [landmarks[self.mp_pose.PoseLandmark.RIGHT_HIP.value].x * frame_width,
45
+ landmarks[self.mp_pose.PoseLandmark.RIGHT_HIP.value].y * frame_height]
46
+ left_knee = [landmarks[self.mp_pose.PoseLandmark.LEFT_KNEE.value].x * frame_width,
47
+ landmarks[self.mp_pose.PoseLandmark.LEFT_KNEE.value].y * frame_height]
48
+ right_knee = [landmarks[self.mp_pose.PoseLandmark.RIGHT_KNEE.value].x * frame_width,
49
+ landmarks[self.mp_pose.PoseLandmark.RIGHT_KNEE.value].y * frame_height]
50
+ left_ankle = [landmarks[self.mp_pose.PoseLandmark.LEFT_ANKLE.value].x * frame_width,
51
+ landmarks[self.mp_pose.PoseLandmark.LEFT_ANKLE.value].y * frame_height]
52
+ right_ankle = [landmarks[self.mp_pose.PoseLandmark.RIGHT_ANKLE.value].x * frame_width,
53
+ landmarks[self.mp_pose.PoseLandmark.RIGHT_ANKLE.value].y * frame_height]
54
+
55
+ # Calculate knee angles relative to hip-ankle line
56
+ left_knee_angle = self.calculate_angle(left_hip, left_knee, left_ankle)
57
+ right_knee_angle = self.calculate_angle(right_hip, right_knee, right_ankle)
58
+
59
+ # Also check knee width ratio
60
+ hip_width = abs(left_hip[0] - right_hip[0])
61
+ knee_width = abs(left_knee[0] - right_knee[0])
62
+ ankle_width = abs(left_ankle[0] - right_ankle[0])
63
+
64
+ # Multiple criteria for knee valgus
65
+ knee_ratio = knee_width / hip_width if hip_width > 0 else 1
66
+ ankle_ratio = knee_width / ankle_width if ankle_width > 0 else 1
67
+
68
+ # Knees cave in if they're too close relative to hips and ankles
69
+ knee_valgus = (knee_ratio < 0.75 or ankle_ratio < 0.9 or
70
+ left_knee_angle < 160 or right_knee_angle < 160)
71
+
72
+ return knee_valgus
73
+
74
+ except (AttributeError, IndexError, ZeroDivisionError):
75
+ return False
76
+
77
+ def analyze_forward_lean(self, landmarks):
78
+ """Check for excessive forward lean"""
79
+ try:
80
+ # Get shoulder, hip, and ankle landmarks
81
+ left_shoulder = [landmarks[self.mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
82
+ landmarks[self.mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
83
+ right_shoulder = [landmarks[self.mp_pose.PoseLandmark.RIGHT_SHOULDER.value].x,
84
+ landmarks[self.mp_pose.PoseLandmark.RIGHT_SHOULDER.value].y]
85
+ left_hip = [landmarks[self.mp_pose.PoseLandmark.LEFT_HIP.value].x,
86
+ landmarks[self.mp_pose.PoseLandmark.LEFT_HIP.value].y]
87
+ right_hip = [landmarks[self.mp_pose.PoseLandmark.RIGHT_HIP.value].x,
88
+ landmarks[self.mp_pose.PoseLandmark.RIGHT_HIP.value].y]
89
+
90
+ # Calculate average positions
91
+ avg_shoulder = [(left_shoulder[0] + right_shoulder[0]) / 2,
92
+ (left_shoulder[1] + right_shoulder[1]) / 2]
93
+ avg_hip = [(left_hip[0] + right_hip[0]) / 2,
94
+ (left_hip[1] + right_hip[1]) / 2]
95
+
96
+ # Calculate forward lean angle
97
+ horizontal_distance = abs(avg_shoulder[0] - avg_hip[0])
98
+ vertical_distance = abs(avg_shoulder[1] - avg_hip[1])
99
+
100
+ if vertical_distance > 0:
101
+ lean_angle = math.degrees(math.atan(horizontal_distance / vertical_distance))
102
+ return lean_angle > 15 # More than 15 degrees forward lean
103
+
104
+ return False
105
+
106
+ except (AttributeError, IndexError, ZeroDivisionError):
107
+ return False
108
+
109
+ def analyze_depth(self, landmarks):
110
+ """Check if squat depth is adequate"""
111
+ try:
112
+ # Get hip, knee, and ankle landmarks
113
+ left_hip = [landmarks[self.mp_pose.PoseLandmark.LEFT_HIP.value].x,
114
+ landmarks[self.mp_pose.PoseLandmark.LEFT_HIP.value].y]
115
+ right_hip = [landmarks[self.mp_pose.PoseLandmark.RIGHT_HIP.value].x,
116
+ landmarks[self.mp_pose.PoseLandmark.RIGHT_HIP.value].y]
117
+ left_knee = [landmarks[self.mp_pose.PoseLandmark.LEFT_KNEE.value].x,
118
+ landmarks[self.mp_pose.PoseLandmark.LEFT_KNEE.value].y]
119
+ right_knee = [landmarks[self.mp_pose.PoseLandmark.RIGHT_KNEE.value].x,
120
+ landmarks[self.mp_pose.PoseLandmark.RIGHT_KNEE.value].y]
121
+
122
+ # Calculate average positions
123
+ avg_hip = [(left_hip[0] + right_hip[0]) / 2, (left_hip[1] + right_hip[1]) / 2]
124
+ avg_knee = [(left_knee[0] + right_knee[0]) / 2, (left_knee[1] + right_knee[1]) / 2]
125
+
126
+ # Check if hips drop below knees (good depth)
127
+ return avg_hip[1] > avg_knee[1] # In image coordinates, y increases downward
128
+
129
+ except (AttributeError, IndexError):
130
+ return False
131
+
132
+ def process_video(self, video_path):
133
+ """Process video and analyze squat form"""
134
+ cap = cv2.VideoCapture(video_path)
135
+
136
+ # Get video properties
137
+ fps = int(cap.get(cv2.CAP_PROP_FPS))
138
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
139
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
140
+
141
+ # Create output video writer
142
+ output_path = tempfile.mktemp(suffix='.mp4')
143
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
144
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
145
+
146
+ frame_count = 0
147
+ analysis_results = {
148
+ 'knee_inward': [],
149
+ 'forward_lean': [],
150
+ 'depth_issues': [],
151
+ 'total_frames': 0
152
+ }
153
+
154
+ while cap.isOpened():
155
+ ret, frame = cap.read()
156
+ if not ret:
157
+ break
158
+
159
+ frame_count += 1
160
+
161
+ # Convert BGR to RGB
162
+ rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
163
+
164
+ # Process frame with MediaPipe
165
+ results = self.pose.process(rgb_frame)
166
+
167
+ # Initialize error flags
168
+ knee_error = False
169
+ lean_error = False
170
+ depth_ok = True
171
+
172
+ if results.pose_landmarks:
173
+ # Draw pose landmarks
174
+ annotated_frame = frame.copy()
175
+ self.mp_drawing.draw_landmarks(
176
+ annotated_frame, results.pose_landmarks, self.mp_pose.POSE_CONNECTIONS)
177
+
178
+ # Analyze form
179
+ knee_error = self.analyze_knee_alignment(results.pose_landmarks.landmark, width, height)
180
+ lean_error = self.analyze_forward_lean(results.pose_landmarks.landmark)
181
+ depth_ok = self.analyze_depth(results.pose_landmarks.landmark)
182
+
183
+ # Add analysis results
184
+ analysis_results['knee_inward'].append(knee_error)
185
+ analysis_results['forward_lean'].append(lean_error)
186
+ analysis_results['depth_issues'].append(not depth_ok)
187
+
188
+ # Add status indicators
189
+ y_offset = 30
190
+ if knee_error:
191
+ cv2.putText(annotated_frame, "KNEE VALGUS", (10, y_offset),
192
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
193
+ cv2.circle(annotated_frame, (width - 50, y_offset - 10), 15, (0, 0, 255), -1)
194
+ y_offset += 40
195
+
196
+ if lean_error:
197
+ cv2.putText(annotated_frame, "FORWARD LEAN", (10, y_offset),
198
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
199
+ cv2.circle(annotated_frame, (width - 50, y_offset - 10), 15, (0, 0, 255), -1)
200
+ y_offset += 40
201
+
202
+ if not depth_ok:
203
+ cv2.putText(annotated_frame, "SHALLOW DEPTH", (10, y_offset),
204
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 165, 0), 2)
205
+ cv2.circle(annotated_frame, (width - 50, y_offset - 10), 15, (255, 165, 0), -1)
206
+
207
+ # Green light if form is good
208
+ if not knee_error and not lean_error and depth_ok:
209
+ cv2.putText(annotated_frame, "GOOD FORM", (10, 30),
210
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
211
+ cv2.circle(annotated_frame, (width - 50, 20), 15, (0, 255, 0), -1)
212
+
213
+ out.write(annotated_frame)
214
+ else:
215
+ out.write(frame)
216
+
217
+ cap.release()
218
+ out.release()
219
+
220
+ analysis_results['total_frames'] = frame_count
221
+
222
+ return output_path, analysis_results
223
+
224
+ def generate_summary(self, analysis_results):
225
+ """Generate analysis summary"""
226
+ total_frames = analysis_results['total_frames']
227
+ if total_frames == 0:
228
+ return "No pose detected in video"
229
+
230
+ knee_issues = sum(analysis_results['knee_inward'])
231
+ lean_issues = sum(analysis_results['forward_lean'])
232
+ depth_issues = sum(analysis_results['depth_issues'])
233
+
234
+ knee_percentage = (knee_issues / total_frames) * 100
235
+ lean_percentage = (lean_issues / total_frames) * 100
236
+ depth_percentage = (depth_issues / total_frames) * 100
237
+
238
+ summary = f"""
239
+ ## Squat Form Analysis Results
240
+
241
+ **Total Frames Analyzed:** {total_frames}
242
+
243
+ ### Form Issues Detected:
244
+
245
+ 🦡 **Knee Valgus (Knees Inward):** {knee_percentage:.1f}% of frames
246
+ {'❌ Significant issue - Focus on pushing knees out' if knee_percentage > 20 else 'βœ… Good knee alignment' if knee_percentage < 10 else '⚠️ Minor issue - Monitor knee tracking'}
247
+
248
+ πŸƒ **Forward Lean:** {lean_percentage:.1f}% of frames
249
+ {'❌ Excessive forward lean detected' if lean_percentage > 30 else 'βœ… Good posture' if lean_percentage < 15 else '⚠️ Some forward lean - Keep chest up'}
250
+
251
+ πŸ“ **Squat Depth:** {depth_percentage:.1f}% of frames with shallow depth
252
+ {'❌ Insufficient depth - Go lower' if depth_percentage > 50 else 'βœ… Good depth' if depth_percentage < 20 else '⚠️ Inconsistent depth'}
253
+
254
+ ### Recommendations:
255
+ """
256
+
257
+ if knee_percentage > 20:
258
+ summary += "\n- Focus on pushing your knees out in line with your toes"
259
+ summary += "\n- Strengthen your glutes and hip external rotators"
260
+
261
+ if lean_percentage > 30:
262
+ summary += "\n- Work on ankle mobility and thoracic spine extension"
263
+ summary += "\n- Keep your chest up and maintain a neutral spine"
264
+
265
+ if depth_percentage > 50:
266
+ summary += "\n- Improve ankle and hip mobility"
267
+ summary += "\n- Practice bodyweight squats to full depth"
268
+
269
+ if knee_percentage < 10 and lean_percentage < 15 and depth_percentage < 20:
270
+ summary += "\nβœ… **Excellent squat form! Keep up the great work!**"
271
+
272
+ return summary
273
+
274
+
275
+ # Initialize the analyzer
276
+ analyzer = SquatFormAnalyzer()
277
+
278
+
279
+ def analyze_squat_video(video_file):
280
+ """Main function to process uploaded video"""
281
+ if video_file is None:
282
+ return None, "Please upload a video file."
283
+
284
+ # Check file size (limit to 100MB)
285
+ file_size = os.path.getsize(video_file) / (1024 * 1024) # Size in MB
286
+ if file_size > 100:
287
+ return None, "File size too large. Please upload a video smaller than 100MB."
288
+
289
+ try:
290
+ # Process the video
291
+ output_video, results = analyzer.process_video(video_file)
292
+
293
+ # Generate summary
294
+ summary = analyzer.generate_summary(results)
295
+
296
+ return output_video, summary
297
+
298
+ except Exception as e:
299
+ return None, f"Error processing video: {str(e)}. Please ensure the video format is supported (MP4, AVI, MOV)."
300
+
301
+
302
+ # Create Gradio interface
303
+ demo = gr.Interface(
304
+ fn=analyze_squat_video,
305
+ inputs=[
306
+ gr.Video(
307
+ label="Upload Squat Video",
308
+ format="mp4"
309
+ )
310
+ ],
311
+ outputs=[
312
+ gr.Video(
313
+ label="Analyzed Video",
314
+ format="mp4"
315
+ ),
316
+ gr.Markdown(
317
+ label="Analysis Report"
318
+ )
319
+ ],
320
+ title="πŸ‹οΈ Squat Form Analyzer",
321
+ description="""
322
+ Upload a video of yourself performing squats to get real-time form analysis.
323
+
324
+ **The app will analyze:**
325
+ - Knee alignment (knees caving inward)
326
+ - Forward lean of the torso
327
+ - Squat depth
328
+
329
+ **Instructions:**
330
+ 1. Upload a clear video showing your full body
331
+ 2. Perform squats in the side view for best results
332
+ 3. Ensure good lighting and minimal background clutter
333
+ """,
334
+ examples=[],
335
+ cache_examples=False
336
+ )
337
+
338
+ if __name__ == "__main__":
339
+ # Launch with settings optimized for Docker and Hugging Face Spaces
340
+ demo.launch(
341
+ server_name="0.0.0.0", # Allow external connections
342
+ server_port=7860, # Standard port for Hugging Face Spaces
343
+ share=False, # Don't create public URLs in production
344
+ show_error=True, # Show detailed error messages
345
+ quiet=False, # Enable logging
346
+ enable_queue=True, # Handle multiple requests
347
+ max_threads=4 # Limit concurrent processing
348
+ )
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio==4.44.0
2
+ opencv-python-headless==4.8.1.78
3
+ mediapipe==0.10.8
4
+ numpy==1.24.3
5
+ Pillow==10.0.1
6
+ setuptools==68.2.2
7
+ wheel==0.41.2