webapp1 commited on
Commit
6193b4b
·
verified ·
1 Parent(s): d67ae34

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +398 -204
app.py CHANGED
@@ -1,5 +1,14 @@
1
  import os
2
- import io
 
 
 
 
 
 
 
 
 
3
  import cv2
4
  import base64
5
  import torch
@@ -18,44 +27,50 @@ from email.message import EmailMessage
18
  from flask import Flask, request, render_template, jsonify, Response, send_from_directory
19
  from werkzeug.utils import secure_filename
20
 
 
 
 
 
 
 
 
21
  # --- Auto-install missing libraries ---
22
  try:
23
  from ultralytics import YOLO
24
  import easyocr
 
25
  except ModuleNotFoundError:
26
  import sys
27
  import subprocess
28
- print("Installing libraries... This might take a minute...")
29
- subprocess.check_call([sys.executable, "-m", "pip", "install", "ultralytics", "scikit-learn==1.6.1", "easyocr", "pandas", "requests"])
30
  from ultralytics import YOLO
31
  import easyocr
 
32
 
33
  from torchvision.models.video import r3d_18
34
 
 
 
35
  app = Flask(__name__)
36
 
37
- # ==========================================
38
- # 🚨 ALERT CONFIGURATION (EMAIL SETUP) 🚨
39
- # ==========================================
40
  ALERT_EMAIL_SENDER = "gowreeshgowri50@gmail.com"
41
  ALERT_EMAIL_PASSWORD = "omzw fjsu nwnr sgvl"
42
  ALERT_EMAIL_RECEIVER = "ridhinmr32@gmail.com"
43
  ENABLE_EMAIL_ALERTS = True
44
 
45
- # --- Configurations ---
46
  UPLOAD_FOLDER = 'uploads'
47
  MODEL_FOLDER = 'models'
48
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
49
-
50
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
51
  os.makedirs(MODEL_FOLDER, exist_ok=True)
52
 
53
  device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
54
  classes = ['major', 'minor', 'moderate']
 
55
 
56
- # --- Load Models ---
57
- print("Loading AI Models & ALPR... This might take a few seconds.")
58
  models_loaded = False
 
59
  try:
60
  yolo_path = os.path.join(MODEL_FOLDER, 'yolov8_accident_model.pt')
61
  model_yolo = YOLO(yolo_path)
@@ -70,21 +85,25 @@ try:
70
  svm_path = os.path.join(MODEL_FOLDER, 'ensemble_svm_model.pkl')
71
  model_svm = joblib.load(svm_path)
72
 
73
- print("Loading OCR Engine...")
74
  ocr_reader = easyocr.Reader(['en'], gpu=torch.cuda.is_available())
75
-
76
  models_loaded = True
77
- print("✅ Visual AI Models & OCR Loaded Successfully!")
 
 
 
 
 
 
 
 
78
  except Exception as e:
79
- print(f"⚠️ Warning: Could not load real visual models. Using mock simulation. Error: {e}")
80
 
81
- # Load Traffic Predictor (Tabular Model)
82
  try:
83
  traffic_model_path = os.path.join(MODEL_FOLDER, 'traffic_predictor.pkl')
84
  model_traffic = joblib.load(traffic_model_path)
85
- print("✅ Traffic Risk Predictor Loaded Successfully!")
86
  except Exception as e:
87
- print(f"⚠️ Warning: Could not load traffic predictor model. Error: {e}")
88
  model_traffic = None
89
 
90
  def cleanup_old_files():
@@ -94,13 +113,14 @@ def cleanup_old_files():
94
  if os.path.isfile(file_path):
95
  if time.time() - os.path.getmtime(file_path) > 3600:
96
  os.remove(file_path)
97
- except Exception as e: pass
98
 
99
  def get_camera_info_from_ip(url):
100
  try:
101
  parsed = urlparse(url)
102
  netloc = parsed.netloc.split(':')[0]
103
  if not netloc: return None, None
 
104
  res = requests.get(f"http://ip-api.com/json/{netloc}", timeout=5).json()
105
  if res.get("status") == "success":
106
  city = res.get("city", "Unknown City")
@@ -111,214 +131,388 @@ def get_camera_info_from_ip(url):
111
  try:
112
  wx_res = requests.get(f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&current_weather=true", timeout=5).json()
113
  temp = wx_res["current_weather"]["temperature"]
114
- cam_weather = f"{temp}°C, Active"
115
- except Exception:
116
- cam_weather = "--"
117
- return cam_location, cam_weather
118
- except Exception: pass
119
  return None, None
120
 
121
- def send_email_alert(location, confidence, plates_data, image_b64, severity, video_path=None):
122
- if not ENABLE_EMAIL_ALERTS: return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  try:
124
- msg = EmailMessage()
125
- msg['Subject'] = f"🚨 {severity.upper()} COLLISION DETECTED - {location}"
126
- msg['From'] = ALERT_EMAIL_SENDER
127
- msg['To'] = ALERT_EMAIL_RECEIVER
128
- plates_text = ', '.join([p['text'] for p in plates_data]) if plates_data else 'None Detected'
129
- content = f"EMERGENCY DISPATCH ALERT\nLocation: {location}\nSeverity: {severity.upper()}\nAI Confidence: {confidence}%\nDetected Plates: {plates_text}"
130
- msg.set_content(content)
131
- if image_b64:
132
- msg.add_attachment(base64.b64decode(image_b64), maintype='image', subtype='jpeg', filename='incident.jpg')
133
- context = ssl.create_default_context()
134
- with smtplib.SMTP_SSL('smtp.gmail.com', 465, context=context) as smtp:
135
- smtp.login(ALERT_EMAIL_SENDER, ALERT_EMAIL_PASSWORD)
136
- smtp.send_message(msg)
137
- except Exception: pass
138
-
139
- def process_video_or_image(file_path):
140
- is_image = file_path.lower().endswith(('.png', '.jpg', '.jpeg'))
141
- frames_3d, yolo_probs = [], []
142
- annotated_frame = best_raw_frame = None
143
- if is_image:
144
- frame = cv2.imread(file_path)
145
- best_raw_frame = frame.copy()
146
- res = model_yolo(file_path, verbose=False)[0]
147
- annotated_frame = res.plot()
148
- if res.probs is not None: yolo_probs.append(res.probs.data.cpu().numpy())
149
- elif res.boxes is not None and len(res.boxes) > 0:
150
- confs = np.zeros(4)
151
- for box in res.boxes:
152
- cls_id, conf = int(box.cls[0].item()), box.conf[0].item()
153
- if cls_id < 4 and conf > confs[cls_id]: confs[cls_id] = conf
154
- yolo_probs.append(confs)
155
- f_3d = cv2.resize(frame, (112, 112))
156
- f_3d = cv2.cvtColor(f_3d, cv2.COLOR_BGR2RGB)
157
- frames_3d = [f_3d] * 16
158
- else:
159
- cap = cv2.VideoCapture(file_path)
160
- frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
161
- if frame_count <= 0: return None, None, None, None
162
- intervals_3d = np.linspace(int(frame_count*0.7), max(0, frame_count-1), 16, dtype=int)
163
- intervals_yolo = np.linspace(int(frame_count*0.75), max(0, frame_count-1), 5, dtype=int)
164
- for idx in set(intervals_3d).union(set(intervals_yolo)):
165
- cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
166
- ret, frame = cap.read()
167
- if not ret: continue
168
- if idx in intervals_3d:
169
- f_3d = cv2.resize(frame, (112, 112))
170
- f_3d = cv2.cvtColor(f_3d, cv2.COLOR_BGR2RGB)
171
- frames_3d.append(f_3d)
172
- if idx in intervals_yolo:
173
- res = model_yolo(frame, verbose=False)[0]
174
- best_raw_frame, annotated_frame = frame.copy(), res.plot()
175
- if res.probs is not None: yolo_probs.append(res.probs.data.cpu().numpy())
176
- elif res.boxes is not None and len(res.boxes) > 0:
177
- confs = np.zeros(4)
178
- for box in res.boxes:
179
- cls_id, conf = int(box.cls[0].item()), box.conf[0].item()
180
- if cls_id < 4 and conf > confs[cls_id]: confs[cls_id] = conf
181
- yolo_probs.append(confs)
182
- cap.release()
183
- return frames_3d, yolo_probs, annotated_frame, best_raw_frame
184
-
185
- def get_real_prediction(file_path, location_data):
186
- frames_3d, yolo_probs, annotated_frame, best_raw_frame = process_video_or_image(file_path)
187
- if not frames_3d: return mock_predict(location_data, file_path)
188
- if len(frames_3d) == 16:
189
- tensor_3d = torch.tensor(np.array(frames_3d), dtype=torch.float32).permute(3, 0, 1, 2).unsqueeze(0).to(device) / 255.0
190
  with torch.no_grad():
191
  out_3d = model_3d(tensor_3d)
192
  prob_3d = torch.nn.functional.softmax(out_3d, dim=1).cpu().numpy()[0]
193
- else: prob_3d = np.array([0.33, 0.33, 0.33])
194
- if yolo_probs:
195
- prob_mean, prob_max, prob_min = np.mean(yolo_probs, axis=0), np.max(yolo_probs, axis=0), np.min(yolo_probs, axis=0)
196
- prob_mean, prob_max, prob_min = [np.pad(p[:4], (0, max(0, 4-len(p)))) if len(p) != 4 else p for p in [prob_mean, prob_max, prob_min]]
197
- else: prob_mean = prob_max = prob_min = np.zeros(4)
198
- combined_features = np.concatenate((prob_mean, prob_max, prob_min, prob_3d)).reshape(1, -1)
199
- ensemble_probs = model_svm.predict_proba(combined_features)[0]
200
- final_pred_idx = model_svm.predict(combined_features)[0]
201
- severity_label, confidence = classes[final_pred_idx], ensemble_probs[final_pred_idx] * 100
202
- yolo_max_conf = float(np.max(prob_max)) * 100
203
- if confidence < 60 and yolo_max_conf > 60:
204
- confidence = yolo_max_conf
205
- severity_label = classes[min(int(np.argmax(prob_max)), 2)]
206
- detected_plates = []
207
- if best_raw_frame is not None:
208
- ocr_results = ocr_reader.readtext(best_raw_frame, detail=1)
209
- for (bbox, text, prob) in ocr_results:
210
- text_clean = text.upper().strip()
211
- if len(text_clean) > 4 and any(c.isalpha() for c in text_clean) and any(c.isdigit() for c in text_clean):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  try:
213
- x_min, x_max = max(0, int(min([p[0] for p in bbox]))), min(best_raw_frame.shape[1], int(max([p[0] for p in bbox])))
214
- y_min, y_max = max(0, int(min([p[1] for p in bbox]))), min(best_raw_frame.shape[0], int(max([p[1] for p in bbox])))
215
- _, buf = cv2.imencode('.jpg', best_raw_frame[y_min:y_max, x_min:x_max])
216
- detected_plates.append({"text": text_clean, "image": base64.b64encode(buf).decode('utf-8')})
217
- except Exception: detected_plates.append({"text": text_clean, "image": ""})
218
- encoded_img = ""
219
- if annotated_frame is not None:
220
- _, buffer = cv2.imencode('.jpg', annotated_frame)
221
- encoded_img = base64.b64encode(buffer).decode('utf-8')
222
- if confidence > 68:
223
- threading.Thread(target=send_email_alert, args=(location_data, confidence, detected_plates, encoded_img, severity_label, file_path)).start()
224
- lbl = f"Accident Detected ({severity_label.capitalize()})"
225
- else:
226
- lbl = "Normal Traffic"
227
- severity_label = "--"
228
- return {"label": lbl, "severity": severity_label.capitalize(), "confidence": round(float(confidence), 1), "cnn": round(float(np.max(prob_max)) * 100, 1), "rcnn": round(float(np.max(prob_3d)) * 100, 1), "alert_sent": confidence > 68, "plates": detected_plates, "image": encoded_img}
229
-
230
- def mock_predict(location_data, file_path=None):
231
- svm_p, cnn_p, rcnn_p = np.random.uniform(0.7, 0.95), np.random.uniform(0.6, 0.95), np.random.uniform(0.6, 0.9)
232
- ensemble_p = (svm_p + cnn_p + rcnn_p) / 3
233
- sev = np.random.choice(["Major", "Moderate", "Minor"])
234
- threading.Thread(target=send_email_alert, args=(location_data, round(float(ensemble_p)*100, 1), [], None, sev, file_path)).start()
235
- return {"label": f"Accident Detected ({sev})", "severity": sev, "confidence": round(float(ensemble_p)*100, 1), "cnn": round(cnn_p*100, 1), "rcnn": round(rcnn_p*100, 1), "alert_sent": True, "plates": [], "image": ""}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
  @app.route('/')
238
- def index(): return render_template('index.html')
 
239
 
240
- @app.route('/upload_media', methods=['POST'])
241
- def upload_media():
 
242
  if 'file' not in request.files: return jsonify({"error": "No file"}), 400
243
  file = request.files['file']
244
  unique_id = f"{uuid.uuid4().hex}_{secure_filename(file.filename)}"
245
- file.save(os.path.join(app.config['UPLOAD_FOLDER'], unique_id))
246
- return jsonify({"video_id": unique_id})
247
-
248
- @app.route('/analyze_media', methods=['POST'])
249
- def analyze_media():
250
- unique_id = request.json.get('video_id')
251
- file_path = os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(unique_id))
252
- if not os.path.exists(file_path): return jsonify({"error": "File not found"}), 404
253
- try:
254
- results = get_real_prediction(file_path, "Uploaded Media") if models_loaded else mock_predict("Uploaded Media", file_path)
255
- results.update({"video_id": unique_id, "is_video": True})
256
- return jsonify(results)
257
- except Exception as e: return jsonify({"error": str(e)}), 500
 
 
 
 
 
 
 
 
 
258
 
259
- @app.route('/predict_stream', methods=['POST'])
260
- def predict_stream():
261
- data = request.json
262
- stream_url, loc = data.get('url', '').replace('&amp;', '&'), data.get('location', 'Unknown')
263
  cam_loc, cam_wx = get_camera_info_from_ip(stream_url)
264
- if cam_loc: loc = cam_loc
265
- cap = cv2.VideoCapture(stream_url)
266
- frames = []
267
- for _ in range(90):
268
- ret, frame = cap.read()
269
- if not ret: break
270
- frames.append(frame)
271
- cap.release()
272
- if not frames: return jsonify({"error": "Stream unreachable"}), 400
273
- uid = f"stream_{uuid.uuid4().hex}.webm"
274
- path = os.path.join(app.config['UPLOAD_FOLDER'], uid)
275
- h, w, _ = frames[0].shape
276
- out = cv2.VideoWriter(path, cv2.VideoWriter_fourcc(*'VP80'), 30.0, (w, h))
277
- for f in frames: out.write(f)
278
- out.release()
279
- results = get_real_prediction(path, loc) if models_loaded else mock_predict(loc, path)
280
- if cam_loc: results.update({"cam_location": cam_loc, "cam_weather": cam_wx})
281
- results.update({"video_id": uid, "is_video": True, "source_type": 'live'})
282
- return jsonify(results)
 
 
 
 
 
 
 
 
 
 
283
 
284
  @app.route('/predict_traffic_risk', methods=['POST'])
285
  def predict_traffic_risk():
286
  data = request.json
287
  try:
288
- if not model_traffic:
289
- p = np.random.uniform(10, 85)
290
- return jsonify({"risk_probability_percentage": round(p, 1), "will_accident_happen": p > 50, "status": "High Risk" if p > 50 else "Low Risk"})
291
- df = pd.DataFrame([{k: (v if v != "" else None) for k, v in data.items()}])
292
  for col in ['Traffic_Density', 'Speed_Limit', 'Number_of_Vehicles', 'Driver_Alcohol', 'Driver_Age', 'Driver_Experience']:
293
  if col in df.columns: df[col] = pd.to_numeric(df[col], errors='coerce')
294
- if 'Speed_Limit' in df.columns and 'Driver_Alcohol' in df.columns: df['Speed_Alcohol_Risk'] = (df['Speed_Limit'] // 10) * (df['Driver_Alcohol'] + 0.1)
295
- if 'Traffic_Density' in df.columns and 'Number_of_Vehicles' in df.columns: df['Congestion_Risk'] = df['Traffic_Density'] * df['Number_of_Vehicles']
 
 
 
 
296
  prob = model_traffic.predict_proba(df)[0][1] * 100
297
- return jsonify({"risk_probability_percentage": round(prob, 1), "will_accident_happen": prob >= 50, "status": "High Risk" if prob >= 50 else "Low Risk"})
298
- except Exception as e: return jsonify({"error": str(e)}), 500
299
-
300
- @app.route('/video/<video_id>')
301
- def get_video(video_id): return send_from_directory(app.config['UPLOAD_FOLDER'], secure_filename(video_id))
302
-
303
- @app.route('/stream_tracking/<video_id>')
304
- def stream_tracking(video_id):
305
- path = os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(video_id))
306
- def generate():
307
- cap = cv2.VideoCapture(path)
308
- fps = cap.get(cv2.CAP_PROP_FPS) or 30
309
- delay = 1.0 / fps
310
- while cap.isOpened():
311
- start = time.time()
312
- ret, frame = cap.read()
313
- if not ret: cap.set(cv2.CAP_PROP_POS_FRAMES, 0); continue
314
- if models_loaded: frame = model_yolo(frame, conf=0.15, verbose=False)[0].plot()
315
- _, buf = cv2.imencode('.jpg', frame)
316
- yield (b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + buf.tobytes() + b'\r\n')
317
- elapsed = time.time() - start
318
- if elapsed < delay: time.sleep(delay - elapsed)
319
- cap.release()
320
- return Response(generate(), mimetype='multipart/x-mixed-replace; boundary=frame')
321
 
322
  if __name__ == '__main__':
323
- # Mandatory for Hugging Face Spaces Docker environments
324
- app.run(host='0.0.0.0', port=7860)
 
1
  import os
2
+ # ==========================================
3
+ # 🚨 ANTI-DEADLOCK CPU LIMITERS 🚨
4
+ # Prevents PyTorch from freezing the Flask server on Windows CPUs
5
+ os.environ["OMP_NUM_THREADS"] = "1"
6
+ os.environ["OPENBLAS_NUM_THREADS"] = "1"
7
+ os.environ["MKL_NUM_THREADS"] = "1"
8
+ os.environ["VECLIB_MAXIMUM_THREADS"] = "1"
9
+ os.environ["NUMEXPR_NUM_THREADS"] = "1"
10
+ # ==========================================
11
+
12
  import cv2
13
  import base64
14
  import torch
 
27
  from flask import Flask, request, render_template, jsonify, Response, send_from_directory
28
  from werkzeug.utils import secure_filename
29
 
30
+ # Force PyTorch to use 1 thread safely for both ops and interops
31
+ torch.set_num_threads(1)
32
+ try:
33
+ torch.set_num_interop_threads(1)
34
+ except:
35
+ pass
36
+
37
  # --- Auto-install missing libraries ---
38
  try:
39
  from ultralytics import YOLO
40
  import easyocr
41
+ import yt_dlp
42
  except ModuleNotFoundError:
43
  import sys
44
  import subprocess
45
+ print("Installing missing libraries... This might take a minute...", flush=True)
46
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "ultralytics", "scikit-learn==1.6.1", "easyocr", "pandas", "requests", "yt-dlp"])
47
  from ultralytics import YOLO
48
  import easyocr
49
+ import yt_dlp
50
 
51
  from torchvision.models.video import r3d_18
52
 
53
+ print("--- BOOT SEQUENCE INITIATED ---", flush=True)
54
+
55
  app = Flask(__name__)
56
 
 
 
 
57
  ALERT_EMAIL_SENDER = "gowreeshgowri50@gmail.com"
58
  ALERT_EMAIL_PASSWORD = "omzw fjsu nwnr sgvl"
59
  ALERT_EMAIL_RECEIVER = "ridhinmr32@gmail.com"
60
  ENABLE_EMAIL_ALERTS = True
61
 
 
62
  UPLOAD_FOLDER = 'uploads'
63
  MODEL_FOLDER = 'models'
64
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
 
65
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
66
  os.makedirs(MODEL_FOLDER, exist_ok=True)
67
 
68
  device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
69
  classes = ['major', 'minor', 'moderate']
70
+ active_streams = {}
71
 
 
 
72
  models_loaded = False
73
+ print("Attempting to load AI Models & ALPR...", flush=True)
74
  try:
75
  yolo_path = os.path.join(MODEL_FOLDER, 'yolov8_accident_model.pt')
76
  model_yolo = YOLO(yolo_path)
 
85
  svm_path = os.path.join(MODEL_FOLDER, 'ensemble_svm_model.pkl')
86
  model_svm = joblib.load(svm_path)
87
 
 
88
  ocr_reader = easyocr.Reader(['en'], gpu=torch.cuda.is_available())
 
89
  models_loaded = True
90
+ print("✅ Visual AI Models & OCR Loaded Successfully!", flush=True)
91
+
92
+ # 🚀 AI WARM-UP SEQUENCE 🚀
93
+ # We run a dummy frame through the models on boot so the server doesn't freeze when the user uploads a video!
94
+ print("Warming up Neural Networks in background...", flush=True)
95
+ dummy_img = np.zeros((640, 640, 3), dtype=np.uint8)
96
+ model_yolo(dummy_img, verbose=False)
97
+ print("✅ AI Warm-up complete. System ready for inference.", flush=True)
98
+
99
  except Exception as e:
100
+ print(f"⚠️ Warning: Could not load real visual models. Error: {e}", flush=True)
101
 
 
102
  try:
103
  traffic_model_path = os.path.join(MODEL_FOLDER, 'traffic_predictor.pkl')
104
  model_traffic = joblib.load(traffic_model_path)
105
+ print("✅ Traffic Risk Predictor Loaded Successfully!", flush=True)
106
  except Exception as e:
 
107
  model_traffic = None
108
 
109
  def cleanup_old_files():
 
113
  if os.path.isfile(file_path):
114
  if time.time() - os.path.getmtime(file_path) > 3600:
115
  os.remove(file_path)
116
+ except Exception: pass
117
 
118
  def get_camera_info_from_ip(url):
119
  try:
120
  parsed = urlparse(url)
121
  netloc = parsed.netloc.split(':')[0]
122
  if not netloc: return None, None
123
+
124
  res = requests.get(f"http://ip-api.com/json/{netloc}", timeout=5).json()
125
  if res.get("status") == "success":
126
  city = res.get("city", "Unknown City")
 
131
  try:
132
  wx_res = requests.get(f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&current_weather=true", timeout=5).json()
133
  temp = wx_res["current_weather"]["temperature"]
134
+ return cam_location, f"{temp}°C, Active"
135
+ except:
136
+ return cam_location, ""
137
+ except: pass
 
138
  return None, None
139
 
140
+ def process_accident_async(stream_id, frame, location, confidence, severity):
141
+ detected_plates = []
142
+ if models_loaded:
143
+ try:
144
+ ocr_results = ocr_reader.readtext(frame, detail=1)
145
+ for (bbox, text, prob) in ocr_results:
146
+ text_clean = text.upper().strip()
147
+ if len(text_clean) > 3 and any(c.isdigit() for c in text_clean):
148
+ try:
149
+ x_min = max(0, int(min([p[0] for p in bbox])))
150
+ x_max = min(frame.shape[1], int(max([p[0] for p in bbox])))
151
+ y_min = max(0, int(min([p[1] for p in bbox])))
152
+ y_max = min(frame.shape[0], int(max([p[1] for p in bbox])))
153
+
154
+ plate_crop = frame[y_min:y_max, x_min:x_max]
155
+ if plate_crop.size > 0:
156
+ _, buffer = cv2.imencode('.jpg', plate_crop)
157
+ plate_b64 = base64.b64encode(buffer).decode('utf-8')
158
+ detected_plates.append({"text": text_clean, "image": plate_b64})
159
+ except Exception: pass
160
+ except Exception: pass
161
+
162
+ if stream_id in active_streams:
163
+ active_streams[stream_id]["plates"] = detected_plates
164
+
165
+ if ENABLE_EMAIL_ALERTS:
166
+ try:
167
+ _, img_buffer = cv2.imencode('.jpg', frame)
168
+ img_b64 = base64.b64encode(img_buffer).decode('utf-8')
169
+ msg = EmailMessage()
170
+ msg['Subject'] = f"🚨 {severity.upper()} COLLISION DETECTED - {location}"
171
+ msg['From'] = ALERT_EMAIL_SENDER
172
+ msg['To'] = ALERT_EMAIL_RECEIVER
173
+ plates_text = ', '.join([p['text'] for p in detected_plates]) if detected_plates else 'None Detected'
174
+ msg.set_content(f"EMERGENCY DISPATCH ALERT\nLocation: {location}\nSeverity: {severity.upper()} COLLISION\nAI Confidence: {confidence}%\nDetected Plates: {plates_text}\nImmediate response requested.")
175
+ msg.add_attachment(base64.b64decode(img_b64), maintype='image', subtype='jpeg', filename='incident_snapshot.jpg')
176
+
177
+ context = ssl.create_default_context()
178
+ with smtplib.SMTP_SSL('smtp.gmail.com', 465, context=context) as smtp:
179
+ smtp.login(ALERT_EMAIL_SENDER, ALERT_EMAIL_PASSWORD)
180
+ smtp.send_message(msg)
181
+ except Exception as e:
182
+ print(f"Email failed: {e}", flush=True)
183
+
184
+ def run_temporal_analysis(stream_id, frames_3d_copy, prob_max, prob_mean, prob_min, yolo_max_conf, raw_frame, location):
185
+ print(f"[AI THREAD] Started temporal analysis for {stream_id}", flush=True)
186
  try:
187
+ tensor_3d = torch.tensor(np.array(frames_3d_copy), dtype=torch.float32).permute(3, 0, 1, 2).unsqueeze(0).to(device) / 255.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  with torch.no_grad():
189
  out_3d = model_3d(tensor_3d)
190
  prob_3d = torch.nn.functional.softmax(out_3d, dim=1).cpu().numpy()[0]
191
+
192
+ combined_features = np.concatenate((prob_mean, prob_max, prob_min, prob_3d)).reshape(1, -1)
193
+
194
+ is_acc = False
195
+ final_severity = "--"
196
+ final_conf = 0
197
+
198
+ try:
199
+ ensemble_probs = model_svm.predict_proba(combined_features)[0]
200
+ final_pred_idx = model_svm.predict(combined_features)[0]
201
+
202
+ confidence = float(ensemble_probs[final_pred_idx] * 100)
203
+ severity_label = classes[final_pred_idx]
204
+
205
+ if confidence > 85 and severity_label in classes:
206
+ is_acc = True
207
+ final_severity = severity_label
208
+ final_conf = confidence
209
+ except Exception as e:
210
+ print(f"[AI THREAD] SVM Error: {e}", flush=True)
211
+
212
+ if not is_acc and yolo_max_conf > 85:
213
+ is_acc = True
214
+ final_severity = classes[min(int(np.argmax(prob_max)), 2)]
215
+ final_conf = yolo_max_conf
216
+
217
+ if yolo_max_conf < 15:
218
+ is_acc = False
219
+
220
+ state = active_streams.get(stream_id, {})
221
+ if state:
222
+ state["cnn"] = round(yolo_max_conf, 1)
223
+ state["rcnn"] = round(float(np.max(prob_3d)) * 100, 1)
224
+
225
+ if is_acc:
226
+ print(f"[AI THREAD] 🚨 ACCIDENT DETECTED! Confidence: {final_conf}%", flush=True)
227
+ state["is_accident"] = True
228
+ state["label"] = f"Accident Detected ({final_severity.capitalize()})"
229
+ state["severity"] = final_severity.capitalize()
230
+ state["confidence"] = round(float(final_conf), 1)
231
+
232
+ if not state.get("alert_sent"):
233
+ state["alert_sent"] = True
234
+ threading.Thread(target=process_accident_async, args=(stream_id, raw_frame, location, final_conf, final_severity)).start()
235
+ else:
236
+ if not state.get("is_accident"):
237
+ state["is_accident"] = False
238
+ state["label"] = "Monitoring Traffic" if yolo_max_conf >= 15 else "No Vehicles Detected"
239
+ state["severity"] = "--"
240
+ state["confidence"] = round(float(np.max(prob_mean)) * 100, 1) if yolo_max_conf >= 15 else 0.0
241
+
242
+ print(f"[AI THREAD] Finished temporal analysis.", flush=True)
243
+ except Exception as e:
244
+ print(f"[AI THREAD] CRASH in background thread: {e}", flush=True)
245
+ finally:
246
+ state = active_streams.get(stream_id, {})
247
+ if state:
248
+ state["is_analyzing"] = False
249
+
250
+ def video_stream_gen(stream_id, source, location, raw_mode=False):
251
+ print(f"[STREAM] Booting up generator for stream: {stream_id}", flush=True)
252
+
253
+ # 🚀 INSTANT CONNECTION FRAME 🚀
254
+ # Send a loading frame instantly so the web browser doesn't timeout while waiting for PyTorch!
255
+ load_frame = np.zeros((480, 640, 3), dtype=np.uint8)
256
+ cv2.putText(load_frame, "Initializing AI Stream...", (100, 240), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2)
257
+ _, buffer = cv2.imencode('.jpg', load_frame)
258
+ yield (b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n')
259
+
260
+ is_image_file = isinstance(source, str) and source.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))
261
+ is_video_file = isinstance(source, str) and not is_image_file and not source.startswith('http')
262
+
263
+ print(f"[STREAM] Mode: Image={is_image_file}, Video={is_video_file}, Source={source}", flush=True)
264
+
265
+ static_frame = cv2.imread(source) if is_image_file else None
266
+ cap = cv2.VideoCapture(source) if not is_image_file else None
267
+
268
+ if not is_image_file and (not cap or not cap.isOpened()):
269
+ print(f"[STREAM ERROR] Failed to open video source: {source}", flush=True)
270
+ if stream_id in active_streams: active_streams[stream_id]["label"] = "Stream Failed to Load"
271
+ err_frame = np.zeros((480, 640, 3), dtype=np.uint8)
272
+ cv2.putText(err_frame, "Stream Offline/Failed", (50, 240), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
273
+ _, buffer = cv2.imencode('.jpg', err_frame)
274
+ yield (b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n')
275
+ return
276
+
277
+ frames_3d = []
278
+ yolo_probs = []
279
+ frame_count = 0
280
+ image_processed = False
281
+ last_annotated_frame = None
282
+
283
+ print("[STREAM] Entering while loop to yield frames...", flush=True)
284
+
285
+ while True:
286
+ start_time = time.time()
287
+
288
+ if is_image_file:
289
+ if static_frame is None: break
290
+ frame = static_frame.copy()
291
+
292
+ # 🚀 CPU BURN OPTIMIZER 🚀
293
+ # If it's a photo, we only run the heavy AI ONCE! Then we just yield the saved image.
294
+ if image_processed and last_annotated_frame is not None:
295
+ time.sleep(0.5)
296
+ _, buffer = cv2.imencode('.jpg', last_annotated_frame)
297
+ yield (b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n')
298
+ continue
299
+ else:
300
+ ret, frame = cap.read()
301
+ if not ret:
302
+ if frame_count == 0:
303
+ print("[STREAM ERROR] Could not read the very first frame of the video!", flush=True)
304
+ if stream_id in active_streams: active_streams[stream_id]["label"] = "Video Read Error"
305
+ err_frame = np.zeros((480, 640, 3), dtype=np.uint8)
306
+ cv2.putText(err_frame, "Video Read Error", (50, 240), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
307
+ _, buffer = cv2.imencode('.jpg', err_frame)
308
+ yield (b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n')
309
+ break
310
+ elif is_video_file:
311
+ print("[STREAM] Video naturally finished playing.", flush=True)
312
+ if stream_id in active_streams:
313
+ if not active_streams[stream_id].get("is_accident", False):
314
+ active_streams[stream_id]["label"] = "No accident detected"
315
+ active_streams[stream_id]["severity"] = "--"
316
+
317
+ end_frame = np.zeros((480, 640, 3), dtype=np.uint8)
318
+ if active_streams.get(stream_id, {}).get("is_accident", False):
319
+ cv2.putText(end_frame, "Finished - Collision Logged", (50, 240), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
320
+ else:
321
+ cv2.putText(end_frame, "Finished - No Accident", (50, 240), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
322
+
323
+ _, buffer = cv2.imencode('.jpg', end_frame)
324
+ yield (b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n')
325
+ break
326
+ else:
327
+ time.sleep(1)
328
+ continue
329
+
330
+ raw_frame = frame.copy()
331
+
332
+ if raw_mode:
333
+ annotated_frame = raw_frame
334
+ else:
335
+ if models_loaded:
336
  try:
337
+ res = model_yolo(frame, conf=0.25, verbose=False)[0]
338
+ annotated_frame = res.plot(labels=True, conf=True)
339
+
340
+ if res.probs is not None:
341
+ yolo_probs.append(res.probs.data.cpu().numpy())
342
+ elif res.boxes is not None and len(res.boxes) > 0:
343
+ confs = np.zeros(4)
344
+ for box in res.boxes:
345
+ cls_id = int(box.cls[0].item())
346
+ conf = box.conf[0].item()
347
+ if cls_id < 4 and conf > confs[cls_id]:
348
+ confs[cls_id] = conf
349
+ yolo_probs.append(confs)
350
+ else:
351
+ yolo_probs.append(np.zeros(4))
352
+
353
+ if len(yolo_probs) > 10: yolo_probs.pop(0)
354
+ except Exception as e:
355
+ print(f"[STREAM ERROR] YOLO Inference crashed: {e}", flush=True)
356
+ annotated_frame = frame
357
+ yolo_probs.append(np.zeros(4))
358
+ else:
359
+ annotated_frame = frame
360
+
361
+ f_3d = cv2.resize(frame, (112, 112))
362
+ f_3d = cv2.cvtColor(f_3d, cv2.COLOR_BGR2RGB)
363
+ frames_3d.append(f_3d)
364
+ if len(frames_3d) > 16: frames_3d.pop(0)
365
+
366
+ if len(frames_3d) < 16:
367
+ frames_3d = [f_3d] * 16
368
+
369
+ if not models_loaded:
370
+ if stream_id in active_streams:
371
+ active_streams[stream_id]["label"] = "Models Missing"
372
+ elif len(frames_3d) == 16 and (frame_count % 8 == 0 or is_image_file or frame_count == 0):
373
+
374
+ state = active_streams.get(stream_id, {})
375
+ if state and not state.get("is_analyzing", False):
376
+ if frame_count % 30 == 0:
377
+ print(f"[STREAM] Dispatching AI Thread for frame {frame_count}", flush=True)
378
+ state["is_analyzing"] = True
379
+
380
+ prob_mean = np.mean(yolo_probs, axis=0) if len(yolo_probs) > 0 else np.zeros(4)
381
+ prob_max = np.max(yolo_probs, axis=0) if len(yolo_probs) > 0 else np.zeros(4)
382
+ prob_min = np.min(yolo_probs, axis=0) if len(yolo_probs) > 0 else np.zeros(4)
383
+
384
+ if len(prob_mean) < 4:
385
+ prob_mean = np.pad(prob_mean, (0, 4 - len(prob_mean)))
386
+ prob_max = np.pad(prob_max, (0, 4 - len(prob_max)))
387
+ prob_min = np.pad(prob_min, (0, 4 - len(prob_min)))
388
+ elif len(prob_mean) > 4:
389
+ prob_mean, prob_max, prob_min = prob_mean[:4], prob_max[:4], prob_min[:4]
390
+
391
+ yolo_max_conf = float(np.max(prob_max)) * 100
392
+
393
+ frames_copy = [np.copy(f) for f in frames_3d]
394
+ threading.Thread(
395
+ target=run_temporal_analysis,
396
+ args=(stream_id, frames_copy, prob_max, prob_mean, prob_min, yolo_max_conf, raw_frame.copy(), location)
397
+ ).start()
398
+
399
+ last_annotated_frame = annotated_frame
400
+ image_processed = True
401
+
402
+ try:
403
+ _, buffer = cv2.imencode('.jpg', annotated_frame)
404
+ yield (b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n')
405
+ if frame_count == 0:
406
+ print("[STREAM] ✅ First frame successfully yielded to web browser!", flush=True)
407
+ except Exception as e:
408
+ print(f"[STREAM ERROR] Failed to encode and yield frame to browser: {e}", flush=True)
409
+
410
+ frame_count += 1
411
+
412
+ elapsed = time.time() - start_time
413
+ if is_video_file and elapsed < 0.033:
414
+ time.sleep(0.033 - elapsed)
415
+
416
+ if cap: cap.release()
417
+ print(f"[STREAM] Generator closed for {stream_id}", flush=True)
418
 
419
  @app.route('/')
420
+ def index():
421
+ return render_template('index.html')
422
 
423
+ @app.route('/init_upload', methods=['POST'])
424
+ def init_upload():
425
+ cleanup_old_files()
426
  if 'file' not in request.files: return jsonify({"error": "No file"}), 400
427
  file = request.files['file']
428
  unique_id = f"{uuid.uuid4().hex}_{secure_filename(file.filename)}"
429
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_id)
430
+ file.save(file_path)
431
+
432
+ print(f"[HTTP] Received Upload: {unique_id}", flush=True)
433
+ active_streams[unique_id] = {"label": "Analyzing...", "confidence": 0, "cnn": 0, "rcnn": 0, "severity": "--", "plates": [], "alert_sent": False, "is_accident": False, "is_analyzing": False}
434
+ return jsonify({"stream_id": unique_id, "location": "Nil", "weather": "Nil", "time": "Nil"})
435
+
436
+ @app.route('/init_stream', methods=['POST'])
437
+ def init_stream():
438
+ cleanup_old_files()
439
+ stream_url = request.json.get('url', '').replace('&amp;', '&')
440
+ location = request.json.get('location', 'Unknown IP')
441
+
442
+ print(f"[HTTP] Connecting to Live IP: {stream_url}", flush=True)
443
+ if 'youtube.com' in stream_url or 'youtu.be' in stream_url:
444
+ try:
445
+ ydl_opts = {'format': 'best', 'quiet': True}
446
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
447
+ info = ydl.extract_info(stream_url, download=False)
448
+ stream_url = info.get('url', stream_url)
449
+ except Exception as e:
450
+ print(f"Failed to extract YouTube link: {e}", flush=True)
451
 
 
 
 
 
452
  cam_loc, cam_wx = get_camera_info_from_ip(stream_url)
453
+ if cam_loc: location = cam_loc
454
+
455
+ stream_id = f"live_{uuid.uuid4().hex}"
456
+ app.config[f'SRC_{stream_id}'] = stream_url
457
+
458
+ active_streams[stream_id] = {"label": "Analyzing...", "confidence": 0, "cnn": 0, "rcnn": 0, "severity": "--", "plates": [], "alert_sent": False, "is_accident": False, "is_analyzing": False}
459
+ return jsonify({"stream_id": stream_id, "location": location, "weather": cam_wx or "--", "time": time.strftime("%H:%M:%S")})
460
+
461
+ @app.route('/video_feed/<stream_id>')
462
+ def video_feed(stream_id):
463
+ print(f"\n>>> BROWSER REQUESTED STREAM: {stream_id}", flush=True)
464
+ source = app.config.get(f'SRC_{stream_id}')
465
+ if not source: source = os.path.join(app.config['UPLOAD_FOLDER'], stream_id)
466
+ location = request.args.get('loc', 'Unknown Location')
467
+ return Response(video_stream_gen(stream_id, source, location, raw_mode=False), mimetype='multipart/x-mixed-replace; boundary=frame')
468
+
469
+ @app.route('/raw_feed/<stream_id>')
470
+ def raw_feed(stream_id):
471
+ source = app.config.get(f'SRC_{stream_id}')
472
+ if not source: return "Not a live stream", 400
473
+ return Response(video_stream_gen(stream_id, source, "Unknown", raw_mode=True), mimetype='multipart/x-mixed-replace; boundary=frame')
474
+
475
+ @app.route('/stream_status/<stream_id>')
476
+ def stream_status(stream_id):
477
+ return jsonify(active_streams.get(stream_id, {}))
478
+
479
+ @app.route('/video/<video_id>')
480
+ def get_video(video_id):
481
+ return send_from_directory(app.config['UPLOAD_FOLDER'], secure_filename(video_id))
482
 
483
  @app.route('/predict_traffic_risk', methods=['POST'])
484
  def predict_traffic_risk():
485
  data = request.json
486
  try:
487
+ if model_traffic is None: return jsonify({"error": "Tabular model not loaded"}), 500
488
+ cleaned_data = {k: (v if v != "" else None) for k, v in data.items()}
489
+ df = pd.DataFrame([cleaned_data])
490
+
491
  for col in ['Traffic_Density', 'Speed_Limit', 'Number_of_Vehicles', 'Driver_Alcohol', 'Driver_Age', 'Driver_Experience']:
492
  if col in df.columns: df[col] = pd.to_numeric(df[col], errors='coerce')
493
+ if 'Speed_Limit' in df.columns and 'Driver_Alcohol' in df.columns:
494
+ df['Speed_Alcohol_Risk'] = (df['Speed_Limit'] // 10) * (df['Driver_Alcohol'] + 0.1)
495
+ if 'Traffic_Density' in df.columns and 'Number_of_Vehicles' in df.columns:
496
+ df['Congestion_Risk'] = df['Traffic_Density'] * df['Number_of_Vehicles']
497
+
498
+ prediction = model_traffic.predict(df)[0]
499
  prob = model_traffic.predict_proba(df)[0][1] * 100
500
+
501
+ # 🚨 100% SAFE JSON SERIALIZATION 🚨
502
+ # Extracts raw int/float mathematically to guarantee no Numpy bool_ errors on mobile
503
+ prediction_val = int(prediction.item()) if hasattr(prediction, 'item') else int(prediction)
504
+ native_prediction = True if prediction_val > 0 else False
505
+ native_prob = float(prob.item()) if hasattr(prob, 'item') else float(prob)
506
+
507
+ return jsonify({
508
+ "risk_probability_percentage": round(native_prob, 1),
509
+ "will_accident_happen": native_prediction,
510
+ "status": "High Risk Detected" if native_prob >= 50 else "Low Risk Environment"
511
+ })
512
+ except Exception as e:
513
+ print(f"[PREDICTOR ERROR] {str(e)}", flush=True)
514
+ return jsonify({"error": str(e)}), 500
 
 
 
 
 
 
 
 
 
515
 
516
  if __name__ == '__main__':
517
+ # Threaded=True prevents single-thread blocking
518
+ app.run(host='0.0.0.0', port=7860, threaded=True)