Spaces:
Paused
Paused
Prathamesh Sarjerao Vaidya
commited on
Commit
Β·
b5cf10c
1
Parent(s):
2dc4e60
made changes
Browse files- Dockerfile +40 -52
- backend/app/main.py +15 -16
- startup.sh +2 -31
Dockerfile
CHANGED
|
@@ -1,8 +1,7 @@
|
|
| 1 |
-
#
|
| 2 |
-
#
|
| 3 |
|
| 4 |
-
|
| 5 |
-
FROM python:3.10-slim as base
|
| 6 |
|
| 7 |
# Set environment variables
|
| 8 |
ENV PYTHONUNBUFFERED=1 \
|
|
@@ -13,7 +12,7 @@ ENV PYTHONUNBUFFERED=1 \
|
|
| 13 |
|
| 14 |
# Install system dependencies
|
| 15 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 16 |
-
libgl1 \
|
| 17 |
libglib2.0-0 \
|
| 18 |
libsm6 \
|
| 19 |
libxext6 \
|
|
@@ -26,74 +25,63 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
| 26 |
# Create app directory
|
| 27 |
WORKDIR /app
|
| 28 |
|
| 29 |
-
#
|
| 30 |
-
FROM base as dependencies
|
| 31 |
-
|
| 32 |
-
# Copy requirements first for better caching
|
| 33 |
COPY backend/requirements.txt .
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
# Stage 3: Production image
|
| 59 |
-
FROM base as production
|
| 60 |
-
|
| 61 |
-
# Copy installed packages from dependencies stage (includes downloaded models)
|
| 62 |
-
COPY --from=dependencies /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
|
| 63 |
-
COPY --from=dependencies /usr/local/bin /usr/local/bin
|
| 64 |
|
| 65 |
# Create necessary directories
|
| 66 |
RUN mkdir -p /app/uploads /app/outputs /app/logs
|
| 67 |
|
| 68 |
-
# Copy
|
| 69 |
COPY backend/app /app/app
|
| 70 |
-
|
| 71 |
-
# Copy frontend files
|
| 72 |
COPY frontend /app/frontend
|
| 73 |
|
| 74 |
# Copy startup script
|
| 75 |
COPY startup.sh /app/startup.sh
|
| 76 |
-
|
| 77 |
-
# Make startup script executable
|
| 78 |
RUN chmod +x /app/startup.sh
|
| 79 |
|
| 80 |
-
#
|
| 81 |
-
RUN
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
-
#
|
| 84 |
-
RUN
|
| 85 |
-
chown -R
|
| 86 |
|
| 87 |
# Switch to non-root user
|
| 88 |
USER appuser
|
| 89 |
|
| 90 |
-
# Expose ports
|
| 91 |
EXPOSE 8000
|
| 92 |
EXPOSE 7860
|
| 93 |
|
| 94 |
-
# Health check
|
| 95 |
HEALTHCHECK --interval=30s --timeout=10s --start-period=5m --retries=3 \
|
| 96 |
CMD python -c "import requests; requests.get('http://localhost:8000/health')" || exit 1
|
| 97 |
|
| 98 |
-
#
|
| 99 |
CMD ["/app/startup.sh"]
|
|
|
|
| 1 |
+
# Optimized Dockerfile for Dance Movement Analyzer
|
| 2 |
+
# Fixes MediaPipe model permission issues
|
| 3 |
|
| 4 |
+
FROM python:3.10-slim
|
|
|
|
| 5 |
|
| 6 |
# Set environment variables
|
| 7 |
ENV PYTHONUNBUFFERED=1 \
|
|
|
|
| 12 |
|
| 13 |
# Install system dependencies
|
| 14 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 15 |
+
libgl1-mesa-glx \
|
| 16 |
libglib2.0-0 \
|
| 17 |
libsm6 \
|
| 18 |
libxext6 \
|
|
|
|
| 25 |
# Create app directory
|
| 26 |
WORKDIR /app
|
| 27 |
|
| 28 |
+
# Copy and install Python dependencies
|
|
|
|
|
|
|
|
|
|
| 29 |
COPY backend/requirements.txt .
|
| 30 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
| 31 |
+
pip install --no-cache-dir -r requirements.txt
|
| 32 |
+
|
| 33 |
+
# Pre-download MediaPipe models (as root, before creating non-root user)
|
| 34 |
+
RUN echo "Downloading MediaPipe Pose models..." && \
|
| 35 |
+
python3 -c "import mediapipe as mp; \
|
| 36 |
+
print('Downloading Lite model...'); \
|
| 37 |
+
pose0 = mp.solutions.pose.Pose(model_complexity=0, min_detection_confidence=0.5); \
|
| 38 |
+
pose0.close(); \
|
| 39 |
+
print('β
Lite model downloaded'); \
|
| 40 |
+
print('Downloading Full model...'); \
|
| 41 |
+
pose1 = mp.solutions.pose.Pose(model_complexity=1, min_detection_confidence=0.5); \
|
| 42 |
+
pose1.close(); \
|
| 43 |
+
print('β
Full model downloaded'); \
|
| 44 |
+
print('Downloading Heavy model...'); \
|
| 45 |
+
pose2 = mp.solutions.pose.Pose(model_complexity=2, min_detection_confidence=0.5); \
|
| 46 |
+
pose2.close(); \
|
| 47 |
+
print('β
Heavy model downloaded'); \
|
| 48 |
+
print('β
All models pre-downloaded successfully');" && \
|
| 49 |
+
echo "MediaPipe models downloaded successfully"
|
| 50 |
+
|
| 51 |
+
# Set proper permissions for MediaPipe models directory
|
| 52 |
+
RUN chmod -R 755 /usr/local/lib/python3.10/site-packages/mediapipe
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
# Create necessary directories
|
| 55 |
RUN mkdir -p /app/uploads /app/outputs /app/logs
|
| 56 |
|
| 57 |
+
# Copy application files
|
| 58 |
COPY backend/app /app/app
|
|
|
|
|
|
|
| 59 |
COPY frontend /app/frontend
|
| 60 |
|
| 61 |
# Copy startup script
|
| 62 |
COPY startup.sh /app/startup.sh
|
|
|
|
|
|
|
| 63 |
RUN chmod +x /app/startup.sh
|
| 64 |
|
| 65 |
+
# Create non-root user
|
| 66 |
+
RUN useradd -m -u 1000 appuser
|
| 67 |
+
|
| 68 |
+
# Set ownership of app directory
|
| 69 |
+
RUN chown -R appuser:appuser /app
|
| 70 |
|
| 71 |
+
# IMPORTANT: Keep MediaPipe models readable by non-root user
|
| 72 |
+
RUN chmod -R 755 /usr/local/lib/python3.10/site-packages/mediapipe && \
|
| 73 |
+
chown -R root:root /usr/local/lib/python3.10/site-packages/mediapipe
|
| 74 |
|
| 75 |
# Switch to non-root user
|
| 76 |
USER appuser
|
| 77 |
|
| 78 |
+
# Expose ports
|
| 79 |
EXPOSE 8000
|
| 80 |
EXPOSE 7860
|
| 81 |
|
| 82 |
+
# Health check
|
| 83 |
HEALTHCHECK --interval=30s --timeout=10s --start-period=5m --retries=3 \
|
| 84 |
CMD python -c "import requests; requests.get('http://localhost:8000/health')" || exit 1
|
| 85 |
|
| 86 |
+
# Start application
|
| 87 |
CMD ["/app/startup.sh"]
|
backend/app/main.py
CHANGED
|
@@ -34,8 +34,7 @@ global_processor: Optional[VideoProcessor] = None
|
|
| 34 |
|
| 35 |
@asynccontextmanager
|
| 36 |
async def lifespan(app: FastAPI):
|
| 37 |
-
"""Lifespan event handler -
|
| 38 |
-
global global_processor
|
| 39 |
|
| 40 |
# Startup
|
| 41 |
logger.info("π Starting Dance Movement Analyzer...")
|
|
@@ -44,15 +43,9 @@ async def lifespan(app: FastAPI):
|
|
| 44 |
Config.initialize_folders()
|
| 45 |
logger.info("β
Folders initialized")
|
| 46 |
|
| 47 |
-
#
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
global_processor = VideoProcessor()
|
| 51 |
-
logger.info("β
Video Processor initialized successfully")
|
| 52 |
-
except Exception as e:
|
| 53 |
-
logger.error(f"β Error initializing Video Processor: {e}")
|
| 54 |
-
# Continue anyway - will retry on first request
|
| 55 |
-
global_processor = None
|
| 56 |
|
| 57 |
logger.info("π Application startup complete!")
|
| 58 |
|
|
@@ -60,9 +53,6 @@ async def lifespan(app: FastAPI):
|
|
| 60 |
|
| 61 |
# Shutdown
|
| 62 |
logger.info("π Shutting down application...")
|
| 63 |
-
if global_processor is not None:
|
| 64 |
-
# Cleanup if needed
|
| 65 |
-
pass
|
| 66 |
|
| 67 |
# Initialize FastAPI app with lifespan
|
| 68 |
app = FastAPI(
|
|
@@ -175,11 +165,20 @@ async def health_check():
|
|
| 175 |
return {
|
| 176 |
"status": "healthy",
|
| 177 |
"models_loaded": global_processor is not None,
|
|
|
|
| 178 |
"timestamp": datetime.now().isoformat(),
|
| 179 |
-
"active_sessions": len(processing_sessions)
|
| 180 |
-
"active_connections": len(manager.active_connections)
|
| 181 |
}
|
| 182 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
@app.post("/api/upload")
|
| 184 |
async def upload_video(file: UploadFile = File(...)):
|
| 185 |
"""Upload a video file for processing"""
|
|
|
|
| 34 |
|
| 35 |
@asynccontextmanager
|
| 36 |
async def lifespan(app: FastAPI):
|
| 37 |
+
"""Lifespan event handler - lazy model loading"""
|
|
|
|
| 38 |
|
| 39 |
# Startup
|
| 40 |
logger.info("π Starting Dance Movement Analyzer...")
|
|
|
|
| 43 |
Config.initialize_folders()
|
| 44 |
logger.info("β
Folders initialized")
|
| 45 |
|
| 46 |
+
# Don't initialize VideoProcessor here - let it lazy load on first request
|
| 47 |
+
# This avoids the permission error during startup
|
| 48 |
+
logger.info("π¦ Models pre-downloaded, will initialize on first request")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
logger.info("π Application startup complete!")
|
| 51 |
|
|
|
|
| 53 |
|
| 54 |
# Shutdown
|
| 55 |
logger.info("π Shutting down application...")
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
# Initialize FastAPI app with lifespan
|
| 58 |
app = FastAPI(
|
|
|
|
| 165 |
return {
|
| 166 |
"status": "healthy",
|
| 167 |
"models_loaded": global_processor is not None,
|
| 168 |
+
"models_ready": True, # Models are pre-downloaded
|
| 169 |
"timestamp": datetime.now().isoformat(),
|
| 170 |
+
"active_sessions": len(processing_sessions)
|
|
|
|
| 171 |
}
|
| 172 |
|
| 173 |
+
def get_video_processor() -> VideoProcessor:
|
| 174 |
+
"""Get or create the global VideoProcessor instance"""
|
| 175 |
+
global global_processor
|
| 176 |
+
if global_processor is None:
|
| 177 |
+
logger.info("Initializing VideoProcessor (first use)...")
|
| 178 |
+
global_processor = VideoProcessor()
|
| 179 |
+
logger.info("β
VideoProcessor initialized")
|
| 180 |
+
return global_processor
|
| 181 |
+
|
| 182 |
@app.post("/api/upload")
|
| 183 |
async def upload_video(file: UploadFile = File(...)):
|
| 184 |
"""Upload a video file for processing"""
|
startup.sh
CHANGED
|
@@ -2,42 +2,13 @@
|
|
| 2 |
set -e
|
| 3 |
|
| 4 |
echo "π Starting Dance Movement Analyzer..."
|
| 5 |
-
|
| 6 |
-
# Verify MediaPipe models are available
|
| 7 |
-
echo "π¦ Verifying MediaPipe models..."
|
| 8 |
-
python3 -c "
|
| 9 |
-
import mediapipe as mp
|
| 10 |
-
import logging
|
| 11 |
-
import os
|
| 12 |
-
|
| 13 |
-
logging.basicConfig(level=logging.INFO)
|
| 14 |
-
logger = logging.getLogger(__name__)
|
| 15 |
-
|
| 16 |
-
try:
|
| 17 |
-
# Quick verification that models are accessible
|
| 18 |
-
logger.info('Checking MediaPipe installation...')
|
| 19 |
-
|
| 20 |
-
# Verify model files exist
|
| 21 |
-
model_path = '/usr/local/lib/python3.10/site-packages/mediapipe/modules/pose_landmark'
|
| 22 |
-
if os.path.exists(model_path):
|
| 23 |
-
logger.info(f'β
Model directory found: {model_path}')
|
| 24 |
-
model_files = os.listdir(model_path)
|
| 25 |
-
logger.info(f'Available models: {len(model_files)} files')
|
| 26 |
-
else:
|
| 27 |
-
logger.warning('β οΈ Model directory not found, models will load on first use')
|
| 28 |
-
|
| 29 |
-
logger.info('β
MediaPipe ready')
|
| 30 |
-
|
| 31 |
-
except Exception as e:
|
| 32 |
-
logger.error(f'β Error verifying models: {e}')
|
| 33 |
-
# Continue anyway - application will handle it
|
| 34 |
-
"
|
| 35 |
|
| 36 |
# Detect port (Hugging Face uses 7860, local uses 8000)
|
| 37 |
PORT=${PORT:-8000}
|
| 38 |
|
| 39 |
echo "π¬ Starting Uvicorn server on port $PORT..."
|
| 40 |
-
echo "π Application
|
| 41 |
echo ""
|
| 42 |
|
| 43 |
# Start the application
|
|
|
|
| 2 |
set -e
|
| 3 |
|
| 4 |
echo "π Starting Dance Movement Analyzer..."
|
| 5 |
+
echo "π¦ MediaPipe models pre-downloaded during build"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
# Detect port (Hugging Face uses 7860, local uses 8000)
|
| 8 |
PORT=${PORT:-8000}
|
| 9 |
|
| 10 |
echo "π¬ Starting Uvicorn server on port $PORT..."
|
| 11 |
+
echo "π Application available at http://0.0.0.0:$PORT"
|
| 12 |
echo ""
|
| 13 |
|
| 14 |
# Start the application
|