From 9fa2b002bafd4d3cc2ceecabcdcc5bc2d6c7f3c8 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 2 May 2025 13:46:30 -0500 Subject: [PATCH] Attributes for object masks (#18003) * Add ability to select attributes for object masks This feature already works correctly on the backend. This just adds the ability to select attributes through the UI. * Add clarity to LPR docs about timestamps * fix sub label score and add info popover --- .../license_plate_recognition.md | 8 +++++++ web/public/locales/en/views/faceLibrary.json | 3 ++- .../settings/ObjectMaskEditPane.tsx | 19 +++------------ web/src/pages/FaceLibrary.tsx | 23 +++++++++++++++++-- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/docs/docs/configuration/license_plate_recognition.md b/docs/docs/configuration/license_plate_recognition.md index 72cc12756..a88594cb6 100644 --- a/docs/docs/configuration/license_plate_recognition.md +++ b/docs/docs/configuration/license_plate_recognition.md @@ -358,3 +358,11 @@ LPR's performance impact depends on your hardware. Ensure you have at least 4GB The YOLOv9 license plate detector model will run (and the metric will appear) if you've enabled LPR but haven't defined `license_plate` as an object to track, either at the global or camera level. If you are detecting `car` on cameras where you don't want to run LPR, make sure you disable LPR it at the camera level. And if you do want to run LPR on those cameras, make sure you define `license_plate` as an object to track. + +### It looks like Frigate picked up my camera's timestamp as the license plate. How can I prevent this? + +This could happen if cars travel close to your camera's timestamp. You could either move the timestamp through your camera's firmware, or apply a mask to it in Frigate. + +If you are using a model that natively detects `license_plate`, add an _object mask_ of type `license_plate` and a _motion mask_ over your timestamp. + +If you are using dedicated LPR camera mode, only a _motion mask_ over your timestamp is required. diff --git a/web/public/locales/en/views/faceLibrary.json b/web/public/locales/en/views/faceLibrary.json index 08ba2c673..289ff40cf 100644 --- a/web/public/locales/en/views/faceLibrary.json +++ b/web/public/locales/en/views/faceLibrary.json @@ -5,7 +5,8 @@ }, "details": { "person": "Person", - "confidence": "Confidence", + "subLabelScore": "Sub Label Score", + "scoreInfo": "The sub label score is the weighted score for all of the recognized face confidences, so this may differ from the score shown on the snapshot.", "face": "Face Details", "faceDesc": "Details for the face and associated object", "timestamp": "Timestamp" diff --git a/web/src/components/settings/ObjectMaskEditPane.tsx b/web/src/components/settings/ObjectMaskEditPane.tsx index 9a3c3295f..0ce89fb94 100644 --- a/web/src/components/settings/ObjectMaskEditPane.tsx +++ b/web/src/components/settings/ObjectMaskEditPane.tsx @@ -37,7 +37,6 @@ import axios from "axios"; import { toast } from "sonner"; import { Toaster } from "../ui/sonner"; import ActivityIndicator from "../indicators/activity-indicator"; -import { getAttributeLabels } from "@/utils/iconUtil"; import { useTranslation } from "react-i18next"; type ObjectMaskEditPaneProps = { @@ -401,14 +400,6 @@ export function ZoneObjectSelector({ camera }: ZoneObjectSelectorProps) { const { t } = useTranslation(["views/settings"]); const { data: config } = useSWR("config"); - const attributeLabels = useMemo(() => { - if (!config) { - return []; - } - - return getAttributeLabels(config); - }, [config]); - const cameraConfig = useMemo(() => { if (config && camera) { return config.cameras[camera]; @@ -424,20 +415,16 @@ export function ZoneObjectSelector({ camera }: ZoneObjectSelectorProps) { Object.values(config.cameras).forEach((camera) => { camera.objects.track.forEach((label) => { - if (!attributeLabels.includes(label)) { - labels.add(label); - } + labels.add(label); }); }); cameraConfig.objects.track.forEach((label) => { - if (!attributeLabels.includes(label)) { - labels.add(label); - } + labels.add(label); }); return [...labels].sort(); - }, [config, cameraConfig, attributeLabels]); + }, [config, cameraConfig]); return ( <> diff --git a/web/src/pages/FaceLibrary.tsx b/web/src/pages/FaceLibrary.tsx index 6586e1c5c..46c90214b 100644 --- a/web/src/pages/FaceLibrary.tsx +++ b/web/src/pages/FaceLibrary.tsx @@ -21,6 +21,11 @@ import { DropdownMenuTrigger, DropdownMenuSeparator, } from "@/components/ui/dropdown-menu"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; import { Toaster } from "@/components/ui/sonner"; import { Tooltip, @@ -42,6 +47,7 @@ import { isDesktop, isMobile } from "react-device-detect"; import { useTranslation } from "react-i18next"; import { LuImagePlus, + LuInfo, LuPencil, LuRefreshCw, LuScanFace, @@ -647,10 +653,23 @@ function TrainingGrid({ {selectedEvent?.data.sub_label_score && (
- {t("details.confidence")} +
+ {t("details.subLabelScore")} + + +
+ + Info +
+
+ + {t("details.scoreInfo")} + +
+
- {Math.round(selectedEvent?.data?.sub_label_score || 0) * 100}% + {Math.round((selectedEvent?.data?.sub_label_score || 0) * 100)}%
)}