From a83545d1db02884d9c29d232b4db2e50850192fc Mon Sep 17 00:00:00 2001 From: sumona-banerjeee Date: Thu, 29 Jan 2026 12:22:21 +0530 Subject: [PATCH] Applied Options --- app/globals.css | 2 +- app/layout.tsx | 15 +- app/page.tsx | 77 +- components/upload-section.tsx | 86 +- components/video-player-section.tsx | 2946 ++------------------------- 5 files changed, 260 insertions(+), 2866 deletions(-) diff --git a/app/globals.css b/app/globals.css index dc2aea1..b815a87 100644 --- a/app/globals.css +++ b/app/globals.css @@ -122,4 +122,4 @@ body { @apply bg-background text-foreground; } -} +} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 2299c3e..82d5b1f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -43,17 +43,4 @@ export default function RootLayout({ ) -} - - - - - - - - - - - - - +} \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index 263be95..11d7373 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,11 +2,14 @@ import { useState } from "react" import { UploadSection } from "@/components/upload-section" -// import { SummarySection } from "@/components/summary-section" import VideoPlayerSection from "@/components/video-player-section" +export type DetectionType = "pothole-detection" | "sign-board-detection" + export type DetectionData = { video_id: string + detection_type?: string + output_video_path?: string video_info: { fps: number width: number @@ -14,14 +17,28 @@ export type DetectionData = { total_frames: number } summary: { - unique_potholes: number + unique_potholes?: number + unique_signboards?: number total_detections: number total_frames: number detection_rate: number } + pothole_list?: Array<{ + pothole_id: number + first_detected_frame: number + first_detected_time: number + confidence: number + }> + signboard_list?: Array<{ + signboard_id: number + type: string + first_detected_frame: number + first_detected_time: number + confidence: number + }> frames: Array<{ frame_id: number - potholes: Array<{ + potholes?: Array<{ pothole_id: number bbox: { x1: number @@ -31,12 +48,36 @@ export type DetectionData = { } confidence: number }> + signboards?: Array<{ + signboard_id: number + type: string + bbox: { + x1: number + y1: number + x2: number + y2: number + } + confidence: number + }> }> } -export default function PotholeDetectionPage() { +export default function DetectionPage() { const [detectionData, setDetectionData] = useState(null) - const [videoFile, setVideoFile] = useState(null) + const [videoId, setVideoId] = useState(null) + const [detectionType, setDetectionType] = useState("pothole-detection") + + const getTitle = () => { + return detectionType === "pothole-detection" + ? "Pothole Detection System" + : "Signboard Detection System" + } + + const getDescription = () => { + return detectionType === "pothole-detection" + ? "Upload a video to detect and track potholes with AI-powered analysis" + : "Upload a video to detect and identify signboards with AI-powered analysis" + } return (
@@ -44,35 +85,33 @@ export default function PotholeDetectionPage() { {/* Header */}

- Pothole Detection System + {getTitle()}

-

Upload a video to detect and track potholes with AI-powered analysis

+

{getDescription()}

{/* Upload Section */}
{ + onDetectionComplete={(data, vId) => { setDetectionData(data) - setVideoFile(file) + setVideoId(vId) }} + onDetectionTypeChange={setDetectionType} />
- {/* Summary Section */} - {/* {detectionData && ( -
- -
- )} */} - {/* Video Player Section */} - {detectionData && videoFile && ( + {detectionData && videoId && (
- +
)}
) -} +} \ No newline at end of file diff --git a/components/upload-section.tsx b/components/upload-section.tsx index cdcad36..a12884e 100644 --- a/components/upload-section.tsx +++ b/components/upload-section.tsx @@ -6,19 +6,22 @@ import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Progress } from "@/components/ui/progress" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Upload, Loader2, AlertCircle } from "lucide-react" -import type { DetectionData } from "@/app/page" +import type { DetectionData, DetectionType } from "@/app/page" const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:8000/api/v1" const WS_URL = process.env.NEXT_PUBLIC_WS_URL || "ws://127.0.0.1:8000/api/v1" type UploadSectionProps = { - onDetectionComplete: (data: DetectionData, file: File) => void + onDetectionComplete: (data: DetectionData, videoId: string) => void + onDetectionTypeChange: (type: DetectionType) => void } -export function UploadSection({ onDetectionComplete }: UploadSectionProps) { +export function UploadSection({ onDetectionComplete, onDetectionTypeChange }: UploadSectionProps) { const [file, setFile] = useState(null) const [speed, setSpeed] = useState(30) + const [detectionType, setDetectionType] = useState("pothole-detection") const [uploading, setUploading] = useState(false) const [progress, setProgress] = useState(0) const [statusMessage, setStatusMessage] = useState("") @@ -26,29 +29,39 @@ export function UploadSection({ onDetectionComplete }: UploadSectionProps) { const fileInputRef = useRef(null) const wsRef = useRef(null) + const handleDetectionTypeChange = (value: DetectionType) => { + setDetectionType(value) + onDetectionTypeChange(value) + } + const connectWebSocket = (videoId: string) => { - console.log("[v0] Connecting WebSocket for video:", videoId) + console.log("[Upload] Connecting WebSocket for video:", videoId) const ws = new WebSocket(`${WS_URL}/ws/${videoId}`) wsRef.current = ws ws.onopen = () => { - console.log("[v0] WebSocket connected") + console.log("[Upload] WebSocket connected") setError(null) } ws.onmessage = (event) => { const data = JSON.parse(event.data) - console.log("[v0] WebSocket message:", data) + console.log("[Upload] WebSocket message:", data) if (data.type === "progress" || data.progress !== undefined) { const progressValue = data.progress || 0 setProgress(progressValue) let message = data.message || "Processing..." + + // Handle both pothole and signboard progress messages if (data.unique_potholes !== undefined) { message += ` | Unique: ${data.unique_potholes} | Total: ${data.total_detections || 0}` + } else if (data.unique_signboards !== undefined) { + message += ` | Unique: ${data.unique_signboards} | Total: ${data.total_detections || 0}` } + setStatusMessage(message) } @@ -67,18 +80,18 @@ export function UploadSection({ onDetectionComplete }: UploadSectionProps) { } ws.onerror = (error) => { - console.error("[v0] WebSocket error:", error) + console.error("[Upload] WebSocket error:", error) setStatusMessage("Connection error. Retrying...") } ws.onclose = () => { - console.log("[v0] WebSocket closed") + console.log("[Upload] WebSocket closed") } } const loadResults = async (videoId: string) => { try { - console.log("[v0] Loading results for:", videoId) + console.log("[Upload] Loading results for:", videoId) const response = await fetch(`${API_URL}/results/${videoId}`, { headers: { "ngrok-skip-browser-warning": "true" @@ -90,18 +103,17 @@ export function UploadSection({ onDetectionComplete }: UploadSectionProps) { } const detectionData: DetectionData = await response.json() - console.log("[v0] Results loaded:", detectionData) + console.log("[Upload] Results loaded:", detectionData) setUploading(false) setProgress(100) setStatusMessage("✓ Complete!") setError(null) - if (file) { - onDetectionComplete(detectionData, file) - } + // Pass video_id instead of file + onDetectionComplete(detectionData, videoId) } catch (error) { - console.error("[v0] Failed to load results:", error) + console.error("[Upload] Failed to load results:", error) setError("Failed to load results. Please try again.") setStatusMessage("") setUploading(false) @@ -116,6 +128,7 @@ export function UploadSection({ onDetectionComplete }: UploadSectionProps) { const formData = new FormData() formData.append("file", file) + formData.append("detection_type", detectionType) formData.append("speed_kmh", speed.toString()) setUploading(true) @@ -124,7 +137,8 @@ export function UploadSection({ onDetectionComplete }: UploadSectionProps) { setError(null) try { - console.log("[v0] Uploading to:", `${API_URL}/upload`) + console.log("[Upload] Uploading to:", `${API_URL}/upload`) + console.log("[Upload] Detection type:", detectionType) const response = await fetch(`${API_URL}/upload`, { method: "POST", @@ -134,7 +148,7 @@ export function UploadSection({ onDetectionComplete }: UploadSectionProps) { body: formData }) - console.log("[v0] Upload response status:", response.status) + console.log("[Upload] Upload response status:", response.status) if (!response.ok) { const errorText = await response.text() @@ -144,14 +158,14 @@ export function UploadSection({ onDetectionComplete }: UploadSectionProps) { const result = await response.json() const videoId = result.video_id - console.log("[v0] Video uploaded successfully, ID:", videoId) + console.log("[Upload] Video uploaded successfully, ID:", videoId) setStatusMessage("Uploaded! Starting processing...") setProgress(10) // Connect WebSocket for progress updates connectWebSocket(videoId) } catch (error) { - console.error("[v0] Upload error:", error) + console.error("[Upload] Upload error:", error) let errorMessage = "Upload failed" @@ -172,10 +186,12 @@ export function UploadSection({ onDetectionComplete }: UploadSectionProps) { Upload Video - Select a video file and vehicle speed to start pothole detection + + Select a video file, detection type, and vehicle speed to start AI-powered analysis + -
+
{/* File Input */}
@@ -200,9 +216,37 @@ export function UploadSection({ onDetectionComplete }: UploadSectionProps) { )}
+ {/* Detection Type Selector */} +
+ + +
+ {/* Speed Input */}
- + -// timestamp: string -// } - -// export function VideoPlayerSection({ data, videoFile }: VideoPlayerSectionProps) { -// const videoRef = useRef(null) -// const canvasRef = useRef(null) -// const containerRef = useRef(null) -// const [currentFrame, setCurrentFrame] = useState(0) -// const [detectionsCount, setDetectionsCount] = useState(0) -// const [logs, setLogs] = useState([]) -// const frameDetectionMap = useRef>(new Map()) -// const lastDrawnFrame = useRef(-1) - -// // Build frame detection map -// useEffect(() => { -// const map = new Map() - -// if (data.frames && Array.isArray(data.frames)) { -// data.frames.forEach((frameData) => { -// const frameId = frameData.frame_id -// const potholes = frameData.potholes || [] - -// if (potholes.length > 0) { -// map.set(frameId, potholes) -// } -// }) -// } - -// frameDetectionMap.current = map -// }, [data]) - -// // Load video file -// useEffect(() => { -// if (videoRef.current && videoFile) { -// videoRef.current.src = URL.createObjectURL(videoFile) -// } -// }, [videoFile]) - -// useEffect(() => { -// const video = videoRef.current -// const canvas = canvasRef.current - -// if (!video || !canvas) return - -// const setupResolution = () => { -// // Set canvas to exact backend resolution -// canvas.width = data.video_info.width -// canvas.height = data.video_info.height - -// // Set display size to match video element -// const rect = video.getBoundingClientRect() -// canvas.style.width = `${rect.width}px` -// canvas.style.height = `${rect.height}px` -// } - -// video.addEventListener("loadedmetadata", setupResolution) -// window.addEventListener("resize", setupResolution) - -// return () => { -// video.removeEventListener("loadedmetadata", setupResolution) -// window.removeEventListener("resize", setupResolution) -// } -// }, [data.video_info.width, data.video_info.height]) - -// // Draw detections on video -// useEffect(() => { -// const video = videoRef.current -// const canvas = canvasRef.current - -// if (!video || !canvas) return - -// const drawDetections = () => { -// const ctx = canvas.getContext("2d") -// if (!ctx) return - -// const frame = Math.floor(video.currentTime * data.video_info.fps) -// setCurrentFrame(frame) - -// // Clear canvas -// ctx.clearRect(0, 0, canvas.width, canvas.height) - -// // Get detections for current frame -// const detections = frameDetectionMap.current.get(frame) -// setDetectionsCount(detections?.length || 0) - -// if (detections && detections.length > 0) { -// if (frame !== lastDrawnFrame.current) { -// lastDrawnFrame.current = frame - -// // Add detailed log with coordinates and confidence -// setLogs((prev) => { -// const newLog: DetectionLog = { -// frame, -// detections: detections.map((det) => ({ -// pothole_id: det.pothole_id, -// bbox: det.bbox, -// confidence: det.confidence, -// })), -// timestamp: new Date().toLocaleTimeString(), -// } -// return [newLog, ...prev].slice(0, 100) -// }) -// } - -// detections.forEach((det) => { -// const { x1, y1, x2, y2 } = det.bbox -// const width = x2 - x1 -// const height = y2 - y1 - -// // Draw red bounding box -// ctx.strokeStyle = "#ef4444" -// ctx.lineWidth = 3 -// ctx.strokeRect(x1, y1, width, height) - -// // Draw semi-transparent fill -// ctx.fillStyle = "rgba(239, 68, 68, 0.15)" -// ctx.fillRect(x1, y1, width, height) - -// // Draw label background -// const label = `Pothole #${det.pothole_id} (${(det.confidence * 100).toFixed(0)}%)` -// ctx.font = "14px Inter, sans-serif" -// const textWidth = ctx.measureText(label).width -// ctx.fillStyle = "rgba(239, 68, 68, 0.9)" -// ctx.fillRect(x1, y1 - 24, textWidth + 12, 24) - -// // Draw label text -// ctx.fillStyle = "#ffffff" -// ctx.fillText(label, x1 + 6, y1 - 8) -// }) -// } - -// requestAnimationFrame(drawDetections) -// } - -// const animationId = requestAnimationFrame(drawDetections) - -// return () => { -// cancelAnimationFrame(animationId) -// } -// }, [data]) - -// return ( -// -// -// Video Playback with Detection -// Watch the analyzed video with real-time bounding box overlays -// -// -//
-// {/* Video Player */} -//
-//
-//
- -// {/* Video Info */} -//
-//
-// Current Frame: -// {currentFrame} -//
-//
-// Detections: -// 0 ? "destructive" : "secondary"}>{detectionsCount} -//
-//
-// Resolution: -// -// {data.video_info.width}×{data.video_info.height} -// -//
-//
-//
- -// {/* Detection Logs */} -//
-//
-//

Detection Logs

-//

Real-time frame-by-frame detection tracking

-//
-// -//
-// {logs.length === 0 ? ( -//

Play the video to see detection logs

-// ) : ( -// logs.map((log, index) => ( -//
-//
-// Frame {log.frame} -// {log.timestamp} -//
- -// {log.detections.length === 0 ? ( -//
No detections
-// ) : ( -//
-//
-// {log.detections.length} pothole{log.detections.length > 1 ? "s" : ""} detected -//
-// {log.detections.map((det, idx) => ( -//
-//
-// Pothole #{det.pothole_id} • {(det.confidence * 100).toFixed(1)}% -//
-//
-// [{det.bbox.x1}, {det.bbox.y1}] → [{det.bbox.x2}, {det.bbox.y2}] -//
-//
-// ))} -//
-// )} -//
-// )) -// )} -//
-//
-//
-//
-//
-//
-// ) -// } - - - - - - - - -// "use client" - -// import { useEffect, useRef, useState, useCallback } from "react" -// import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -// import { ScrollArea } from "@/components/ui/scroll-area" -// import { Badge } from "@/components/ui/badge" - -// type DetectionData = { -// frames: Array<{ -// frame_id: number -// potholes: Array<{ -// pothole_id: number -// bbox: { x1: number; y1: number; x2: number; y2: number } -// confidence: number -// }> -// }> -// video_info: { -// width: number -// height: number -// fps: number -// total_frames: number -// } -// summary: { -// unique_potholes: number -// total_detections: number -// total_frames: number -// detection_rate: number -// } -// } - -// type VideoPlayerSectionProps = { -// data: DetectionData -// videoFile: File -// } - -// type DetectionLog = { -// frame: number -// detections: Array<{ -// pothole_id: number -// bbox: { x1: number; y1: number; x2: number; y2: number } -// confidence: number -// }> -// timestamp: string -// } - -// export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSectionProps) { -// const videoRef = useRef(null) -// const canvasRef = useRef(null) -// const containerRef = useRef(null) -// const animationFrameRef = useRef(null) -// const [currentFrame, setCurrentFrame] = useState(0) -// const [detectionsCount, setDetectionsCount] = useState(0) -// const [logs, setLogs] = useState([]) -// const [showSummary, setShowSummary] = useState(false) -// const frameDetectionMap = useRef>(new Map()) -// const lastProcessedFrame = useRef(-1) -// const loggedFrames = useRef>(new Set()) -// const MAX_LOGS = 0 // Limit log entries to prevent memory issues - -// // Build optimized frame detection map -// useEffect(() => { -// const map = new Map() - -// if (data.frames && Array.isArray(data.frames)) { -// console.log(`Building frame map from ${data.frames.length} frames`) -// data.frames.forEach((frameData) => { -// const frameId = frameData.frame_id -// const potholes = frameData.potholes || [] - -// if (potholes.length > 0) { -// map.set(frameId, potholes) -// } -// }) -// console.log(`Frame map built: ${map.size} frames with detections`) -// } - -// frameDetectionMap.current = map -// }, [data]) - -// // Load video file -// useEffect(() => { -// if (videoRef.current && videoFile) { -// const url = URL.createObjectURL(videoFile) -// videoRef.current.src = url - -// return () => { -// URL.revokeObjectURL(url) -// } -// } -// }, [videoFile]) - -// // Setup canvas resolution -// useEffect(() => { -// const video = videoRef.current -// const canvas = canvasRef.current - -// if (!video || !canvas) return - -// const setupResolution = () => { -// // Set canvas internal resolution to match backend data -// canvas.width = data.video_info.width -// canvas.height = data.video_info.height - -// // Set display size to match video element -// const rect = video.getBoundingClientRect() -// canvas.style.width = `${rect.width}px` -// canvas.style.height = `${rect.height}px` -// } - -// video.addEventListener("loadedmetadata", setupResolution) -// window.addEventListener("resize", setupResolution) - -// return () => { -// video.removeEventListener("loadedmetadata", setupResolution) -// window.removeEventListener("resize", setupResolution) -// } -// }, [data.video_info.width, data.video_info.height]) - -// // Add detection log with deduplication and size limit -// const addDetectionLog = useCallback((frame: number, detections: any[]) => { -// // Skip if already logged this frame -// if (loggedFrames.current.has(frame)) return - -// loggedFrames.current.add(frame) - -// setLogs((prev) => { -// const newLog: DetectionLog = { -// frame, -// detections: detections.map((det) => ({ -// pothole_id: det.pothole_id, -// bbox: det.bbox, -// confidence: det.confidence, -// })), -// timestamp: new Date().toLocaleTimeString(), -// } - -// // Keep only MAX_LOGS entries -// const updated = [newLog, ...prev].slice(0, MAX_LOGS) -// return updated -// }) -// }, []) - -// // Optimized drawing function with batched state updates -// const drawDetections = useCallback(() => { -// const video = videoRef.current -// const canvas = canvasRef.current - -// if (!video || !canvas) { -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// return -// } - -// // Skip drawing if video is paused or ended -// if (video.paused || video.ended) { -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// return -// } - -// const ctx = canvas.getContext("2d", { alpha: true }) -// if (!ctx) return - -// // Calculate current frame -// const frame = Math.floor(video.currentTime * data.video_info.fps) - -// // Only update if frame has changed -// if (frame !== lastProcessedFrame.current) { -// lastProcessedFrame.current = frame - -// // Get detections for current frame -// const detections = frameDetectionMap.current.get(frame) -// const detCount = detections?.length || 0 - -// // Batch state updates to minimize re-renders -// setCurrentFrame(frame) -// setDetectionsCount(detCount) - -// // Clear canvas -// ctx.clearRect(0, 0, canvas.width, canvas.height) - -// if (detections && detections.length > 0) { -// // Add log entry (with deduplication) -// addDetectionLog(frame, detections) - -// // Draw all detections -// detections.forEach((det) => { -// const { x1, y1, x2, y2 } = det.bbox -// const width = x2 - x1 -// const height = y2 - y1 - -// // Draw red bounding box -// ctx.strokeStyle = "#ef4444" -// ctx.lineWidth = 3 -// ctx.strokeRect(x1, y1, width, height) - -// // Draw semi-transparent fill -// ctx.fillStyle = "rgba(239, 68, 68, 0.15)" -// ctx.fillRect(x1, y1, width, height) - -// // Draw label background -// const label = `Pothole #${det.pothole_id} (${(det.confidence * 100).toFixed(0)}%)` -// ctx.font = "bold 14px system-ui" -// const textWidth = ctx.measureText(label).width - -// ctx.fillStyle = "rgba(239, 68, 68, 0.95)" -// ctx.fillRect(x1, y1 - 26, textWidth + 16, 26) - -// // Draw label text -// ctx.fillStyle = "#ffffff" -// ctx.fillText(label, x1 + 8, y1 - 8) -// }) -// } -// } - -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// }, [data.video_info.fps, addDetectionLog]) - -// // Start/stop animation loop -// useEffect(() => { -// animationFrameRef.current = requestAnimationFrame(drawDetections) - -// return () => { -// if (animationFrameRef.current) { -// cancelAnimationFrame(animationFrameRef.current) -// } -// } -// }, [drawDetections]) - -// // Handle video end - show summary -// useEffect(() => { -// const video = videoRef.current -// if (!video) return - -// const handleEnded = () => { -// setShowSummary(true) -// setTimeout(() => { -// setLogs((prev) => { -// const summaryLog: DetectionLog = { -// frame: -1, -// detections: [], -// timestamp: new Date().toLocaleTimeString(), -// } -// return [summaryLog, ...prev].slice(0, MAX_LOGS) -// }) -// }, 500) -// } - -// video.addEventListener("ended", handleEnded) - -// return () => { -// video.removeEventListener("ended", handleEnded) -// } -// }, [data]) - -// // Reset logs when video is seeked or restarted -// useEffect(() => { -// const video = videoRef.current -// if (!video) return - -// const handleSeeked = () => { -// loggedFrames.current.clear() -// } - -// const handlePlay = () => { -// if (video.currentTime < 1) { -// setLogs([]) -// loggedFrames.current.clear() -// lastProcessedFrame.current = -1 -// } -// } - -// video.addEventListener("seeked", handleSeeked) -// video.addEventListener("play", handlePlay) - -// return () => { -// video.removeEventListener("seeked", handleSeeked) -// video.removeEventListener("play", handlePlay) -// } -// }, []) - -// return ( -// -// -// Video Playback with Detection -// Watch the analyzed video with real-time bounding box overlays -// -// -//
-// {/* Video Player */} -//
-//
-//
- -// {/* Video Info */} -//
-//
-// Current Frame: -// {currentFrame} -//
-//
-// Detections: -// 0 ? "destructive" : "secondary"}>{detectionsCount} -//
-//
-// Resolution: -// -// {data.video_info.width}×{data.video_info.height} -// -//
-//
-// FPS: -// {data.video_info.fps.toFixed(1)} -//
-//
-//
- -// {/* Detection Logs */} -//
-//
-//

Detection Logs

-//

Real-time frame-by-frame detection tracking (last {MAX_LOGS})

-//
-// -//
-// {logs.length === 0 ? ( -//

Play the video to see detection logs

-// ) : ( -// logs.map((log, index) => ( -// log.frame === -1 ? ( -// // Summary entry -//
-//
-// 📊 DETECTION SUMMARY -//
-//
-//
-// Unique Potholes: -// {data.summary.unique_potholes} -//
-//
-// Total Detections: -// {data.summary.total_detections} -//
-//
-// Total Frames: -// {data.summary.total_frames} -//
-//
-// Detection Rate: -// {data.summary.detection_rate.toFixed(1)}% -//
-//
-// Video FPS: -// {data.video_info.fps.toFixed(1)} -//
-//
-// Resolution: -// {data.video_info.width}×{data.video_info.height} -//
-//
-//
-// ) : ( -// // Detection entry -//
-//
-// Frame: {log.frame} -// {log.timestamp} -//
- -// {log.detections.length === 0 ? ( -//
No detections
-// ) : ( -//
-// {log.detections.map((det, idx) => ( -//
-//
-// Pothole ID: {det.pothole_id} | Confidence: {(det.confidence * 100).toFixed(1)}% -//
-//
-// Coordinates: ({Math.round(det.bbox.x1)}, {Math.round(det.bbox.y1)}) → ({Math.round(det.bbox.x2)}, {Math.round(det.bbox.y2)}) -//
-//
-// ))} -//
-// )} -//
-// ) -// )) -// )} -//
-//
-//
-//
-//
-//
-// ) -// } - - - - - -// "use client" - -// import { useEffect, useRef, useState, useCallback } from "react" -// import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -// import { ScrollArea } from "@/components/ui/scroll-area" -// import { Badge } from "@/components/ui/badge" - -// type DetectionData = { -// frames: Array<{ -// frame_id: number -// potholes: Array<{ -// pothole_id: number -// bbox: { x1: number; y1: number; x2: number; y2: number } -// confidence: number -// }> -// }> -// video_info: { -// width: number -// height: number -// fps: number -// total_frames: number -// } -// summary: { -// unique_potholes: number -// total_detections: number -// total_frames: number -// detection_rate: number -// } -// } - -// type VideoPlayerSectionProps = { -// data: DetectionData -// videoFile: File -// } - -// type DetectionLog = { -// frame: number -// detections: Array<{ -// pothole_id: number -// bbox: { x1: number; y1: number; x2: number; y2: number } -// confidence: number -// }> -// timestamp: string -// } - -// export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSectionProps) { -// const videoRef = useRef(null) -// const canvasRef = useRef(null) -// const containerRef = useRef(null) -// const animationFrameRef = useRef(null) -// const [currentFrame, setCurrentFrame] = useState(0) -// const [detectionsCount, setDetectionsCount] = useState(0) -// const [logs, setLogs] = useState([]) -// const [showSummary, setShowSummary] = useState(false) -// const frameDetectionMap = useRef>(new Map()) -// const lastProcessedFrame = useRef(-1) -// const loggedFrames = useRef>(new Set()) -// const MAX_LOGS = 0 // Limit log entries to prevent memory issues - -// // Build optimized frame detection map -// useEffect(() => { -// const map = new Map() - -// if (data.frames && Array.isArray(data.frames)) { -// console.log(`Building frame map from ${data.frames.length} frames`) -// data.frames.forEach((frameData) => { -// const frameId = frameData.frame_id -// const potholes = frameData.potholes || [] - -// if (potholes.length > 0) { -// map.set(frameId, potholes) -// } -// }) -// console.log(`Frame map built: ${map.size} frames with detections`) -// } - -// frameDetectionMap.current = map -// }, [data]) - -// // Load video file -// useEffect(() => { -// if (videoRef.current && videoFile) { -// const url = URL.createObjectURL(videoFile) -// videoRef.current.src = url - -// return () => { -// URL.revokeObjectURL(url) -// } -// } -// }, [videoFile]) - -// // Setup canvas resolution -// useEffect(() => { -// const video = videoRef.current -// const canvas = canvasRef.current - -// if (!video || !canvas) return - -// const setupResolution = () => { -// // Set canvas internal resolution to match backend data -// canvas.width = data.video_info.width -// canvas.height = data.video_info.height - -// // Set display size to match video element -// const rect = video.getBoundingClientRect() -// canvas.style.width = `${rect.width}px` -// canvas.style.height = `${rect.height}px` -// } - -// video.addEventListener("loadedmetadata", setupResolution) -// window.addEventListener("resize", setupResolution) - -// return () => { -// video.removeEventListener("loadedmetadata", setupResolution) -// window.removeEventListener("resize", setupResolution) -// } -// }, [data.video_info.width, data.video_info.height]) - -// // Add detection log with deduplication and size limit -// const addDetectionLog = useCallback((frame: number, detections: any[]) => { -// // Skip if already logged this frame -// if (loggedFrames.current.has(frame)) return - -// loggedFrames.current.add(frame) - -// setLogs((prev) => { -// const newLog: DetectionLog = { -// frame, -// detections: detections.map((det) => ({ -// pothole_id: det.pothole_id, -// bbox: det.bbox, -// confidence: det.confidence, -// })), -// timestamp: new Date().toLocaleTimeString(), -// } - -// // Keep only MAX_LOGS entries -// const updated = [newLog, ...prev].slice(0, MAX_LOGS) -// return updated -// }) -// }, []) - -// // Core drawing logic extracted for reuse -// const drawFrameDetections = useCallback((frame: number, shouldLog: boolean = true) => { -// const canvas = canvasRef.current -// if (!canvas) return - -// const ctx = canvas.getContext("2d", { alpha: true }) -// if (!ctx) return - -// // Get detections for current frame -// const detections = frameDetectionMap.current.get(frame) -// const detCount = detections?.length || 0 - -// // Batch state updates to minimize re-renders -// setCurrentFrame(frame) -// setDetectionsCount(detCount) - -// // Clear canvas -// ctx.clearRect(0, 0, canvas.width, canvas.height) - -// if (detections && detections.length > 0) { -// // Add log entry (with deduplication) only during playback -// if (shouldLog) { -// addDetectionLog(frame, detections) -// } - -// // Draw all detections -// detections.forEach((det) => { -// const { x1, y1, x2, y2 } = det.bbox -// const width = x2 - x1 -// const height = y2 - y1 - -// // Draw red bounding box -// ctx.strokeStyle = "#ef4444" -// ctx.lineWidth = 3 -// ctx.strokeRect(x1, y1, width, height) - -// // Draw semi-transparent fill -// ctx.fillStyle = "rgba(239, 68, 68, 0.15)" -// ctx.fillRect(x1, y1, width, height) - -// // Draw label background -// const label = `Pothole #${det.pothole_id} (${(det.confidence * 100).toFixed(0)}%)` -// ctx.font = "bold 14px system-ui" -// const textWidth = ctx.measureText(label).width - -// ctx.fillStyle = "rgba(239, 68, 68, 0.95)" -// ctx.fillRect(x1, y1 - 26, textWidth + 16, 26) - -// // Draw label text -// ctx.fillStyle = "#ffffff" -// ctx.fillText(label, x1 + 8, y1 - 8) -// }) -// } -// }, [addDetectionLog]) - -// // Optimized drawing function with batched state updates -// const drawDetections = useCallback(() => { -// const video = videoRef.current -// const canvas = canvasRef.current - -// if (!video || !canvas) { -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// return -// } - -// // Skip drawing if video is paused or ended -// if (video.paused || video.ended) { -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// return -// } - -// // Calculate current frame -// const frame = Math.floor(video.currentTime * data.video_info.fps) - -// // Only update if frame has changed -// if (frame !== lastProcessedFrame.current) { -// lastProcessedFrame.current = frame -// drawFrameDetections(frame, true) -// } - -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// }, [data.video_info.fps, drawFrameDetections]) - -// // Start/stop animation loop -// useEffect(() => { -// animationFrameRef.current = requestAnimationFrame(drawDetections) - -// return () => { -// if (animationFrameRef.current) { -// cancelAnimationFrame(animationFrameRef.current) -// } -// } -// }, [drawDetections]) - -// // Handle video end - show summary -// useEffect(() => { -// const video = videoRef.current -// if (!video) return - -// const handleEnded = () => { -// setShowSummary(true) -// setTimeout(() => { -// setLogs((prev) => { -// const summaryLog: DetectionLog = { -// frame: -1, -// detections: [], -// timestamp: new Date().toLocaleTimeString(), -// } -// return [summaryLog, ...prev].slice(0, MAX_LOGS) -// }) -// }, 500) -// } - -// video.addEventListener("ended", handleEnded) - -// return () => { -// video.removeEventListener("ended", handleEnded) -// } -// }, [data]) - -// // Reset logs when video is seeked or restarted -// useEffect(() => { -// const video = videoRef.current -// if (!video) return - -// const handleSeeked = () => { -// loggedFrames.current.clear() -// // Draw detections for the seeked frame immediately -// const frame = Math.floor(video.currentTime * data.video_info.fps) -// lastProcessedFrame.current = frame -// drawFrameDetections(frame, false) // Don't log during seeking -// } - -// const handlePlay = () => { -// if (video.currentTime < 1) { -// setLogs([]) -// loggedFrames.current.clear() -// lastProcessedFrame.current = -1 -// } -// } - -// video.addEventListener("seeked", handleSeeked) -// video.addEventListener("play", handlePlay) - -// return () => { -// video.removeEventListener("seeked", handleSeeked) -// video.removeEventListener("play", handlePlay) -// } -// }, [data.video_info.fps, drawFrameDetections]) - -// return ( -// -// -// Video Playback with Detection -// Watch the analyzed video with real-time bounding box overlays -// -// -//
-// {/* Video Player */} -//
-//
-//
- -// {/* Video Info */} -//
-//
-// Current Frame: -// {currentFrame} -//
-//
-// Detections: -// 0 ? "destructive" : "secondary"}>{detectionsCount} -//
-//
-// Resolution: -// -// {data.video_info.width}×{data.video_info.height} -// -//
-//
-// FPS: -// {data.video_info.fps.toFixed(1)} -//
-//
-//
- -// {/* Detection Logs */} -//
-//
-//

Detection Logs

-//

Real-time frame-by-frame detection tracking (last {MAX_LOGS})

-//
-// -//
-// {logs.length === 0 ? ( -//

Play the video to see detection logs

-// ) : ( -// logs.map((log, index) => ( -// log.frame === -1 ? ( -// // Summary entry -//
-//
-// 📊 DETECTION SUMMARY -//
-//
-//
-// Unique Potholes: -// {data.summary.unique_potholes} -//
-//
-// Total Detections: -// {data.summary.total_detections} -//
-//
-// Total Frames: -// {data.summary.total_frames} -//
-//
-// Detection Rate: -// {data.summary.detection_rate.toFixed(1)}% -//
-//
-// Video FPS: -// {data.video_info.fps.toFixed(1)} -//
-//
-// Resolution: -// {data.video_info.width}×{data.video_info.height} -//
-//
-//
-// ) : ( -// // Detection entry -//
-//
-// Frame: {log.frame} -// {log.timestamp} -//
- -// {log.detections.length === 0 ? ( -//
No detections
-// ) : ( -//
-// {log.detections.map((det, idx) => ( -//
-//
-// Pothole ID: {det.pothole_id} | Confidence: {(det.confidence * 100).toFixed(1)}% -//
-//
-// Coordinates: ({Math.round(det.bbox.x1)}, {Math.round(det.bbox.y1)}) → ({Math.round(det.bbox.x2)}, {Math.round(det.bbox.y2)}) -//
-//
-// ))} -//
-// )} -//
-// ) -// )) -// )} -//
-//
-//
-//
-//
-//
-// ) -// } - - - - - - - - - - -//working version - -// "use client" - -// import { useEffect, useRef, useState, useCallback } from "react" -// import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -// import { ScrollArea } from "@/components/ui/scroll-area" -// import { Badge } from "@/components/ui/badge" - -// type DetectionData = { -// frames: Array<{ -// frame_id: number -// potholes: Array<{ -// pothole_id: number -// bbox: { x1: number; y1: number; x2: number; y2: number } -// confidence: number -// }> -// }> -// video_info: { -// width: number -// height: number -// fps: number -// total_frames: number -// } -// summary: { -// unique_potholes: number -// total_detections: number -// total_frames: number -// detection_rate: number -// } -// } - -// type VideoPlayerSectionProps = { -// data: DetectionData -// videoFile: File -// } - -// type DetectionLog = { -// frame: number -// detections: Array<{ -// pothole_id: number -// bbox: { x1: number; y1: number; x2: number; y2: number } -// confidence: number -// }> -// timestamp: string -// } - -// export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSectionProps) { -// const videoRef = useRef(null) -// const canvasRef = useRef(null) -// const containerRef = useRef(null) -// const animationFrameRef = useRef(null) -// const [currentFrame, setCurrentFrame] = useState(0) -// const [detectionsCount, setDetectionsCount] = useState(0) -// const [logs, setLogs] = useState([]) -// const [showSummary, setShowSummary] = useState(false) -// const frameDetectionMap = useRef>(new Map()) -// const lastProcessedFrame = useRef(-1) -// const loggedFrames = useRef>(new Set()) -// const MAX_LOGS = 0 // Limit log entries to prevent memory issues - -// // Build optimized frame detection map -// useEffect(() => { -// const map = new Map() - -// if (data.frames && Array.isArray(data.frames)) { -// console.log(`Building frame map from ${data.frames.length} frames`) -// data.frames.forEach((frameData) => { -// const frameId = frameData.frame_id -// const potholes = frameData.potholes || [] - -// if (potholes.length > 0) { -// map.set(frameId, potholes) -// } -// }) -// console.log(`Frame map built: ${map.size} frames with detections`) -// } - -// frameDetectionMap.current = map -// }, [data]) - -// // Load video file -// useEffect(() => { -// if (videoRef.current && videoFile) { -// const url = URL.createObjectURL(videoFile) -// videoRef.current.src = url - -// return () => { -// URL.revokeObjectURL(url) -// } -// } -// }, [videoFile]) - -// // Setup canvas resolution -// useEffect(() => { -// const video = videoRef.current -// const canvas = canvasRef.current - -// if (!video || !canvas) return - -// const setupResolution = () => { -// // Set canvas internal resolution to match backend data -// canvas.width = data.video_info.width -// canvas.height = data.video_info.height - -// // Set display size to match video element -// const rect = video.getBoundingClientRect() -// canvas.style.width = `${rect.width}px` -// canvas.style.height = `${rect.height}px` -// } - -// video.addEventListener("loadedmetadata", setupResolution) -// window.addEventListener("resize", setupResolution) - -// return () => { -// video.removeEventListener("loadedmetadata", setupResolution) -// window.removeEventListener("resize", setupResolution) -// } -// }, [data.video_info.width, data.video_info.height]) - -// // Add detection log with deduplication and size limit -// const addDetectionLog = useCallback((frame: number, detections: any[]) => { -// // Skip if already logged this frame -// if (loggedFrames.current.has(frame)) return - -// loggedFrames.current.add(frame) - -// setLogs((prev) => { -// const newLog: DetectionLog = { -// frame, -// detections: detections.map((det) => ({ -// pothole_id: det.pothole_id, -// bbox: det.bbox, -// confidence: det.confidence, -// })), -// timestamp: new Date().toLocaleTimeString(), -// } - -// // Keep only MAX_LOGS entries -// const updated = [newLog, ...prev].slice(0, MAX_LOGS) -// return updated -// }) -// }, []) - -// // Core drawing logic extracted for reuse -// const drawFrameDetections = useCallback((frame: number, shouldLog: boolean = true) => { -// const canvas = canvasRef.current -// if (!canvas) return - -// const ctx = canvas.getContext("2d", { alpha: true }) -// if (!ctx) return - -// // Get detections for current frame -// const detections = frameDetectionMap.current.get(frame) -// const detCount = detections?.length || 0 - -// // Batch state updates to minimize re-renders -// setCurrentFrame(frame) -// setDetectionsCount(detCount) - -// // Clear canvas -// ctx.clearRect(0, 0, canvas.width, canvas.height) - -// if (detections && detections.length > 0) { -// // Add log entry (with deduplication) only during playback -// if (shouldLog) { -// addDetectionLog(frame, detections) -// } - -// // Draw all detections -// detections.forEach((det) => { -// const { x1, y1, x2, y2 } = det.bbox -// const width = x2 - x1 -// const height = y2 - y1 - -// // Draw red bounding box -// ctx.strokeStyle = "#ef4444" -// ctx.lineWidth = 3 -// ctx.strokeRect(x1, y1, width, height) - -// // Draw semi-transparent fill -// ctx.fillStyle = "rgba(239, 68, 68, 0.15)" -// ctx.fillRect(x1, y1, width, height) - -// // Draw label background -// const label = `Pothole #${det.pothole_id} (${(det.confidence * 100).toFixed(0)}%)` -// ctx.font = "bold 14px system-ui" -// const textWidth = ctx.measureText(label).width - -// ctx.fillStyle = "rgba(239, 68, 68, 0.95)" -// ctx.fillRect(x1, y1 - 26, textWidth + 16, 26) - -// // Draw label text -// ctx.fillStyle = "#ffffff" -// ctx.fillText(label, x1 + 8, y1 - 8) -// }) -// } -// }, [addDetectionLog]) - -// // Optimized drawing function with batched state updates -// const drawDetections = useCallback(() => { -// const video = videoRef.current -// const canvas = canvasRef.current - -// if (!video || !canvas) { -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// return -// } - -// // Skip drawing if video is paused or ended -// if (video.paused || video.ended) { -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// return -// } - -// // Calculate current frame -// const frame = Math.floor(video.currentTime * data.video_info.fps) - -// // Only update if frame has changed -// if (frame !== lastProcessedFrame.current) { -// lastProcessedFrame.current = frame -// drawFrameDetections(frame, true) -// } - -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// }, [data.video_info.fps, drawFrameDetections]) - -// // Start/stop animation loop -// useEffect(() => { -// animationFrameRef.current = requestAnimationFrame(drawDetections) - -// return () => { -// if (animationFrameRef.current) { -// cancelAnimationFrame(animationFrameRef.current) -// } -// } -// }, [drawDetections]) - -// // Handle video end - show summary -// useEffect(() => { -// const video = videoRef.current -// if (!video) return - -// const handleEnded = () => { -// setShowSummary(true) -// setTimeout(() => { -// setLogs((prev) => { -// const summaryLog: DetectionLog = { -// frame: -1, -// detections: [], -// timestamp: new Date().toLocaleTimeString(), -// } -// return [summaryLog, ...prev].slice(0, MAX_LOGS) -// }) -// }, 500) -// } - -// video.addEventListener("ended", handleEnded) - -// return () => { -// video.removeEventListener("ended", handleEnded) -// } -// }, [data]) - -// // Handle seeking with optimized video frame sync -// useEffect(() => { -// const video = videoRef.current -// if (!video) return - -// let rafId: number | null = null - -// const updateFrame = () => { -// const frame = Math.floor(video.currentTime * data.video_info.fps) -// if (frame !== lastProcessedFrame.current) { -// lastProcessedFrame.current = frame -// drawFrameDetections(frame, false) -// } -// } - -// const handleSeeking = () => { -// // Cancel any pending updates -// if (rafId) cancelAnimationFrame(rafId) -// // Update immediately on seeking start -// updateFrame() -// } - -// const handleSeeked = () => { -// loggedFrames.current.clear() -// // Final update when seeking completes -// if (rafId) cancelAnimationFrame(rafId) -// rafId = requestAnimationFrame(updateFrame) -// } - -// const handleTimeUpdate = () => { -// // Throttled updates during scrubbing when paused -// if (video.paused && !rafId) { -// rafId = requestAnimationFrame(() => { -// updateFrame() -// rafId = null -// }) -// } -// } - -// const handlePlay = () => { -// if (video.currentTime < 1) { -// setLogs([]) -// loggedFrames.current.clear() -// lastProcessedFrame.current = -1 -// } -// } - -// video.addEventListener("seeking", handleSeeking) -// video.addEventListener("seeked", handleSeeked) -// video.addEventListener("timeupdate", handleTimeUpdate) -// video.addEventListener("play", handlePlay) - -// return () => { -// if (rafId) cancelAnimationFrame(rafId) -// video.removeEventListener("seeking", handleSeeking) -// video.removeEventListener("seeked", handleSeeked) -// video.removeEventListener("timeupdate", handleTimeUpdate) -// video.removeEventListener("play", handlePlay) -// } -// }, [data.video_info.fps, drawFrameDetections]) - -// return ( -// -// -// Video Playback with Detection -// Watch the analyzed video with real-time bounding box overlays -// -// -//
-// {/* Video Player */} -//
-//
-//
- -// {/* Video Info */} -//
-//
-// Current Frame: -// {currentFrame} -//
-//
-// Detections: -// 0 ? "destructive" : "secondary"}>{detectionsCount} -//
-//
-// Resolution: -// -// {data.video_info.width}×{data.video_info.height} -// -//
-//
-// FPS: -// {data.video_info.fps.toFixed(1)} -//
-//
-//
- -// {/* Detection Logs */} -//
-//
-//

Detection Logs

-//

Real-time frame-by-frame detection tracking (last {MAX_LOGS})

-//
-// -//
-// {logs.length === 0 ? ( -//

Play the video to see detection logs

-// ) : ( -// logs.map((log, index) => ( -// log.frame === -1 ? ( -// // Summary entry -//
-//
-// 📊 DETECTION SUMMARY -//
-//
-//
-// Unique Potholes: -// {data.summary.unique_potholes} -//
-//
-// Total Detections: -// {data.summary.total_detections} -//
-//
-// Total Frames: -// {data.summary.total_frames} -//
-//
-// Detection Rate: -// {data.summary.detection_rate.toFixed(1)}% -//
-//
-// Video FPS: -// {data.video_info.fps.toFixed(1)} -//
-//
-// Resolution: -// {data.video_info.width}×{data.video_info.height} -//
-//
-//
-// ) : ( -// // Detection entry -//
-//
-// Frame: {log.frame} -// {log.timestamp} -//
- -// {log.detections.length === 0 ? ( -//
No detections
-// ) : ( -//
-// {log.detections.map((det, idx) => ( -//
-//
-// Pothole ID: {det.pothole_id} | Confidence: {(det.confidence * 100).toFixed(1)}% -//
-//
-// Coordinates: ({Math.round(det.bbox.x1)}, {Math.round(det.bbox.y1)}) → ({Math.round(det.bbox.x2)}, {Math.round(det.bbox.y2)}) -//
-//
-// ))} -//
-// )} -//
-// ) -// )) -// )} -//
-//
-//
-//
-//
-//
-// ) -// } - - - - - -// "use client" - -// import { useEffect, useRef, useState, useCallback } from "react" -// import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -// import { ScrollArea } from "@/components/ui/scroll-area" -// import { Badge } from "@/components/ui/badge" - -// type DetectionData = { -// frames: Array<{ -// frame_id: number -// potholes: Array<{ -// pothole_id: number -// bbox: { x1: number; y1: number; x2: number; y2: number } -// confidence: number -// }> -// }> -// video_info: { -// width: number -// height: number -// fps: number -// total_frames: number -// } -// summary: { -// unique_potholes: number -// total_detections: number -// total_frames: number -// detection_rate: number -// } -// } - -// type VideoPlayerSectionProps = { -// data: DetectionData -// videoFile: File -// } - -// type DetectionLog = { -// frame: number -// detections: Array<{ -// pothole_id: number -// bbox: { x1: number; y1: number; x2: number; y2: number } -// confidence: number -// }> -// timestamp: string -// } - -// export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSectionProps) { -// const videoRef = useRef(null) -// const canvasRef = useRef(null) -// const containerRef = useRef(null) -// const animationFrameRef = useRef(null) -// const [currentFrame, setCurrentFrame] = useState(0) -// const [detectionsCount, setDetectionsCount] = useState(0) -// const [logs, setLogs] = useState([]) -// const [showSummary, setShowSummary] = useState(false) -// const frameDetectionMap = useRef>(new Map()) -// const lastProcessedFrame = useRef(-1) -// const loggedFrames = useRef>(new Set()) -// const MAX_LOGS = 0 // Limit log entries to prevent memory issues - -// // Build optimized frame detection map -// useEffect(() => { -// const map = new Map() - -// if (data.frames && Array.isArray(data.frames)) { -// console.log(`Building frame map from ${data.frames.length} frames`) -// data.frames.forEach((frameData) => { -// const frameId = frameData.frame_id -// const potholes = frameData.potholes || [] - -// if (potholes.length > 0) { -// map.set(frameId, potholes) -// } -// }) -// console.log(`Frame map built: ${map.size} frames with detections`) -// } - -// frameDetectionMap.current = map -// }, [data]) - -// // Load video file -// useEffect(() => { -// if (videoRef.current && videoFile) { -// const url = URL.createObjectURL(videoFile) -// videoRef.current.src = url - -// return () => { -// URL.revokeObjectURL(url) -// } -// } -// }, [videoFile]) - -// // Setup canvas resolution with performance optimizations -// useEffect(() => { -// const video = videoRef.current -// const canvas = canvasRef.current - -// if (!video || !canvas) return - -// const setupResolution = () => { -// // Set canvas internal resolution to match backend data -// canvas.width = data.video_info.width -// canvas.height = data.video_info.height - -// // Set display size to match video element -// const rect = video.getBoundingClientRect() -// canvas.style.width = `${rect.width}px` -// canvas.style.height = `${rect.height}px` - -// // Enable GPU acceleration -// canvas.style.willChange = 'transform' -// } - -// video.addEventListener("loadedmetadata", setupResolution) -// window.addEventListener("resize", setupResolution) - -// return () => { -// video.removeEventListener("loadedmetadata", setupResolution) -// window.removeEventListener("resize", setupResolution) -// } -// }, [data.video_info.width, data.video_info.height]) - -// // Add detection log with deduplication and size limit -// const addDetectionLog = useCallback((frame: number, detections: any[]) => { -// // Skip if already logged this frame -// if (loggedFrames.current.has(frame)) return - -// loggedFrames.current.add(frame) - -// setLogs((prev) => { -// const newLog: DetectionLog = { -// frame, -// detections: detections.map((det) => ({ -// pothole_id: det.pothole_id, -// bbox: det.bbox, -// confidence: det.confidence, -// })), -// timestamp: new Date().toLocaleTimeString(), -// } - -// // Keep only MAX_LOGS entries -// const updated = [newLog, ...prev].slice(0, MAX_LOGS) -// return updated -// }) -// }, []) - -// // Core drawing logic extracted for reuse (optimized for performance) -// const drawFrameDetections = useCallback((frame: number, shouldLog: boolean = true) => { -// const canvas = canvasRef.current -// if (!canvas) return - -// const ctx = canvas.getContext("2d", { alpha: false, desynchronized: true }) -// if (!ctx) return - -// // Get detections for current frame -// const detections = frameDetectionMap.current.get(frame) -// const detCount = detections?.length || 0 - -// // Batch state updates to minimize re-renders -// setCurrentFrame(frame) -// setDetectionsCount(detCount) - -// // Clear canvas (fast method) -// canvas.width = canvas.width - -// if (detections && detections.length > 0) { -// // Add log entry (with deduplication) only during playback -// if (shouldLog) { -// addDetectionLog(frame, detections) -// } - -// // Batch all drawing operations -// ctx.save() -// ctx.lineWidth = 3 -// ctx.font = "bold 14px system-ui" - -// // Draw all detections in one pass -// detections.forEach((det) => { -// const { x1, y1, x2, y2 } = det.bbox -// const width = x2 - x1 -// const height = y2 - y1 - -// // Draw bounding box -// ctx.strokeStyle = "#ef4444" -// ctx.strokeRect(x1, y1, width, height) - -// // Draw semi-transparent fill -// ctx.fillStyle = "rgba(239, 68, 68, 0.15)" -// ctx.fillRect(x1, y1, width, height) - -// // Draw label -// const label = `Pothole #${det.pothole_id} (${(det.confidence * 100).toFixed(0)}%)` -// const textWidth = ctx.measureText(label).width - -// ctx.fillStyle = "rgba(239, 68, 68, 0.95)" -// ctx.fillRect(x1, y1 - 26, textWidth + 16, 26) - -// ctx.fillStyle = "#ffffff" -// ctx.fillText(label, x1 + 8, y1 - 8) -// }) - -// ctx.restore() -// } -// }, [addDetectionLog]) - -// // Optimized drawing function with batched state updates -// const drawDetections = useCallback(() => { -// const video = videoRef.current -// const canvas = canvasRef.current - -// if (!video || !canvas) { -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// return -// } - -// // Skip drawing if video is paused or ended -// if (video.paused || video.ended) { -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// return -// } - -// // Calculate current frame -// const frame = Math.floor(video.currentTime * data.video_info.fps) - -// // Only update if frame has changed -// if (frame !== lastProcessedFrame.current) { -// lastProcessedFrame.current = frame -// drawFrameDetections(frame, true) -// } - -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// }, [data.video_info.fps, drawFrameDetections]) - -// // Start/stop animation loop -// useEffect(() => { -// animationFrameRef.current = requestAnimationFrame(drawDetections) - -// return () => { -// if (animationFrameRef.current) { -// cancelAnimationFrame(animationFrameRef.current) -// } -// } -// }, [drawDetections]) - -// // Handle video end - show summary -// useEffect(() => { -// const video = videoRef.current -// if (!video) return - -// const handleEnded = () => { -// setShowSummary(true) -// setTimeout(() => { -// setLogs((prev) => { -// const summaryLog: DetectionLog = { -// frame: -1, -// detections: [], -// timestamp: new Date().toLocaleTimeString(), -// } -// return [summaryLog, ...prev].slice(0, MAX_LOGS) -// }) -// }, 500) -// } - -// video.addEventListener("ended", handleEnded) - -// return () => { -// video.removeEventListener("ended", handleEnded) -// } -// }, [data]) - -// // Handle seeking with high-performance sync matching video FPS -// useEffect(() => { -// const video = videoRef.current -// if (!video) return - -// let isSeeking = false -// let seekingRaf: number | null = null - -// const updateFrame = () => { -// const frame = Math.floor(video.currentTime * data.video_info.fps) -// if (frame !== lastProcessedFrame.current) { -// lastProcessedFrame.current = frame -// drawFrameDetections(frame, false) -// } -// } - -// const syncLoop = () => { -// if (isSeeking) { -// updateFrame() -// seekingRaf = requestAnimationFrame(syncLoop) -// } -// } - -// const handleSeeking = () => { -// // Start continuous sync loop during scrubbing -// if (!isSeeking) { -// isSeeking = true -// syncLoop() -// } -// } - -// const handleSeeked = () => { -// // Stop sync loop when scrubbing ends -// isSeeking = false -// if (seekingRaf) { -// cancelAnimationFrame(seekingRaf) -// seekingRaf = null -// } -// loggedFrames.current.clear() -// updateFrame() -// } - -// const handleTimeUpdate = () => { -// // Update when paused but not seeking -// if (video.paused && !isSeeking) { -// updateFrame() -// } -// } - -// const handleLoadedData = () => { -// // Draw initial frame when video loads -// updateFrame() -// } - -// const handlePlay = () => { -// if (video.currentTime < 1) { -// setLogs([]) -// loggedFrames.current.clear() -// lastProcessedFrame.current = -1 -// } -// } - -// video.addEventListener("seeking", handleSeeking) -// video.addEventListener("seeked", handleSeeked) -// video.addEventListener("timeupdate", handleTimeUpdate) -// video.addEventListener("loadeddata", handleLoadedData) -// video.addEventListener("play", handlePlay) - -// return () => { -// isSeeking = false -// if (seekingRaf) cancelAnimationFrame(seekingRaf) -// video.removeEventListener("seeking", handleSeeking) -// video.removeEventListener("seeked", handleSeeked) -// video.removeEventListener("timeupdate", handleTimeUpdate) -// video.removeEventListener("loadeddata", handleLoadedData) -// video.removeEventListener("play", handlePlay) -// } -// }, [data.video_info.fps, drawFrameDetections]) - -// return ( -// -// -// Video Playback with Detection -// Watch the analyzed video with real-time bounding box overlays -// -// -//
-// {/* Video Player */} -//
-//
-//
- -// {/* Video Info */} -//
-//
-// Current Frame: -// {currentFrame} -//
-//
-// Detections: -// 0 ? "destructive" : "secondary"}>{detectionsCount} -//
-//
-// Resolution: -// -// {data.video_info.width}×{data.video_info.height} -// -//
-//
-// FPS: -// {data.video_info.fps.toFixed(1)} -//
-//
-//
- -// {/* Detection Logs */} -//
-//
-//

Detection Logs

-//

Real-time frame-by-frame detection tracking (last {MAX_LOGS})

-//
-// -//
-// {logs.length === 0 ? ( -//

Play the video to see detection logs

-// ) : ( -// logs.map((log, index) => ( -// log.frame === -1 ? ( -// // Summary entry -//
-//
-// 📊 DETECTION SUMMARY -//
-//
-//
-// Unique Potholes: -// {data.summary.unique_potholes} -//
-//
-// Total Detections: -// {data.summary.total_detections} -//
-//
-// Total Frames: -// {data.summary.total_frames} -//
-//
-// Detection Rate: -// {data.summary.detection_rate.toFixed(1)}% -//
-//
-// Video FPS: -// {data.video_info.fps.toFixed(1)} -//
-//
-// Resolution: -// {data.video_info.width}×{data.video_info.height} -//
-//
-//
-// ) : ( -// // Detection entry -//
-//
-// Frame: {log.frame} -// {log.timestamp} -//
- -// {log.detections.length === 0 ? ( -//
No detections
-// ) : ( -//
-// {log.detections.map((det, idx) => ( -//
-//
-// Pothole ID: {det.pothole_id} | Confidence: {(det.confidence * 100).toFixed(1)}% -//
-//
-// Coordinates: ({Math.round(det.bbox.x1)}, {Math.round(det.bbox.y1)}) → ({Math.round(det.bbox.x2)}, {Math.round(det.bbox.y2)}) -//
-//
-// ))} -//
-// )} -//
-// ) -// )) -// )} -//
-//
-//
-//
-//
-//
-// ) -// } - - - - - -//************************************Perfect working ********************************************8 - -// "use client" - -// import { useEffect, useRef, useState, useCallback } from "react" -// import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -// import { ScrollArea } from "@/components/ui/scroll-area" -// import { Badge } from "@/components/ui/badge" - -// type DetectionData = { -// frames: Array<{ -// frame_id: number -// potholes: Array<{ -// pothole_id: number -// bbox: { x1: number; y1: number; x2: number; y2: number } -// confidence: number -// }> -// }> -// video_info: { -// width: number -// height: number -// fps: number -// total_frames: number -// } -// summary: { -// unique_potholes: number -// total_detections: number -// total_frames: number -// detection_rate: number -// } -// } - -// type VideoPlayerSectionProps = { -// data: DetectionData -// videoFile: File -// } - -// type DetectionLog = { -// frame: number -// detections: Array<{ -// pothole_id: number -// bbox: { x1: number; y1: number; x2: number; y2: number } -// confidence: number -// }> -// timestamp: string -// } - -// export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSectionProps) { -// const videoRef = useRef(null) -// const canvasRef = useRef(null) -// const containerRef = useRef(null) -// const animationFrameRef = useRef(null) -// const [currentFrame, setCurrentFrame] = useState(0) -// const [detectionsCount, setDetectionsCount] = useState(0) -// const [logs, setLogs] = useState([]) -// const [showSummary, setShowSummary] = useState(false) -// const frameDetectionMap = useRef>(new Map()) -// const lastProcessedFrame = useRef(-1) -// const loggedFrames = useRef>(new Set()) -// const MAX_LOGS = 0 // Limit log entries to prevent memory issues - -// // Build optimized frame detection map -// useEffect(() => { -// const map = new Map() - -// if (data.frames && Array.isArray(data.frames)) { -// console.log(`Building frame map from ${data.frames.length} frames`) -// data.frames.forEach((frameData) => { -// const frameId = frameData.frame_id -// const potholes = frameData.potholes || [] - -// if (potholes.length > 0) { -// map.set(frameId, potholes) -// } -// }) -// console.log(`Frame map built: ${map.size} frames with detections`) -// } - -// frameDetectionMap.current = map -// }, [data]) - -// // Load video file -// useEffect(() => { -// if (videoRef.current && videoFile) { -// const url = URL.createObjectURL(videoFile) -// videoRef.current.src = url - -// return () => { -// URL.revokeObjectURL(url) -// } -// } -// }, [videoFile]) - -// // Setup canvas resolution with performance optimizations -// useEffect(() => { -// const video = videoRef.current -// const canvas = canvasRef.current - -// if (!video || !canvas) return - -// const setupResolution = () => { -// // Set canvas internal resolution to match backend data -// canvas.width = data.video_info.width -// canvas.height = data.video_info.height - -// // Set display size to match video element -// const rect = video.getBoundingClientRect() -// canvas.style.width = `${rect.width}px` -// canvas.style.height = `${rect.height}px` - -// // Enable GPU acceleration -// canvas.style.willChange = 'transform' - -// // Ensure canvas is transparent -// const ctx = canvas.getContext("2d", { alpha: true }) -// if (ctx) { -// ctx.clearRect(0, 0, canvas.width, canvas.height) -// } -// } - -// video.addEventListener("loadedmetadata", setupResolution) -// window.addEventListener("resize", setupResolution) - -// return () => { -// video.removeEventListener("loadedmetadata", setupResolution) -// window.removeEventListener("resize", setupResolution) -// } -// }, [data.video_info.width, data.video_info.height]) - -// // Add detection log with deduplication and size limit -// const addDetectionLog = useCallback((frame: number, detections: any[]) => { -// // Skip if already logged this frame -// if (loggedFrames.current.has(frame)) return - -// loggedFrames.current.add(frame) - -// setLogs((prev) => { -// const newLog: DetectionLog = { -// frame, -// detections: detections.map((det) => ({ -// pothole_id: det.pothole_id, -// bbox: det.bbox, -// confidence: det.confidence, -// })), -// timestamp: new Date().toLocaleTimeString(), -// } - -// // Keep only MAX_LOGS entries -// const updated = [newLog, ...prev].slice(0, MAX_LOGS) -// return updated -// }) -// }, []) - -// // Core drawing logic extracted for reuse (heavily optimized for performance) -// const drawFrameDetections = useCallback((frame: number, shouldLog: boolean = true) => { -// const canvas = canvasRef.current -// if (!canvas) return - -// const ctx = canvas.getContext("2d", { -// alpha: true, -// desynchronized: true, -// willReadFrequently: false -// }) -// if (!ctx) return - -// // Get detections for current frame -// const detections = frameDetectionMap.current.get(frame) -// const detCount = detections?.length || 0 - -// // Clear canvas first for instant visual update -// ctx.clearRect(0, 0, canvas.width, canvas.height) - -// // Batch state updates AFTER clearing to minimize re-renders blocking draw -// requestAnimationFrame(() => { -// setCurrentFrame(frame) -// setDetectionsCount(detCount) -// }) - -// if (detections && detections.length > 0) { -// // Add log entry (with deduplication) only during playback -// if (shouldLog) { -// requestAnimationFrame(() => addDetectionLog(frame, detections)) -// } - -// // Optimized drawing with minimal state changes -// ctx.lineWidth = 3 -// ctx.font = "bold 14px system-ui" - -// // Draw all boxes first (single pass) -// ctx.strokeStyle = "#ef4444" -// ctx.fillStyle = "rgba(239, 68, 68, 0.15)" -// detections.forEach((det) => { -// const { x1, y1, x2, y2 } = det.bbox -// const w = x2 - x1 -// const h = y2 - y1 -// ctx.strokeRect(x1, y1, w, h) -// ctx.fillRect(x1, y1, w, h) -// }) - -// // Draw all labels second (single pass) -// ctx.fillStyle = "rgba(239, 68, 68, 0.95)" -// detections.forEach((det) => { -// const { x1, y1 } = det.bbox -// const label = `Pothole #${det.pothole_id} (${(det.confidence * 100).toFixed(0)}%)` -// const textWidth = ctx.measureText(label).width -// ctx.fillRect(x1, y1 - 26, textWidth + 16, 26) -// }) - -// // Draw all text third (single pass) -// ctx.fillStyle = "#ffffff" -// detections.forEach((det) => { -// const { x1, y1 } = det.bbox -// const label = `Pothole #${det.pothole_id} (${(det.confidence * 100).toFixed(0)}%)` -// ctx.fillText(label, x1 + 8, y1 - 8) -// }) -// } -// }, [addDetectionLog]) - -// // Optimized drawing function with batched state updates -// const drawDetections = useCallback(() => { -// const video = videoRef.current -// const canvas = canvasRef.current - -// if (!video || !canvas) { -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// return -// } - -// // Calculate current frame with proper rounding for accuracy -// // Add small epsilon to handle floating point precision -// const frame = Math.round(video.currentTime * data.video_info.fps) - -// // Update on every frame during playback for smooth tracking -// if (!video.paused && !video.ended && frame !== lastProcessedFrame.current) { -// lastProcessedFrame.current = frame -// drawFrameDetections(frame, true) -// } - -// animationFrameRef.current = requestAnimationFrame(drawDetections) -// }, [data.video_info.fps, drawFrameDetections]) - -// // Start/stop animation loop -// useEffect(() => { -// animationFrameRef.current = requestAnimationFrame(drawDetections) - -// return () => { -// if (animationFrameRef.current) { -// cancelAnimationFrame(animationFrameRef.current) -// } -// } -// }, [drawDetections]) - -// // Handle video end - show summary -// useEffect(() => { -// const video = videoRef.current -// if (!video) return - -// const handleEnded = () => { -// setShowSummary(true) -// // Add summary log immediately -// const summaryLog: DetectionLog = { -// frame: -1, -// detections: [], -// timestamp: new Date().toLocaleTimeString(), -// } -// setLogs((prev) => [summaryLog, ...prev].slice(0, MAX_LOGS)) -// } - -// video.addEventListener("ended", handleEnded) - -// return () => { -// video.removeEventListener("ended", handleEnded) -// } -// }, [data]) - -// // Handle seeking with ultra-smooth scrubbing and instant bbox sync -// useEffect(() => { -// const video = videoRef.current -// if (!video) return - -// let isSeeking = false -// let seekingRaf: number | null = null - -// const updateFrame = () => { -// // Use Math.round for more accurate frame calculation -// const frame = Math.round(video.currentTime * data.video_info.fps) -// if (frame !== lastProcessedFrame.current) { -// lastProcessedFrame.current = frame -// drawFrameDetections(frame, false) -// } -// } - -// // Ultra high-frequency update loop during scrubbing for instant feedback -// const syncLoop = () => { -// if (isSeeking) { -// updateFrame() // Update on every animation frame for instant response -// seekingRaf = requestAnimationFrame(syncLoop) -// } -// } - -// const handleSeeking = () => { -// // Start instant sync loop during scrubbing -// if (!isSeeking) { -// isSeeking = true -// updateFrame() // Immediate first update -// syncLoop() -// } -// } - -// const handleSeeked = () => { -// // Stop sync loop and do final update when scrubbing ends -// isSeeking = false -// if (seekingRaf) { -// cancelAnimationFrame(seekingRaf) -// seekingRaf = null -// } -// loggedFrames.current.clear() -// updateFrame() // Immediate final update -// } - -// const handleTimeUpdate = () => { -// // Instant update when paused for frame-by-frame navigation -// if (video.paused && !isSeeking) { -// updateFrame() -// } -// } - -// const handleLoadedData = () => { -// // Draw initial frame immediately when video loads -// updateFrame() -// } - -// const handlePlay = () => { -// // Reset logs when starting from beginning -// if (video.currentTime < 0.1) { -// setLogs([]) -// loggedFrames.current.clear() -// lastProcessedFrame.current = -1 -// } -// } - -// const handlePause = () => { -// // Instant update when pausing -// updateFrame() -// } - -// video.addEventListener("seeking", handleSeeking) -// video.addEventListener("seeked", handleSeeked) -// video.addEventListener("timeupdate", handleTimeUpdate) -// video.addEventListener("loadeddata", handleLoadedData) -// video.addEventListener("play", handlePlay) -// video.addEventListener("pause", handlePause) - -// return () => { -// isSeeking = false -// if (seekingRaf) cancelAnimationFrame(seekingRaf) -// video.removeEventListener("seeking", handleSeeking) -// video.removeEventListener("seeked", handleSeeked) -// video.removeEventListener("timeupdate", handleTimeUpdate) -// video.removeEventListener("loadeddata", handleLoadedData) -// video.removeEventListener("play", handlePlay) -// video.removeEventListener("pause", handlePause) -// } -// }, [data.video_info.fps, drawFrameDetections]) - -// return ( -// -// -// Video Playback with Detection -// Watch the analyzed video with real-time bounding box overlays -// -// -//
-// {/* Video Player */} -//
-//
-//
- -// {/* Video Info */} -//
-//
-// Current Frame: -// {currentFrame} -//
-//
-// Detections: -// 0 ? "destructive" : "secondary"}>{detectionsCount} -//
-//
-// Resolution: -// -// {data.video_info.width}×{data.video_info.height} -// -//
-//
-// FPS: -// {data.video_info.fps.toFixed(1)} -//
-//
-//
- -// {/* Detection Logs */} -//
-//
-//

Detection Logs

-//

Real-time frame-by-frame detection tracking (last {MAX_LOGS})

-//
-// -//
-// {logs.length === 0 ? ( -//

Play the video to see detection logs

-// ) : ( -// logs.map((log, index) => ( -// log.frame === -1 ? ( -// // Summary entry -//
-//
-// DETECTION SUMMARY -//
-//
-// {/*
-// Unique Potholes: -// {data.summary.unique_potholes} -//
*/} -//
-// Total Detections: -// {data.summary.total_detections} -//
-//
-// Total Frames: -// {data.summary.total_frames} -//
-//
-// Detection Rate: -// {data.summary.detection_rate.toFixed(1)}% -//
-//
-// Video FPS: -// {data.video_info.fps.toFixed(1)} -//
-//
-// Resolution: -// {data.video_info.width}×{data.video_info.height} -//
-//
-//
-// ) : ( -// // Detection entry -//
-//
-// Frame: {log.frame} -// {log.timestamp} -//
- -// {log.detections.length === 0 ? ( -//
No detections
-// ) : ( -//
-// {log.detections.map((det, idx) => ( -//
-//
-// Pothole ID: {det.pothole_id} | Confidence: {(det.confidence * 100).toFixed(1)}% -//
-//
-// Coordinates: ({Math.round(det.bbox.x1)}, {Math.round(det.bbox.y1)}) → ({Math.round(det.bbox.x2)}, {Math.round(det.bbox.y2)}) -//
-//
-// ))} -//
-// )} -//
-// ) -// )) -// )} -//
-//
-//
-//
-//
-//
-// ) -// } - - - +"use client" import { useEffect, useRef, useState, useCallback } from "react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { ScrollArea } from "@/components/ui/scroll-area" import { Badge } from "@/components/ui/badge" -import { Target, AlertTriangle, Film, Activity, Gauge, Monitor } from "lucide-react" +import { Target, AlertTriangle, Film, Activity, Gauge, Monitor, SignpostBig } from "lucide-react" +import type { DetectionData, DetectionType } from "@/app/page" -type DetectionData = { - frames: Array<{ - frame_id: number - potholes: Array<{ - pothole_id: number - bbox: { x1: number; y1: number; x2: number; y2: number } - confidence: number - }> - }> - video_info: { - width: number - height: number - fps: number - total_frames: number - } - summary: { - unique_potholes: number - total_detections: number - total_frames: number - detection_rate: number - } -} +const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:8000/api/v1" type VideoPlayerSectionProps = { data: DetectionData - videoFile: File + videoId: string + detectionType: DetectionType } type DetectionLog = { frame: number detections: Array<{ - pothole_id: number + id: number + type?: string // For signboards bbox: { x1: number; y1: number; x2: number; y2: number } confidence: number }> timestamp: string } -function SummarySection({ data, show }: { data: DetectionData; show: boolean }) { +function SummarySection({ data, show, detectionType }: { data: DetectionData; show: boolean; detectionType: DetectionType }) { if (!show) return null + const isPothole = detectionType === "pothole-detection" + const isSignboard = detectionType === "sign-board-detection" + const stats = [ { - label: "Unique Potholes", - value: data.summary.unique_potholes || 0, - icon: AlertTriangle, - color: "text-red-500", - bgColor: "bg-red-50 dark:bg-red-950/30", + label: isPothole ? "Unique Potholes" : "Unique Signboards", + value: (isPothole ? data.summary.unique_potholes : data.summary.unique_signboards) || 0, + icon: isPothole ? AlertTriangle : SignpostBig, + color: isPothole ? "text-red-500" : "text-blue-500", + bgColor: isPothole ? "bg-red-50 dark:bg-red-950/30" : "bg-blue-50 dark:bg-blue-950/30", }, { label: "Total Detections", @@ -2673,7 +81,9 @@ function SummarySection({ data, show }: { data: DetectionData; show: boolean }) Detection Summary - Overview of pothole detection results + + Overview of {isPothole ? "pothole" : "signboard"} detection results +
@@ -2699,84 +109,74 @@ function SummarySection({ data, show }: { data: DetectionData; show: boolean }) ) } -export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSectionProps) { +export default function VideoPlayerSection({ data, videoId, detectionType }: VideoPlayerSectionProps) { const videoRef = useRef(null) - const canvasRef = useRef(null) - const containerRef = useRef(null) - const animationFrameRef = useRef(null) const [currentFrame, setCurrentFrame] = useState(0) const [detectionsCount, setDetectionsCount] = useState(0) const [logs, setLogs] = useState([]) const [showSummary, setShowSummary] = useState(false) const [hasPlayedOnce, setHasPlayedOnce] = useState(false) + const [videoError, setVideoError] = useState(null) const frameDetectionMap = useRef>(new Map()) const lastProcessedFrame = useRef(-1) const loggedFrames = useRef>(new Set()) - const MAX_LOGS = 50 // Changed from 0 to 50 + const MAX_LOGS = 50 + + const isPothole = detectionType === "pothole-detection" + const isSignboard = detectionType === "sign-board-detection" // Build optimized frame detection map useEffect(() => { const map = new Map() if (data.frames && Array.isArray(data.frames)) { - console.log(`Building frame map from ${data.frames.length} frames`) + console.log(`[VideoPlayer] Building frame map from ${data.frames.length} frames`) data.frames.forEach((frameData) => { const frameId = frameData.frame_id - const potholes = frameData.potholes || [] + + // Handle both pothole and signboard detections + const detections = isPothole ? (frameData.potholes || []) : (frameData.signboards || []) - if (potholes.length > 0) { - map.set(frameId, potholes) + if (detections.length > 0) { + map.set(frameId, detections) } }) - console.log(`Frame map built: ${map.size} frames with detections`) + console.log(`[VideoPlayer] Frame map built: ${map.size} frames with detections`) } frameDetectionMap.current = map - }, [data]) + }, [data, isPothole]) - // Load video file + // Load processed video from backend useEffect(() => { - if (videoRef.current && videoFile) { - const url = URL.createObjectURL(videoFile) - videoRef.current.src = url + if (videoRef.current && videoId) { + const videoUrl = `${API_URL}/video/${videoId}` + console.log(`[VideoPlayer] Loading processed video from: ${videoUrl}`) + + videoRef.current.src = videoUrl + + // Handle video load errors + const handleError = () => { + console.error("[VideoPlayer] Failed to load processed video") + setVideoError("Failed to load processed video. Please try refreshing the page.") + } + + const handleLoaded = () => { + console.log("[VideoPlayer] Processed video loaded successfully") + setVideoError(null) + } + + videoRef.current.addEventListener("error", handleError) + videoRef.current.addEventListener("loadeddata", handleLoaded) return () => { - URL.revokeObjectURL(url) + if (videoRef.current) { + videoRef.current.removeEventListener("error", handleError) + videoRef.current.removeEventListener("loadeddata", handleLoaded) + } } } - }, [videoFile]) - - // Setup canvas resolution with performance optimizations - useEffect(() => { - const video = videoRef.current - const canvas = canvasRef.current - - if (!video || !canvas) return - - const setupResolution = () => { - canvas.width = data.video_info.width - canvas.height = data.video_info.height - - const rect = video.getBoundingClientRect() - canvas.style.width = `${rect.width}px` - canvas.style.height = `${rect.height}px` - - canvas.style.willChange = 'transform' - - const ctx = canvas.getContext("2d", { alpha: true }) - if (ctx) { - ctx.clearRect(0, 0, canvas.width, canvas.height) - } - } - - video.addEventListener("loadedmetadata", setupResolution) - window.addEventListener("resize", setupResolution) - - return () => { - video.removeEventListener("loadedmetadata", setupResolution) - window.removeEventListener("resize", setupResolution) - } - }, [data.video_info.width, data.video_info.height]) + }, [videoId]) // Add detection log with deduplication and size limit const addDetectionLog = useCallback((frame: number, detections: any[]) => { @@ -2788,7 +188,8 @@ export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSecti const newLog: DetectionLog = { frame, detections: detections.map((det) => ({ - pothole_id: det.pothole_id, + id: isPothole ? det.pothole_id : det.signboard_id, + type: isSignboard ? det.type : undefined, bbox: det.bbox, confidence: det.confidence, })), @@ -2798,103 +199,53 @@ export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSecti const updated = [newLog, ...prev].slice(0, MAX_LOGS) return updated }) - }, []) + }, [isPothole, isSignboard]) - // Core drawing logic extracted for reuse - const drawFrameDetections = useCallback((frame: number, shouldLog: boolean = true) => { - const canvas = canvasRef.current - if (!canvas) return - - const ctx = canvas.getContext("2d", { - alpha: true, - desynchronized: true, - willReadFrequently: false - }) - if (!ctx) return - - const detections = frameDetectionMap.current.get(frame) - const detCount = detections?.length || 0 - - ctx.clearRect(0, 0, canvas.width, canvas.height) - - requestAnimationFrame(() => { - setCurrentFrame(frame) - setDetectionsCount(detCount) - }) - - if (detections && detections.length > 0) { - if (shouldLog) { - requestAnimationFrame(() => addDetectionLog(frame, detections)) - } - - ctx.lineWidth = 3 - ctx.font = "bold 14px system-ui" - - ctx.strokeStyle = "#ef4444" - ctx.fillStyle = "rgba(239, 68, 68, 0.15)" - detections.forEach((det) => { - const { x1, y1, x2, y2 } = det.bbox - const w = x2 - x1 - const h = y2 - y1 - ctx.strokeRect(x1, y1, w, h) - ctx.fillRect(x1, y1, w, h) - }) - - ctx.fillStyle = "rgba(239, 68, 68, 0.95)" - detections.forEach((det) => { - const { x1, y1 } = det.bbox - const label = `Pothole #${det.pothole_id} (${(det.confidence * 100).toFixed(0)}%)` - const textWidth = ctx.measureText(label).width - ctx.fillRect(x1, y1 - 26, textWidth + 16, 26) - }) - - ctx.fillStyle = "#ffffff" - detections.forEach((det) => { - const { x1, y1 } = det.bbox - const label = `Pothole #${det.pothole_id} (${(det.confidence * 100).toFixed(0)}%)` - ctx.fillText(label, x1 + 8, y1 - 8) - }) - } - }, [addDetectionLog]) - - // Optimized drawing function - const drawDetections = useCallback(() => { + // Track current frame and log detections + const updateFrameInfo = useCallback(() => { const video = videoRef.current - const canvas = canvasRef.current - - if (!video || !canvas) { - animationFrameRef.current = requestAnimationFrame(drawDetections) - return - } + if (!video) return const frame = Math.round(video.currentTime * data.video_info.fps) if (!video.paused && !video.ended && frame !== lastProcessedFrame.current) { lastProcessedFrame.current = frame - drawFrameDetections(frame, true) - } - - animationFrameRef.current = requestAnimationFrame(drawDetections) - }, [data.video_info.fps, drawFrameDetections]) - - // Start/stop animation loop - useEffect(() => { - animationFrameRef.current = requestAnimationFrame(drawDetections) - - return () => { - if (animationFrameRef.current) { - cancelAnimationFrame(animationFrameRef.current) + setCurrentFrame(frame) + + const detections = frameDetectionMap.current.get(frame) + setDetectionsCount(detections?.length || 0) + + if (detections && detections.length > 0) { + addDetectionLog(frame, detections) } } - }, [drawDetections]) + }, [data.video_info.fps, addDetectionLog]) - // Handle video end - show summary and mark as played once + // Video playback monitoring + useEffect(() => { + const video = videoRef.current + if (!video) return + + let animationId: number + + const animate = () => { + updateFrameInfo() + animationId = requestAnimationFrame(animate) + } + + animationId = requestAnimationFrame(animate) + + return () => { + cancelAnimationFrame(animationId) + } + }, [updateFrameInfo]) + + // Handle video end - show summary useEffect(() => { const video = videoRef.current if (!video) return const handleEnded = () => { - // Only show summary and mark as played once on first complete playback if (!hasPlayedOnce) { setHasPlayedOnce(true) setShowSummary(true) @@ -2908,55 +259,22 @@ export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSecti } }, [hasPlayedOnce]) - // Handle seeking + // Handle seeking and time updates useEffect(() => { const video = videoRef.current if (!video) return - let isSeeking = false - let seekingRaf: number | null = null - - const updateFrame = () => { + const handleTimeUpdate = () => { const frame = Math.round(video.currentTime * data.video_info.fps) - if (frame !== lastProcessedFrame.current) { - lastProcessedFrame.current = frame - drawFrameDetections(frame, false) - } - } - - const syncLoop = () => { - if (isSeeking) { - updateFrame() - seekingRaf = requestAnimationFrame(syncLoop) - } - } - - const handleSeeking = () => { - if (!isSeeking) { - isSeeking = true - updateFrame() - syncLoop() - } + setCurrentFrame(frame) + + const detections = frameDetectionMap.current.get(frame) + setDetectionsCount(detections?.length || 0) } const handleSeeked = () => { - isSeeking = false - if (seekingRaf) { - cancelAnimationFrame(seekingRaf) - seekingRaf = null - } loggedFrames.current.clear() - updateFrame() - } - - const handleTimeUpdate = () => { - if (video.paused && !isSeeking) { - updateFrame() - } - } - - const handleLoadedData = () => { - updateFrame() + handleTimeUpdate() } const handlePlay = () => { @@ -2967,41 +285,37 @@ export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSecti } } - const handlePause = () => { - updateFrame() - } - - video.addEventListener("seeking", handleSeeking) - video.addEventListener("seeked", handleSeeked) video.addEventListener("timeupdate", handleTimeUpdate) - video.addEventListener("loadeddata", handleLoadedData) + video.addEventListener("seeked", handleSeeked) video.addEventListener("play", handlePlay) - video.addEventListener("pause", handlePause) return () => { - isSeeking = false - if (seekingRaf) cancelAnimationFrame(seekingRaf) - video.removeEventListener("seeking", handleSeeking) - video.removeEventListener("seeked", handleSeeked) video.removeEventListener("timeupdate", handleTimeUpdate) - video.removeEventListener("loadeddata", handleLoadedData) + video.removeEventListener("seeked", handleSeeked) video.removeEventListener("play", handlePlay) - video.removeEventListener("pause", handlePause) } - }, [data.video_info.fps, drawFrameDetections]) + }, [data.video_info.fps]) return (
Video Playback with Detection - Watch the analyzed video with real-time bounding box overlays + + Watch the processed video with {isPothole ? "pothole" : "signboard"} detections already drawn +
{/* Video Player */}
-
+ {videoError && ( +
+ {videoError} +
+ )} + +
{/* Video Info */} @@ -3025,7 +336,9 @@ export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSecti
Detections: - 0 ? "destructive" : "secondary"}>{detectionsCount} + 0 ? (isPothole ? "destructive" : "default") : "secondary"}> + {detectionsCount} +
Resolution: @@ -3044,17 +357,23 @@ export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSecti

Detection Logs

-

Real-time frame-by-frame detection tracking (last {MAX_LOGS})

+

+ Real-time frame-by-frame {isPothole ? "pothole" : "signboard"} tracking (last {MAX_LOGS}) +

{logs.length === 0 ? ( -

Play the video to see detection logs

+

+ Play the video to see detection logs +

) : ( logs.map((log, index) => (
Frame: {log.frame} @@ -3068,7 +387,12 @@ export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSecti {log.detections.map((det, idx) => (
- Pothole ID: {det.pothole_id} | Confidence: {(det.confidence * 100).toFixed(1)}% + {isPothole ? ( + <>Pothole ID: {det.id} + ) : ( + <>{det.type || 'Signboard'} ID: {det.id} + )} + {" "}| Confidence: {(det.confidence * 100).toFixed(1)}%
Coordinates: ({Math.round(det.bbox.x1)}, {Math.round(det.bbox.y1)}) → ({Math.round(det.bbox.x2)}, {Math.round(det.bbox.y2)}) @@ -3088,7 +412,7 @@ export default function VideoPlayerSection({ data, videoFile }: VideoPlayerSecti {/* Summary Section - Shows after first complete playback and persists */} - +
) } \ No newline at end of file