Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,14 @@
|
|
| 1 |
import os
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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
|
| 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}¤t_weather=true", timeout=5).json()
|
| 113 |
temp = wx_res["current_weather"]["temperature"]
|
| 114 |
-
|
| 115 |
-
except
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
except Exception: pass
|
| 119 |
return None, None
|
| 120 |
|
| 121 |
-
def
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
try:
|
| 124 |
-
|
| 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 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
try:
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
|
| 237 |
@app.route('/')
|
| 238 |
-
def index():
|
|
|
|
| 239 |
|
| 240 |
-
@app.route('/
|
| 241 |
-
def
|
|
|
|
| 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 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
unique_id
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
|
| 259 |
-
@app.route('/predict_stream', methods=['POST'])
|
| 260 |
-
def predict_stream():
|
| 261 |
-
data = request.json
|
| 262 |
-
stream_url, loc = data.get('url', '').replace('&', '&'), data.get('location', 'Unknown')
|
| 263 |
cam_loc, cam_wx = get_camera_info_from_ip(stream_url)
|
| 264 |
-
if cam_loc:
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
|
| 284 |
@app.route('/predict_traffic_risk', methods=['POST'])
|
| 285 |
def predict_traffic_risk():
|
| 286 |
data = request.json
|
| 287 |
try:
|
| 288 |
-
if
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 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:
|
| 295 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
prob = model_traffic.predict_proba(df)[0][1] * 100
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 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 |
-
#
|
| 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}¤t_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('&', '&')
|
| 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)
|