karan-01/Hp_Eye_Head_Movement_Tracking
0
1"""2app.py3======4Streamlit UI – AI Proctoring + Behavior Analysis Dashboard5UPGRADED: Full Module 2.1/2.2/2.3 display + Event Log + Evidence Viewer6Hugging Face Spaces compatible.7"""8 9import streamlit as st10import cv211import numpy as np12from datetime import datetime13import base6414 15from main import process_frame16from utils.behavior_analyzer import get_event_log, clear_event_log17from supabase_client import supabase18from database.queries import fetch_recent_logs19 20# ── Page Config ──────────────────────────────────────────────────────────────21st.set_page_config(22 page_title="AI Behavior Analysis Proctoring",23 layout="wide",24 page_icon="🎯"25)26 27# ── Custom CSS ───────────────────────────────────────────────────────────────28st.markdown("""29<style>30.risk-badge-high { background:#dc2626; color:white; padding:2px 8px; border-radius:4px; font-weight:bold; }31.risk-badge-med { background:#d97706; color:white; padding:2px 8px; border-radius:4px; font-weight:bold; }32.risk-badge-low { background:#16a34a; color:white; padding:2px 8px; border-radius:4px; font-weight:bold; }33.module-header { font-size:0.82rem; font-weight:700; color:#60a5fa; text-transform:uppercase; letter-spacing:.05em; }34.flag-pill { background:#7f1d1d; color:#fca5a5; padding:1px 6px; border-radius:3px; font-size:0.78rem; margin:1px; display:inline-block; }35</style>36""", unsafe_allow_html=True)37 38# ── Header ───────────────────────────────────────────────────────────────────39st.title("🎯 AI Behavior Analysis – Proctoring System")40st.caption("Module 2.1: Eye & Head | Module 2.2: Person & Object | Module 2.3: Mobile & Gesture")41 42tab1, tab2, tab3, tab4, tab5 = st.tabs([43 "🎥 Live Monitoring",44 "📋 Event Audit Log",45 "📊 Database Logs",46 "🖼️ Evidence Viewer",47 "ℹ️ System Info"48])49 50# ── Session state ─────────────────────────────────────────────────────────────51if "evidence_frames" not in st.session_state:52 st.session_state.evidence_frames = []53if "frame_count" not in st.session_state:54 st.session_state.frame_count = 055 56 57# =============================================================================58# TAB 1 – Live Monitoring59# =============================================================================60with tab1:61 col_cam, col_panel = st.columns([2, 1])62 63 with col_cam:64 st.subheader("📷 Live Camera Feed")65 camera = st.camera_input("Enable Camera", key="live_cam")66 67 results = None68 if camera is not None:69 try:70 file_bytes = np.asarray(bytearray(camera.read()), dtype=np.uint8)71 frame = cv2.imdecode(file_bytes, 1)72 st.session_state.frame_count += 173 74 output_frame, results = process_frame(frame)75 76 # Store evidence77 if results and results.get("data", {}).get("violation_captured"):78 evidence = results.get("evidence", {})79 if evidence and len(st.session_state.evidence_frames) < 50:80 st.session_state.evidence_frames.append({81 "frame_b64": evidence.get("image_b64", ""),82 "timestamp": evidence.get("timestamp", ""),83 "flags": evidence.get("flags", []),84 "risk_score": evidence.get("risk_score", 0)85 })86 87 if output_frame is not None:88 st.image(output_frame, channels="BGR", use_column_width=True)89 st.caption(f"Frame #{st.session_state.frame_count} · "90 f"Processed: {results['data'].get('processing_ms', 0):.1f}ms")91 92 except Exception as error:93 st.error(f"❌ Frame processing failed: {error}")94 else:95 st.info("📷 Enable your camera above to start monitoring.")96 97 # ── Results Panel ─────────────────────────────────────────────────────────98 with col_panel:99 st.subheader("📦 Live Analysis")100 101 if results and results.get("success"):102 data = results.get("data", {})103 104 # ── Risk score ──────────────────────────────────────────────────105 risk = data.get("risk_score", 0)106 if risk >= 50:107 badge = "high"108 elif risk >= 25:109 badge = "med"110 else:111 badge = "low"112 113 st.markdown(f"**Risk Score** "114 f"<span class='risk-badge-{badge}'>{risk}/100</span>",115 unsafe_allow_html=True)116 st.progress(risk / 100)117 118 # Flags119 flags = data.get("risk_flags", [])120 if flags:121 pills = " ".join(f"<span class='flag-pill'>{f}</span>" for f in flags)122 st.markdown(pills, unsafe_allow_html=True)123 else:124 st.success("✅ No risk flags")125 126 st.markdown("---")127 128 # ── 2.1 Eye & Head ──────────────────────────────────────────────129 st.markdown("<div class='module-header'>2.1 Eye & Head Tracking</div>",130 unsafe_allow_html=True)131 132 la = data.get("looking_away", False)133 st.write(f"• Looking Away: {'🔴 YES' if la else '🟢 No'}")134 st.write(f"• Gaze: `{data.get('gaze_direction')}` "135 f"(L:`{data.get('left_gaze')}` R:`{data.get('right_gaze')}`)")136 st.write(f"• Head: `{data.get('head_direction')}`")137 138 yaw = data.get("yaw", 0.0)139 pitch = data.get("pitch", 0.0)140 roll = data.get("roll", 0.0)141 st.write(f"• Yaw/Pitch/Roll: `{yaw:.1f}° / {pitch:.1f}° / {roll:.1f}°`")142 143 ear_l = data.get("ear_left", 0.0)144 ear_r = data.get("ear_right", 0.0)145 blinks = data.get("blink_count", 0)146 freq = data.get("look_away_frequency", 0)147 st.write(f"• EAR: `{ear_l:.2f}` / `{ear_r:.2f}` | Blinks: `{blinks}`")148 st.write(f"• Look-Away/min: `{freq}` "149 f"{'🚨 Suspicious' if data.get('frequent_looking_away') else '✅'}")150 151 att = data.get("attention_score", 0)152 att_lbl = data.get("attention_label", "N/A")153 att_icon = "🟢" if att >= 70 else ("🟠" if att >= 40 else "🔴")154 st.write(f"• Attention: {att_icon} `{att}/100` – {att_lbl}")155 156 st.markdown("---")157 158 # ── 2.2 Person & Object ─────────────────────────────────────────159 st.markdown("<div class='module-header'>2.2 Person & Object Detection</div>",160 unsafe_allow_html=True)161 p_cnt = data.get("person_count", 0)162 multi = data.get("multiple_persons", False)163 st.write(f"• Persons: `{p_cnt}` {'🚨 UNAUTHORIZED' if multi else '✅ OK'} "164 f"[{data.get('person_engine', '?')}]")165 166 objs = data.get("prohibited_objects", [])167 st.write(f"• Objects: `{', '.join(objs) if objs else 'None'}` "168 f"[{data.get('object_engine', '?')}]")169 st.write(f"• Phone:`{data.get('phone_detected')}` "170 f"Book:`{data.get('book_detected')}` "171 f"Notes:`{data.get('notes_detected')}` "172 f"Laptop:`{data.get('laptop_detected')}`")173 174 st.markdown("---")175 176 # ── 2.3 Mobile / Gesture ────────────────────────────────────────177 st.markdown("<div class='module-header'>2.3 Mobile & Gesture</div>",178 unsafe_allow_html=True)179 ph = data.get("mobile_phone_detected", False)180 phc = data.get("mobile_phone_confidence", 0.0)181 st.write(f"• Phone: {'🚨 DETECTED' if ph else '✅ None'} ({phc:.0%})")182 st.write(f"• Hands: `{data.get('hands_detected', 0)}` | "183 f"Gesture: `{', '.join(data.get('gesture_labels', [])) or 'None'}`")184 st.write(f"• Fingers: `{data.get('finger_counts', [])}`")185 ug = data.get("unusual_gesture", False)186 st.write(f"• Suspicious Gesture: {'🚨 YES' if ug else '✅ No'}")187 st.write(f"• Motion Score: `{data.get('motion_score', 0.0):.4f}`")188 189 st.markdown("---")190 191 # Risk breakdown192 with st.expander("📊 Risk Breakdown"):193 bd = data.get("risk_breakdown", {})194 for k, v in bd.items():195 st.write(f"• {k}: **+{v}**")196 197 with st.expander("🔍 Full JSON"):198 st.json(results)199 else:200 st.info("Waiting for camera input...")201 202 203# =============================================================================204# TAB 2 – Event Audit Log205# =============================================================================206with tab2:207 st.subheader("📋 Event Audit Trail")208 col_ev1, col_ev2 = st.columns([3, 1])209 with col_ev2:210 if st.button("🗑️ Clear Log"):211 clear_event_log()212 st.rerun()213 214 events = get_event_log()215 if events:216 st.info(f"{len(events)} events recorded")217 import pandas as pd218 df = pd.DataFrame(reversed(events))219 st.dataframe(df, use_container_width=True)220 221 # Download as JSON222 ev_json = str(events).encode()223 st.download_button(224 "⬇️ Download Event Log (JSON)",225 data=__import__("json").dumps(events, indent=2),226 file_name=f"event_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",227 mime="application/json"228 )229 else:230 st.info("No events recorded yet. Start monitoring to capture events.")231 232 233# =============================================================================234# TAB 3 – Database Logs235# =============================================================================236with tab3:237 st.subheader("📊 Database Behavior Logs")238 col_a, col_b = st.columns(2)239 240 with col_a:241 st.markdown("#### Proctoring Logs")242 try:243 logs = fetch_recent_logs("proctoring_logs", limit=15)244 if logs:245 st.success(f"✅ {len(logs)} records")246 import pandas as pd247 st.dataframe(pd.DataFrame(logs), use_container_width=True)248 else:249 st.warning("No proctoring logs (DB not connected or empty)")250 except Exception as e:251 st.warning(f"⚠️ {e}")252 253 with col_b:254 st.markdown("#### Behavior Analysis Logs")255 try:256 blogs = fetch_recent_logs("behavior_logs", limit=15)257 if blogs:258 st.success(f"✅ {len(blogs)} records")259 import pandas as pd260 st.dataframe(pd.DataFrame(blogs), use_container_width=True)261 else:262 st.warning("No behavior logs (DB not connected or empty)")263 except Exception as e:264 st.warning(f"⚠️ {e}")265 266 267# =============================================================================268# TAB 4 – Evidence Viewer269# =============================================================================270with tab4:271 st.subheader("🖼️ Captured Violation Evidence")272 evidence_list = st.session_state.get("evidence_frames", [])273 274 if evidence_list:275 st.info(f"{len(evidence_list)} violation frames captured")276 for i, ev in enumerate(reversed(evidence_list)):277 with st.expander(278 f"🚨 [{ev.get('timestamp','')}] Risk:{ev.get('risk_score',0)} | "279 f"{', '.join(ev.get('flags', [])[:3])}"280 ):281 b64 = ev.get("frame_b64", "")282 if b64:283 try:284 img_data = base64.b64decode(b64)285 img_array = np.frombuffer(img_data, dtype=np.uint8)286 img_frame = cv2.imdecode(img_array, cv2.IMREAD_COLOR)287 if img_frame is not None:288 st.image(img_frame, channels="BGR",289 caption=f"Risk: {ev.get('risk_score')} | Flags: {ev.get('flags')}")290 except Exception as e:291 st.warning(f"Could not decode image: {e}")292 293 if st.button("🗑️ Clear Evidence"):294 st.session_state.evidence_frames = []295 st.rerun()296 else:297 st.info("No violation frames captured yet. Violations at risk ≥ 50 will appear here.")298 299 300# =============================================================================301# TAB 5 – System Info302# =============================================================================303with tab5:304 st.subheader("ℹ️ System Architecture & Status")305 306 # Detect what's loaded307 try:308 import mediapipe309 mp_status = f"✅ MediaPipe {mediapipe.__version__}"310 except Exception:311 mp_status = "⚠️ Not installed"312 313 try:314 import ultralytics315 yolo_status = f"✅ Ultralytics {ultralytics.__version__} (YOLOv8n)"316 except Exception:317 yolo_status = "⚠️ Not installed (using heuristic fallback)"318 319 db_status = "✅ Connected" if supabase is not None else "⚠️ Not connected"320 321 col_s1, col_s2 = st.columns(2)322 with col_s1:323 st.markdown("#### Engine Status")324 st.write(f"**MediaPipe (Eye/Head/Hands):** {mp_status}")325 st.write(f"**YOLOv8n (Person/Object):** {yolo_status}")326 st.write(f"**Supabase DB:** {db_status}")327 328 with col_s2:329 st.markdown("#### Session Stats")330 st.write(f"**Frames Processed:** {st.session_state.frame_count}")331 st.write(f"**Events Logged:** {len(get_event_log())}")332 st.write(f"**Evidence Captured:** {len(st.session_state.get('evidence_frames', []))}")333 334 st.markdown("---")335 st.markdown("""336 ### Module Completion Status337 338 | Module | Feature | Engine | Status |339 |--------|---------|--------|--------|340 | **2.1** | Eye Tracking – Iris Gaze Estimation | MediaPipe Face Mesh | ✅ Active |341 | **2.1** | Blink Detection (EAR) | MediaPipe | ✅ Active |342 | **2.1** | Look-Away Frequency Tracking | Internal | ✅ Active |343 | **2.1** | Head Pose – Yaw/Pitch/Roll | MediaPipe + SolvePnP | ✅ Active |344 | **2.1** | Attention Score (composite) | Multi-signal | ✅ Active |345 | **2.2** | Person Detection | YOLOv8n (auto-download) | ✅ Active |346 | **2.2** | Phone Detection | YOLOv8n | ✅ Active |347 | **2.2** | Book/Notes/Laptop Detection | YOLOv8n | ✅ Active |348 | **2.2** | Confidence-based Classification | YOLOv8n | ✅ Active |349 | **2.2** | Evidence Screenshot Capture | OpenCV+Base64 | ✅ Active |350 | **2.3** | Mobile Phone Detection | YOLO + Contour | ✅ Active |351 | **2.3** | Hand Landmark Tracking (21 pts) | MediaPipe Hands | ✅ Active |352 | **2.3** | Finger Count Detection | MediaPipe Hands | ✅ Active |353 | **2.3** | Gesture Classification | Rule-based | ✅ Active |354 | **2.3** | Suspicious Gesture (Phone-Hold, Writing) | MediaPipe | ✅ Active |355 | **2.3** | Motion Score (Optical Flow) | OpenCV | ✅ Active |356 | **ALL** | Dynamic Confidence-Weighted Risk | Multi-module | ✅ Active |357 | **ALL** | Event Audit Trail | In-memory + DB | ✅ Active |358 | **ALL** | Violation Evidence Viewer | Base64 + Streamlit | ✅ Active |359 | **ALL** | Supabase DB Logging | supabase-py | ✅ Active |360 | **ALL** | Hugging Face Deployment | Streamlit | ✅ Ready |361 362 ### Risk Scoring363 | Event | Base Risk | Dynamic? |364 |-------|-----------|---------|365 | Multiple persons detected | +50 | × confidence |366 | Phone detected | +30 | × confidence |367 | Laptop detected | +25 | × confidence |368 | Phone-hold gesture | +20 | × 0.9 |369 | Book detected | +20 | × confidence |370 | Notes detected | +15 | × 0.6 |371 | Unusual hand gesture | +15 | × 0.85 |372 | Looking away | +10 | × 1.0 |373 | Head turned | +10–25 | × angle |374 | Frequent look-away | +10–30 | × frequency |375 | Writing gesture | +12 | × 0.75 |376 | Low blink rate | +5 | × 0.6 |377 """)378 379 st.markdown("### Fallback Chain")380 st.info("""381 **Eye Tracking:** MediaPipe Face Mesh (iris) → OpenCV Haar cascade (pupil centroid)382 383 **Head Pose:** MediaPipe Face Mesh + SolvePnP → OpenCV Haar + SolvePnP → face-centre heuristic384 385 **Person/Object:** YOLOv8n (auto-download) → HOG + Haar cascade + contour heuristic386 387 **Phone Detection:** YOLOv8n → contour aspect-ratio heuristic388 389 **Hand Gesture:** MediaPipe Hands (21 landmarks) → skin-mask + optical flow390 """)