motion tuner fixes and tweaks

This commit is contained in:
Josh Hawkins 2024-04-17 21:27:04 -05:00
parent 36a3a0a478
commit 1ba4502b07
5 changed files with 218 additions and 153 deletions

View File

@ -248,7 +248,10 @@ export default function MotionMaskEditPane({
<Separator className="my-3 bg-secondary" /> <Separator className="my-3 bg-secondary" />
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6 flex flex-col flex-1"
>
<FormField <FormField
control={form.control} control={form.control}
name="polygon.name" name="polygon.name"
@ -267,25 +270,27 @@ export default function MotionMaskEditPane({
</FormItem> </FormItem>
)} )}
/> />
<div className="flex flex-row gap-2 pt-5"> <div className="flex flex-col flex-1 justify-end">
<Button className="flex flex-1" onClick={onCancel}> <div className="flex flex-row gap-2 pt-5">
Cancel <Button className="flex flex-1" onClick={onCancel}>
</Button> Cancel
<Button </Button>
variant="select" <Button
disabled={isLoading} variant="select"
className="flex flex-1" disabled={isLoading}
type="submit" className="flex flex-1"
> type="submit"
{isLoading ? ( >
<div className="flex flex-row items-center gap-2"> {isLoading ? (
<ActivityIndicator /> <div className="flex flex-row items-center gap-2">
<span>Saving...</span> <ActivityIndicator />
</div> <span>Saving...</span>
) : ( </div>
"Save" ) : (
)} "Save"
</Button> )}
</Button>
</div>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -27,6 +27,9 @@ import { Button } from "../ui/button";
import { Switch } from "../ui/switch"; import { Switch } from "../ui/switch";
import { Toaster } from "@/components/ui/sonner"; import { Toaster } from "@/components/ui/sonner";
import { toast } from "sonner"; import { toast } from "sonner";
import { Separator } from "../ui/separator";
import { Link } from "react-router-dom";
import { LuExternalLink } from "react-icons/lu";
type MotionTunerProps = { type MotionTunerProps = {
selectedCamera: string; selectedCamera: string;
@ -45,9 +48,6 @@ export default function MotionTuner({ selectedCamera }: MotionTunerProps) {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false); const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
// const [selectedCamera, setSelectedCamera] = useState(cameras[0]?.name);
const [nextSelectedCamera, setNextSelectedCamera] = useState("");
const { send: sendMotionThreshold } = useMotionThreshold(selectedCamera); const { send: sendMotionThreshold } = useMotionThreshold(selectedCamera);
const { send: sendMotionContourArea } = useMotionContourArea(selectedCamera); const { send: sendMotionContourArea } = useMotionContourArea(selectedCamera);
const { send: sendImproveContrast } = useImproveContrast(selectedCamera); const { send: sendImproveContrast } = useImproveContrast(selectedCamera);
@ -58,6 +58,12 @@ export default function MotionTuner({ selectedCamera }: MotionTunerProps) {
improve_contrast: undefined, improve_contrast: undefined,
}); });
const [origMotionSettings, setOrigMotionSettings] = useState<MotionSettings>({
threshold: undefined,
contour_area: undefined,
improve_contrast: undefined,
});
const cameraConfig = useMemo(() => { const cameraConfig = useMemo(() => {
if (config && selectedCamera) { if (config && selectedCamera) {
return config.cameras[selectedCamera]; return config.cameras[selectedCamera];
@ -71,6 +77,11 @@ export default function MotionTuner({ selectedCamera }: MotionTunerProps) {
contour_area: cameraConfig.motion.contour_area, contour_area: cameraConfig.motion.contour_area,
improve_contrast: cameraConfig.motion.improve_contrast, improve_contrast: cameraConfig.motion.improve_contrast,
}); });
setOrigMotionSettings({
threshold: cameraConfig.motion.threshold,
contour_area: cameraConfig.motion.contour_area,
improve_contrast: cameraConfig.motion.improve_contrast,
});
} }
}, [cameraConfig]); }, [cameraConfig]);
@ -150,15 +161,16 @@ export default function MotionTuner({ selectedCamera }: MotionTunerProps) {
selectedCamera, selectedCamera,
]); ]);
const onCancel = useCallback(() => {}, []); const onCancel = useCallback(() => {
setMotionSettings(origMotionSettings);
setChangedValue(false);
}, [origMotionSettings]);
const handleDialog = useCallback( const handleDialog = useCallback(
(save: boolean) => { (save: boolean) => {
if (save) { if (save) {
saveToConfig(); saveToConfig();
} }
// setSelectedCamera(nextSelectedCamera);
setNextSelectedCamera("");
setConfirmationDialogOpen(false); setConfirmationDialogOpen(false);
setChangedValue(false); setChangedValue(false);
}, },
@ -176,72 +188,117 @@ export default function MotionTuner({ selectedCamera }: MotionTunerProps) {
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
Motion Detection Tuner Motion Detection Tuner
</Heading> </Heading>
<div className="text-sm text-muted-foreground my-3 space-y-3">
<p>
Frigate uses motion detection as a first line check to see if there
is anything happening in the frame worth checking with object
detection.
</p>
<div className="flex flex-col w-full space-y-10"> <div className="flex items-center text-primary">
<div className="flex flex-row mb-5"> <Link
<Slider to="https://docs.frigate.video/configuration/motion_detection"
id="motion-threshold" target="_blank"
className="w-[300px]" rel="noopener noreferrer"
disabled={motionSettings.threshold === undefined} className="inline"
value={[motionSettings.threshold ?? 0]} >
min={10} Read the Motion Tuning Guide{" "}
max={80} <LuExternalLink className="size-3 ml-2 inline-flex" />
step={1} </Link>
onValueChange={(value) => {
handleMotionConfigChange({ threshold: value[0] });
}}
/>
<Label htmlFor="motion-threshold" className="px-2">
Threshold: {motionSettings.threshold}
</Label>
</div> </div>
<div className="flex flex-row"> </div>
<Slider <Separator className="flex my-2 bg-secondary" />
id="motion-contour-area" <div className="flex flex-col w-full space-y-6">
className="w-[300px]" <div className="mt-2 space-y-6">
disabled={motionSettings.contour_area === undefined} <div className="space-y-0.5">
value={[motionSettings.contour_area ?? 0]} <Label htmlFor="motion-threshold" className="text-md">
min={10} Threshold
max={200} </Label>
step={5} <div className="text-sm text-muted-foreground my-2">
onValueChange={(value) => { <p>
handleMotionConfigChange({ contour_area: value[0] }); The threshold value dictates how much of a change in a pixel's
}} luminance is required to be considered motion.{" "}
/> <em>Default: 30</em>
<Label htmlFor="motion-contour-area" className="px-2"> </p>
Contour Area: {motionSettings.contour_area} </div>
</Label> </div>
<div className="flex flex-row justify-between">
<Slider
id="motion-threshold"
className="w-full"
disabled={motionSettings.threshold === undefined}
value={[motionSettings.threshold ?? 0]}
min={5}
max={80}
step={1}
onValueChange={(value) => {
handleMotionConfigChange({ threshold: value[0] });
}}
/>
<div className="text-lg ml-6 mr-2 flex align-center">
{motionSettings.threshold}
</div>
</div>
</div> </div>
<div className="flex flex-row"> <div className="mt-2 space-y-6">
<div className="space-y-0.5">
<Label htmlFor="motion-threshold" className="text-md">
Contour Area
</Label>
<div className="text-sm text-muted-foreground my-2">
<p>
The contour area value is used to decide which groups of
changed pixels qualify as motion. <em>Default: 10</em>
</p>
</div>
</div>
<div className="flex flex-row justify-between">
<Slider
id="motion-contour-area"
className="w-full"
disabled={motionSettings.contour_area === undefined}
value={[motionSettings.contour_area ?? 0]}
min={5}
max={100}
step={1}
onValueChange={(value) => {
handleMotionConfigChange({ contour_area: value[0] });
}}
/>
<div className="text-lg ml-6 mr-2 flex align-center">
{motionSettings.contour_area}
</div>
</div>
</div>
<Separator className="flex my-2 bg-secondary" />
<div className="flex flex-row items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="improve-contrast">Improve Contrast</Label>
<div className="text-sm text-muted-foreground">
Improve contrast for darker scenes. <em>Default: ON</em>
</div>
</div>
<Switch <Switch
id="improve-contrast" id="improve-contrast"
className="ml-3"
disabled={motionSettings.improve_contrast === undefined} disabled={motionSettings.improve_contrast === undefined}
checked={motionSettings.improve_contrast === true} checked={motionSettings.improve_contrast === true}
onCheckedChange={(isChecked) => { onCheckedChange={(isChecked) => {
handleMotionConfigChange({ improve_contrast: isChecked }); handleMotionConfigChange({ improve_contrast: isChecked });
}} }}
/> />
<Label htmlFor="improve-contrast">Improve Contrast</Label>
</div> </div>
<div className="flex flex-row items-center justify-between rounded-lg border p-4"> </div>
<div className="space-y-0.5"> <div className="flex flex-col flex-1 justify-end">
<div className="text-base">Improve Contrast</div>
<div>Improve contrast for darker scenes.</div>
</div>
<div>
<div checked={field.value} onCheckedChange={field.onChange} />
</div>
</div>
<div className="flex flex-row gap-2 pt-5"> <div className="flex flex-row gap-2 pt-5">
<Button className="flex flex-1" onClick={onCancel}> <Button className="flex flex-1" onClick={onCancel}>
Cancel Reset
</Button> </Button>
<Button <Button
variant="select" variant="select"
disabled={isLoading} disabled={!changedValue || isLoading}
className="flex flex-1" className="flex flex-1"
type="submit" onClick={saveToConfig}
> >
{isLoading ? ( {isLoading ? (
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">

View File

@ -325,70 +325,77 @@ export default function ObjectMaskEditPane({
<Separator className="my-3 bg-secondary" /> <Separator className="my-3 bg-secondary" />
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <form
<FormField onSubmit={form.handleSubmit(onSubmit)}
control={form.control} className="space-y-6 flex flex-col flex-1"
name="polygon.name" >
render={() => ( <div>
<FormItem> <FormField
<FormMessage /> control={form.control}
</FormItem> name="polygon.name"
)} render={() => (
/> <FormItem>
<FormField <FormMessage />
control={form.control} </FormItem>
name="objects"
render={({ field }) => (
<FormItem>
<FormLabel>Objects</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select an object type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<ZoneObjectSelector camera={polygon.camera} />
</SelectContent>
</Select>
<FormDescription>
The object type that that applies to this object mask.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="polygon.isFinished"
render={() => (
<FormItem>
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-row gap-2 pt-5">
<Button className="flex flex-1" onClick={onCancel}>
Cancel
</Button>
<Button
variant="select"
disabled={isLoading}
className="flex flex-1"
type="submit"
>
{isLoading ? (
<div className="flex flex-row items-center gap-2">
<ActivityIndicator />
<span>Saving...</span>
</div>
) : (
"Save"
)} )}
</Button> />
<FormField
control={form.control}
name="objects"
render={({ field }) => (
<FormItem>
<FormLabel>Objects</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select an object type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<ZoneObjectSelector camera={polygon.camera} />
</SelectContent>
</Select>
<FormDescription>
The object type that that applies to this object mask.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="polygon.isFinished"
render={() => (
<FormItem>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex flex-col flex-1 justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button className="flex flex-1" onClick={onCancel}>
Cancel
</Button>
<Button
variant="select"
disabled={isLoading}
className="flex flex-1"
type="submit"
>
{isLoading ? (
<div className="flex flex-row items-center gap-2">
<ActivityIndicator />
<span>Saving...</span>
</div>
) : (
"Save"
)}
</Button>
</div>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -394,7 +394,7 @@ export default function ZoneEditPane({
<Separator className="my-3 bg-secondary" /> <Separator className="my-3 bg-secondary" />
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <form onSubmit={form.handleSubmit(onSubmit)} className="mt-2 space-y-6">
<FormField <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -416,9 +416,7 @@ export default function ZoneEditPane({
</FormItem> </FormItem>
)} )}
/> />
<div className="flex my-2"> <Separator className="flex my-2 bg-secondary" />
<Separator className="bg-secondary" />
</div>
<FormField <FormField
control={form.control} control={form.control}
name="inertia" name="inertia"
@ -440,9 +438,7 @@ export default function ZoneEditPane({
</FormItem> </FormItem>
)} )}
/> />
<div className="flex my-2"> <Separator className="flex my-2 bg-secondary" />
<Separator className="bg-secondary" />
</div>
<FormField <FormField
control={form.control} control={form.control}
name="loitering_time" name="loitering_time"
@ -464,9 +460,7 @@ export default function ZoneEditPane({
</FormItem> </FormItem>
)} )}
/> />
<div className="flex my-2"> <Separator className="flex my-2 bg-secondary" />
<Separator className="bg-secondary" />
</div>
<FormItem> <FormItem>
<FormLabel>Objects</FormLabel> <FormLabel>Objects</FormLabel>
<FormDescription> <FormDescription>
@ -490,14 +484,14 @@ export default function ZoneEditPane({
}} }}
/> />
</FormItem> </FormItem>
<div className="flex my-2">
<Separator className="bg-secondary" /> <Separator className="flex my-2 bg-secondary" />
</div>
<FormField <FormField
control={form.control} control={form.control}
name="review_alerts" name="review_alerts"
render={({ field }) => ( render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4"> <FormItem className="flex flex-row items-center justify-between">
<div className="space-y-0.5"> <div className="space-y-0.5">
<FormLabel>Alerts</FormLabel> <FormLabel>Alerts</FormLabel>
<FormDescription> <FormDescription>
@ -509,6 +503,7 @@ export default function ZoneEditPane({
<Switch <Switch
checked={field.value} checked={field.value}
onCheckedChange={field.onChange} onCheckedChange={field.onChange}
className="ml-3"
/> />
</FormControl> </FormControl>
</FormItem> </FormItem>
@ -518,7 +513,7 @@ export default function ZoneEditPane({
control={form.control} control={form.control}
name="review_detections" name="review_detections"
render={({ field }) => ( render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4"> <FormItem className="flex flex-row items-center justify-between">
<div className="space-y-0.5"> <div className="space-y-0.5">
<FormLabel className="text-base">Detections</FormLabel> <FormLabel className="text-base">Detections</FormLabel>
<FormDescription> <FormDescription>
@ -530,6 +525,7 @@ export default function ZoneEditPane({
<Switch <Switch
checked={field.value} checked={field.value}
onCheckedChange={field.onChange} onCheckedChange={field.onChange}
className="ml-3"
/> />
</FormControl> </FormControl>
</FormItem> </FormItem>

View File

@ -18,7 +18,7 @@ const Slider = React.forwardRef<
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary"> <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
<SliderPrimitive.Range className="absolute h-full bg-primary" /> <SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track> </SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" /> <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full cursor-pointer border-2 border-primary bg-primary ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root> </SliderPrimitive.Root>
)); ));
Slider.displayName = SliderPrimitive.Root.displayName; Slider.displayName = SliderPrimitive.Root.displayName;