Spaces:
Sleeping
Sleeping
Jaya242 commited on
Commit ·
a24843f
1
Parent(s): 495ac9b
add analytics dashboard: per-class chart + time-series chart
Browse files- app.py +3 -1
- requirements.txt +2 -0
- src/detector.py +69 -3
app.py
CHANGED
|
@@ -3,7 +3,7 @@ from src.detector import process_video
|
|
| 3 |
|
| 4 |
def run(video_input):
|
| 5 |
if video_input is None:
|
| 6 |
-
return None, None, "⚠️ Please upload a video first."
|
| 7 |
return process_video(video_input)
|
| 8 |
|
| 9 |
|
|
@@ -14,6 +14,8 @@ demo = gr.Interface(
|
|
| 14 |
gr.Video(label="Annotated Output (with tracking + counting lines)"),
|
| 15 |
gr.File(label="Crossings CSV"),
|
| 16 |
gr.Markdown(label="Summary"),
|
|
|
|
|
|
|
| 17 |
],
|
| 18 |
title="🚦 Traffic Analytics Pipeline",
|
| 19 |
description=(
|
|
|
|
| 3 |
|
| 4 |
def run(video_input):
|
| 5 |
if video_input is None:
|
| 6 |
+
return None, None, "⚠️ Please upload a video first.", None, None
|
| 7 |
return process_video(video_input)
|
| 8 |
|
| 9 |
|
|
|
|
| 14 |
gr.Video(label="Annotated Output (with tracking + counting lines)"),
|
| 15 |
gr.File(label="Crossings CSV"),
|
| 16 |
gr.Markdown(label="Summary"),
|
| 17 |
+
gr.Plot(label="📊 Crossings by Class"),
|
| 18 |
+
gr.Plot(label="⏱️ Crossings Over Time"),
|
| 19 |
],
|
| 20 |
title="🚦 Traffic Analytics Pipeline",
|
| 21 |
description=(
|
requirements.txt
CHANGED
|
@@ -1,3 +1,5 @@
|
|
| 1 |
gradio>=4.0
|
| 2 |
ultralytics>=8.0
|
| 3 |
opencv-python-headless>=4.8
|
|
|
|
|
|
|
|
|
| 1 |
gradio>=4.0
|
| 2 |
ultralytics>=8.0
|
| 3 |
opencv-python-headless>=4.8
|
| 4 |
+
|
| 5 |
+
matplotlib>=3.7
|
src/detector.py
CHANGED
|
@@ -4,6 +4,10 @@ import csv
|
|
| 4 |
from ultralytics import YOLO
|
| 5 |
import tempfile
|
| 6 |
import re
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
model = YOLO('yolov8n.pt')
|
| 9 |
|
|
@@ -43,6 +47,61 @@ def detect(frame):
|
|
| 43 |
return detections, annotated
|
| 44 |
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
def process_video(input_video_path):
|
| 47 |
output_dir = tempfile.mkdtemp(prefix="traffic_")
|
| 48 |
output_video_path = os.path.join(output_dir, "traffic_tracked.mp4")
|
|
@@ -161,7 +220,11 @@ def process_video(input_video_path):
|
|
| 161 |
_Built with YOLOv8 + ByteTrack. See repo for methodology._
|
| 162 |
"""
|
| 163 |
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
|
| 166 |
|
| 167 |
if __name__ == "__main__":
|
|
@@ -170,7 +233,7 @@ if __name__ == "__main__":
|
|
| 170 |
project_dir = os.path.dirname(script_dir)
|
| 171 |
VIDEO_PATH = os.path.join(project_dir, "data", "traffic.mp4")
|
| 172 |
|
| 173 |
-
annotated, csv_out, summary = process_video(VIDEO_PATH)
|
| 174 |
|
| 175 |
print(f"\n✅ Done!")
|
| 176 |
print(f"📹 Annotated video: {annotated}")
|
|
@@ -183,4 +246,7 @@ if __name__ == "__main__":
|
|
| 183 |
if m:
|
| 184 |
auto = int(m.group(1))
|
| 185 |
acc = round(100 * (1 - abs(auto - MANUAL_UNIQUE) / MANUAL_UNIQUE), 1)
|
| 186 |
-
print(f"\n🎯 Unique vehicle accuracy vs manual ({MANUAL_UNIQUE}): {acc}%")
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
from ultralytics import YOLO
|
| 5 |
import tempfile
|
| 6 |
import re
|
| 7 |
+
import matplotlib
|
| 8 |
+
matplotlib.use("Agg") # non-interactive backend (required for server / Gradio)
|
| 9 |
+
import matplotlib.pyplot as plt
|
| 10 |
+
from collections import Counter, defaultdict
|
| 11 |
|
| 12 |
model = YOLO('yolov8n.pt')
|
| 13 |
|
|
|
|
| 47 |
return detections, annotated
|
| 48 |
|
| 49 |
|
| 50 |
+
def make_per_class_chart(crossings):
|
| 51 |
+
"""Bar chart: count of crossings per vehicle class."""
|
| 52 |
+
class_counts = Counter(c["class"] for c in crossings)
|
| 53 |
+
if not class_counts:
|
| 54 |
+
# Empty fallback
|
| 55 |
+
fig, ax = plt.subplots(figsize=(6, 4))
|
| 56 |
+
ax.text(0.5, 0.5, "No crossings detected", ha="center", va="center", fontsize=14)
|
| 57 |
+
ax.axis("off")
|
| 58 |
+
return fig
|
| 59 |
+
|
| 60 |
+
classes = list(class_counts.keys())
|
| 61 |
+
counts = list(class_counts.values())
|
| 62 |
+
|
| 63 |
+
fig, ax = plt.subplots(figsize=(6, 4))
|
| 64 |
+
ax.bar(classes, counts, color=["#3498db", "#e67e22", "#2ecc71", "#9b59b6"][:len(classes)])
|
| 65 |
+
ax.set_title("Crossings by Vehicle Class", fontsize=13, fontweight="bold")
|
| 66 |
+
ax.set_xlabel("Vehicle Class")
|
| 67 |
+
ax.set_ylabel("Number of Crossings")
|
| 68 |
+
for i, v in enumerate(counts):
|
| 69 |
+
ax.text(i, v + 0.1, str(v), ha="center", fontweight="bold")
|
| 70 |
+
plt.tight_layout()
|
| 71 |
+
return fig
|
| 72 |
+
|
| 73 |
+
def make_time_series_chart(crossings, fps, total_frames):
|
| 74 |
+
"""Time-series: crossings per 5-second window."""
|
| 75 |
+
if not crossings:
|
| 76 |
+
fig, ax = plt.subplots(figsize=(8, 4))
|
| 77 |
+
ax.text(0.5, 0.5, "No crossings detected", ha="center", va="center", fontsize=14)
|
| 78 |
+
ax.axis("off")
|
| 79 |
+
return fig
|
| 80 |
+
|
| 81 |
+
bucket_size_sec = 5
|
| 82 |
+
total_duration_sec = total_frames / fps if fps > 0 else 0
|
| 83 |
+
|
| 84 |
+
buckets = defaultdict(int)
|
| 85 |
+
for c in crossings:
|
| 86 |
+
bucket = int(c["timestamp_sec"] // bucket_size_sec)
|
| 87 |
+
buckets[bucket] += 1
|
| 88 |
+
|
| 89 |
+
max_bucket = int(total_duration_sec // bucket_size_sec) + 1
|
| 90 |
+
x = list(range(max_bucket))
|
| 91 |
+
y = [buckets.get(b, 0) for b in x]
|
| 92 |
+
x_labels = [f"{b * bucket_size_sec}s" for b in x]
|
| 93 |
+
|
| 94 |
+
fig, ax = plt.subplots(figsize=(8, 4))
|
| 95 |
+
ax.plot(x_labels, y, marker="o", linewidth=2, color="#e67e22")
|
| 96 |
+
ax.fill_between(range(len(x_labels)), y, alpha=0.2, color="#e67e22")
|
| 97 |
+
ax.set_title("Crossings Over Time (5-second windows)", fontsize=13, fontweight="bold")
|
| 98 |
+
ax.set_xlabel("Time into clip")
|
| 99 |
+
ax.set_ylabel("Crossings in window")
|
| 100 |
+
ax.grid(True, alpha=0.3)
|
| 101 |
+
plt.xticks(rotation=45, ha="right")
|
| 102 |
+
plt.tight_layout()
|
| 103 |
+
return fig
|
| 104 |
+
|
| 105 |
def process_video(input_video_path):
|
| 106 |
output_dir = tempfile.mkdtemp(prefix="traffic_")
|
| 107 |
output_video_path = os.path.join(output_dir, "traffic_tracked.mp4")
|
|
|
|
| 220 |
_Built with YOLOv8 + ByteTrack. See repo for methodology._
|
| 221 |
"""
|
| 222 |
|
| 223 |
+
per_class_chart = make_per_class_chart(crossings)
|
| 224 |
+
time_series_chart = make_time_series_chart(crossings, fps, frame_count)
|
| 225 |
+
|
| 226 |
+
return output_video_path, output_csv_path, summary, per_class_chart, time_series_chart
|
| 227 |
+
|
| 228 |
|
| 229 |
|
| 230 |
if __name__ == "__main__":
|
|
|
|
| 233 |
project_dir = os.path.dirname(script_dir)
|
| 234 |
VIDEO_PATH = os.path.join(project_dir, "data", "traffic.mp4")
|
| 235 |
|
| 236 |
+
annotated, csv_out, summary, _, _ = process_video(VIDEO_PATH)
|
| 237 |
|
| 238 |
print(f"\n✅ Done!")
|
| 239 |
print(f"📹 Annotated video: {annotated}")
|
|
|
|
| 246 |
if m:
|
| 247 |
auto = int(m.group(1))
|
| 248 |
acc = round(100 * (1 - abs(auto - MANUAL_UNIQUE) / MANUAL_UNIQUE), 1)
|
| 249 |
+
print(f"\n🎯 Unique vehicle accuracy vs manual ({MANUAL_UNIQUE}): {acc}%")
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
|