CVIP2 / app.py
SuriRaja's picture
Create app.py
e1bf146 verified
# ==========================================================
# CVIP2 — Computer Vision Intelligence Platform
# Full merged application entrypoint
# ==========================================================
import streamlit as st
import cv2
import time
import json
from pathlib import Path
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor
from core.video import VideoSource
from core.detector import CVDetector
from core.tracker import SimpleTracker
from core.context import FrameContext
from anomalies.registry import build_registry, load_anomaly_meta
from storage.local_store import (
save_events, save_attendance, save_tickets, save_camera_metrics,
load_events, load_attendance, load_tickets
)
# ==========================================================
# App Config
# ==========================================================
st.set_page_config(
page_title="CVIP2 – Computer Vision Intelligence Platform",
layout="wide"
)
DATA_DIR = Path("data")
DATA_DIR.mkdir(exist_ok=True)
# ==========================================================
# Load configs
# ==========================================================
ANOM_META = load_anomaly_meta("config/anomalies.json")
INDUSTRIES = json.load(open("config/industries.json", "r", encoding="utf-8"))
ANOM_REGISTRY = build_registry()
# ==========================================================
# Helpers
# ==========================================================
def parse_roi(txt):
try:
if not txt:
return None
x1, y1, x2, y2 = map(int, txt.split(","))
return (x1, y1, x2, y2)
except Exception:
return None
def now_iso():
return datetime.utcnow().isoformat()
# ==========================================================
# Sidebar – Industry & Anomaly Selection
# ==========================================================
st.sidebar.title("CVIP2 Configuration")
industry_key = st.sidebar.selectbox(
"Industry Segment",
list(INDUSTRIES.keys()),
format_func=lambda k: INDUSTRIES[k]["label"]
)
industry_cfg = INDUSTRIES[industry_key]
default_anoms = industry_cfg["default_anomalies"]
st.sidebar.subheader("Anomaly Selection (Cross-Industry Allowed)")
selected_anomalies = st.sidebar.multiselect(
"Enable Anomalies",
list(ANOM_META["anomalies"].keys()),
default=default_anoms,
format_func=lambda a: ANOM_META["anomalies"][a]["label"]
)
# ==========================================================
# Thresholds & Parameters
# ==========================================================
st.sidebar.subheader("Thresholds / Parameters")
idle_emit_sec = st.sidebar.slider("Idle emit sec", 2, 60, 10)
sleep_idle_sec = st.sidebar.slider("Sleep idle sec", 30, 600, 120)
phone_long_sec = st.sidebar.slider("Phone call max sec", 10, 3600, 180)
break_limit_sec = st.sidebar.slider("Break limit sec", 30, 3600, 600)
desk_absent_sec = st.sidebar.slider("Desk absent sec", 10, 3600, 120)
loiter_sec = st.sidebar.slider("Loiter window sec", 10, 600, 120)
crowd_threshold = st.sidebar.slider("Crowd threshold", 2, 50, 10)
queue_threshold = st.sidebar.slider("Queue threshold", 2, 30, 6)
entry_count_threshold = st.sidebar.slider("Zone entry count", 1, 100, 10)
entry_window_sec = st.sidebar.slider("Entry window sec", 10, 3600, 300)
meeting_capacity = st.sidebar.slider("Meeting room capacity", 1, 50, 6)
guard_patrol_interval_sec = st.sidebar.slider("Guard patrol interval sec", 60, 7200, 900)
# ==========================================================
# ROI Configuration
# ==========================================================
st.sidebar.subheader("ROI Configuration (x1,y1,x2,y2)")
zone_roi_txt = st.sidebar.text_input("Work Zone ROI", "")
break_roi_txt = st.sidebar.text_input("Break Area ROI", "")
desk_roi_txt = st.sidebar.text_input("Desk ROI", "")
entry_roi_txt = st.sidebar.text_input("Entry ROI", "")
perimeter_roi_txt = st.sidebar.text_input("Perimeter ROI", "")
meeting_roi_txt = st.sidebar.text_input("Meeting Room ROI", "")
guard_roi_txt = st.sidebar.text_input("Guard Patrol ROI", "")
queue_roi_txt = st.sidebar.text_input("Queue ROI", "")
qr_roi_txt = st.sidebar.text_input("QR ROI", "")
theft_roi_txt = st.sidebar.text_input("Theft ROI", "")
sla_roi_txt = st.sidebar.text_input("Housekeeping SLA ROI", "")
ROIS = {
"zone_roi": parse_roi(zone_roi_txt),
"break_roi": parse_roi(break_roi_txt),
"desk_roi": parse_roi(desk_roi_txt),
"entry_roi": parse_roi(entry_roi_txt),
"perimeter_roi": parse_roi(perimeter_roi_txt),
"meeting_roi": parse_roi(meeting_roi_txt),
"guard_roi": parse_roi(guard_roi_txt),
"queue_roi": parse_roi(queue_roi_txt),
"qr_roi": parse_roi(qr_roi_txt),
"theft_roi": parse_roi(theft_roi_txt),
"sla_roi": parse_roi(sla_roi_txt),
}
# ==========================================================
# Camera Inputs
# ==========================================================
st.header("Multi-Camera Inputs")
cam_sources = st.text_area(
"Camera Sources (one per line: RTSP / file path / webcam index)",
value="0"
).splitlines()
use_parallel = st.checkbox("Run cameras in parallel (Local only)", value=False)
max_workers = st.slider("Max camera workers", 1, 16, 4)
# ==========================================================
# Runtime Configuration
# ==========================================================
runtime_cfg = {
"industry": industry_key,
"anomaly_meta": ANOM_META["anomalies"],
"enabled_anomalies": set(selected_anomalies),
"rois": ROIS,
# thresholds
"idle_emit_sec": idle_emit_sec,
"sleep_idle_sec": sleep_idle_sec,
"phone_long_sec": phone_long_sec,
"break_limit_sec": break_limit_sec,
"desk_absent_sec": desk_absent_sec,
"loiter_sec": loiter_sec,
"crowd_threshold": crowd_threshold,
"queue_threshold": queue_threshold,
"entry_count_threshold": entry_count_threshold,
"entry_window_sec": entry_window_sec,
"meeting_capacity": meeting_capacity,
"guard_patrol_interval_sec": guard_patrol_interval_sec,
# constants
"idle_motion_px": 3.0,
"phone_overlap_min": 0.1,
"theft_diff_thr": 25,
"theft_change_ratio": 0.08,
"theft_baseline_alpha": 0.05,
"freeze_diff_mean": 1.5,
"freeze_frames": 30,
"tamper_low_contrast": 8.0,
"tamper_dark_mean": 15.0,
"tamper_bright_mean": 240.0,
"tamper_frames": 20,
"sla_seconds": 900,
# working hours
"working_start": 9,
"working_end": 18,
"after_hours_enabled": True,
}
# ==========================================================
# Camera Worker
# ==========================================================
def run_camera(camera_id, source):
detector = CVDetector()
tracker = SimpleTracker()
vs = VideoSource(source)
frame_count = 0
event_count = 0
for frame in vs.frames():
frame_count += 1
detections = detector.detect(frame)
tracks = tracker.update(detections)
ctx = FrameContext(
frame=frame,
detections=detections,
tracks=tracks,
camera_id=str(camera_id),
zone="default",
fps=vs.fps,
face_identities=detector.face_identities,
now_ts=time.time()
)
events = []
for aid in runtime_cfg["enabled_anomalies"]:
anomaly = ANOM_REGISTRY.get(aid)
if anomaly:
events.extend(anomaly.evaluate(ctx, runtime_cfg))
if events:
save_events([e.__dict__ for e in events])
event_count += len(events)
save_camera_metrics([{
"ts": now_iso(),
"camera_id": str(camera_id),
"frames_processed": frame_count,
"events": event_count,
"attendance_changes": 0,
"tickets": 0,
"industry": industry_key
}])
vs.release()
# ==========================================================
# Run Button
# ==========================================================
if st.button("▶ Start CVIP2 Monitoring"):
st.success("Monitoring started")
if use_parallel and len(cam_sources) > 1:
with ThreadPoolExecutor(max_workers=max_workers) as ex:
for idx, src in enumerate(cam_sources):
ex.submit(run_camera, idx, src.strip())
else:
for idx, src in enumerate(cam_sources):
run_camera(idx, src.strip())
# ==========================================================
# Dashboards
# ==========================================================
st.header("📊 Dashboards")
tab1, tab2, tab3 = st.tabs(["Events", "Attendance", "Tickets"])
with tab1:
df = load_events()
st.dataframe(df, use_container_width=True)
with tab2:
df = load_attendance()
st.dataframe(df, use_container_width=True)
with tab3:
df = load_tickets()
st.dataframe(df, use_container_width=True)