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__':
|