import { useCallback, useEffect, useMemo } from "react"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import AutoUpdatingCameraImage from "@/components/camera/AutoUpdatingCameraImage"; import { CameraConfig, FrigateConfig } from "@/types/frigateConfig"; import { Toaster } from "@/components/ui/sonner"; import { Slider } from "@/components/ui/slider"; import { Label } from "@/components/ui/label"; import useSWR from "swr"; import Heading from "@/components/ui/heading"; import { Switch } from "@/components/ui/switch"; import { usePersistence } from "@/hooks/use-persistence"; import { Skeleton } from "@/components/ui/skeleton"; import { useCameraActivity } from "@/hooks/use-camera-activity"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { ObjectType } from "@/types/ws"; import useDeepMemo from "@/hooks/use-deep-memo"; import { Card } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { getIconForLabel } from "@/utils/iconUtil"; import { capitalizeFirstLetter } from "@/utils/stringUtil"; import { LuExternalLink, LuInfo } from "react-icons/lu"; import { Link } from "react-router-dom"; type ObjectSettingsViewProps = { selectedCamera?: string; }; type Options = { [key: string]: boolean | number }; const emptyObject = Object.freeze({}); export default function ObjectSettingsView({ selectedCamera, }: ObjectSettingsViewProps) { const { data: config } = useSWR("config"); const DEBUG_OPTIONS = [ { param: "bbox", title: "Bounding boxes", description: "Show bounding boxes around tracked objects", info: ( <>

Object Bounding Box Colors

), }, { param: "timestamp", title: "Timestamp", description: "Overlay a timestamp on the image", }, { param: "zones", title: "Zones", description: "Show an outline of any defined zones", }, { param: "mask", title: "Motion masks", description: "Show motion mask polygons", }, { param: "motion", title: "Motion boxes", description: "Show boxes around areas where motion is detected", info: ( <>

Motion Boxes

Red boxes will be overlaid on areas of the frame where motion is currently being detected

), }, { param: "regions", title: "Regions", description: "Show a box of the region of interest sent to the object detector", info: ( <>

Region Boxes

Bright green boxes will be overlaid on areas of interest in the frame that are being sent to the object detector.

), }, ]; const [options, setOptions, optionsLoaded] = usePersistence( `${selectedCamera}-feed`, emptyObject, ); const handleSetOption = useCallback( (id: string, value: boolean | number) => { const newOptions = { ...options, [id]: value }; setOptions(newOptions); }, [options, setOptions], ); const cameraConfig = useMemo(() => { if (config && selectedCamera) { return config.cameras[selectedCamera]; } }, [config, selectedCamera]); const { objects } = useCameraActivity(cameraConfig ?? ({} as CameraConfig)); const memoizedObjects = useDeepMemo(objects); const searchParams = useMemo(() => { if (!optionsLoaded) { return new URLSearchParams(); } const params = new URLSearchParams( Object.keys(options || {}).reduce((memo, key) => { switch (typeof options[key]) { case "boolean": { //@ts-expect-error we know this is correct memo.push([key, options[key] === true ? "1" : "0"]); break; } case "number": { memo.push([key, options[key]]); break; } } return memo; }, []), ); return params; }, [options, optionsLoaded]); useEffect(() => { document.title = "Object Settings - Frigate"; }, []); if (!cameraConfig) { return ; } return (
Debug

Frigate uses your detectors{" "} {config ? "(" + Object.keys(config?.detectors) .map((detector) => capitalizeFirstLetter(detector)) .join(",") + ")" : ""}{" "} to detect objects in your camera's video stream.

Debugging view shows a real-time view of tracked objects and their statistics. The object list shows a time-delayed summary of detected objects.

{config?.cameras[cameraConfig.name]?.webui_url && (
Open {capitalizeFirstLetter(cameraConfig.name)}'s Web UI
)} Debugging Object List
{DEBUG_OPTIONS.map(({ param, title, description, info }) => (
{info && (
Info
{info}
)}
{description}
{ handleSetOption(param, isChecked); }} />
))}
Info

Edge Detection

Bright red lines will be overlaid on areas of focus. Use the slider below to adjust the threshold if too many or too little lines are visible.

Highlights edges to help set the camera focus
{ handleSetOption("edges", isChecked); }} />
{ handleSetOption("ethreshold", value[0]); }} />
{options["ethreshold"]}
{ObjectList(memoizedObjects)}
{cameraConfig ? (
) : ( )}
); } function ObjectList(objects?: ObjectType[]) { const { data: config } = useSWR("config"); const colormap = useMemo(() => { if (!config) { return; } return config.model?.colormap; }, [config]); const getColorForObjectName = useCallback( (objectName: string) => { return colormap && colormap[objectName] ? `rgb(${colormap[objectName][2]}, ${colormap[objectName][1]}, ${colormap[objectName][0]})` : "rgb(128, 128, 128)"; }, [colormap], ); return (
{objects && objects.length > 0 ? ( objects.map((obj) => { return (
{getIconForLabel(obj.label, "size-5 text-white")}
{capitalizeFirstLetter(obj.label.replaceAll("_", " "))}

Score

{obj.score ? (obj.score * 100).toFixed(1).toString() : "-"} %

Ratio

{obj.ratio ? obj.ratio.toFixed(2).toString() : "-"}

Area

{obj.area ? obj.area.toString() : "-"}
); }) ) : (
No objects
)}
); }