CoolFace
Apppublic

karan-01/Hp_Eye_Head_Movement_Tracking

sourceHugging Faceupdated 4mo agoView on Hugging Face
0likes
app.py390 linesDownload Raw Back to root
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    """)