diff --git a/web/src/pages/FaceLibrary.tsx b/web/src/pages/FaceLibrary.tsx index 696691997..a3c3a7bf4 100644 --- a/web/src/pages/FaceLibrary.tsx +++ b/web/src/pages/FaceLibrary.tsx @@ -32,6 +32,7 @@ import { useFormattedTimestamp } from "@/hooks/use-date-utils"; import useKeyboardListener from "@/hooks/use-keyboard-listener"; import useOptimisticState from "@/hooks/use-optimistic-state"; import { cn } from "@/lib/utils"; +import { Event } from "@/types/event"; import { FaceLibraryData, RecognizedFaceData } from "@/types/face"; import { FaceRecognitionConfig, FrigateConfig } from "@/types/frigateConfig"; import axios from "axios"; @@ -395,6 +396,41 @@ function TrainingGrid({ // face data + const faceGroups = useMemo(() => { + const groups: { [eventId: string]: RecognizedFaceData[] } = {}; + + attemptImages.forEach((image) => { + const parts = image.split("-"); + const data = { + filename: image, + timestamp: Number.parseFloat(parts[0]), + eventId: `${parts[0]}-${parts[1]}`, + name: parts[2], + score: Number.parseFloat(parts[3]), + }; + + if (groups[data.eventId]) { + groups[data.eventId].push(data); + } else { + groups[data.eventId] = [data]; + } + }); + + return groups; + }, [attemptImages]); + + const eventIdsQuery = useMemo( + () => Object.keys(faceGroups).join(","), + [faceGroups], + ); + + const { data: events } = useSWR([ + "event_ids", + { ids: eventIdsQuery }, + ]); + + // selection + const [selectedEvent, setSelectedEvent] = useState(); const formattedDate = useFormattedTimestamp( @@ -405,6 +441,10 @@ function TrainingGrid({ config?.ui.timezone, ); + if (!events) { + return null; + } + return ( <>
- {attemptImages.map((image: string) => ( - { - if (meta) { - onClickFace(image, meta); - } else { - setSelectedEvent(data); - } - }} - onRefresh={onRefresh} - /> - ))} + {Object.entries(faceGroups).map(([key, group]) => { + const event = events.find((ev) => ev.id == key); + + if (!event) { + return null; + } + + return ( +
+
+ {event.label} ({Math.round(event.data.top_score * 100)}%){" "} + {event.sub_label && + `${event.sub_label} (${Math.round((event.data.sub_label_score || 0) * 100)}%)`} +
+
+ {group.map((data: RecognizedFaceData) => ( + { + if (meta) { + onClickFace(data.filename, meta); + } else { + setSelectedEvent(data); + } + }} + onRefresh={onRefresh} + /> + ))} +
+
+ ); + })}
); } type FaceAttemptProps = { - image: string; + data: RecognizedFaceData; faceNames: string[]; recognitionConfig: FaceRecognitionConfig; selected: boolean; @@ -477,7 +536,7 @@ type FaceAttemptProps = { onRefresh: () => void; }; function FaceAttempt({ - image, + data, faceNames, recognitionConfig, selected, @@ -485,16 +544,6 @@ function FaceAttempt({ onRefresh, }: FaceAttemptProps) { const { t } = useTranslation(["views/faceLibrary"]); - const data = useMemo(() => { - const parts = image.split("-"); - - return { - timestamp: Number.parseFloat(parts[0]), - eventId: `${parts[0]}-${parts[1]}`, - name: parts[2], - score: Number.parseFloat(parts[3]), - }; - }, [image]); const scoreStatus = useMemo(() => { if (data.score >= recognitionConfig.recognition_threshold) { @@ -519,7 +568,9 @@ function FaceAttempt({ const onTrainAttempt = useCallback( (trainName: string) => { axios - .post(`/faces/train/${trainName}/classify`, { training_file: image }) + .post(`/faces/train/${trainName}/classify`, { + training_file: data.eventId, + }) .then((resp) => { if (resp.status == 200) { toast.success(t("toast.success.trainedFace"), { @@ -538,12 +589,12 @@ function FaceAttempt({ }); }); }, - [image, onRefresh, t], + [data, onRefresh, t], ); const onReprocess = useCallback(() => { axios - .post(`/faces/reprocess`, { training_file: image }) + .post(`/faces/reprocess`, { training_file: data.filename }) .then((resp) => { if (resp.status == 200) { toast.success(t("toast.success.updatedFaceScore"), { @@ -561,7 +612,7 @@ function FaceAttempt({ position: "top-center", }); }); - }, [image, onRefresh, t]); + }, [data, onRefresh, t]); return (
onClick(data, e.metaKey || e.ctrlKey)} />
diff --git a/web/src/types/face.ts b/web/src/types/face.ts index 37521340f..dc481b64f 100644 --- a/web/src/types/face.ts +++ b/web/src/types/face.ts @@ -3,6 +3,7 @@ export type FaceLibraryData = { }; export type RecognizedFaceData = { + filename: string; timestamp: number; eventId: string; name: string;