diff --git a/docs/docs/configuration/object_filters.md b/docs/docs/configuration/object_filters.md index dfea51804..8a492960d 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 6a62da2df..d82f17cb7 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 f5c33d1e6..4fda92afd 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 541b90e7b..43db9bda4 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 00d09ec4f..026c0a1b6 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 0de067406..273952fb7 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];