""" Lightweight speed estimation using pixel displacement between frames. No camera calibration required — classifies vehicles into relative speed categories (Slow/Normal/Fast) based on percentile ranking of pixel-per-frame displacement within the video. """ import numpy as np def estimate_speeds(track_positions): """Compute per-track speed category from centroid displacement history. Args: track_positions: dict {track_id: [(frame_idx, cx, cy), ...]} Returns: dict with per_track speeds and aggregate distribution """ displacements = {} for tid, positions in track_positions.items(): if len(positions) < 2: continue positions.sort(key=lambda x: x[0]) total_disp = 0.0 count = 0 for i in range(1, len(positions)): dx = positions[i][1] - positions[i - 1][1] dy = positions[i][2] - positions[i - 1][2] total_disp += np.sqrt(dx * dx + dy * dy) count += 1 displacements[tid] = total_disp / count if count > 0 else 0.0 if not displacements: return {"per_track": {}, "distribution": {"slow": 0, "normal": 0, "fast": 0}} speeds = np.array(list(displacements.values())) p33 = np.percentile(speeds, 33) p66 = np.percentile(speeds, 66) per_track = {} counts = {"slow": 0, "normal": 0, "fast": 0} for tid, spd in displacements.items(): if spd <= p33: cat = "slow" elif spd <= p66: cat = "normal" else: cat = "fast" per_track[int(tid)] = {"px_per_frame": round(spd, 1), "category": cat} counts[cat] += 1 total = sum(counts.values()) distribution = { "slow": round(counts["slow"] / total * 100, 1) if total else 0, "normal": round(counts["normal"] / total * 100, 1) if total else 0, "fast": round(counts["fast"] / total * 100, 1) if total else 0, } return {"per_track": per_track, "distribution": distribution}