diff --git a/docs/docs/configuration/object_filters.md b/docs/docs/configuration/object_filters.md index dfea518040..8a492960df 100644 --- a/docs/docs/configuration/object_filters.md +++ b/docs/docs/configuration/object_filters.md @@ -24,6 +24,12 @@ For object filters, any single detection below `min_score` will be ignored as a In frame 2, the score is below the `min_score` value, so Frigate ignores it and it becomes a 0.0. The computed score is the median of the score history (padding to at least 3 values), and only when that computed score crosses the `threshold` is the object marked as a true positive. That happens in frame 4 in the example. +The **top score** is the highest computed score the tracked object has ever reached during its lifetime. Because the computed score rises and falls as new frames come in, the top score can be thought of as the peak confidence Frigate had in the object. In Frigate's UI (such as the Tracking Details pane in Explore), you may see all three values: + +- **Score** — the raw detector score for that single frame. +- **Computed Score** — the median of the most recent score history at that moment. This is the value compared against `threshold`. +- **Top Score** — the highest computed score reached so far for the tracked object. + ### Minimum Score Any detection below `min_score` will be immediately thrown out and never tracked because it is considered a false positive. If `min_score` is too low then false positives may be detected and tracked which can confuse the object tracker and may lead to wasted resources. If `min_score` is too high then lower scoring true positives like objects that are further away or partially occluded may be thrown out which can also confuse the tracker and cause valid tracked objects to be lost or disjointed. diff --git a/frigate/timeline.py b/frigate/timeline.py index 6a62da2df9..d82f17cb7d 100644 --- a/frigate/timeline.py +++ b/frigate/timeline.py @@ -116,6 +116,8 @@ class TimelineProcessor(threading.Thread): ), "attribute": "", "score": event_data["score"], + "computed_score": event_data.get("computed_score"), + "top_score": event_data.get("top_score"), }, } diff --git a/frigate/track/tracked_object.py b/frigate/track/tracked_object.py index f5c33d1e6c..4fda92afd0 100644 --- a/frigate/track/tracked_object.py +++ b/frigate/track/tracked_object.py @@ -400,6 +400,7 @@ class TrackedObject: "start_time": self.obj_data["start_time"], "end_time": self.obj_data.get("end_time", None), "score": self.obj_data["score"], + "computed_score": self.computed_score, "box": self.obj_data["box"], "area": self.obj_data["area"], "ratio": self.obj_data["ratio"], diff --git a/web/public/locales/en/views/explore.json b/web/public/locales/en/views/explore.json index 541b90e7b5..43db9bda48 100644 --- a/web/public/locales/en/views/explore.json +++ b/web/public/locales/en/views/explore.json @@ -62,7 +62,10 @@ "zones": "Zones", "ratio": "Ratio", "area": "Area", - "score": "Score" + "score": "Score", + "computedScore": "Computed Score", + "topScore": "Top Score", + "toggleAdvancedScores": "Toggle advanced scores" } }, "annotationSettings": { diff --git a/web/src/components/overlay/detail/TrackingDetails.tsx b/web/src/components/overlay/detail/TrackingDetails.tsx index 00d09ec4ff..026c0a1b62 100644 --- a/web/src/components/overlay/detail/TrackingDetails.tsx +++ b/web/src/components/overlay/detail/TrackingDetails.tsx @@ -9,7 +9,12 @@ import { FrigateConfig } from "@/types/frigateConfig"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import { use24HourTime } from "@/hooks/use-date-utils"; import { getIconForLabel } from "@/utils/iconUtil"; -import { LuCircle, LuFolderX } from "react-icons/lu"; +import { + LuChevronDown, + LuChevronRight, + LuCircle, + LuFolderX, +} from "react-icons/lu"; import { cn } from "@/lib/utils"; import HlsVideoPlayer from "@/components/player/HlsVideoPlayer"; import { baseUrl } from "@/api/baseUrl"; @@ -899,6 +904,7 @@ function LifecycleIconRow({ const { t } = useTranslation(["views/explore", "components/player"]); const { data: config } = useSWR("config"); const [isOpen, setIsOpen] = useState(false); + const [showAdvancedScores, setShowAdvancedScores] = useState(false); const navigate = useNavigate(); const isAdmin = useIsAdmin(); @@ -993,12 +999,31 @@ function LifecycleIconRow({ [item.data.box], ); - const score = useMemo(() => { - if (item.data.score !== undefined) { - return (item.data.score * 100).toFixed(0) + "%"; - } - return "N/A"; - }, [item.data.score]); + const currentScore = useMemo( + () => + item.data.score !== undefined + ? (item.data.score * 100).toFixed(0) + "%" + : null, + [item.data.score], + ); + const computedScore = useMemo( + () => + item.data.computed_score !== undefined && + item.data.computed_score !== null && + item.data.computed_score > 0 + ? (item.data.computed_score * 100).toFixed(0) + "%" + : null, + [item.data.computed_score], + ); + const topScore = useMemo( + () => + item.data.top_score !== undefined && + item.data.top_score !== null && + item.data.top_score > 0 + ? (item.data.top_score * 100).toFixed(0) + "%" + : null, + [item.data.top_score], + ); return (
{t("trackingDetails.lifecycleItemDesc.header.score")} - {score} + + {currentScore ?? "N/A"} + + {(computedScore || topScore) && ( + + )}
+ {showAdvancedScores && computedScore && ( +
+ + {t( + "trackingDetails.lifecycleItemDesc.header.computedScore", + )} + + + {computedScore} + +
+ )} + {showAdvancedScores && topScore && ( +
+ + {t("trackingDetails.lifecycleItemDesc.header.topScore")} + + {topScore} +
+ )}
{t("trackingDetails.lifecycleItemDesc.header.ratio")} diff --git a/web/src/types/timeline.ts b/web/src/types/timeline.ts index 0de0674062..273952fb7c 100644 --- a/web/src/types/timeline.ts +++ b/web/src/types/timeline.ts @@ -17,6 +17,8 @@ export type TrackingDetailsSequence = { camera: string; label: string; score: number; + computed_score?: number; + top_score?: number; sub_label: string; box?: [number, number, number, number]; region: [number, number, number, number];