Spaces:
Paused
Paused
Prathamesh Sarjerao Vaidya
commited on
Commit
Β·
2dc4e60
1
Parent(s):
e501ce8
made changes
Browse files- Dockerfile +26 -11
- backend/app/main.py +14 -6
- requirements.txt β backend/requirements.txt +0 -0
- startup.sh +26 -16
Dockerfile
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# Multi-stage Dockerfile for Dance Movement Analyzer
|
| 2 |
-
# Optimized for Hugging Face Spaces
|
| 3 |
|
| 4 |
# Stage 1: Base image with dependencies
|
| 5 |
FROM python:3.10-slim as base
|
|
@@ -30,18 +30,35 @@ WORKDIR /app
|
|
| 30 |
FROM base as dependencies
|
| 31 |
|
| 32 |
# Copy requirements first for better caching
|
| 33 |
-
COPY requirements.txt .
|
| 34 |
|
| 35 |
# Install Python dependencies
|
| 36 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 37 |
|
| 38 |
-
# Pre-download MediaPipe models
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
# Stage 3: Production image
|
| 42 |
FROM base as production
|
| 43 |
|
| 44 |
-
# Copy installed packages from dependencies stage
|
| 45 |
COPY --from=dependencies /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
|
| 46 |
COPY --from=dependencies /usr/local/bin /usr/local/bin
|
| 47 |
|
|
@@ -60,25 +77,23 @@ COPY startup.sh /app/startup.sh
|
|
| 60 |
# Make startup script executable
|
| 61 |
RUN chmod +x /app/startup.sh
|
| 62 |
|
| 63 |
-
# Set permissions
|
| 64 |
RUN chmod -R 755 /app
|
| 65 |
|
| 66 |
# Create non-root user for security
|
| 67 |
RUN useradd -m -u 1000 appuser && \
|
| 68 |
chown -R appuser:appuser /app
|
| 69 |
|
|
|
|
| 70 |
USER appuser
|
| 71 |
|
| 72 |
-
# Expose
|
| 73 |
EXPOSE 8000
|
| 74 |
EXPOSE 7860
|
| 75 |
|
| 76 |
-
#
|
| 77 |
-
# Health check with 5 minute startup period (for model loading)
|
| 78 |
-
# This allows the container up to 5 minutes to start before health checks begin
|
| 79 |
HEALTHCHECK --interval=30s --timeout=10s --start-period=5m --retries=3 \
|
| 80 |
CMD python -c "import requests; requests.get('http://localhost:8000/health')" || exit 1
|
| 81 |
-
# β¬οΈ HEALTHCHECK ENDS HERE β¬οΈ
|
| 82 |
|
| 83 |
# Run the application using startup script
|
| 84 |
CMD ["/app/startup.sh"]
|
|
|
|
| 1 |
# Multi-stage Dockerfile for Dance Movement Analyzer
|
| 2 |
+
# Optimized for Hugging Face Spaces with MediaPipe model pre-download
|
| 3 |
|
| 4 |
# Stage 1: Base image with dependencies
|
| 5 |
FROM python:3.10-slim as base
|
|
|
|
| 30 |
FROM base as dependencies
|
| 31 |
|
| 32 |
# Copy requirements first for better caching
|
| 33 |
+
COPY backend/requirements.txt .
|
| 34 |
|
| 35 |
# Install Python dependencies
|
| 36 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 37 |
|
| 38 |
+
# Pre-download ALL MediaPipe models (as root before user switch)
|
| 39 |
+
# This downloads the models to the pip packages directory
|
| 40 |
+
RUN python3 -c "\
|
| 41 |
+
import mediapipe as mp; \
|
| 42 |
+
print('Downloading MediaPipe Pose models...'); \
|
| 43 |
+
# Model complexity 0 (Lite) \
|
| 44 |
+
pose0 = mp.solutions.pose.Pose(model_complexity=0, min_detection_confidence=0.5); \
|
| 45 |
+
pose0.close(); \
|
| 46 |
+
print('β
Lite model downloaded'); \
|
| 47 |
+
# Model complexity 1 (Full) \
|
| 48 |
+
pose1 = mp.solutions.pose.Pose(model_complexity=1, min_detection_confidence=0.5); \
|
| 49 |
+
pose1.close(); \
|
| 50 |
+
print('β
Full model downloaded'); \
|
| 51 |
+
# Model complexity 2 (Heavy) \
|
| 52 |
+
pose2 = mp.solutions.pose.Pose(model_complexity=2, min_detection_confidence=0.5); \
|
| 53 |
+
pose2.close(); \
|
| 54 |
+
print('β
Heavy model downloaded'); \
|
| 55 |
+
print('β
All MediaPipe models pre-downloaded successfully'); \
|
| 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 |
|
|
|
|
| 77 |
# Make startup script executable
|
| 78 |
RUN chmod +x /app/startup.sh
|
| 79 |
|
| 80 |
+
# Set permissions for all app files
|
| 81 |
RUN chmod -R 755 /app
|
| 82 |
|
| 83 |
# Create non-root user for security
|
| 84 |
RUN useradd -m -u 1000 appuser && \
|
| 85 |
chown -R appuser:appuser /app
|
| 86 |
|
| 87 |
+
# Switch to non-root user
|
| 88 |
USER appuser
|
| 89 |
|
| 90 |
+
# Expose ports (7860 for Hugging Face, 8000 for local)
|
| 91 |
EXPOSE 8000
|
| 92 |
EXPOSE 7860
|
| 93 |
|
| 94 |
+
# Health check with 5 minute startup period
|
|
|
|
|
|
|
| 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 |
# Run the application using startup script
|
| 99 |
CMD ["/app/startup.sh"]
|
backend/app/main.py
CHANGED
|
@@ -34,7 +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
|
|
@@ -44,17 +44,25 @@ async def lifespan(app: FastAPI):
|
|
| 44 |
Config.initialize_folders()
|
| 45 |
logger.info("β
Folders initialized")
|
| 46 |
|
| 47 |
-
#
|
| 48 |
-
logger.info("π¦
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
logger.info("π Application startup complete!")
|
| 53 |
|
| 54 |
yield
|
| 55 |
|
| 56 |
-
# Shutdown
|
| 57 |
logger.info("π Shutting down application...")
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
# Initialize FastAPI app with lifespan
|
| 60 |
app = FastAPI(
|
|
|
|
| 34 |
|
| 35 |
@asynccontextmanager
|
| 36 |
async def lifespan(app: FastAPI):
|
| 37 |
+
"""Lifespan event handler - models already downloaded in Docker build"""
|
| 38 |
global global_processor
|
| 39 |
|
| 40 |
# Startup
|
|
|
|
| 44 |
Config.initialize_folders()
|
| 45 |
logger.info("β
Folders initialized")
|
| 46 |
|
| 47 |
+
# Initialize VideoProcessor (models already downloaded, just instantiate)
|
| 48 |
+
logger.info("π¦ Initializing Video Processor...")
|
| 49 |
+
try:
|
| 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 |
|
| 59 |
yield
|
| 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(
|
requirements.txt β backend/requirements.txt
RENAMED
|
File without changes
|
startup.sh
CHANGED
|
@@ -3,32 +3,42 @@ set -e
|
|
| 3 |
|
| 4 |
echo "π Starting Dance Movement Analyzer..."
|
| 5 |
|
| 6 |
-
#
|
| 7 |
-
echo "π¦
|
| 8 |
python3 -c "
|
| 9 |
import mediapipe as mp
|
| 10 |
import logging
|
|
|
|
|
|
|
| 11 |
logging.basicConfig(level=logging.INFO)
|
| 12 |
logger = logging.getLogger(__name__)
|
| 13 |
|
| 14 |
try:
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
except Exception as e:
|
| 25 |
-
logger.error(f'β Error
|
| 26 |
-
# Continue anyway -
|
| 27 |
-
"
|
| 28 |
|
| 29 |
# Detect port (Hugging Face uses 7860, local uses 8000)
|
| 30 |
PORT=${PORT:-8000}
|
| 31 |
|
| 32 |
-
# Start the application
|
| 33 |
echo "π¬ Starting Uvicorn server on port $PORT..."
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 will be available at http://0.0.0.0:$PORT"
|
| 41 |
+
echo ""
|
| 42 |
+
|
| 43 |
+
# Start the application
|
| 44 |
+
exec uvicorn app.main:app --host 0.0.0.0 --port $PORT --workers 1 --log-level info
|