Files changed (1) hide show
  1. src/web_app.py +55 -13
src/web_app.py CHANGED
@@ -6,9 +6,11 @@ Supports image upload, video upload, and live webcam streaming
6
  from flask import Flask, render_template, request, jsonify, send_file
7
  from werkzeug.utils import secure_filename
8
  from pathlib import Path
 
9
  import cv2
10
  import numpy as np
11
  import base64
 
12
 
13
  from face_detection_yolov12 import YOLOv12FaceDetector, detect_from_video
14
 
@@ -21,7 +23,12 @@ UPLOAD_FOLDER = PROJECT_ROOT / "data" / "uploads"
21
  MODELS_DIR = PROJECT_ROOT / "models"
22
  ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif', 'mp4', 'avi', 'mov', 'mkv'}
23
  MAX_FILE_SIZE = 500 * 1024 * 1024 # 500MB
24
-
 
 
 
 
 
25
  UPLOAD_FOLDER.mkdir(exist_ok=True)
26
 
27
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
@@ -33,12 +40,33 @@ detector_cache = {}
33
 
34
  def get_detector(model_name):
35
  """Get or create detector instance (cached)"""
36
- if model_name not in detector_cache:
37
- model_path = MODELS_DIR / model_name
38
- if not model_path.exists():
39
- raise FileNotFoundError(f"Model not found: {model_path}")
40
- detector_cache[model_name] = YOLOv12FaceDetector(str(model_path))
41
- return detector_cache[model_name]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
 
44
  def allowed_file(filename):
@@ -80,6 +108,9 @@ def detect_image():
80
 
81
  # Get model selection
82
  model = request.form.get('model', 'yolov12l-face.pt')
 
 
 
83
 
84
  # Get detector
85
  detector = get_detector(model)
@@ -130,7 +161,8 @@ def detect_image():
130
  return jsonify(response)
131
 
132
  except Exception as e:
133
- return jsonify({'error': str(e)}), 500
 
134
 
135
 
136
  @app.route('/api/detect-video', methods=['POST'])
@@ -149,7 +181,10 @@ def detect_video():
149
 
150
  # Get model selection
151
  model = request.form.get('model', 'yolov12m-face.pt')
152
-
 
 
 
153
  # Save uploaded file
154
  filename = secure_filename(file.filename)
155
  input_path = UPLOAD_FOLDER / f"input_{filename}"
@@ -175,7 +210,9 @@ def detect_video():
175
  return jsonify(response)
176
 
177
  except Exception as e:
178
- return jsonify({'error': str(e)}), 500
 
 
179
 
180
 
181
  @app.route('/api/download/<filename>', methods=['GET'])
@@ -188,7 +225,9 @@ def download_file(filename):
188
 
189
  return send_file(filepath, as_attachment=True)
190
  except Exception as e:
191
- return jsonify({'error': str(e)}), 500
 
 
192
 
193
 
194
  @app.route('/api/models', methods=['GET'])
@@ -268,11 +307,14 @@ if __name__ == '__main__':
268
  print(" GET /api/models - Get available models")
269
  print(" GET /api/health - Health check")
270
  print("\n" + "="*70 + "\n")
 
 
 
271
 
272
  # Run Flask app
273
  app.run(
274
  host='0.0.0.0',
275
  port=7860,
276
- debug=True,
277
  use_reloader=False
278
- )
 
6
  from flask import Flask, render_template, request, jsonify, send_file
7
  from werkzeug.utils import secure_filename
8
  from pathlib import Path
9
+ import os
10
  import cv2
11
  import numpy as np
12
  import base64
13
+ import logging
14
 
15
  from face_detection_yolov12 import YOLOv12FaceDetector, detect_from_video
16
 
 
23
  MODELS_DIR = PROJECT_ROOT / "models"
24
  ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif', 'mp4', 'avi', 'mov', 'mkv'}
25
  MAX_FILE_SIZE = 500 * 1024 * 1024 # 500MB
26
+ ALLOWED_MODELS = {
27
+ 'yolov12n-face.pt',
28
+ 'yolov12s-face.pt',
29
+ 'yolov12m-face.pt',
30
+ 'yolov12l-face.pt'
31
+ }
32
  UPLOAD_FOLDER.mkdir(exist_ok=True)
33
 
34
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
 
40
 
41
  def get_detector(model_name):
42
  """Get or create detector instance (cached)"""
43
+ safe_name = secure_filename(model_name)
44
+
45
+ if safe_name not in ALLOWED_MODELS:
46
+ logging.error(f"Attempt to load unsupported model: {safe_name}")
47
+ raise ValueError(f"Unsupported model: {safe_name}")
48
+
49
+ if safe_name not in detector_cache:
50
+ model_path = MODELS_DIR / safe_name
51
+
52
+ try:
53
+ final_path = model_path.resolve()
54
+ safe_root = MODELS_DIR.resolve()
55
+ if not str(final_path).startswith(str(safe_root)):
56
+ logging.error(f"Security Alert: Symlink attack detected! {final_path}")
57
+ raise ValueError("Invalid model path (Symlink violation)")
58
+
59
+ except Exception as e:
60
+ logging.error(f"Error resolving model path: {str(e)}")
61
+ raise FileNotFoundError(f"Model path error: {str(e)}")
62
+
63
+ if not final_path.exists():
64
+ logging.error(f"Model file not found: {final_path}")
65
+ raise FileNotFoundError(f"Model not found: {final_path}")
66
+
67
+ detector_cache[safe_name] = YOLOv12FaceDetector(str(final_path))
68
+
69
+ return detector_cache[safe_name]
70
 
71
 
72
  def allowed_file(filename):
 
108
 
109
  # Get model selection
110
  model = request.form.get('model', 'yolov12l-face.pt')
111
+ if model not in ALLOWED_MODELS:
112
+ app.logger.info(f"Invalid model '{model}' requested. Fallback to default.")
113
+ model = 'yolov12l-face.pt'
114
 
115
  # Get detector
116
  detector = get_detector(model)
 
161
  return jsonify(response)
162
 
163
  except Exception as e:
164
+ logging.exception("Error during image detection")
165
+ return jsonify({'error': 'Internal server error during image detection'}), 500
166
 
167
 
168
  @app.route('/api/detect-video', methods=['POST'])
 
181
 
182
  # Get model selection
183
  model = request.form.get('model', 'yolov12m-face.pt')
184
+ if model not in ALLOWED_MODELS:
185
+ app.logger.info(f"Invalid model '{model}' requested. Fallback to default.")
186
+ model = 'yolov12m-face.pt'
187
+
188
  # Save uploaded file
189
  filename = secure_filename(file.filename)
190
  input_path = UPLOAD_FOLDER / f"input_{filename}"
 
210
  return jsonify(response)
211
 
212
  except Exception as e:
213
+ # Log the full exception server-side without exposing details to the client
214
+ app.logger.exception("Error while processing video detection request")
215
+ return jsonify({'error': 'Internal server error'}), 500
216
 
217
 
218
  @app.route('/api/download/<filename>', methods=['GET'])
 
225
 
226
  return send_file(filepath, as_attachment=True)
227
  except Exception as e:
228
+ # Log the full exception server-side without exposing details to the client
229
+ app.logger.exception("Error while processing download request for %s", filename)
230
+ return jsonify({'error': 'Internal server error'}), 500
231
 
232
 
233
  @app.route('/api/models', methods=['GET'])
 
307
  print(" GET /api/models - Get available models")
308
  print(" GET /api/health - Health check")
309
  print("\n" + "="*70 + "\n")
310
+
311
+ # Determine debug mode from environment (default: disabled)
312
+ debug_mode = os.getenv("FLASK_ENV") == "development"
313
 
314
  # Run Flask app
315
  app.run(
316
  host='0.0.0.0',
317
  port=7860,
318
+ debug=debug_mode,
319
  use_reloader=False
320
+ )