Spaces:
Sleeping
Sleeping
Sean Carnahan commited on
Commit ·
5dd4f2e
1
Parent(s): cd361a4
Add robust error handling and logging for HF Spaces
Browse files- Dockerfile +6 -3
- app.py +47 -15
Dockerfile
CHANGED
|
@@ -12,12 +12,15 @@ RUN apt-get update && apt-get install -y \
|
|
| 12 |
COPY requirements.txt .
|
| 13 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
# Copy the rest of the application
|
| 16 |
COPY . .
|
| 17 |
|
| 18 |
-
# Create necessary directories
|
| 19 |
-
RUN mkdir -p static/uploads
|
| 20 |
-
|
| 21 |
# Set environment variables
|
| 22 |
ENV PYTHONUNBUFFERED=1
|
| 23 |
ENV FLASK_APP=app.py
|
|
|
|
| 12 |
COPY requirements.txt .
|
| 13 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 14 |
|
| 15 |
+
# Create necessary directories with proper permissions
|
| 16 |
+
RUN mkdir -p /code/static/uploads \
|
| 17 |
+
&& mkdir -p /code/logs \
|
| 18 |
+
&& chmod -R 777 /code/static/uploads \
|
| 19 |
+
&& chmod -R 777 /code/logs
|
| 20 |
+
|
| 21 |
# Copy the rest of the application
|
| 22 |
COPY . .
|
| 23 |
|
|
|
|
|
|
|
|
|
|
| 24 |
# Set environment variables
|
| 25 |
ENV PYTHONUNBUFFERED=1
|
| 26 |
ENV FLASK_APP=app.py
|
app.py
CHANGED
|
@@ -36,19 +36,35 @@ from bodybuilding_pose_analyzer.src.movenet_analyzer import MoveNetAnalyzer
|
|
| 36 |
from bodybuilding_pose_analyzer.src.pose_analyzer import PoseAnalyzer
|
| 37 |
|
| 38 |
# Configure logging
|
| 39 |
-
logging.basicConfig(
|
|
|
|
|
|
|
|
|
|
| 40 |
logger = logging.getLogger(__name__)
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
def log_memory_usage():
|
| 43 |
"""Log current memory usage."""
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
def cleanup_memory():
|
| 49 |
"""Force garbage collection and log memory usage."""
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
def wrap_text(text: str, font_face: int, font_scale: float, thickness: int, max_width: int) -> list[str]:
|
| 54 |
"""Wrap text to fit within max_width."""
|
|
@@ -94,8 +110,17 @@ app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB max file size
|
|
| 94 |
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
| 95 |
|
| 96 |
# Load CNN model for bodybuilding pose classification
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
cnn_class_labels = ['side_chest', 'front_double_biceps', 'back_double_biceps', 'front_lat_spread', 'back_lat_spread']
|
| 100 |
|
| 101 |
def predict_pose_cnn(img_path):
|
|
@@ -387,7 +412,7 @@ def index():
|
|
| 387 |
@app.route('/upload', methods=['POST'])
|
| 388 |
def upload_file():
|
| 389 |
try:
|
| 390 |
-
cleanup_memory()
|
| 391 |
if 'video' not in request.files:
|
| 392 |
logger.error("[UPLOAD] No video file in request")
|
| 393 |
return jsonify({'error': 'No video file provided'}), 400
|
|
@@ -413,6 +438,9 @@ def upload_file():
|
|
| 413 |
unique_filename = f"{base}_{int(time.time())}{ext}"
|
| 414 |
filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
|
| 415 |
|
|
|
|
|
|
|
|
|
|
| 416 |
logger.info(f"[UPLOAD] Saving file to: {filepath}")
|
| 417 |
file.save(filepath)
|
| 418 |
|
|
@@ -435,8 +463,9 @@ def upload_file():
|
|
| 435 |
|
| 436 |
logger.info(f"[UPLOAD] Processing complete. Output URL: {output_path_url}")
|
| 437 |
|
| 438 |
-
|
| 439 |
-
|
|
|
|
| 440 |
return jsonify({'error': 'Output video file not found'}), 500
|
| 441 |
|
| 442 |
return jsonify({
|
|
@@ -446,7 +475,7 @@ def upload_file():
|
|
| 446 |
|
| 447 |
except Exception as e:
|
| 448 |
logger.error(f"[UPLOAD] Error processing video: {str(e)}")
|
| 449 |
-
traceback.
|
| 450 |
return jsonify({'error': f'Error processing video: {str(e)}'}), 500
|
| 451 |
|
| 452 |
finally:
|
|
@@ -459,22 +488,25 @@ def upload_file():
|
|
| 459 |
|
| 460 |
except Exception as e:
|
| 461 |
logger.error(f"[UPLOAD] Unexpected error: {str(e)}")
|
| 462 |
-
traceback.
|
| 463 |
return jsonify({'error': 'Internal server error'}), 500
|
| 464 |
finally:
|
| 465 |
-
cleanup_memory()
|
| 466 |
|
| 467 |
-
# Add error handlers
|
| 468 |
@app.errorhandler(413)
|
| 469 |
def request_entity_too_large(error):
|
|
|
|
| 470 |
return jsonify({'error': 'File too large. Maximum size is 100MB'}), 413
|
| 471 |
|
| 472 |
@app.errorhandler(500)
|
| 473 |
def internal_server_error(error):
|
|
|
|
| 474 |
return jsonify({'error': 'Internal server error. Please try again later.'}), 500
|
| 475 |
|
| 476 |
@app.errorhandler(404)
|
| 477 |
def not_found_error(error):
|
|
|
|
| 478 |
return jsonify({'error': 'Resource not found'}), 404
|
| 479 |
|
| 480 |
if __name__ == '__main__':
|
|
|
|
| 36 |
from bodybuilding_pose_analyzer.src.pose_analyzer import PoseAnalyzer
|
| 37 |
|
| 38 |
# Configure logging
|
| 39 |
+
logging.basicConfig(
|
| 40 |
+
level=logging.INFO,
|
| 41 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| 42 |
+
)
|
| 43 |
logger = logging.getLogger(__name__)
|
| 44 |
|
| 45 |
+
# Add file handler for persistent logging
|
| 46 |
+
log_dir = 'logs'
|
| 47 |
+
os.makedirs(log_dir, exist_ok=True)
|
| 48 |
+
file_handler = logging.FileHandler(os.path.join(log_dir, 'app.log'))
|
| 49 |
+
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
|
| 50 |
+
logger.addHandler(file_handler)
|
| 51 |
+
|
| 52 |
def log_memory_usage():
|
| 53 |
"""Log current memory usage."""
|
| 54 |
+
try:
|
| 55 |
+
process = psutil.Process()
|
| 56 |
+
memory_info = process.memory_info()
|
| 57 |
+
logger.info(f"Memory usage: {memory_info.rss / 1024 / 1024:.2f} MB")
|
| 58 |
+
except Exception as e:
|
| 59 |
+
logger.error(f"Error logging memory usage: {e}")
|
| 60 |
|
| 61 |
def cleanup_memory():
|
| 62 |
"""Force garbage collection and log memory usage."""
|
| 63 |
+
try:
|
| 64 |
+
gc.collect()
|
| 65 |
+
log_memory_usage()
|
| 66 |
+
except Exception as e:
|
| 67 |
+
logger.error(f"Error in cleanup_memory: {e}")
|
| 68 |
|
| 69 |
def wrap_text(text: str, font_face: int, font_scale: float, thickness: int, max_width: int) -> list[str]:
|
| 70 |
"""Wrap text to fit within max_width."""
|
|
|
|
| 110 |
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
| 111 |
|
| 112 |
# Load CNN model for bodybuilding pose classification
|
| 113 |
+
try:
|
| 114 |
+
logger.info("Loading CNN model...")
|
| 115 |
+
cnn_model_path = 'external/BodybuildingPoseClassifier/bodybuilding_pose_classifier.h5'
|
| 116 |
+
if not os.path.exists(cnn_model_path):
|
| 117 |
+
raise FileNotFoundError(f"CNN model not found at {cnn_model_path}")
|
| 118 |
+
cnn_model = load_model(cnn_model_path)
|
| 119 |
+
logger.info("CNN model loaded successfully")
|
| 120 |
+
except Exception as e:
|
| 121 |
+
logger.error(f"Error loading CNN model: {e}")
|
| 122 |
+
raise
|
| 123 |
+
|
| 124 |
cnn_class_labels = ['side_chest', 'front_double_biceps', 'back_double_biceps', 'front_lat_spread', 'back_lat_spread']
|
| 125 |
|
| 126 |
def predict_pose_cnn(img_path):
|
|
|
|
| 412 |
@app.route('/upload', methods=['POST'])
|
| 413 |
def upload_file():
|
| 414 |
try:
|
| 415 |
+
cleanup_memory()
|
| 416 |
if 'video' not in request.files:
|
| 417 |
logger.error("[UPLOAD] No video file in request")
|
| 418 |
return jsonify({'error': 'No video file provided'}), 400
|
|
|
|
| 438 |
unique_filename = f"{base}_{int(time.time())}{ext}"
|
| 439 |
filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
|
| 440 |
|
| 441 |
+
# Ensure upload directory exists
|
| 442 |
+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
| 443 |
+
|
| 444 |
logger.info(f"[UPLOAD] Saving file to: {filepath}")
|
| 445 |
file.save(filepath)
|
| 446 |
|
|
|
|
| 463 |
|
| 464 |
logger.info(f"[UPLOAD] Processing complete. Output URL: {output_path_url}")
|
| 465 |
|
| 466 |
+
output_path = os.path.join(app.config['UPLOAD_FOLDER'], os.path.basename(output_path_url))
|
| 467 |
+
if not os.path.exists(output_path):
|
| 468 |
+
logger.error(f"[UPLOAD] Output file not found: {output_path}")
|
| 469 |
return jsonify({'error': 'Output video file not found'}), 500
|
| 470 |
|
| 471 |
return jsonify({
|
|
|
|
| 475 |
|
| 476 |
except Exception as e:
|
| 477 |
logger.error(f"[UPLOAD] Error processing video: {str(e)}")
|
| 478 |
+
logger.error(traceback.format_exc())
|
| 479 |
return jsonify({'error': f'Error processing video: {str(e)}'}), 500
|
| 480 |
|
| 481 |
finally:
|
|
|
|
| 488 |
|
| 489 |
except Exception as e:
|
| 490 |
logger.error(f"[UPLOAD] Unexpected error: {str(e)}")
|
| 491 |
+
logger.error(traceback.format_exc())
|
| 492 |
return jsonify({'error': 'Internal server error'}), 500
|
| 493 |
finally:
|
| 494 |
+
cleanup_memory()
|
| 495 |
|
| 496 |
+
# Add more specific error handlers
|
| 497 |
@app.errorhandler(413)
|
| 498 |
def request_entity_too_large(error):
|
| 499 |
+
logger.error(f"File too large: {error}")
|
| 500 |
return jsonify({'error': 'File too large. Maximum size is 100MB'}), 413
|
| 501 |
|
| 502 |
@app.errorhandler(500)
|
| 503 |
def internal_server_error(error):
|
| 504 |
+
logger.error(f"Internal server error: {error}")
|
| 505 |
return jsonify({'error': 'Internal server error. Please try again later.'}), 500
|
| 506 |
|
| 507 |
@app.errorhandler(404)
|
| 508 |
def not_found_error(error):
|
| 509 |
+
logger.error(f"Resource not found: {error}")
|
| 510 |
return jsonify({'error': 'Resource not found'}), 404
|
| 511 |
|
| 512 |
if __name__ == '__main__':
|