Subh775 commited on
Commit
b87a24a
·
1 Parent(s): fe9d010

png xet enabled

Browse files
.gitattributes CHANGED
@@ -1,5 +1,6 @@
1
  *.7z filter=lfs diff=lfs merge=lfs -text
2
  *.arrow filter=lfs diff=lfs merge=lfs -text
 
3
  *.bin filter=lfs diff=lfs merge=lfs -text
4
  *.bz2 filter=lfs diff=lfs merge=lfs -text
5
  *.ckpt filter=lfs diff=lfs merge=lfs -text
 
1
  *.7z filter=lfs diff=lfs merge=lfs -text
2
  *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.png filter=xet diff=xet merge=xet -text
4
  *.bin filter=lfs diff=lfs merge=lfs -text
5
  *.bz2 filter=lfs diff=lfs merge=lfs -text
6
  *.ckpt filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ .env
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ libgl1 \
7
+ libglib2.0-0 \
8
+ curl \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ COPY requirements.txt .
12
+ RUN pip install --no-cache-dir -r requirements.txt
13
+
14
+ COPY backend/ ./backend/
15
+ COPY frontend/ ./frontend/
16
+ COPY .env .env
17
+
18
+ EXPOSE 7860
19
+
20
+ CMD ["python", "backend/server.py"]
README.md CHANGED
@@ -10,4 +10,39 @@ short_description: Monitoring Made Easy
10
  thumbnail: >-
11
  https://cdn-uploads.huggingface.co/production/uploads/66c6048d0bf40704e4159a23/2EUWmy9YzOM4eHft6E04y.png
12
  ---
13
- Monitoring made Easy with Computer Vision
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  thumbnail: >-
11
  https://cdn-uploads.huggingface.co/production/uploads/66c6048d0bf40704e4159a23/2EUWmy9YzOM4eHft6E04y.png
12
  ---
13
+
14
+ # UrbanFlow 🚛
15
+ Monitoring made Easy with Computer Vision
16
+
17
+ Full-stack traffic analytics dashboard. YOLO + ByteTrack backend processes uploaded videos, counts vehicles crossing a user-drawn line, and streams results to the browser in real time via WebSocket.
18
+
19
+ ## Stack
20
+
21
+ - **Backend**: FastAPI, Ultralytics YOLO (ONNX), ByteTrack, OpenCV
22
+ - **Frontend**: Vanilla HTML/JS, TailwindCSS CDN, Chart.js
23
+ - **Infra**: Docker, CPU-only inference
24
+
25
+ ## Run with Docker
26
+
27
+ ```bash
28
+ docker build -t funky .
29
+ docker run -p 7860:7860 funky
30
+ ```
31
+
32
+ Open `http://localhost:7860`
33
+
34
+ ## Run Locally
35
+
36
+ ```bash
37
+ pip install -r requirements.txt
38
+ python backend/server.py
39
+ ```
40
+
41
+ ## Flow
42
+
43
+ 1. Select Traffic Analytics module
44
+ 2. Upload video file
45
+ 3. System auto-calculates optimal inference settings (adjustable)
46
+ 4. Draw counting line on first frame
47
+ 5. Engine processes frames, dashboard updates in real time
48
+ 6. View run details and live analytics
backend/config.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import multiprocessing as mp
3
+
4
+ BASE_IMG_SIZE = 640
5
+ REF_PIXELS = 640 * 640
6
+ REF_FPS_CPU = 13.0
7
+ TRACK_STABILITY_STRIDE = 3
8
+
9
+
10
+ def _cpu_score():
11
+ return mp.cpu_count()
12
+
13
+
14
+ def _video_meta(path):
15
+ cap = cv2.VideoCapture(path)
16
+ fps = cap.get(cv2.CAP_PROP_FPS)
17
+ frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
18
+ w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
19
+ h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
20
+ cap.release()
21
+
22
+ duration = frames / fps if fps else 0
23
+ pixels = w * h
24
+ return fps, frames, duration, w, h, pixels
25
+
26
+
27
+ def _estimate_fps(imgsz, cpu_score):
28
+ scale = (imgsz * imgsz) / REF_PIXELS
29
+ return (REF_FPS_CPU * cpu_score / 12) / scale
30
+
31
+
32
+ def _select_imgsz(pixels):
33
+ if pixels >= 3840 * 2160:
34
+ return 640
35
+ if pixels >= 2560 * 1440:
36
+ return 704
37
+ if pixels >= 1920 * 1080:
38
+ return 736
39
+ if pixels >= 1280 * 720:
40
+ return 800
41
+ return 960
42
+
43
+
44
+ def _select_stride(video_fps, model_fps):
45
+ if model_fps >= video_fps:
46
+ return 1
47
+ ratio = video_fps / model_fps
48
+ stride = int(round(ratio))
49
+ return max(1, min(stride, TRACK_STABILITY_STRIDE))
50
+
51
+
52
+ def get_optimal_config(video_path):
53
+ fps, frames, duration, w, h, pixels = _video_meta(video_path)
54
+ cpu_score = _cpu_score()
55
+
56
+ imgsz = _select_imgsz(pixels)
57
+ model_fps = _estimate_fps(imgsz, cpu_score)
58
+ detect_stride = _select_stride(fps, model_fps)
59
+ effective_fps = model_fps / detect_stride
60
+ realtime_possible = effective_fps >= fps
61
+
62
+ return {
63
+ "video_fps": fps,
64
+ "frames": frames,
65
+ "duration": round(duration, 2),
66
+ "resolution": [w, h],
67
+ "pixels": pixels,
68
+ "cpu_score": cpu_score,
69
+ "imgsz": imgsz,
70
+ "detect_stride": detect_stride,
71
+ "model_fps_est": round(model_fps, 2),
72
+ "effective_fps_est": round(effective_fps, 2),
73
+ "realtime_possible": realtime_possible
74
+ }
backend/constants.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MODEL_CLASSES = {
2
+ 0: "Hatchback", 1: "Sedan", 2: "SUV", 3: "MUV", 4: "Bus", 5: "Truck",
3
+ 6: "Three-wheeler", 7: "Two-wheeler", 8: "LCV", 9: "Mini-bus",
4
+ 10: "Tempo-traveller", 11: "Bicycle", 12: "Van", 13: "Others"
5
+ }
6
+
7
+ BUSINESS_MAP = {
8
+ "Cars": [0, 1, 2, 3, 12],
9
+ "Buses": [4, 9, 10],
10
+ "Two-wheelers": [7, 11],
11
+ "Three-wheelers": [6],
12
+ "Trucks": [5, 8],
13
+ "Others": [13]
14
+ }
backend/engine.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ import numpy as np
3
+ import cv2
4
+ from collections import defaultdict
5
+
6
+
7
+ def _side(p, a, b):
8
+ return np.sign((b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]))
9
+
10
+
11
+ def _point_to_segment_dist(px, py, ax, ay, bx, by):
12
+ A = np.array([ax, ay], dtype=float)
13
+ B = np.array([bx, by], dtype=float)
14
+ P = np.array([px, py], dtype=float)
15
+ AB = B - A
16
+ t = np.clip(np.dot(P - A, AB) / np.dot(AB, AB), 0, 1)
17
+ return np.linalg.norm(P - (A + t * AB))
18
+
19
+
20
+ def run(model, video_path, line, config, on_frame):
21
+ """
22
+ Runs YOLO tracking on video. Calls on_frame(update_dict) after each processed frame.
23
+ line: [[x1,y1], [x2,y2]]
24
+ """
25
+ cap = cv2.VideoCapture(video_path)
26
+ fps = cap.get(cv2.CAP_PROP_FPS)
27
+ total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
28
+ cap.release()
29
+
30
+ stride = config["detect_stride"]
31
+ total_iters = total // stride
32
+
33
+ prev_side = {}
34
+ counted_ids = set()
35
+ class_in = defaultdict(int)
36
+ class_out = defaultdict(int)
37
+ congestion = []
38
+ flow_times = []
39
+
40
+ start = time.time()
41
+
42
+ results = model.track(
43
+ source=video_path,
44
+ tracker="bytetrack.yaml",
45
+ imgsz=config["imgsz"],
46
+ conf=config.get("conf", 0.12),
47
+ iou=config.get("iou", 0.6),
48
+ vid_stride=stride,
49
+ stream=True,
50
+ verbose=False,
51
+ persist=True
52
+ )
53
+
54
+ a = line[0]
55
+ b = line[1]
56
+
57
+ for frame_idx, r in enumerate(results):
58
+ active = 0
59
+
60
+ if r.boxes.id is not None:
61
+ ids = r.boxes.id.cpu().numpy()
62
+ cls = r.boxes.cls.cpu().numpy()
63
+ xyxy = r.boxes.xyxy.cpu().numpy()
64
+
65
+ active = len(ids)
66
+
67
+ for obj_id, c, box in zip(ids, cls, xyxy):
68
+ cx = int((box[0] + box[2]) / 2)
69
+ cy = int((box[1] + box[3]) / 2)
70
+
71
+ current = _side((cx, cy), a, b)
72
+
73
+ if obj_id in prev_side and obj_id not in counted_ids:
74
+ if prev_side[obj_id] != current:
75
+ dist = _point_to_segment_dist(cx, cy, a[0], a[1], b[0], b[1])
76
+ if dist < 12:
77
+ t = frame_idx * stride / fps
78
+ flow_times.append(round(t, 2))
79
+
80
+ if current > 0:
81
+ class_in[int(c)] += 1
82
+ else:
83
+ class_out[int(c)] += 1
84
+
85
+ counted_ids.add(obj_id)
86
+
87
+ prev_side[obj_id] = current
88
+
89
+ congestion.append(active)
90
+
91
+ elapsed = time.time() - start
92
+
93
+ update = {
94
+ "frame_index": frame_idx + 1,
95
+ "total_iters": total_iters,
96
+ "total_frames": total,
97
+ "active": active,
98
+ "congestion": congestion.copy(),
99
+ "class_in": {str(k): v for k, v in class_in.items()},
100
+ "class_out": {str(k): v for k, v in class_out.items()},
101
+ "flow_times": flow_times.copy(),
102
+ "elapsed": round(elapsed, 2),
103
+ "fps": round((frame_idx + 1) / elapsed, 2) if elapsed > 0 else 0,
104
+ }
105
+
106
+ on_frame(update)
107
+
108
+ processing_time = round(time.time() - start, 2)
109
+ actual_fps = round(config["frames"] / processing_time, 2) if processing_time > 0 else 0
110
+ speed_vs_rt = round(actual_fps / fps, 2) if fps > 0 else 0
111
+
112
+ return {
113
+ "class_in": dict(class_in),
114
+ "class_out": dict(class_out),
115
+ "congestion": congestion,
116
+ "flow_times": flow_times,
117
+ "processing_time": processing_time,
118
+ "actual_fps": actual_fps,
119
+ "speed_vs_realtime": speed_vs_rt,
120
+ }
backend/model.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+ from dotenv import load_dotenv
4
+ from ultralytics import YOLO
5
+
6
+ load_dotenv()
7
+
8
+ MODEL_DIR = Path(__file__).parent / "weights"
9
+ PT_PATH = MODEL_DIR / "best.pt"
10
+ ONNX_PATH = MODEL_DIR / "best.onnx"
11
+
12
+
13
+ def ensure_onnx():
14
+ MODEL_DIR.mkdir(exist_ok=True)
15
+
16
+ if not PT_PATH.exists():
17
+ token = os.getenv("HF_TOKEN")
18
+ os.system(
19
+ f'curl -L -H "Authorization: Bearer {token}" '
20
+ f'-o {PT_PATH} '
21
+ f'https://huggingface.co/Perception365/VehicleNet-Y26s/resolve/main/weights/best.pt'
22
+ )
23
+
24
+ if not ONNX_PATH.exists():
25
+ YOLO(str(PT_PATH)).export(format="onnx", dynamic=True)
26
+ exported = PT_PATH.with_suffix(".onnx")
27
+ if exported != ONNX_PATH:
28
+ exported.rename(ONNX_PATH)
29
+
30
+
31
+ def load_model():
32
+ ensure_onnx()
33
+ return YOLO(str(ONNX_PATH), task="detect")
backend/server.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import uuid
4
+ import asyncio
5
+ import tempfile
6
+ from pathlib import Path
7
+
8
+ import cv2
9
+ from fastapi import FastAPI, WebSocket, UploadFile, File
10
+ from fastapi.responses import FileResponse, Response
11
+ from fastapi.staticfiles import StaticFiles
12
+
13
+ from model import load_model
14
+ from config import get_optimal_config
15
+ from engine import run
16
+ from constants import MODEL_CLASSES, BUSINESS_MAP
17
+
18
+ app = FastAPI()
19
+
20
+ BASE = Path(__file__).parent.parent
21
+ FRONTEND = BASE / "frontend"
22
+ UPLOAD_DIR = Path(tempfile.gettempdir()) / "funky_uploads"
23
+ UPLOAD_DIR.mkdir(exist_ok=True)
24
+
25
+ videos = {}
26
+ model = None
27
+
28
+
29
+ @app.on_event("startup")
30
+ def startup():
31
+ global model
32
+ model = load_model()
33
+
34
+
35
+ @app.get("/")
36
+ def index():
37
+ return FileResponse(FRONTEND / "initial.html")
38
+
39
+
40
+ @app.post("/upload")
41
+ async def upload(file: UploadFile = File(...)):
42
+ video_id = str(uuid.uuid4())[:8]
43
+ path = UPLOAD_DIR / f"{video_id}.mp4"
44
+ with open(path, "wb") as f:
45
+ f.write(await file.read())
46
+ videos[video_id] = str(path)
47
+ return {"video_id": video_id}
48
+
49
+
50
+ @app.get("/config/{video_id}")
51
+ def config_endpoint(video_id: str):
52
+ path = videos.get(video_id)
53
+ cfg = get_optimal_config(path)
54
+ return cfg
55
+
56
+
57
+ @app.get("/first-frame/{video_id}")
58
+ def first_frame(video_id: str):
59
+ path = videos.get(video_id)
60
+ cap = cv2.VideoCapture(path)
61
+ ret, frame = cap.read()
62
+ cap.release()
63
+ _, buf = cv2.imencode(".jpg", frame)
64
+ return Response(content=buf.tobytes(), media_type="image/jpeg")
65
+
66
+
67
+ @app.get("/constants")
68
+ def constants():
69
+ return {"classes": MODEL_CLASSES, "business_map": BUSINESS_MAP}
70
+
71
+
72
+ @app.websocket("/ws/run")
73
+ async def ws_run(ws: WebSocket):
74
+ await ws.accept()
75
+ data = json.loads(await ws.receive_text())
76
+
77
+ video_id = data["video_id"]
78
+ line = data["line"]
79
+ cfg = data["config"]
80
+
81
+ path = videos.get(video_id)
82
+
83
+ loop = asyncio.get_event_loop()
84
+
85
+ queue = asyncio.Queue()
86
+
87
+ def on_frame(update):
88
+ loop.call_soon_threadsafe(queue.put_nowait, update)
89
+
90
+ task = loop.run_in_executor(None, run, model, path, line, cfg, on_frame)
91
+
92
+ try:
93
+ while True:
94
+ done = task.done()
95
+ while not queue.empty():
96
+ update = queue.get_nowait()
97
+ await ws.send_text(json.dumps(update))
98
+
99
+ if done:
100
+ break
101
+
102
+ await asyncio.sleep(0.05)
103
+
104
+ result = task.result()
105
+ await ws.send_text(json.dumps({
106
+ "done": True,
107
+ "processing_time": result["processing_time"],
108
+ "actual_fps": result["actual_fps"],
109
+ "speed_vs_realtime": result["speed_vs_realtime"],
110
+ }))
111
+ await ws.close()
112
+ except Exception:
113
+ pass
114
+
115
+
116
+ app.mount("/", StaticFiles(directory=str(FRONTEND)), name="frontend")
117
+
118
+ if __name__ == "__main__":
119
+ import uvicorn
120
+ uvicorn.run(app, host="0.0.0.0", port=7860)
frontend/initial.html ADDED
@@ -0,0 +1,447 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>UrbanFlow - Enterprise Setup</title>
8
+ <link rel="icon" type="image/svg+xml" href="uf.svg">
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+ <link
12
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700;800;900&display=swap"
13
+ rel="stylesheet">
14
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
15
+ <style>
16
+ body {
17
+ font-family: 'Inter', sans-serif;
18
+ }
19
+
20
+ .font-montserrat {
21
+ font-family: 'Montserrat', sans-serif;
22
+ }
23
+
24
+ .mono-font {
25
+ font-family: 'JetBrains Mono', monospace;
26
+ }
27
+
28
+ .fade-in {
29
+ animation: fadeIn 0.4s ease-in-out forwards;
30
+ }
31
+
32
+ @keyframes fadeIn {
33
+ from {
34
+ opacity: 0;
35
+ transform: translateY(10px);
36
+ }
37
+
38
+ to {
39
+ opacity: 1;
40
+ transform: translateY(0);
41
+ }
42
+ }
43
+
44
+ .bg-glow {
45
+ position: absolute;
46
+ width: 600px;
47
+ height: 600px;
48
+ background: radial-gradient(circle, rgba(241, 245, 249, 1) 0%, rgba(255, 255, 255, 0) 70%);
49
+ top: 50%;
50
+ left: 0;
51
+ transform: translateY(-50%);
52
+ z-index: -1;
53
+ pointer-events: none;
54
+ }
55
+ </style>
56
+ </head>
57
+
58
+ <body
59
+ class="bg-white text-slate-900 min-h-screen w-full overflow-x-hidden flex flex-col items-center selection:bg-black selection:text-white relative z-0">
60
+
61
+ <div class="bg-glow"></div>
62
+
63
+ <header class="mt-8 flex flex-col items-center flex-shrink-0 w-full z-10">
64
+ <img src="uf_logo.png" alt="UrbanFlow Logo" class="h-44 md:h-52 w-auto object-contain mb-3">
65
+ <div class="flex items-center space-x-3">
66
+ <span class="w-12 h-[1px] bg-slate-200"></span>
67
+ <p class="font-montserrat font-bold tracking-[0.25em] uppercase text-[10px] text-slate-400 text-center">
68
+ Enterprise AI Pipeline
69
+ </p>
70
+ <span class="w-12 h-[1px] bg-slate-200"></span>
71
+ </div>
72
+ </header>
73
+
74
+ <main
75
+ class="flex-1 w-full max-w-[90rem] mx-auto grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-20 px-10 py-6 items-center z-10">
76
+
77
+ <div class="lg:col-span-7 flex flex-col justify-center xl:pl-10 pb-10 lg:pb-0">
78
+ <h1
79
+ class="text-5xl xl:text-[4.5rem] font-montserrat font-extrabold mb-4 leading-[1.1] text-slate-900 tracking-tight">
80
+ Automated <br>Vision Intelligence
81
+ </h1>
82
+ <p
83
+ class="font-montserrat font-bold mb-8 text-sm uppercase tracking-[0.2em] text-slate-400 flex items-center">
84
+ <span class="bg-slate-100 text-slate-600 px-3 py-1 rounded-full text-[10px] mr-3">v1.0 CORE</span>
85
+ Powered by Deep Learning Inference
86
+ </p>
87
+ <ul class="space-y-4 xl:space-y-5 text-base xl:text-lg font-montserrat font-medium text-slate-700">
88
+ <li class="flex items-center"><i class="fa-solid fa-check text-black mr-5 text-xl"></i> Real-time
89
+ spatial detection and tracking</li>
90
+ <li class="flex items-center"><i class="fa-solid fa-check text-black mr-5 text-xl"></i> Multi-class
91
+ object categorization</li>
92
+ <li class="flex items-center"><i class="fa-solid fa-check text-black mr-5 text-xl"></i> Bidirectional
93
+ movement analysis</li>
94
+ <li class="flex items-center"><i class="fa-solid fa-check text-black mr-5 text-xl"></i> High-performance
95
+ multi-object tracking</li>
96
+ <li class="flex items-center"><i class="fa-solid fa-check text-black mr-5 text-xl"></i> Intelligent
97
+ processing optimization</li>
98
+ <li class="flex items-center"><i class="fa-solid fa-check text-black mr-5 text-xl"></i> Comprehensive
99
+ analytics reporting</li>
100
+ </ul>
101
+ </div>
102
+
103
+ <div
104
+ class="lg:col-span-5 flex flex-col justify-center w-full max-w-[32rem] mx-auto min-h-[450px] mb-12 lg:mb-0">
105
+
106
+ <!-- STEP: Module Select -->
107
+ <div id="step-modules" class="w-full flex flex-col fade-in justify-center">
108
+ <h2 class="text-3xl font-montserrat font-bold mb-2 text-slate-900">Select AI Module</h2>
109
+ <p class="text-[13px] font-medium text-slate-400 mb-6">Choose an intelligence pipeline for your media
110
+ stream.</p>
111
+
112
+ <div class="grid grid-cols-2 gap-4">
113
+ <div onclick="showStep('upload')"
114
+ class="group relative bg-white border-2 border-slate-900 rounded-[1.5rem] p-6 cursor-pointer hover:shadow-2xl hover:-translate-y-1 transition-all duration-300">
115
+ <div
116
+ class="absolute top-4 right-4 bg-slate-900 text-white text-[9px] font-bold px-2.5 py-1 rounded-full uppercase tracking-wider">
117
+ Active</div>
118
+ <i class="fa-solid fa-car-side text-3xl text-slate-900 mb-4 block"></i>
119
+ <h3 class="font-montserrat font-bold text-sm mb-2 leading-tight">Traffic <br>Analytics</h3>
120
+ <p class="text-[10px] text-slate-500 font-medium">Detect, track, and analyze vehicles in
121
+ real-world environments using state-of-the-art vision models.</p>
122
+ </div>
123
+ <div
124
+ class="relative bg-slate-50 border border-slate-100 rounded-[1.5rem] p-6 opacity-60 cursor-not-allowed">
125
+ <div
126
+ class="absolute top-4 right-4 bg-white border border-slate-200 text-slate-400 text-[9px] font-bold px-2.5 py-1 rounded-full uppercase tracking-wider">
127
+ Coming Soon</div>
128
+ <i class="fa-solid fa-layer-group text-3xl text-slate-300 mb-4 block"></i>
129
+ <h3 class="font-montserrat font-bold text-sm mb-2 leading-tight text-slate-700">Semantic
130
+ <br>Segmentation</h3>
131
+ <p class="text-[10px] text-slate-400 font-medium">Pixel-perfect instance segmentation for
132
+ complex spatial scene understanding.</p>
133
+ </div>
134
+ <div
135
+ class="relative bg-slate-50 border border-slate-100 rounded-[1.5rem] p-6 opacity-60 cursor-not-allowed">
136
+ <div
137
+ class="absolute top-4 right-4 bg-white border border-slate-200 text-slate-400 text-[9px] font-bold px-2.5 py-1 rounded-full uppercase tracking-wider">
138
+ Coming Soon</div>
139
+ <i class="fa-solid fa-tags text-3xl text-slate-300 mb-4 block"></i>
140
+ <h3 class="font-montserrat font-bold text-sm mb-2 leading-tight text-slate-700">Image
141
+ <br>Classification</h3>
142
+ <p class="text-[10px] text-slate-400 font-medium">High-speed categorical labeling for vast and
143
+ diverse image datasets.</p>
144
+ </div>
145
+ <div
146
+ class="relative bg-slate-50 border border-slate-100 rounded-[1.5rem] p-6 opacity-60 cursor-not-allowed">
147
+ <div
148
+ class="absolute top-4 right-4 bg-white border border-slate-200 text-slate-400 text-[9px] font-bold px-2.5 py-1 rounded-full uppercase tracking-wider">
149
+ Coming Soon</div>
150
+ <i class="fa-solid fa-expand text-3xl text-slate-300 mb-4 block"></i>
151
+ <h3 class="font-montserrat font-bold text-sm mb-2 leading-tight text-slate-700">Custom
152
+ <br>Detection</h3>
153
+ <p class="text-[10px] text-slate-400 font-medium">Deploy proprietary neural networks for highly
154
+ specialized edge inference.</p>
155
+ </div>
156
+ </div>
157
+ </div>
158
+
159
+ <!-- STEP: Upload -->
160
+ <div id="step-upload" class="hidden w-full flex flex-col fade-in justify-center">
161
+ <button onclick="showStep('modules')"
162
+ class="text-slate-400 hover:text-black transition flex items-center text-xs font-bold uppercase tracking-widest mb-6 w-fit">
163
+ <i class="fa-solid fa-arrow-left mr-2"></i> Back to Modules
164
+ </button>
165
+ <h2 class="text-3xl font-montserrat font-bold mb-2 text-slate-900">Initialize Media Source</h2>
166
+ <p class="text-[13px] font-medium text-slate-400 mb-8">Provide the target video footage to configure the
167
+ Traffic Analytics pipeline.</p>
168
+
169
+ <div id="dropzone"
170
+ class="border border-dashed border-slate-300 rounded-[2rem] p-12 flex flex-col items-center justify-center cursor-pointer hover:border-black hover:bg-slate-50 transition-all duration-300 group">
171
+ <i
172
+ class="fa-solid fa-arrow-up-from-bracket text-4xl text-slate-300 group-hover:text-black transition mb-5"></i>
173
+ <span class="font-montserrat font-semibold text-slate-800 text-lg mb-2 text-center">Select or drop
174
+ media file to proceed</span>
175
+ <span class="text-[10px] font-bold uppercase tracking-widest text-slate-400 text-center">Accepted
176
+ formats: MP4, AVI, MOV</span>
177
+ <input id="file-input" type="file" accept="video/*" class="hidden">
178
+ </div>
179
+
180
+ <div id="upload-progress-container" class="hidden mt-10 w-full">
181
+ <div
182
+ class="flex justify-between text-[11px] font-montserrat font-bold uppercase tracking-widest mb-3 text-slate-900">
183
+ <span id="upload-text">Uploading...</span>
184
+ <span id="upload-percentage">0%</span>
185
+ </div>
186
+ <div class="w-full h-1 bg-slate-100 rounded-full overflow-hidden">
187
+ <div id="upload-bar"
188
+ class="h-full bg-black w-0 transition-all duration-75 ease-linear rounded-full"></div>
189
+ </div>
190
+ </div>
191
+ </div>
192
+
193
+ <!-- STEP: Config -->
194
+ <div id="step-config" class="hidden w-full flex flex-col fade-in justify-center">
195
+ <h2 class="text-3xl font-montserrat font-bold mb-2 text-slate-900">System Configuration</h2>
196
+ <p class="text-[11px] font-montserrat font-bold text-slate-400 mb-8 uppercase tracking-widest">
197
+ Optimal values auto-configured for performance
198
+ </p>
199
+
200
+ <div class="space-y-4 xl:space-y-5 mb-8" id="config-fields"></div>
201
+
202
+ <button onclick="showStep('draw')"
203
+ class="w-full py-3.5 bg-black text-white rounded-full font-montserrat font-semibold hover:bg-slate-800 transition-all text-base xl:text-lg flex justify-center items-center">
204
+ Continue <i class="fa-solid fa-arrow-right ml-3 text-sm"></i>
205
+ </button>
206
+ </div>
207
+
208
+ <!-- STEP: Draw Line -->
209
+ <div id="step-draw" class="hidden w-full flex flex-col fade-in justify-center">
210
+ <h2 class="text-3xl font-montserrat font-bold mb-2 text-slate-900">Define Boundary</h2>
211
+ <p class="text-[11px] font-montserrat font-bold uppercase tracking-widest text-slate-400 mb-6">Click two
212
+ points to establish counting threshold</p>
213
+
214
+ <div
215
+ class="relative w-full aspect-video bg-slate-900 rounded-3xl overflow-hidden cursor-crosshair mb-6 shadow-inner">
216
+ <img id="frame-img" class="absolute inset-0 w-full h-full object-contain" style="display:none;">
217
+ <div id="frame-placeholder"
218
+ class="absolute inset-0 flex flex-col items-center justify-center text-slate-500 pointer-events-none select-none">
219
+ <i class="fa-solid fa-video text-4xl mb-3 opacity-30"></i>
220
+ <span
221
+ class="font-montserrat font-semibold text-[10px] uppercase tracking-widest opacity-50">Media
222
+ Frame Preview</span>
223
+ </div>
224
+ <canvas id="drawing-canvas" class="absolute inset-0 w-full h-full"></canvas>
225
+ </div>
226
+
227
+ <div class="flex space-x-3">
228
+ <button onclick="resetCanvas()"
229
+ class="w-1/3 py-3.5 bg-white border border-slate-200 text-slate-800 rounded-full font-montserrat font-semibold hover:border-black hover:text-black transition-all text-sm xl:text-base">
230
+ Reset
231
+ </button>
232
+ <button id="btn-proceed" onclick="startRun()"
233
+ class="w-2/3 py-3.5 bg-black text-white rounded-full font-montserrat font-semibold hover:bg-slate-800 transition-all text-center text-sm xl:text-base">
234
+ Proceed
235
+ </button>
236
+ </div>
237
+ </div>
238
+
239
+ </div>
240
+ </main>
241
+
242
+ <script>
243
+ let videoId = null;
244
+ let runConfig = {};
245
+
246
+ const configParams = [
247
+ { key: "imgsz", label: "Image Size", step: 32, min: 320, max: 1280 },
248
+ { key: "conf", label: "Confidence", step: 0.01, min: 0.01, max: 1.0, decimals: 2 },
249
+ { key: "iou", label: "IoU Threshold", step: 0.05, min: 0.1, max: 1.0, decimals: 2 },
250
+ { key: "detect_stride", label: "Frame Stride", step: 1, min: 1, max: 10 }
251
+ ];
252
+
253
+ function showStep(name) {
254
+ ['modules', 'upload', 'config', 'draw'].forEach(s => {
255
+ document.getElementById('step-' + s).classList.add('hidden');
256
+ });
257
+ document.getElementById('step-' + name).classList.remove('hidden');
258
+
259
+ if (name === 'config') renderConfig();
260
+ if (name === 'draw') loadFirstFrame();
261
+ }
262
+
263
+ // Upload
264
+ const dropzone = document.getElementById('dropzone');
265
+ const fileInput = document.getElementById('file-input');
266
+
267
+ dropzone.addEventListener('click', () => fileInput.click());
268
+ dropzone.addEventListener('dragover', e => { e.preventDefault(); dropzone.classList.add('border-black', 'bg-slate-50'); });
269
+ dropzone.addEventListener('dragleave', () => dropzone.classList.remove('border-black', 'bg-slate-50'));
270
+ dropzone.addEventListener('drop', e => {
271
+ e.preventDefault();
272
+ dropzone.classList.remove('border-black', 'bg-slate-50');
273
+ if (e.dataTransfer.files.length) uploadFile(e.dataTransfer.files[0]);
274
+ });
275
+ fileInput.addEventListener('change', () => { if (fileInput.files.length) uploadFile(fileInput.files[0]); });
276
+
277
+ function uploadFile(file) {
278
+ dropzone.classList.add('hidden');
279
+ const prog = document.getElementById('upload-progress-container');
280
+ const bar = document.getElementById('upload-bar');
281
+ const pct = document.getElementById('upload-percentage');
282
+ const txt = document.getElementById('upload-text');
283
+ prog.classList.remove('hidden');
284
+
285
+ const form = new FormData();
286
+ form.append('file', file);
287
+
288
+ const xhr = new XMLHttpRequest();
289
+ xhr.open('POST', '/upload');
290
+
291
+ xhr.upload.onprogress = e => {
292
+ if (e.lengthComputable) {
293
+ const p = Math.round(e.loaded / e.total * 100);
294
+ bar.style.width = p + '%';
295
+ pct.innerText = p + '%';
296
+ }
297
+ };
298
+
299
+ xhr.onload = () => {
300
+ const res = JSON.parse(xhr.responseText);
301
+ videoId = res.video_id;
302
+ txt.innerText = 'Extracting Metadata...';
303
+ bar.style.width = '100%';
304
+ pct.innerText = '100%';
305
+
306
+ fetch('/config/' + videoId)
307
+ .then(r => r.json())
308
+ .then(cfg => {
309
+ runConfig = cfg;
310
+ runConfig.conf = 0.12;
311
+ runConfig.iou = 0.60;
312
+ txt.innerText = 'Initialization Complete';
313
+ setTimeout(() => showStep('config'), 400);
314
+ });
315
+ };
316
+
317
+ xhr.send(form);
318
+ }
319
+
320
+ // Config
321
+ function renderConfig() {
322
+ const container = document.getElementById('config-fields');
323
+ container.innerHTML = '';
324
+
325
+ configParams.forEach(p => {
326
+ let val = runConfig[p.key];
327
+ const d = p.decimals || 0;
328
+
329
+ const row = document.createElement('div');
330
+ row.className = 'flex items-center justify-between border-b border-slate-100 pb-3';
331
+ row.innerHTML = `
332
+ <span class="font-montserrat font-medium text-slate-700 text-sm xl:text-base">${p.label}</span>
333
+ <div class="flex items-center space-x-3 bg-slate-50 px-3 py-1.5 rounded-full border border-slate-200 shadow-sm">
334
+ <button class="text-slate-400 hover:text-black transition p-1" data-dir="-1" data-key="${p.key}">
335
+ <i class="fa-solid fa-chevron-left text-[10px]"></i>
336
+ </button>
337
+ <span class="font-mono font-bold w-12 text-center text-slate-900 text-base" id="cfg-${p.key}">${d ? val.toFixed(d) : val}</span>
338
+ <button class="text-slate-400 hover:text-black transition p-1" data-dir="1" data-key="${p.key}">
339
+ <i class="fa-solid fa-chevron-right text-[10px]"></i>
340
+ </button>
341
+ </div>
342
+ `;
343
+ container.appendChild(row);
344
+ });
345
+
346
+ container.querySelectorAll('button').forEach(btn => {
347
+ btn.addEventListener('click', () => {
348
+ const key = btn.dataset.key;
349
+ const dir = parseInt(btn.dataset.dir);
350
+ const param = configParams.find(p => p.key === key);
351
+ let val = runConfig[key] + dir * param.step;
352
+ val = Math.max(param.min, Math.min(param.max, val));
353
+ val = param.decimals ? parseFloat(val.toFixed(param.decimals)) : Math.round(val);
354
+ runConfig[key] = val;
355
+ const d = param.decimals || 0;
356
+ document.getElementById('cfg-' + key).innerText = d ? val.toFixed(d) : val;
357
+ });
358
+ });
359
+ }
360
+
361
+ // Draw line
362
+ const canvas = document.getElementById('drawing-canvas');
363
+ const ctx = canvas.getContext('2d');
364
+ let points = [];
365
+ let imgNatW = 0, imgNatH = 0;
366
+
367
+ function loadFirstFrame() {
368
+ const img = document.getElementById('frame-img');
369
+ img.src = '/first-frame/' + videoId;
370
+ img.onload = () => {
371
+ imgNatW = img.naturalWidth;
372
+ imgNatH = img.naturalHeight;
373
+ img.style.display = 'block';
374
+ document.getElementById('frame-placeholder').style.display = 'none';
375
+ initCanvas();
376
+ };
377
+ }
378
+
379
+ function initCanvas() {
380
+ canvas.width = canvas.offsetWidth;
381
+ canvas.height = canvas.offsetHeight;
382
+ }
383
+
384
+ window.addEventListener('resize', () => {
385
+ if (!document.getElementById('step-draw').classList.contains('hidden')) {
386
+ initCanvas();
387
+ points.forEach(p => drawDot(p.cx, p.cy));
388
+ if (points.length === 2) drawLine();
389
+ }
390
+ });
391
+
392
+ canvas.addEventListener('mousedown', e => {
393
+ if (points.length >= 2) return;
394
+
395
+ const rect = canvas.getBoundingClientRect();
396
+ const cx = e.clientX - rect.left;
397
+ const cy = e.clientY - rect.top;
398
+
399
+ const rx = cx / canvas.width * imgNatW;
400
+ const ry = cy / canvas.height * imgNatH;
401
+
402
+ points.push({ cx, cy, rx: Math.round(rx), ry: Math.round(ry) });
403
+ drawDot(cx, cy);
404
+ if (points.length === 2) drawLine();
405
+ });
406
+
407
+ function drawDot(x, y) {
408
+ ctx.beginPath();
409
+ ctx.arc(x, y, 5, 0, Math.PI * 2);
410
+ ctx.fillStyle = '#10b981';
411
+ ctx.fill();
412
+ ctx.strokeStyle = '#ffffff';
413
+ ctx.lineWidth = 2;
414
+ ctx.stroke();
415
+ }
416
+
417
+ function drawLine() {
418
+ ctx.beginPath();
419
+ ctx.moveTo(points[0].cx, points[0].cy);
420
+ ctx.lineTo(points[1].cx, points[1].cy);
421
+ ctx.strokeStyle = '#10b981';
422
+ ctx.lineWidth = 3;
423
+ ctx.stroke();
424
+ }
425
+
426
+ function resetCanvas() {
427
+ points = [];
428
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
429
+ }
430
+
431
+ function startRun() {
432
+ if (points.length < 2) return;
433
+
434
+ const line = [[points[0].rx, points[0].ry], [points[1].rx, points[1].ry]];
435
+
436
+ sessionStorage.setItem('funky_run', JSON.stringify({
437
+ video_id: videoId,
438
+ line: line,
439
+ config: runConfig
440
+ }));
441
+
442
+ window.location.href = 'vehicles.html';
443
+ }
444
+ </script>
445
+ </body>
446
+
447
+ </html>
frontend/run_details.html ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <meta http-equiv="refresh" content="0;url=vehicles.html">
6
+ </head>
7
+
8
+ </html>
frontend/uf.svg ADDED
frontend/uf_logo.png ADDED

Git LFS Details

  • SHA256: aea0f702168f1863a09ba70bd18f16175474bd94488d15102839657da1025154
  • Pointer size: 132 Bytes
  • Size of remote file: 5.05 MB
frontend/vehicles.html ADDED
@@ -0,0 +1,510 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>UrbanFlow - Dashboard</title>
8
+ <link rel="icon" type="image/svg+xml" href="uf.svg">
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
12
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
13
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
14
+ <style>
15
+ body {
16
+ font-family: 'Inter', sans-serif;
17
+ }
18
+
19
+ .mono-font {
20
+ font-family: 'JetBrains Mono', monospace;
21
+ }
22
+
23
+ ::-webkit-scrollbar {
24
+ width: 5px;
25
+ height: 5px;
26
+ }
27
+
28
+ ::-webkit-scrollbar-track {
29
+ background: #f1f5f9;
30
+ border-radius: 4px;
31
+ }
32
+
33
+ ::-webkit-scrollbar-thumb {
34
+ background: #cbd5e1;
35
+ border-radius: 4px;
36
+ }
37
+
38
+ ::-webkit-scrollbar-thumb:hover {
39
+ background: #94a3b8;
40
+ }
41
+ </style>
42
+ </head>
43
+
44
+ <body class="bg-[#F3F4F8] text-slate-900 h-screen w-screen overflow-hidden flex">
45
+
46
+ <!-- Sidebar -->
47
+ <aside class="w-60 bg-white shadow-xl flex flex-col z-20 flex-shrink-0 border-r border-slate-200 relative">
48
+ <div class="h-28 bg-white flex items-center justify-center px-4 my-2 border-b border-slate-100 flex-shrink-0">
49
+ <img src="uf_logo.png" alt="UrbanFlow Logo" class="h-24 w-auto object-contain">
50
+ </div>
51
+ <nav class="flex-1 px-4 py-4 space-y-1.5 overflow-y-auto text-sm">
52
+ <a onclick="switchTab('overview')" id="nav-overview"
53
+ class="flex items-center px-4 py-2.5 bg-slate-900 text-white rounded-lg shadow-md transition cursor-pointer">
54
+ <i class="fa-solid fa-desktop w-6"></i> <span class="font-medium">Overview</span>
55
+ </a>
56
+ <a onclick="switchTab('run-details')" id="nav-run-details"
57
+ class="flex items-center px-4 py-2.5 text-slate-600 hover:bg-slate-50 hover:text-slate-900 rounded-lg transition cursor-pointer">
58
+ <i class="fa-solid fa-microchip w-6"></i> <span class="font-medium">Run Details</span>
59
+ </a>
60
+ <a
61
+ class="flex items-center justify-between px-4 py-2.5 text-slate-400 bg-slate-50 rounded-lg opacity-60 cursor-not-allowed">
62
+ <div class="flex items-center"><i class="fa-solid fa-file-lines w-6"></i> <span
63
+ class="font-medium">Reports</span></div>
64
+ <i class="fa-solid fa-lock text-[10px]"></i>
65
+ </a>
66
+ <a
67
+ class="flex items-center justify-between px-4 py-2.5 text-slate-400 bg-slate-50 rounded-lg opacity-60 cursor-not-allowed">
68
+ <div class="flex items-center"><i class="fa-solid fa-chart-line w-6"></i> <span
69
+ class="font-medium">Analytics</span></div>
70
+ <i class="fa-solid fa-lock text-[10px]"></i>
71
+ </a>
72
+ <a
73
+ class="flex items-center justify-between px-4 py-2.5 text-slate-400 bg-slate-50 rounded-lg opacity-60 cursor-not-allowed">
74
+ <div class="flex items-center"><i class="fa-solid fa-video w-6"></i> <span class="font-medium">RTSP
75
+ Feed</span></div>
76
+ <i class="fa-solid fa-lock text-[10px]"></i>
77
+ </a>
78
+ <a
79
+ class="flex items-center justify-between px-4 py-2.5 text-slate-400 bg-slate-50 rounded-lg opacity-60 cursor-not-allowed">
80
+ <div class="flex items-center"><i class="fa-solid fa-gear w-6"></i> <span
81
+ class="font-medium">Settings</span></div>
82
+ <i class="fa-solid fa-lock text-[10px]"></i>
83
+ </a>
84
+ </nav>
85
+ <div class="mt-auto border-t border-slate-100 p-4 flex items-center justify-center bg-white flex-shrink-0">
86
+ <img src="uf_logo.png" alt="UrbanFlow Bottom Logo" class="h-20 w-auto object-contain opacity-80">
87
+ </div>
88
+ </aside>
89
+
90
+ <main class="flex-1 flex flex-col h-full min-w-0 p-4 gap-4">
91
+
92
+ <!-- Progress Bar (shared) -->
93
+ <div
94
+ class="bg-white rounded-xl px-6 py-4 border border-slate-200 shadow-sm flex items-center justify-between flex-shrink-0">
95
+ <div class="flex items-center space-x-4 w-3/4">
96
+ <span class="text-[11px] font-black text-slate-900 uppercase tracking-wider"
97
+ id="proc-label">Processing</span>
98
+ <div class="flex-1 h-1.5 bg-slate-200 rounded-full overflow-hidden relative">
99
+ <div id="proc-bar" class="h-full bg-slate-900 rounded-full transition-all duration-500 ease-out"
100
+ style="width: 0%"></div>
101
+ </div>
102
+ </div>
103
+ <div class="flex items-center space-x-6 text-xs font-bold text-slate-900">
104
+ <span id="proc-frames">0 / 0 Frames</span>
105
+ <span id="proc-pct">0%</span>
106
+ </div>
107
+ </div>
108
+
109
+ <!-- TAB: Overview -->
110
+ <div id="tab-overview" class="grid grid-cols-4 gap-4 flex-1 min-h-0 grid-rows-2">
111
+
112
+ <!-- Congestion Index -->
113
+ <div class="col-span-2 bg-white rounded-xl p-5 border border-slate-200 shadow-sm flex flex-col">
114
+ <div class="flex justify-between items-center mb-2">
115
+ <h3 class="font-bold text-slate-900 text-sm">Congestion Index <span
116
+ class="text-[10px] font-normal text-slate-500 ml-1">(Active Vehicles per Frame)</span></h3>
117
+ </div>
118
+ <div class="flex-1 relative w-full min-h-0">
119
+ <canvas id="congestionChart"></canvas>
120
+ </div>
121
+ </div>
122
+
123
+ <!-- Counters -->
124
+ <div class="col-span-1 flex flex-col gap-4">
125
+ <div
126
+ class="bg-white rounded-xl p-4 border border-slate-200 shadow-sm flex-1 flex flex-col items-center justify-center">
127
+ <div class="text-slate-500 text-[10px] font-bold uppercase tracking-wide text-center">Total Vehicles
128
+ </div>
129
+ <div class="flex items-center justify-center mt-1">
130
+ <span class="text-3xl font-black text-slate-900" id="cnt-total">0</span>
131
+ </div>
132
+ </div>
133
+ <div
134
+ class="bg-white rounded-xl p-4 border border-slate-200 shadow-sm flex-1 flex flex-col items-center justify-center">
135
+ <div class="text-slate-500 text-[10px] font-bold uppercase tracking-wide text-center">Incoming
136
+ (Crossed)</div>
137
+ <div class="flex items-center justify-center mt-1">
138
+ <span class="text-3xl font-black text-slate-900" id="cnt-in">0</span>
139
+ </div>
140
+ </div>
141
+ <div
142
+ class="bg-white rounded-xl p-4 border border-slate-200 shadow-sm flex-1 flex flex-col items-center justify-center">
143
+ <div class="text-slate-500 text-[10px] font-bold uppercase tracking-wide text-center">Outgoing
144
+ (Crossed)</div>
145
+ <div class="flex items-center justify-center mt-1">
146
+ <span class="text-3xl font-black text-slate-900" id="cnt-out">0</span>
147
+ </div>
148
+ </div>
149
+ </div>
150
+
151
+ <!-- Vehicle Classification Breakdown -->
152
+ <div
153
+ class="col-span-1 bg-white rounded-xl p-5 border border-slate-200 shadow-sm flex flex-col overflow-hidden">
154
+ <div class="flex justify-between items-center mb-1">
155
+ <h3 class="font-bold text-slate-900 text-sm">Vehicle Classification Breakdown</h3>
156
+ </div>
157
+ <div class="text-[10px] text-slate-400 mb-3">Detailed distribution by vehicle subclass</div>
158
+ <div class="flex-1 overflow-y-auto pr-3 space-y-2" id="class-breakdown"></div>
159
+ </div>
160
+
161
+ <!-- Class Dominance -->
162
+ <div class="col-span-2 bg-white rounded-xl p-5 border border-slate-200 shadow-sm flex flex-col">
163
+ <div class="flex justify-between items-center mb-2">
164
+ <h3 class="font-bold text-slate-900 text-sm">Class Dominance</h3>
165
+ <span class="text-[10px] text-slate-500 font-bold uppercase tracking-wider">Grouped
166
+ Categories</span>
167
+ </div>
168
+ <div class="flex-1 w-full relative min-h-0">
169
+ <canvas id="dominanceChart"></canvas>
170
+ </div>
171
+ </div>
172
+
173
+ <!-- Traffic Flow Over Time -->
174
+ <div class="col-span-2 bg-white rounded-xl p-5 border border-slate-200 shadow-sm flex flex-col">
175
+ <div class="flex justify-between items-center mb-2">
176
+ <h3 class="font-bold text-slate-900 text-sm">Traffic Flow Over Time</h3>
177
+ <span class="text-[10px] text-slate-500 font-bold uppercase tracking-wider">Vehicles / Sec</span>
178
+ </div>
179
+ <div class="flex-1 w-full relative min-h-0">
180
+ <canvas id="flowChart"></canvas>
181
+ </div>
182
+ </div>
183
+
184
+ </div>
185
+
186
+ <!-- TAB: Run Details -->
187
+ <div id="tab-run-details" class="hidden flex-1 min-h-0 overflow-y-auto">
188
+ <div class="grid grid-cols-2 gap-4 h-full">
189
+ <!-- Video Input -->
190
+ <div class="bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden">
191
+ <div class="px-5 py-3 border-b border-slate-100 bg-slate-50/50">
192
+ <h3 class="font-bold text-slate-800 text-sm"><i
193
+ class="fa-solid fa-film mr-2 text-slate-400"></i> Video Input</h3>
194
+ </div>
195
+ <div class="p-5 space-y-3" id="panel-video"></div>
196
+ </div>
197
+
198
+ <!-- Performance -->
199
+ <div class="bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden">
200
+ <div class="px-5 py-3 border-b border-slate-100 bg-slate-50/50">
201
+ <h3 class="font-bold text-slate-800 text-sm"><i
202
+ class="fa-solid fa-gauge-high mr-2 text-slate-400"></i> Performance</h3>
203
+ </div>
204
+ <div class="p-5 space-y-3" id="panel-perf"></div>
205
+ </div>
206
+
207
+ <!-- Model Config -->
208
+ <div class="bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden">
209
+ <div class="px-5 py-3 border-b border-slate-100 bg-slate-50/50">
210
+ <h3 class="font-bold text-slate-800 text-sm"><i
211
+ class="fa-solid fa-cube mr-2 text-slate-400"></i> Model Config</h3>
212
+ </div>
213
+ <div class="p-5 space-y-3" id="panel-model"></div>
214
+ </div>
215
+
216
+ <!-- Inference Settings -->
217
+ <div class="bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden">
218
+ <div class="px-5 py-3 border-b border-slate-100 bg-slate-50/50">
219
+ <h3 class="font-bold text-slate-800 text-sm"><i
220
+ class="fa-solid fa-sliders mr-2 text-slate-400"></i> Inference Settings</h3>
221
+ </div>
222
+ <div class="p-5 grid grid-cols-2 gap-x-8 gap-y-3" id="panel-infer"></div>
223
+ </div>
224
+
225
+ <!-- Processing Results -->
226
+ <div
227
+ class="col-span-2 bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden">
228
+ <div class="px-5 py-3 border-b border-slate-100 bg-slate-50/50">
229
+ <h3 class="font-bold text-slate-800 text-sm"><i
230
+ class="fa-solid fa-stopwatch mr-2 text-slate-400"></i> Processing Results</h3>
231
+ </div>
232
+ <div class="p-5 space-y-3" id="panel-proc-results">
233
+ <div class="text-xs text-slate-400 italic">Waiting for processing to complete...</div>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ </div>
238
+
239
+ </main>
240
+
241
+ <script>
242
+ // =========== Tab switching ===========
243
+ function switchTab(tab) {
244
+ document.getElementById('tab-overview').classList.toggle('hidden', tab !== 'overview');
245
+ document.getElementById('tab-run-details').classList.toggle('hidden', tab !== 'run-details');
246
+
247
+ const navO = document.getElementById('nav-overview');
248
+ const navR = document.getElementById('nav-run-details');
249
+
250
+ if (tab === 'overview') {
251
+ navO.className = 'flex items-center px-4 py-2.5 bg-slate-900 text-white rounded-lg shadow-md transition cursor-pointer';
252
+ navR.className = 'flex items-center px-4 py-2.5 text-slate-600 hover:bg-slate-50 hover:text-slate-900 rounded-lg transition cursor-pointer';
253
+ } else {
254
+ navR.className = 'flex items-center px-4 py-2.5 bg-slate-900 text-white rounded-lg shadow-md transition cursor-pointer';
255
+ navO.className = 'flex items-center px-4 py-2.5 text-slate-600 hover:bg-slate-50 hover:text-slate-900 rounded-lg transition cursor-pointer';
256
+ }
257
+ }
258
+
259
+ // =========== Run Details panel ===========
260
+ function detailRow(label, value, extra) {
261
+ extra = extra || '';
262
+ return `<div class="flex justify-between items-center border-b border-slate-50 pb-2">
263
+ <span class="text-xs font-medium text-slate-500 mono-font">${label}</span>
264
+ <span class="text-sm font-bold text-slate-800">${value}${extra}</span>
265
+ </div>`;
266
+ }
267
+
268
+ function boolBadge(val) {
269
+ if (val) return `<span class="inline-flex items-center bg-green-50 text-green-700 text-[10px] font-bold px-2 py-0.5 rounded border border-green-200"><i class="fa-solid fa-check mr-1"></i>TRUE</span>`;
270
+ return `<span class="text-[10px] font-bold text-slate-300">FALSE</span>`;
271
+ }
272
+
273
+ function populateRunDetails(c) {
274
+ const res = c.resolution || [0, 0];
275
+
276
+ document.getElementById('panel-video').innerHTML =
277
+ detailRow('video_fps', c.video_fps) +
278
+ detailRow('frames', c.frames) +
279
+ detailRow('duration', c.duration + ' sec') +
280
+ detailRow('resolution', res[0] + ' <span class="text-slate-400 text-xs">x</span> ' + res[1]) +
281
+ detailRow('pixels', (c.pixels || 0).toLocaleString());
282
+
283
+ const cpuPct = Math.min(100, Math.round((c.cpu_score / 10) * 100));
284
+ document.getElementById('panel-perf').innerHTML =
285
+ `<div class="flex justify-between items-center border-b border-slate-50 pb-2">
286
+ <span class="text-xs font-medium text-slate-500 mono-font">cpu_score</span>
287
+ <div class="flex items-center">
288
+ <span class="text-sm font-bold text-slate-800 mr-2">${c.cpu_score}</span>
289
+ <div class="w-16 h-1.5 bg-slate-100 rounded-full overflow-hidden">
290
+ <div class="h-full bg-emerald-500" style="width:${cpuPct}%"></div>
291
+ </div>
292
+ </div>
293
+ </div>` +
294
+ detailRow('model_fps_est', c.model_fps_est, ' <span class="text-xs text-slate-400 font-normal">fps</span>') +
295
+ detailRow('effective_fps', c.effective_fps_est, ' <span class="text-xs text-slate-400 font-normal">fps</span>') +
296
+ `<div class="flex justify-between items-center pt-1">
297
+ <span class="text-xs font-medium text-slate-500 mono-font">realtime_possible</span>
298
+ ${boolBadge(c.realtime_possible)}
299
+ </div>`;
300
+
301
+ document.getElementById('panel-model').innerHTML =
302
+ `<div class="flex justify-between items-center border-b border-slate-50 pb-2">
303
+ <span class="text-xs font-medium text-slate-500 mono-font">model</span>
304
+ <span class="text-sm font-bold text-blue-600 bg-blue-50 px-2 py-0.5 rounded border border-blue-100 mono-font">Perception365/VehicleNet-Y26s</span>
305
+ </div>` +
306
+ detailRow('task', 'detect') +
307
+ detailRow('tracker', 'ByteTrack');
308
+
309
+ document.getElementById('panel-infer').innerHTML =
310
+ detailRow('imgsz', c.imgsz) +
311
+ detailRow('detect_stride', c.detect_stride) +
312
+ detailRow('conf', c.conf || 0.12) +
313
+ detailRow('iou', c.iou || 0.60) +
314
+ `<div class="flex justify-between items-center border-b border-slate-50 pb-2">
315
+ <span class="text-xs font-medium text-slate-500 mono-font">stream</span>
316
+ <span class="text-[10px] font-bold text-slate-400">TRUE</span>
317
+ </div>` +
318
+ `<div class="flex justify-between items-center border-b border-slate-50 pb-2">
319
+ <span class="text-xs font-medium text-slate-500 mono-font">verbose</span>
320
+ ${boolBadge(false)}
321
+ </div>`;
322
+ }
323
+
324
+ // =========== Charts ===========
325
+ Chart.defaults.font.family = "'Inter', sans-serif";
326
+ Chart.defaults.color = '#64748b';
327
+
328
+ let MODEL_CLASSES = {};
329
+ let BUSINESS_MAP = {};
330
+
331
+ const congChart = new Chart(document.getElementById('congestionChart').getContext('2d'), {
332
+ type: 'line',
333
+ data: { labels: [], datasets: [{ data: [], borderColor: '#f97316', backgroundColor: 'rgba(249, 115, 22, 0.08)', fill: true, tension: 0.2, borderWidth: 1.5, pointRadius: 0 }] },
334
+ options: {
335
+ responsive: true, maintainAspectRatio: false,
336
+ plugins: { legend: { display: false } },
337
+ scales: {
338
+ x: { grid: { display: false }, ticks: { maxTicksLimit: 10, font: { size: 10 } }, title: { display: true, text: 'Frame Index', font: { size: 11, weight: '600' }, color: '#475569' } },
339
+ y: { grid: { color: '#e2e8f0' }, beginAtZero: true, ticks: { font: { size: 10 } }, title: { display: true, text: 'Active Vehicles', font: { size: 11, weight: '600' }, color: '#475569' } }
340
+ },
341
+ animation: { duration: 0 }
342
+ }
343
+ });
344
+
345
+ const domChart = new Chart(document.getElementById('dominanceChart').getContext('2d'), {
346
+ type: 'bar',
347
+ data: { labels: [], datasets: [{ data: [], backgroundColor: '#14b8a6', borderRadius: 2 }] },
348
+ options: {
349
+ responsive: true, maintainAspectRatio: false,
350
+ plugins: { legend: { display: false } },
351
+ scales: {
352
+ x: { grid: { display: false }, ticks: { font: { size: 10, weight: '500' } } },
353
+ y: { grid: { color: '#e2e8f0' }, beginAtZero: true, ticks: { font: { size: 10 } }, title: { display: true, text: 'Total Vehicle Count', font: { size: 11, weight: '600' }, color: '#475569' } }
354
+ },
355
+ animation: { duration: 0 }
356
+ }
357
+ });
358
+
359
+ const flowChart = new Chart(document.getElementById('flowChart').getContext('2d'), {
360
+ type: 'bar',
361
+ data: { labels: [], datasets: [{ data: [], backgroundColor: '#3b82f6', borderColor: '#ffffff', borderWidth: 1.5, barPercentage: 1.0, categoryPercentage: 1.0 }] },
362
+ options: {
363
+ responsive: true, maintainAspectRatio: false,
364
+ plugins: { legend: { display: false } },
365
+ scales: {
366
+ x: { grid: { display: false }, ticks: { maxTicksLimit: 10, font: { size: 10 } }, title: { display: true, text: 'Time (seconds)', font: { size: 11, weight: '600' }, color: '#475569' } },
367
+ y: { grid: { color: '#e2e8f0' }, beginAtZero: true, ticks: { font: { size: 10 } }, title: { display: true, text: 'Vehicles Crossed', font: { size: 11, weight: '600' }, color: '#475569' } }
368
+ },
369
+ animation: { duration: 0 }
370
+ }
371
+ });
372
+
373
+ // =========== Update functions ===========
374
+ function sumValues(obj) { return Object.values(obj).reduce((a, b) => a + b, 0); }
375
+
376
+ function updateBreakdown(classIn, classOut) {
377
+ const container = document.getElementById('class-breakdown');
378
+ const totalAll = sumValues(classIn) + sumValues(classOut);
379
+ container.innerHTML = '';
380
+
381
+ Object.keys(MODEL_CLASSES).map(Number).sort((a, b) => a - b).forEach(id => {
382
+ const inC = classIn[String(id)] || 0;
383
+ const outC = classOut[String(id)] || 0;
384
+ const total = inC + outC;
385
+ const pct = totalAll > 0 ? ((total / totalAll) * 100).toFixed(1) : '0.0';
386
+
387
+ const row = document.createElement('div');
388
+ row.className = 'flex items-center justify-between text-xs py-2 border-b border-slate-50';
389
+ row.innerHTML = `
390
+ <div class="w-[30%] font-bold text-slate-800 truncate" title="${MODEL_CLASSES[id]}">${MODEL_CLASSES[id]}</div>
391
+ <div class="w-[20%] text-slate-500 text-[11px]">${total} total</div>
392
+ <div class="w-[15%] text-slate-500 text-[11px]"><i class="fa-solid fa-arrow-down text-[9px] mr-1"></i>${inC}</div>
393
+ <div class="w-[15%] text-slate-500 text-[11px]"><i class="fa-solid fa-arrow-up text-[9px] mr-1"></i>${outC}</div>
394
+ <div class="w-[20%] text-right font-bold text-slate-900">${pct}%</div>
395
+ `;
396
+ container.appendChild(row);
397
+ });
398
+ }
399
+
400
+ function updateDominance(classIn, classOut) {
401
+ const labels = [], values = [];
402
+ for (const [group, ids] of Object.entries(BUSINESS_MAP)) {
403
+ let total = 0;
404
+ ids.forEach(id => { total += (classIn[String(id)] || 0) + (classOut[String(id)] || 0); });
405
+ labels.push(group);
406
+ values.push(total);
407
+ }
408
+ domChart.data.labels = labels;
409
+ domChart.data.datasets[0].data = values;
410
+ domChart.update();
411
+ }
412
+
413
+ function buildFlowHistogram(flowTimes, videoDuration) {
414
+ const binCount = Math.max(1, Math.ceil(videoDuration));
415
+ const bins = new Array(binCount).fill(0);
416
+ const labels = [];
417
+ for (let i = 0; i < binCount; i++) labels.push(i);
418
+ flowTimes.forEach(t => { bins[Math.min(Math.floor(t), binCount - 1)]++; });
419
+ flowChart.data.labels = labels;
420
+ flowChart.data.datasets[0].data = bins;
421
+ flowChart.update();
422
+ }
423
+
424
+ function updateCongestion(congestion, stride) {
425
+ const len = congestion.length;
426
+ if (len <= 200) {
427
+ congChart.data.labels = congestion.map((_, i) => i * stride);
428
+ congChart.data.datasets[0].data = congestion;
429
+ } else {
430
+ const step = 10;
431
+ const sampled = [], labels = [];
432
+ for (let i = 0; i < len; i += step) { labels.push(i * stride); sampled.push(congestion[i]); }
433
+ congChart.data.labels = labels;
434
+ congChart.data.datasets[0].data = sampled;
435
+ }
436
+ congChart.update();
437
+ }
438
+
439
+ // =========== Main ===========
440
+ async function init() {
441
+ const raw = sessionStorage.getItem('funky_run');
442
+ if (!raw) { window.location.href = '/'; return; }
443
+
444
+ const params = JSON.parse(raw);
445
+
446
+ const cRes = await fetch('/constants');
447
+ const cData = await cRes.json();
448
+ MODEL_CLASSES = cData.classes;
449
+ BUSINESS_MAP = cData.business_map;
450
+
451
+ populateRunDetails(params.config);
452
+
453
+ const videoDuration = params.config.duration || 10;
454
+ const stride = params.config.detect_stride || 1;
455
+
456
+ const proto = location.protocol === 'https:' ? 'wss' : 'ws';
457
+ const ws = new WebSocket(`${proto}://${location.host}/ws/run`);
458
+
459
+ ws.onopen = () => {
460
+ ws.send(JSON.stringify({
461
+ video_id: params.video_id,
462
+ line: params.line,
463
+ config: params.config
464
+ }));
465
+ };
466
+
467
+ let lastUIUpdate = 0;
468
+
469
+ ws.onmessage = e => {
470
+ const d = JSON.parse(e.data);
471
+
472
+ if (d.done) {
473
+ document.getElementById('proc-label').innerText = 'Complete';
474
+ document.getElementById('proc-bar').style.width = '100%';
475
+ document.getElementById('proc-pct').innerText = '100%';
476
+
477
+ document.getElementById('panel-proc-results').innerHTML =
478
+ detailRow('processing_time', d.processing_time + ' sec') +
479
+ detailRow('actual_fps', d.actual_fps, ' <span class="text-xs text-slate-400 font-normal">fps</span>') +
480
+ detailRow('speed_vs_realtime', d.speed_vs_realtime + 'x');
481
+ return;
482
+ }
483
+
484
+ const pct = ((d.frame_index / d.total_iters) * 100).toFixed(1);
485
+ document.getElementById('proc-bar').style.width = pct + '%';
486
+ document.getElementById('proc-frames').innerText = `${d.frame_index} / ${d.total_iters} Frames`;
487
+ document.getElementById('proc-pct').innerText = pct + '%';
488
+
489
+ const totalIn = sumValues(d.class_in);
490
+ const totalOut = sumValues(d.class_out);
491
+ document.getElementById('cnt-total').innerText = totalIn + totalOut;
492
+ document.getElementById('cnt-in').innerText = totalIn;
493
+ document.getElementById('cnt-out').innerText = totalOut;
494
+
495
+ const now = performance.now();
496
+ if (now - lastUIUpdate < 300) return;
497
+ lastUIUpdate = now;
498
+
499
+ updateCongestion(d.congestion, stride);
500
+ updateBreakdown(d.class_in, d.class_out);
501
+ updateDominance(d.class_in, d.class_out);
502
+ buildFlowHistogram(d.flow_times, videoDuration);
503
+ };
504
+ }
505
+
506
+ init();
507
+ </script>
508
+ </body>
509
+
510
+ </html>
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ python-multipart==0.0.6
4
+ opencv-python-headless==4.8.1.78
5
+ ultralytics>=8.3.0
6
+ numpy==1.24.3
7
+ python-dotenv==1.0.0
8
+ websockets==12.0
9
+ onnxruntime
10
+ onnx>=1.12.0
11
+ onnxslim>=0.1.71
12
+ lap>=0.5.12
13
+ torch==2.1.0+cpu
14
+ torchvision==0.16.0+cpu
15
+ --extra-index-url https://download.pytorch.org/whl/cpu