""" ╔══════════════════════════════════════════════════════════════╗ ║ CROWD ANALYSIS & THREAT DETECTION SYSTEM ║ ║ Hugging Face Deployment ║ ║ Detects: Ambulance Stuck | Fighting | Fire | Smoke | Weapon ║ ║ Features: Real-time Alerts | Incident Log | RAG Summary ║ ╚══════════════════════════════════════════════════════════════╝ """ # ── Standard Library ────────────────────────────────────────── import os import cv2 import time import tempfile import threading import numpy as np from datetime import datetime from collections import Counter from pathlib import Path # ── ML / Detection ──────────────────────────────────────────── import torch from PIL import Image from ultralytics import YOLO # ── Document Storage ────────────────────────────────────────── from docx import Document as DocxDocument from docx.shared import RGBColor, Pt from docx.enum.text import WD_ALIGN_PARAGRAPH # ── RAG Pipeline ────────────────────────────────────────────── from langchain_community.document_loaders import Docx2txtLoader try: from langchain_text_splitters import RecursiveCharacterTextSplitter except ImportError: from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.vectorstores import FAISS from langchain_community.embeddings import HuggingFaceEmbeddings from langchain.chains import RetrievalQA from langchain_community.llms import HuggingFacePipeline from transformers import pipeline as hf_pipeline # ── Gradio UI ───────────────────────────────────────────────── import gradio as gr # ══════════════════════════════════════════════════════════════ # CONFIGURATION # ══════════════════════════════════════════════════════════════ FINAL_CLASSES = ['ambulance', 'fighting', 'fire', 'smoke', 'weapon'] CONF_THRESHOLD = 0.25 LOG_PATH = 'incident_log.docx' MODEL_PATH = 'best.pt' ALERT_CONFIG = { 'ambulance': { 'level': 'HIGH', 'emoji': '🚑', 'msg' : 'AMBULANCE STUCK IN CROWD — Clear the path immediately!', 'color': (0, 165, 255), 'hex' : '#FFA500', }, 'fire': { 'level': 'CRITICAL', 'emoji': '🔥', 'msg' : 'FIRE DETECTED — Evacuate area, call fire brigade!', 'color': (0, 0, 255), 'hex' : '#FF2222', }, 'smoke': { 'level': 'HIGH', 'emoji': '💨', 'msg' : 'SMOKE DETECTED — Possible fire, investigate immediately!', 'color': (180, 180, 180), 'hex' : '#AAAAAA', }, 'weapon': { 'level': 'CRITICAL', 'emoji': '🔫', 'msg' : 'WEAPON DETECTED — Alert security forces immediately!', 'color': (255, 0, 0), 'hex' : '#FF0055', }, 'fighting': { 'level': 'HIGH', 'emoji': '⚔️', 'msg' : 'FIGHTING DETECTED — Deploy crowd control!', 'color': (0, 200, 0), 'hex' : '#00CC44', }, } # ══════════════════════════════════════════════════════════════ # MODEL LOADER # ══════════════════════════════════════════════════════════════ def load_model(): if not os.path.exists(MODEL_PATH): raise FileNotFoundError( f'Model not found: {MODEL_PATH}\n' 'Please upload best.pt to this Space.' ) model = YOLO(MODEL_PATH) print(f'✅ YOLOv8m loaded | GPU: {torch.cuda.is_available()}') return model model = load_model() # ══════════════════════════════════════════════════════════════ # DETECTION UTILITIES # ══════════════════════════════════════════════════════════════ def draw_boxes(img_bgr, results): """Draw bounding boxes with labels on image.""" for r in results: for box in r.boxes: cls_idx = int(box.cls) conf = float(box.conf) name = FINAL_CLASSES[cls_idx] color = ALERT_CONFIG[name]['color'] x1,y1,x2,y2 = map(int, box.xyxy[0]) label = f'{name} {conf:.2f}' cv2.rectangle(img_bgr, (x1,y1),(x2,y2), color, 2) cv2.rectangle(img_bgr, (x1,y1-30),(x1+len(label)*12,y1), color, -1) cv2.putText(img_bgr, label, (x1+3,y1-8), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (255,255,255), 2) return img_bgr def extract_detections(results): """Extract list of detection dicts from results.""" return [ {'class': FINAL_CLASSES[int(box.cls)], 'conf': float(box.conf)} for r in results for box in r.boxes ] def build_alert_html(detections): """Build HTML alert panel from detections.""" triggered = {} for det in detections: name = det['class'] if name not in triggered: triggered[name] = {**ALERT_CONFIG[name], 'conf': det['conf']} if not triggered: return '''
✅ SYSTEM CLEAR — No threats detected
''' sorted_alerts = sorted( triggered.values(), key=lambda x: 0 if x['level'] == 'CRITICAL' else 1 ) html = '
' for alert in sorted_alerts: bg = '#2a0000' if alert['level'] == 'CRITICAL' else '#2a1500' border = alert['hex'] html += f'''
{alert["emoji"]} [{alert["level"]}] {alert["msg"]}
Confidence: {alert["conf"]:.2f} | Time: {datetime.now().strftime("%H:%M:%S")}
''' html += '
' return html # ══════════════════════════════════════════════════════════════ # INCIDENT LOG (.docx) # ══════════════════════════════════════════════════════════════ _log_lock = threading.Lock() def append_to_log(source, detections, triggered_alerts): """Append incident entry to docx log (thread-safe).""" with _log_lock: if os.path.exists(LOG_PATH): doc = DocxDocument(LOG_PATH) else: doc = DocxDocument() title = doc.add_heading('CROWD ANALYSIS — INCIDENT LOG', 0) title.alignment = WD_ALIGN_PARAGRAPH.CENTER doc.add_paragraph( f'System : Crowd Analysis & Threat Detection\n' f'Model : YOLOv8m (V1)\n' f'Classes : {", ".join(FINAL_CLASSES)}\n' f'Created : {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}' ) doc.add_paragraph('─' * 60) doc.add_heading( f'INCIDENT — {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}', level=1 ) doc.add_paragraph(f'Source : {source}') doc.add_paragraph(f'Total detections : {len(detections)}') doc.add_paragraph(f'Alerts triggered : {len(triggered_alerts)}') doc.add_paragraph('') doc.add_heading('Alerts', level=2) if triggered_alerts: for alert in triggered_alerts: p = doc.add_paragraph(style='List Bullet') run = p.add_run( f"{alert['emoji']} [{alert['level']}] " f"{alert['msg']} (conf: {alert['conf']:.2f})" ) run.bold = True run.font.size = Pt(11) run.font.color.rgb = ( RGBColor(200,0,0) if alert['level'] == 'CRITICAL' else RGBColor(200,100,0) ) else: doc.add_paragraph('No threats detected.') doc.add_paragraph('') doc.add_heading('All Detections', level=2) if detections: table = doc.add_table(rows=1, cols=3) table.style = 'Table Grid' hdr = table.rows[0].cells hdr[0].text, hdr[1].text, hdr[2].text = 'Class','Confidence','Alert Level' for det in detections: row = table.add_row().cells row[0].text = det['class'] row[1].text = f"{det['conf']:.2f}" row[2].text = ALERT_CONFIG[det['class']]['level'] else: doc.add_paragraph('No objects detected.') doc.add_paragraph('') doc.add_paragraph('─' * 60) doc.add_paragraph('') doc.save(LOG_PATH) # ══════════════════════════════════════════════════════════════ # RAG — INCIDENT SUMMARY # ══════════════════════════════════════════════════════════════ _rag_chain = None _embeddings = None def get_embeddings(): global _embeddings if _embeddings is None: _embeddings = HuggingFaceEmbeddings( model_name='sentence-transformers/all-MiniLM-L6-v2' ) return _embeddings def build_rag_chain(): global _rag_chain if not os.path.exists(LOG_PATH): return None, '⚠️ No incident log found. Run detections first!' try: loader = Docx2txtLoader(LOG_PATH) docs = loader.load() splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50) chunks = splitter.split_documents(docs) if not chunks: return None, '⚠️ Incident log is empty.' vectorstore = FAISS.from_documents(chunks, get_embeddings()) llm_pipe = hf_pipeline( 'text2text-generation', model='google/flan-t5-base', max_length=512, truncation=True, ) llm = HuggingFacePipeline(pipeline=llm_pipe) _rag_chain = RetrievalQA.from_chain_type( llm=llm, chain_type='stuff', retriever=vectorstore.as_retriever(search_kwargs={'k': 4}), return_source_documents=False, ) return _rag_chain, '✅ RAG ready — ask questions about incidents!' except Exception as e: return None, f'❌ Error: {str(e)}' def query_rag(question, history): global _rag_chain if not question.strip(): return history, '' if _rag_chain is None: _rag_chain, status = build_rag_chain() if _rag_chain is None: history.append((question, status)) return history, '' try: result = _rag_chain.invoke({'query': question}) answer = result.get('result', 'No answer found.') answer = f'📋 {answer}\n\n_Answer based on incident log only — zero hallucination_' except Exception as e: answer = f'❌ Error: {str(e)}' history.append((question, answer)) return history, '' # ══════════════════════════════════════════════════════════════ # INFERENCE FUNCTIONS # ══════════════════════════════════════════════════════════════ def detect_image(image): if image is None: return None, '
No image uploaded.
', None img_bgr = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) results = model.predict(img_bgr, conf=CONF_THRESHOLD, verbose=False) img_out = draw_boxes(img_bgr.copy(), results) img_rgb = cv2.cvtColor(img_out, cv2.COLOR_BGR2RGB) detections = extract_detections(results) triggered = [ {**ALERT_CONFIG[n], 'conf': c, 'class': n} for n, c in {d['class']: d['conf'] for d in detections}.items() ] alert_html = build_alert_html(detections) threading.Thread( target=append_to_log, args=('image_upload', detections, triggered), daemon=True ).start() return Image.fromarray(img_rgb), alert_html, LOG_PATH def detect_video(video_path): if video_path is None: return None, '
No video uploaded.
', None # ── Size check ──────────────────────────────────────────── size_mb = os.path.getsize(video_path) / 1e6 print(f'📹 Video: {size_mb:.1f}MB') cap = cv2.VideoCapture(video_path) fps = cap.get(cv2.CAP_PROP_FPS) or 25 w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) dur = total / fps print(f' Resolution : {w}x{h}') print(f' Duration : {dur:.1f}s') print(f' Frames : {total}') # ── Limit to 30 seconds for performance ─────────────────── MAX_FRAMES = int(fps * 30) if total > MAX_FRAMES: print(f'⚠️ Trimming to first 30 seconds ({MAX_FRAMES} frames)') total = MAX_FRAMES tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') writer = cv2.VideoWriter( tmp.name, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h) ) all_detections = [] frame_n = 0 while cap.isOpened() and frame_n < total: ret, frame = cap.read() if not ret: break # Process every 3rd frame for speed if frame_n % 3 == 0: results = model.predict( frame, conf=CONF_THRESHOLD, verbose=False ) frame = draw_boxes(frame, results) all_detections.extend(extract_detections(results)) # Progress overlay pct = int((frame_n / total) * 100) cv2.putText(frame, f'Processing: {pct}% | Frame {frame_n}/{total}', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 255, 255), 2) writer.write(frame) frame_n += 1 # Log progress every 50 frames if frame_n % 50 == 0: print(f' ⏳ {pct}% complete', end='\r') cap.release() writer.release() print(f'\n✅ Processing complete — {frame_n} frames') triggered = [ {**ALERT_CONFIG[n], 'conf': c, 'class': n} for n, c in { d['class']: d['conf'] for d in all_detections }.items() ] alert_html = build_alert_html(all_detections) threading.Thread( target=append_to_log, args=(f'video ({frame_n} frames, {dur:.1f}s)', all_detections, triggered), daemon=True ).start() return tmp.name, alert_html, LOG_PATH def detect_live(frame): if frame is None: return None, '
No feed.
' img_bgr = cv2.cvtColor(np.array(frame), cv2.COLOR_RGB2BGR) results = model.predict(img_bgr, conf=CONF_THRESHOLD, verbose=False) img_out = draw_boxes(img_bgr.copy(), results) img_rgb = cv2.cvtColor(img_out, cv2.COLOR_BGR2RGB) detections = extract_detections(results) alert_html = build_alert_html(detections) if detections: triggered = [ {**ALERT_CONFIG[n], 'conf': c, 'class': n} for n, c in {d['class']: d['conf'] for d in detections}.items() ] threading.Thread( target=append_to_log, args=('live_feed', detections, triggered), daemon=True ).start() return Image.fromarray(img_rgb), alert_html # ══════════════════════════════════════════════════════════════ # GRADIO UI # ══════════════════════════════════════════════════════════════ CSS = """ @import url('https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;600;700&family=Share+Tech+Mono&family=Exo+2:wght@300;400;600&display=swap'); :root { --bg-dark : #050a0f; --bg-panel : #0a1520; --bg-card : #0d1e2e; --red : #ff2244; --blue : #00aaff; --gold : #ffaa00; --text : #c8d8e8; --dim : #5a7a9a; --border : #1a3a5a; } body, .gradio-container { background: var(--bg-dark) !important; font-family: 'Exo 2', sans-serif !important; color: var(--text) !important; } .sys-header { background: linear-gradient(135deg, #050a0f, #0a1a2e, #050a0f); border-bottom: 2px solid var(--red); padding: 32px 24px 24px; text-align: center; } .sys-title { font-family: 'Rajdhani', sans-serif !important; font-size: 44px !important; font-weight: 700 !important; letter-spacing: 10px !important; color: #fff !important; text-shadow: 0 0 30px rgba(255,34,68,0.8), 0 0 60px rgba(255,34,68,0.3) !important; margin: 0 !important; } .sys-sub { font-family: 'Share Tech Mono', monospace; font-size: 12px; color: var(--blue); letter-spacing: 4px; margin-top: 8px; } .sys-badges { display: flex; justify-content: center; gap: 10px; margin-top: 14px; flex-wrap: wrap; } .badge { background: rgba(0,170,255,0.08); border: 1px solid rgba(0,170,255,0.25); border-radius: 3px; padding: 3px 10px; font-family: 'Share Tech Mono', monospace; font-size: 10px; color: var(--blue); letter-spacing: 1px; } .badge.red { background: rgba(255,34,68,0.08); border-color: rgba(255,34,68,0.25); color: var(--red); } .tab-nav button { background: var(--bg-panel) !important; border: 1px solid var(--border) !important; color: var(--dim) !important; font-family: 'Rajdhani', sans-serif !important; font-size: 13px !important; font-weight: 600 !important; letter-spacing: 2px !important; text-transform: uppercase !important; transition: all 0.2s !important; } .tab-nav button.selected { background: var(--bg-card) !important; border-color: var(--red) !important; color: #fff !important; box-shadow: 0 0 15px rgba(255,34,68,0.25) !important; } .section-lbl { font-family: 'Share Tech Mono', monospace; font-size: 11px; color: var(--blue); letter-spacing: 3px; text-transform: uppercase; padding: 14px 0 8px; } .alert-panel { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 16px; min-height: 60px; } .gr-button { font-family: 'Rajdhani', sans-serif !important; font-weight: 700 !important; letter-spacing: 2px !important; text-transform: uppercase !important; } label { font-family: 'Share Tech Mono', monospace !important; font-size: 11px !important; letter-spacing: 1px !important; color: var(--dim) !important; } .footer-bar { background: var(--bg-panel); border-top: 1px solid var(--border); padding: 10px 24px; display: flex; justify-content: space-between; font-family: 'Share Tech Mono', monospace; font-size: 10px; color: var(--dim); margin-top: 16px; } """ HEADER = """
⚡ CROWD SHIELD
REAL-TIME THREAT DETECTION & INCIDENT MANAGEMENT SYSTEM
🔴 LIVE SYSTEM YOLOv8m 5 THREAT CLASSES AUTO INCIDENT LOG RAG REPORTING
""" RAG_INFO = """
▸ RAG SYSTEM INFO
EMBEDDINGS:
all-MiniLM-L6-v2
VECTOR STORE:
FAISS (CPU)
LLM:
Flan-T5-Base
METHOD:
RetrievalQA
SOURCE:
incident_log.docx
HALLUCINATION:
ZERO ✅
""" AWAIT_HTML = '
// AWAITING INPUT //
' with gr.Blocks(title='CrowdShield — Threat Detection') as app: gr.HTML(HEADER) with gr.Tabs(): # ── Tab 1: Image ────────────────────────────────────── with gr.Tab('📷 IMAGE'): gr.HTML('
▸ Upload Image for Threat Analysis
') with gr.Row(): with gr.Column(): img_input = gr.Image(type='pil', label='INPUT FEED', height=380) img_btn = gr.Button('⚡ ANALYZE THREAT', variant='primary') with gr.Column(): img_output = gr.Image(type='pil', label='DETECTION OUTPUT', height=380) gr.HTML('
▸ Alert Status
') img_alert = gr.HTML(value=AWAIT_HTML) img_log = gr.File(label='📄 DOWNLOAD INCIDENT LOG (.docx)') img_btn.click( fn=detect_image, inputs=[img_input], outputs=[img_output, img_alert, img_log] ) # ── Tab 2: Video ────────────────────────────────────── with gr.Tab('🎥 VIDEO'): gr.HTML('
▸ Upload Video for Threat Analysis
') with gr.Row(): with gr.Column(): vid_input = gr.Video(label='INPUT FEED', height=380) vid_btn = gr.Button('⚡ ANALYZE THREAT', variant='primary') with gr.Column(): vid_output = gr.Video(label='DETECTION OUTPUT', height=380) gr.HTML('
▸ Alert Status
') vid_alert = gr.HTML(value=AWAIT_HTML) vid_log = gr.File(label='📄 DOWNLOAD INCIDENT LOG (.docx)') vid_btn.click( fn=detect_video, inputs=[vid_input], outputs=[vid_output, vid_alert, vid_log] ) # ── Tab 3: Live ─────────────────────────────────────── with gr.Tab('📡 LIVE FEED'): gr.HTML('
▸ Connect Live Camera Feed
') with gr.Row(): with gr.Column(): live_input = gr.Image( sources=['webcam'], type='pil', label='LIVE INPUT', streaming=True, height=380 ) with gr.Column(): live_output = gr.Image(type='pil', label='DETECTION OUTPUT', height=380) gr.HTML('
▸ Alert Status
') live_alert = gr.HTML(value=AWAIT_HTML) live_input.stream( fn=detect_live, inputs=[live_input], outputs=[live_output, live_alert] ) # ── Tab 4: RAG ──────────────────────────────────────── with gr.Tab('📋 INCIDENT REPORT'): gr.HTML('''
▸ RAG INCIDENT QUERY — ZERO HALLUCINATION
Ask questions about logged incidents. Answers come from incident_log.docx ONLY.
Try: "Summarize all CRITICAL incidents" | "Generate official report" | "How many weapon detections?"
''') with gr.Row(): with gr.Column(scale=3): chatbot = gr.Chatbot( label='INCIDENT QUERY INTERFACE', height=440, ) with gr.Row(): query_box = gr.Textbox( placeholder='Ask about incidents... e.g. "Generate official report for authorities"', label='', scale=4, container=False, ) ask_btn = gr.Button('⚡ ASK', variant='primary', scale=1) gr.Examples( examples=[ ['Summarize all incidents for the officials'], ['How many CRITICAL alerts were triggered?'], ['Which incidents involved fire or smoke?'], ['List all weapon detections with timestamps'], ['Generate a complete incident report'], ['What was the most dangerous incident?'], ], inputs=[query_box], label='QUICK QUERIES', ) with gr.Column(scale=1): gr.HTML(RAG_INFO) rag_status = gr.Textbox( label='SYSTEM STATUS', value='Click LOAD to initialize RAG', interactive=False, lines=2, ) load_btn = gr.Button('📂 LOAD INCIDENT LOG', variant='secondary') log_upload = gr.File( label='↑ Or upload incident_log.docx', file_types=['.docx'], ) def load_rag(uploaded=None): global _rag_chain _rag_chain = None if uploaded: import shutil shutil.copy(uploaded.name, LOG_PATH) _, status = build_rag_chain() return status load_btn.click(fn=load_rag, inputs=[log_upload], outputs=[rag_status]) ask_btn.click(fn=query_rag, inputs=[query_box, chatbot], outputs=[chatbot, query_box]) query_box.submit(fn=query_rag, inputs=[query_box, chatbot], outputs=[chatbot, query_box]) # Footer gr.HTML(''' ''') if __name__ == '__main__': app.launch(css=CSS)