feat: implement drain issue
This commit is contained in:
@@ -41,7 +41,13 @@ export default function VideoProcessingPage() {
|
|||||||
setProgress(data.progress || 0);
|
setProgress(data.progress || 0);
|
||||||
let message = data.message || 'Processing...';
|
let message = data.message || 'Processing...';
|
||||||
const uniqueCount =
|
const uniqueCount =
|
||||||
data.unique_potholes ?? data.unique_signboards ?? data.unique_culverts;
|
data.unique_potholes ??
|
||||||
|
data.unique_pothole ??
|
||||||
|
data.unique_signboards ??
|
||||||
|
data.unique_signboard ??
|
||||||
|
data.unique_culverts ??
|
||||||
|
data.unique_culvert ??
|
||||||
|
data.unique_drain_issue;
|
||||||
if (uniqueCount !== undefined) {
|
if (uniqueCount !== undefined) {
|
||||||
message += ` | Unique: ${uniqueCount} | Total: ${data.total_detections || 0}`;
|
message += ` | Unique: ${uniqueCount} | Total: ${data.total_detections || 0}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import dynamic from 'next/dynamic';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Loader2, Milestone } from 'lucide-react';
|
import { Loader2, Milestone } from 'lucide-react';
|
||||||
import { Detection } from '@/types';
|
import { Detection } from '@/types';
|
||||||
|
import { DETECTION_TYPES } from '@/constants/detectionModeConfig';
|
||||||
|
|
||||||
// Dynamically import the map to avoid SSR issues with Leaflet
|
// Dynamically import the map to avoid SSR issues with Leaflet
|
||||||
const DashboardMapContent = dynamic(() => import('./dashboard-map-content'), {
|
const DashboardMapContent = dynamic(() => import('./dashboard-map-content'), {
|
||||||
@@ -78,20 +79,6 @@ export function DashboardMap({
|
|||||||
// Filter detections with valid GPS coordinates
|
// Filter detections with valid GPS coordinates
|
||||||
const validDetections = detections.filter((d) => d.latitude && d.longitude);
|
const validDetections = detections.filter((d) => d.latitude && d.longitude);
|
||||||
|
|
||||||
// Count detections by category
|
|
||||||
const counts = {
|
|
||||||
defected_sign_board: validDetections.filter(
|
|
||||||
(d) => d.type?.toLowerCase() === 'defected_sign_board',
|
|
||||||
).length,
|
|
||||||
pothole: validDetections.filter((d) => d.type?.toLowerCase() === 'pothole').length,
|
|
||||||
road_crack: validDetections.filter((d) => d.type?.toLowerCase() === 'road_crack').length,
|
|
||||||
damaged_road_marking: validDetections.filter(
|
|
||||||
(d) => d.type?.toLowerCase() === 'damaged_road_marking',
|
|
||||||
).length,
|
|
||||||
good_sign_board: validDetections.filter((d) => d.type?.toLowerCase() === 'good_sign_board')
|
|
||||||
.length,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={`overflow-hidden ${className}`}>
|
<Card className={`overflow-hidden ${className}`}>
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
@@ -110,36 +97,24 @@ export function DashboardMap({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Compact Color Legend */}
|
{/* Dynamic Color Legend */}
|
||||||
<div className="flex flex-wrap items-center justify-end gap-x-4 gap-y-1 text-[10px] max-w-[60%]">
|
<div className="flex flex-wrap items-center justify-end gap-x-4 gap-y-1 text-[10px] max-w-[60%]">
|
||||||
<div className="flex items-center gap-1">
|
{Object.values(DETECTION_TYPES).map((type) => {
|
||||||
<div className="w-2.5 h-2.5 rounded-full bg-red-500" />
|
const count = validDetections.filter(
|
||||||
<span className="text-muted-foreground font-medium">Potholes ({counts.pothole})</span>
|
(d) => (d.type || d.class || '').toLowerCase() === type.id.toLowerCase()
|
||||||
</div>
|
).length;
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<div className="w-2.5 h-2.5 rounded-full bg-blue-500" />
|
if (count === 0 && (type.id.includes('culvert') || type.id === 'drain_issue')) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={type.id} className="flex items-center gap-1">
|
||||||
|
<div className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: type.color }} />
|
||||||
<span className="text-muted-foreground font-medium">
|
<span className="text-muted-foreground font-medium">
|
||||||
Defected Signs ({counts.defected_sign_board})
|
{type.label} ({count})
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<div className="w-2.5 h-2.5 rounded-full bg-amber-500" />
|
|
||||||
<span className="text-muted-foreground font-medium">
|
|
||||||
Cracks ({counts.road_crack})
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<div className="w-2.5 h-2.5 rounded-full bg-indigo-500" />
|
|
||||||
<span className="text-muted-foreground font-medium">
|
|
||||||
Markings ({counts.damaged_road_marking})
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<div className="w-2.5 h-2.5 rounded-full bg-emerald-500" />
|
|
||||||
<span className="text-muted-foreground font-medium">
|
|
||||||
Good Signs ({counts.good_sign_board})
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Activity, Film } from 'lucide-react';
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { DetectionLogEntry } from '@/types';
|
import { DetectionLogEntry } from '@/types';
|
||||||
|
import { DETECTION_TYPES } from '@/constants/detectionModeConfig';
|
||||||
|
|
||||||
interface DetectionLogsProps {
|
interface DetectionLogsProps {
|
||||||
logs: DetectionLogEntry[];
|
logs: DetectionLogEntry[];
|
||||||
@@ -41,11 +42,15 @@ const DetectionLogs = ({ logs, onSeek }: DetectionLogsProps) => {
|
|||||||
<span className="text-xs text-muted-foreground/80">{log.videoTime}</span>
|
<span className="text-xs text-muted-foreground/80">{log.videoTime}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{log.detections.map((d, j) => (
|
{log.detections.map((d, j) => {
|
||||||
|
const typeId = (d.type || '').toLowerCase();
|
||||||
|
const typeConfig = DETECTION_TYPES[typeId];
|
||||||
|
const label = typeConfig ? typeConfig.label : typeId.replace(/_/g, ' ');
|
||||||
|
|
||||||
|
return (
|
||||||
<div key={`${d.id}-${j}`} className="space-y-1">
|
<div key={`${d.id}-${j}`} className="space-y-1">
|
||||||
<div className="text-[13px] font-bold">
|
<div className="text-[13px] font-bold">
|
||||||
{(d.type || '').replace(/ /g, '_')} ID: {d.id} | Confidence:{' '}
|
{label} ID: {d.id} | Confidence: {(d.confidence * 100).toFixed(1)}%
|
||||||
{(d.confidence * 100).toFixed(1)}%
|
|
||||||
</div>
|
</div>
|
||||||
{d.bbox && (
|
{d.bbox && (
|
||||||
<div className="text-[12px] text-muted-foreground/80 font-medium">
|
<div className="text-[12px] text-muted-foreground/80 font-medium">
|
||||||
@@ -60,7 +65,8 @@ const DetectionLogs = ({ logs, onSeek }: DetectionLogsProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ export const DETECTION_TYPES: Record<string, DetectionTypeConfig> = {
|
|||||||
listKey: 'good_sign_board_list',
|
listKey: 'good_sign_board_list',
|
||||||
frameCountKey: 'good_sign_board',
|
frameCountKey: 'good_sign_board',
|
||||||
},
|
},
|
||||||
|
drain_issue: {
|
||||||
|
id: 'drain_issue',
|
||||||
|
label: 'Drain Issue',
|
||||||
|
color: '#a855f7',
|
||||||
|
countKey: 'unique_drain_issue',
|
||||||
|
listKey: 'drain_issue_list',
|
||||||
|
frameCountKey: 'drain_issue',
|
||||||
|
},
|
||||||
good_culvert: {
|
good_culvert: {
|
||||||
id: 'good_culvert',
|
id: 'good_culvert',
|
||||||
label: 'Good Culvert',
|
label: 'Good Culvert',
|
||||||
@@ -77,10 +85,34 @@ export const DETECTION_TYPES: Record<string, DetectionTypeConfig> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DETECTION_MODES: Record<string, DetectionModeConfig> = {
|
export const DETECTION_MODES: Record<string, DetectionModeConfig> = {
|
||||||
|
yolo: {
|
||||||
|
id: 'yolo',
|
||||||
|
label: 'YOLO Detection',
|
||||||
|
enabledTypes: [
|
||||||
|
'pothole',
|
||||||
|
'defected_sign_board',
|
||||||
|
'road_crack',
|
||||||
|
'damaged_road_marking',
|
||||||
|
'good_sign_board',
|
||||||
|
'drain_issue',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
yolo_vl: {
|
||||||
|
id: 'yolo_vl',
|
||||||
|
label: 'YOLO-VL Detection',
|
||||||
|
enabledTypes: [
|
||||||
|
'pothole',
|
||||||
|
'defected_sign_board',
|
||||||
|
'road_crack',
|
||||||
|
'damaged_road_marking',
|
||||||
|
'good_sign_board',
|
||||||
|
'drain_issue',
|
||||||
|
],
|
||||||
|
},
|
||||||
'pothole-detection': {
|
'pothole-detection': {
|
||||||
id: 'pothole-detection',
|
id: 'pothole-detection',
|
||||||
label: 'Pothole Detection',
|
label: 'Pothole Detection',
|
||||||
enabledTypes: ['pothole', 'road_crack', 'damaged_road_marking'],
|
enabledTypes: ['pothole', 'road_crack', 'damaged_road_marking', 'drain_issue'],
|
||||||
},
|
},
|
||||||
'sign-board-detection': {
|
'sign-board-detection': {
|
||||||
id: 'sign-board-detection',
|
id: 'sign-board-detection',
|
||||||
@@ -96,6 +128,7 @@ export const DETECTION_MODES: Record<string, DetectionModeConfig> = {
|
|||||||
'road_crack',
|
'road_crack',
|
||||||
'damaged_road_marking',
|
'damaged_road_marking',
|
||||||
'good_sign_board',
|
'good_sign_board',
|
||||||
|
'drain_issue',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
culvert_detection: {
|
culvert_detection: {
|
||||||
|
|||||||
@@ -23,10 +23,8 @@ export const useFrameDetectionMap = (data: DetectionData, detectionType: string)
|
|||||||
.map((d: any) => ({
|
.map((d: any) => ({
|
||||||
...d,
|
...d,
|
||||||
_detType: (d.type || '').toLowerCase(),
|
_detType: (d.type || '').toLowerCase(),
|
||||||
// Map old ID fields for backward compatibility in UI components
|
// Map old ID fields and new ID fields dynamically for backward compatibility
|
||||||
pothole_id: d.type === 'pothole' ? d.detection_id : undefined,
|
[`${(d.type || '').toLowerCase()}_id`]: d.detection_id,
|
||||||
signboard_id: d.type.includes('sign') ? d.detection_id : undefined,
|
|
||||||
culvert_id: d.type.includes('culvert') ? d.detection_id : undefined,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (filteredDetections.length > 0) {
|
if (filteredDetections.length > 0) {
|
||||||
@@ -37,26 +35,30 @@ export const useFrameDetectionMap = (data: DetectionData, detectionType: string)
|
|||||||
let detections: any[] = [];
|
let detections: any[] = [];
|
||||||
|
|
||||||
enabledTypes.forEach((type) => {
|
enabledTypes.forEach((type) => {
|
||||||
const listKey =
|
// Try common plural naming conventions for legacy support
|
||||||
type.id === 'pothole'
|
const possibleKeys = [
|
||||||
? 'potholes'
|
`${type.id}s`,
|
||||||
: type.id.includes('sign')
|
type.id.endsWith('y') ? `${type.id.slice(0, -1)}ies` : `${type.id}s`,
|
||||||
? 'signboards'
|
type.listKey.replace('_list', 's'),
|
||||||
: type.id === 'good_culvert'
|
type.listKey,
|
||||||
? 'good_culverts'
|
];
|
||||||
: type.id === 'defective_culvert'
|
|
||||||
? 'defective_culverts'
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (listKey && (frameData as any)[listKey]) {
|
for (const listKey of possibleKeys) {
|
||||||
|
if ((frameData as any)[listKey]) {
|
||||||
detections = [
|
detections = [
|
||||||
...detections,
|
...detections,
|
||||||
...(frameData as any)[listKey].map((item: any) => ({
|
...(frameData as any)[listKey].map((item: any) => ({
|
||||||
...item,
|
...item,
|
||||||
_detType: type.id,
|
_detType: type.id,
|
||||||
type: type.id,
|
type: type.id,
|
||||||
|
[`${type.id.toLowerCase()}_id`]:
|
||||||
|
(item as any).detection_id ??
|
||||||
|
(item as any)[`${type.id.toLowerCase()}_id`] ??
|
||||||
|
item.id,
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export type DetectionData = {
|
|||||||
unique_road_crack?: number;
|
unique_road_crack?: number;
|
||||||
unique_damaged_road_marking?: number;
|
unique_damaged_road_marking?: number;
|
||||||
unique_good_sign_board?: number;
|
unique_good_sign_board?: number;
|
||||||
|
unique_drain_issue?: number;
|
||||||
unique_good_culvert?: number;
|
unique_good_culvert?: number;
|
||||||
unique_defective_culvert?: number;
|
unique_defective_culvert?: number;
|
||||||
total_road_damage?: number;
|
total_road_damage?: number;
|
||||||
@@ -31,6 +32,7 @@ export type DetectionData = {
|
|||||||
road_crack_list?: Array<DetectionListItem>;
|
road_crack_list?: Array<DetectionListItem>;
|
||||||
damaged_road_marking_list?: Array<DetectionListItem>;
|
damaged_road_marking_list?: Array<DetectionListItem>;
|
||||||
good_sign_board_list?: Array<DetectionListItem>;
|
good_sign_board_list?: Array<DetectionListItem>;
|
||||||
|
drain_issue_list?: Array<DetectionListItem>;
|
||||||
good_culvert_list?: Array<DetectionListItem>;
|
good_culvert_list?: Array<DetectionListItem>;
|
||||||
defective_culvert_list?: Array<DetectionListItem>;
|
defective_culvert_list?: Array<DetectionListItem>;
|
||||||
signboard_list?: Array<DetectionListItem>; // Keeping for backward compatibility
|
signboard_list?: Array<DetectionListItem>; // Keeping for backward compatibility
|
||||||
@@ -64,6 +66,7 @@ export type DetectionData = {
|
|||||||
road_crack: number;
|
road_crack: number;
|
||||||
damaged_road_marking: number;
|
damaged_road_marking: number;
|
||||||
good_sign_board: number;
|
good_sign_board: number;
|
||||||
|
drain_issue: number;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
}>;
|
}>;
|
||||||
|
|||||||
@@ -17,7 +17,12 @@ export type DetectionType =
|
|||||||
| 'pothole-detection'
|
| 'pothole-detection'
|
||||||
| 'sign-board-detection'
|
| 'sign-board-detection'
|
||||||
| 'pot-sign-detection'
|
| 'pot-sign-detection'
|
||||||
| 'culvert_detection';
|
| 'culvert_detection'
|
||||||
|
| 'yolo'
|
||||||
|
| 'yolo_vl'
|
||||||
|
| 'sam3'
|
||||||
|
| 'yoloe'
|
||||||
|
| 'yoloe_trained_vl';
|
||||||
|
|
||||||
export interface DetectionListItem {
|
export interface DetectionListItem {
|
||||||
detection_id?: number;
|
detection_id?: number;
|
||||||
@@ -38,6 +43,7 @@ export interface DetectionCounts {
|
|||||||
road_crack: number;
|
road_crack: number;
|
||||||
damaged_road_marking: number;
|
damaged_road_marking: number;
|
||||||
good_sign_board: number;
|
good_sign_board: number;
|
||||||
|
drain_issue: number;
|
||||||
good_culvert?: number;
|
good_culvert?: number;
|
||||||
defective_culvert?: number;
|
defective_culvert?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export interface Video {
|
|||||||
unique_road_crack?: number;
|
unique_road_crack?: number;
|
||||||
unique_damaged_road_marking?: number;
|
unique_damaged_road_marking?: number;
|
||||||
unique_good_sign_board?: number;
|
unique_good_sign_board?: number;
|
||||||
|
unique_drain_issue?: number;
|
||||||
total_road_damage?: number;
|
total_road_damage?: number;
|
||||||
total_detections?: number;
|
total_detections?: number;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user