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