object masks and deletion of all types

This commit is contained in:
Josh Hawkins 2024-04-17 15:59:55 -05:00
parent 93b206d6b4
commit 848d881976
10 changed files with 461 additions and 139 deletions

View File

@ -7,7 +7,7 @@ import { Polygon, PolygonType } from "@/types/canvas";
import { interpolatePoints, parseCoordinates } from "@/utils/canvasUtil"; import { interpolatePoints, parseCoordinates } from "@/utils/canvasUtil";
import { Skeleton } from "../ui/skeleton"; import { Skeleton } from "../ui/skeleton";
import { useResizeObserver } from "@/hooks/resize-observer"; import { useResizeObserver } from "@/hooks/resize-observer";
import { LuExternalLink, LuInfo, LuPlus } from "react-icons/lu"; import { LuExternalLink, LuPlus } from "react-icons/lu";
import { import {
HoverCard, HoverCard,
HoverCardContent, HoverCardContent,
@ -25,12 +25,6 @@ import ObjectMaskEditPane from "./ObjectMaskEditPane";
import PolygonItem from "./PolygonItem"; import PolygonItem from "./PolygonItem";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
// export type ZoneObjects = {
// camera: string;
// zoneName: string;
// objects: string[];
// };
type MasksAndZoneProps = { type MasksAndZoneProps = {
selectedCamera: string; selectedCamera: string;
selectedZoneMask?: PolygonType[]; selectedZoneMask?: PolygonType[];
@ -209,12 +203,12 @@ export default function MasksAndZones({
setActivePolygonIndex(allPolygons.length); setActivePolygonIndex(allPolygons.length);
let polygonColor = [128, 128, 0]; let polygonColor = [128, 128, 0];
if (type == "motion_mask") { if (type == "motion_mask") {
polygonColor = [0, 0, 220]; polygonColor = [0, 0, 220];
} }
if (type == "object_mask") { if (type == "object_mask") {
polygonColor = [128, 128, 128]; polygonColor = [128, 128, 128];
// TODO - get this from config object after mutation so label can be set
} }
setEditingPolygons([ setEditingPolygons([
@ -224,6 +218,7 @@ export default function MasksAndZones({
isFinished: false, isFinished: false,
// isUnsaved: true, // isUnsaved: true,
type, type,
typeIndex: 9999,
name: "", name: "",
objects: [], objects: [],
camera: selectedCamera, camera: selectedCamera,
@ -305,12 +300,22 @@ export default function MasksAndZones({
}), }),
); );
let motionMasks: Polygon[] = [];
let globalObjectMasks: Polygon[] = [];
let objectMasks: Polygon[] = [];
if (
cameraConfig.motion.mask !== null &&
cameraConfig.motion.mask !== undefined
) {
// this can be an array or a string // this can be an array or a string
const motionMasks = Object.entries( motionMasks = (
Array.isArray(cameraConfig.motion.mask) Array.isArray(cameraConfig.motion.mask)
? cameraConfig.motion.mask ? cameraConfig.motion.mask
: [cameraConfig.motion.mask], : cameraConfig.motion.mask
).map(([, maskData], index) => ({ ? [cameraConfig.motion.mask]
: []
).map((maskData, index) => ({
type: "motion_mask" as PolygonType, type: "motion_mask" as PolygonType,
typeIndex: index, typeIndex: index,
camera: cameraConfig.name, camera: cameraConfig.name,
@ -326,9 +331,18 @@ export default function MasksAndZones({
isFinished: true, isFinished: true,
color: [0, 0, 255], color: [0, 0, 255],
})); }));
}
const globalObjectMasks = Object.entries(cameraConfig.objects.mask).map( const globalObjectMasksArray = Array.isArray(cameraConfig.objects.mask)
([, maskData], index) => ({ ? cameraConfig.objects.mask
: cameraConfig.objects.mask
? [cameraConfig.objects.mask]
: [];
if (
cameraConfig.objects.mask !== null &&
cameraConfig.objects.mask !== undefined
) {
globalObjectMasks = globalObjectMasksArray.map((maskData, index) => ({
type: "object_mask" as PolygonType, type: "object_mask" as PolygonType,
typeIndex: index, typeIndex: index,
camera: cameraConfig.name, camera: cameraConfig.name,
@ -342,24 +356,37 @@ export default function MasksAndZones({
scaledHeight, scaledHeight,
), ),
isFinished: true, isFinished: true,
// isUnsaved: false, color: [128, 128, 128],
color: [0, 0, 255], }));
}), }
);
// if (globalObjectMasks && !Array.isArray(globalObjectMasks)) {
// globalObjectMasks = [globalObjectMasks];
// }
console.log("global", globalObjectMasks);
const globalObjectMasksCount = globalObjectMasks.length; const globalObjectMasksCount = globalObjectMasks.length;
const objectMasks = Object.entries(cameraConfig.objects.filters).flatMap( console.log("filters", cameraConfig.objects.filters);
([objectName, { mask }]): Polygon[] =>
mask !== null && mask !== undefined let index = 0;
? mask.flatMap((maskItem, subIndex) => objectMasks = Object.entries(cameraConfig.objects.filters)
maskItem !== null && maskItem !== undefined .filter(([_, { mask }]) => mask || Array.isArray(mask))
? [ .flatMap(([objectName, { mask }]): Polygon[] => {
{ console.log("index", index);
console.log("outer", objectName, mask);
const maskArray = Array.isArray(mask) ? mask : mask ? [mask] : [];
return maskArray.flatMap((maskItem, subIndex) => {
const maskItemString = maskItem;
const newMask = {
type: "object_mask" as PolygonType, type: "object_mask" as PolygonType,
typeIndex: subIndex, typeIndex: subIndex,
camera: cameraConfig.name, camera: cameraConfig.name,
name: `Object Mask ${globalObjectMasksCount + subIndex + 1} (${objectName})`, name: `Object Mask ${globalObjectMasksCount + index + 1} (${objectName})`,
objects: [objectName], objects: [objectName],
points: interpolatePoints( points: interpolatePoints(
parseCoordinates(maskItem), parseCoordinates(maskItem),
@ -369,14 +396,26 @@ export default function MasksAndZones({
scaledHeight, scaledHeight,
), ),
isFinished: true, isFinished: true,
// isUnsaved: false,
color: [128, 128, 128], color: [128, 128, 128],
}, };
] index++;
: [],
if (
globalObjectMasksArray.some(
(globalMask) => globalMask === maskItemString,
) )
: [], ) {
); index--;
return [];
} else {
return [newMask];
}
});
});
console.log(Object.entries(cameraConfig.objects.filters));
console.log("final object masks", objectMasks);
// console.log("setting all and editing"); // console.log("setting all and editing");
setAllPolygons([ setAllPolygons([
@ -404,9 +443,9 @@ export default function MasksAndZones({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [cameraConfig, containerRef, scaledHeight, scaledWidth]); }, [cameraConfig, containerRef, scaledHeight, scaledWidth]);
useEffect(() => { // useEffect(() => {
console.log("editing polygons changed:", editingPolygons); // console.log("editing polygons changed:", editingPolygons);
}, [editingPolygons]); // }, [editingPolygons]);
useEffect(() => { useEffect(() => {
if (editPane === undefined) { if (editPane === undefined) {

View File

@ -19,6 +19,7 @@ import {
import axios from "axios"; import axios from "axios";
import { toast } from "sonner"; import { toast } from "sonner";
import { Toaster } from "../ui/sonner"; import { Toaster } from "../ui/sonner";
import ActivityIndicator from "../indicators/activity-indicator";
type MotionMaskEditPaneProps = { type MotionMaskEditPaneProps = {
polygons?: Polygon[]; polygons?: Polygon[];
@ -105,7 +106,9 @@ export default function MotionMaskEditPane({
let index = Array.isArray(cameraConfig.motion.mask) let index = Array.isArray(cameraConfig.motion.mask)
? cameraConfig.motion.mask.length ? cameraConfig.motion.mask.length
: 1; : cameraConfig.motion.mask
? 1
: 0;
console.log("are we an array?", Array.isArray(cameraConfig.motion.mask)); console.log("are we an array?", Array.isArray(cameraConfig.motion.mask));
console.log("index", index); console.log("index", index);
@ -114,20 +117,14 @@ export default function MotionMaskEditPane({
// editing existing mask, not creating a new one // editing existing mask, not creating a new one
if (editingMask) { if (editingMask) {
index = polygon.typeIndex; index = polygon.typeIndex;
if (polygon.name) {
const match = polygon.name.match(/\d+/);
if (match) {
// index = parseInt(match[0]) - 1;
console.log("editing, index", index); console.log("editing, index", index);
} }
}
}
const filteredMask = Array.isArray(cameraConfig.motion.mask) const filteredMask = (
Array.isArray(cameraConfig.motion.mask)
? cameraConfig.motion.mask ? cameraConfig.motion.mask
: [cameraConfig.motion.mask].filter( : [cameraConfig.motion.mask]
(_, currentIndex) => currentIndex !== index, ).filter((_, currentIndex) => currentIndex !== index);
);
console.log("filtered", filteredMask); console.log("filtered", filteredMask);
// if (editingMask) { // if (editingMask) {
@ -163,7 +160,7 @@ export default function MotionMaskEditPane({
}) })
.then((res) => { .then((res) => {
if (res.status === 200) { if (res.status === 200) {
toast.success(`Zone ${name} saved.`, { toast.success(`${polygon.name || "Motion Mask"} has been saved.`, {
position: "top-center", position: "top-center",
}); });
// setChangedValue(false); // setChangedValue(false);
@ -274,8 +271,20 @@ export default function MotionMaskEditPane({
<Button className="flex flex-1" onClick={onCancel}> <Button className="flex flex-1" onClick={onCancel}>
Cancel Cancel
</Button> </Button>
<Button variant="select" className="flex flex-1" type="submit"> <Button
Save 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> </Button>
</div> </div>
</form> </form>

View File

@ -19,20 +19,33 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";
import { useMemo } from "react"; import { useCallback, useMemo } from "react";
import { ATTRIBUTE_LABELS, FrigateConfig } from "@/types/frigateConfig"; import { ATTRIBUTE_LABELS, FrigateConfig } from "@/types/frigateConfig";
import useSWR from "swr"; import useSWR from "swr";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
import { Polygon } from "@/types/canvas"; import { ObjectMaskFormValuesType, Polygon } from "@/types/canvas";
import PolygonEditControls from "./PolygonEditControls"; import PolygonEditControls from "./PolygonEditControls";
import { FaCheckCircle } from "react-icons/fa"; import { FaCheckCircle } from "react-icons/fa";
import {
flattenPoints,
interpolatePoints,
parseCoordinates,
} from "@/utils/canvasUtil";
import axios from "axios";
import { toast } from "sonner";
import { Toaster } from "../ui/sonner";
import ActivityIndicator from "../indicators/activity-indicator";
type ObjectMaskEditPaneProps = { type ObjectMaskEditPaneProps = {
polygons?: Polygon[]; polygons?: Polygon[];
setPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>; setPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
activePolygonIndex?: number; activePolygonIndex?: number;
scaledWidth?: number;
scaledHeight?: number;
isLoading: boolean;
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
onSave?: () => void; onSave?: () => void;
onCancel?: () => void; onCancel?: () => void;
}; };
@ -41,9 +54,15 @@ export default function ObjectMaskEditPane({
polygons, polygons,
setPolygons, setPolygons,
activePolygonIndex, activePolygonIndex,
scaledWidth,
scaledHeight,
isLoading,
setIsLoading,
onSave, onSave,
onCancel, onCancel,
}: ObjectMaskEditPaneProps) { }: ObjectMaskEditPaneProps) {
const { data: config, mutate: updateConfig } =
useSWR<FrigateConfig>("config");
// const { data: config } = useSWR<FrigateConfig>("config"); // const { data: config } = useSWR<FrigateConfig>("config");
// const cameras = useMemo(() => { // const cameras = useMemo(() => {
@ -64,6 +83,12 @@ export default function ObjectMaskEditPane({
} }
}, [polygons, activePolygonIndex]); }, [polygons, activePolygonIndex]);
const cameraConfig = useMemo(() => {
if (polygon?.camera && config) {
return config.cameras[polygon.camera];
}
}, [polygon, config]);
const defaultName = useMemo(() => { const defaultName = useMemo(() => {
if (!polygons) { if (!polygons) {
return; return;
@ -101,20 +126,157 @@ export default function ObjectMaskEditPane({
}, },
}); });
function onSubmit(values: z.infer<typeof formSchema>) { const saveToConfig = useCallback(
console.log("form values", values); async (
// if (activePolygonIndex === undefined || !polygons) { { objects: form_objects }: ObjectMaskFormValuesType, // values submitted via the form
objects: string[],
) => {
if (!scaledWidth || !scaledHeight || !polygon || !cameraConfig) {
return;
}
// console.log("loitering time", loitering_time);
// const alertsZones = config?.cameras[camera]?.review.alerts.required_zones;
// const detectionsZones =
// config?.cameras[camera]?.review.detections.required_zones;
// console.log("out of try except", mutatedConfig);
console.log("form objects:", form_objects);
console.log("objects:", objects);
console.log(cameraConfig.objects.filters);
const coordinates = flattenPoints(
interpolatePoints(polygon.points, scaledWidth, scaledHeight, 1, 1),
).join(",");
let queryString = "";
let configObject;
let createFilter = false;
let globalMask = false;
let filteredMask = [coordinates];
const editingMask = polygon.name.length > 0;
// global mask on camera for all objects
if (form_objects == "all_labels") {
configObject = cameraConfig.objects.mask;
globalMask = true;
} else {
if (
cameraConfig.objects.filters[form_objects] &&
cameraConfig.objects.filters[form_objects].mask !== null
) {
configObject = cameraConfig.objects.filters[form_objects].mask;
} else {
createFilter = true;
}
}
if (!createFilter) {
let index = Array.isArray(configObject)
? configObject.length
: configObject
? 1
: 0;
if (editingMask) {
index = polygon.typeIndex;
}
console.log("are we an array?", Array.isArray(configObject));
console.log("index", index);
// editing existing mask, not creating a new one
if (editingMask) {
index = polygon.typeIndex;
}
filteredMask = (
Array.isArray(configObject) ? configObject : [configObject as string]
).filter((_, currentIndex) => currentIndex !== index);
console.log("filtered", filteredMask);
filteredMask.splice(index, 0, coordinates);
console.log("filtered after splice", filteredMask);
}
queryString = filteredMask
.map((pointsArray) => {
const coordinates = flattenPoints(parseCoordinates(pointsArray)).join(
",",
);
return globalMask
? `cameras.${polygon?.camera}.objects.mask=${coordinates}&`
: `cameras.${polygon?.camera}.objects.filters.${form_objects}.mask=${coordinates}&`;
})
.join("");
console.log("polygon", polygon);
console.log(queryString);
// console.log(
// `config/set?cameras.${polygon?.camera}.objects.mask=${coordinates}&${queryString}`,
// );
// console.log("object masks", cameraConfig.objects.mask);
// console.log("new coords", coordinates);
// return; // return;
// }
// const updatedPolygons = [...polygons]; if (!queryString) {
// const activePolygon = updatedPolygons[activePolygonIndex]; console.log("no query string");
// updatedPolygons[activePolygonIndex] = { return;
// ...activePolygon, }
// name: defaultName ?? "foo",
// };
// setPolygons(updatedPolygons);
axios
.put(`config/set?${queryString}`, {
requires_restart: 0,
})
.then((res) => {
if (res.status === 200) {
toast.success(`${polygon.name || "Object Mask"} has been saved.`, {
position: "top-center",
});
// setChangedValue(false);
updateConfig();
} else {
toast.error(`Failed to save config changes: ${res.statusText}`, {
position: "top-center",
});
}
})
.catch((error) => {
toast.error(
`Failed to save config changes: ${error.response.data.message}`,
{ position: "top-center" },
);
})
.finally(() => {
setIsLoading(false);
});
},
[updateConfig, polygon, scaledWidth, scaledHeight, setIsLoading],
);
function onSubmit(values: z.infer<typeof formSchema>) {
if (activePolygonIndex === undefined || !values || !polygons) {
return;
}
setIsLoading(true);
// polygons[activePolygonIndex].name = values.name;
// console.log("form values", values);
// console.log(
// "string",
// flattenPoints(
// interpolatePoints(polygon.points, scaledWidth, scaledHeight, 1, 1),
// ).join(","),
// );
// console.log("active polygon", polygons[activePolygonIndex]);
saveToConfig(
values as ObjectMaskFormValuesType,
polygons[activePolygonIndex].objects,
);
if (onSave) { if (onSave) {
onSave(); onSave();
} }
@ -126,6 +288,7 @@ export default function ObjectMaskEditPane({
return ( return (
<> <>
<Toaster position="top-center" />
<Heading as="h3" className="my-2"> <Heading as="h3" className="my-2">
{polygon.name.length ? "Edit" : "New"} Object Mask {polygon.name.length ? "Edit" : "New"} Object Mask
</Heading> </Heading>
@ -211,8 +374,20 @@ export default function ObjectMaskEditPane({
<Button className="flex flex-1" onClick={onCancel}> <Button className="flex flex-1" onClick={onCancel}>
Cancel Cancel
</Button> </Button>
<Button variant="select" className="flex flex-1" type="submit"> <Button
Save 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> </Button>
</div> </div>
</form> </form>

View File

@ -106,6 +106,7 @@ export default function PolygonDrawer({
onMouseOver={isActive ? handleGroupMouseOver : undefined} onMouseOver={isActive ? handleGroupMouseOver : undefined}
onTouchStart={isActive ? handleGroupMouseOver : undefined} onTouchStart={isActive ? handleGroupMouseOver : undefined}
onMouseOut={isActive ? handleGroupMouseOut : undefined} onMouseOut={isActive ? handleGroupMouseOut : undefined}
zIndex={isActive ? 999 : 100}
> >
<Line <Line
points={flattenedPoints} points={flattenedPoints}

View File

@ -44,6 +44,10 @@ export default function PolygonEditControls({
setPolygons(updatedPolygons); setPolygons(updatedPolygons);
}; };
if (activePolygonIndex === undefined || !polygons) {
return;
}
return ( return (
<div className="flex flex-row justify-center gap-2"> <div className="flex flex-row justify-center gap-2">
<Tooltip> <Tooltip>
@ -51,6 +55,7 @@ export default function PolygonEditControls({
<Button <Button
variant="secondary" variant="secondary"
className="size-6 p-1 rounded-md text-background bg-secondary-foreground" className="size-6 p-1 rounded-md text-background bg-secondary-foreground"
disabled={!polygons[activePolygonIndex].points.length}
onClick={undo} onClick={undo}
> >
<MdUndo /> <MdUndo />
@ -63,6 +68,7 @@ export default function PolygonEditControls({
<Button <Button
variant="secondary" variant="secondary"
className="size-6 p-1 rounded-md text-background bg-secondary-foreground" className="size-6 p-1 rounded-md text-background bg-secondary-foreground"
disabled={!polygons[activePolygonIndex].points.length}
onClick={reset} onClick={reset}
> >
<MdOutlineRestartAlt /> <MdOutlineRestartAlt />

View File

@ -37,7 +37,6 @@ import { reviewQueries } from "@/utils/zoneEdutUtil";
type PolygonItemProps = { type PolygonItemProps = {
polygon: Polygon; polygon: Polygon;
setAllPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>; setAllPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
setReindexPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
index: number; index: number;
activePolygonIndex: number | undefined; activePolygonIndex: number | undefined;
hoveredPolygonIndex: number | null; hoveredPolygonIndex: number | null;
@ -95,13 +94,6 @@ export default function PolygonItem({
} }
if (polygon.type == "motion_mask") { if (polygon.type == "motion_mask") {
console.log("deleting", polygon.typeIndex); console.log("deleting", polygon.typeIndex);
if (polygon.name) {
const match = polygon.name.match(/\d+/);
if (match) {
// index = parseInt(match[0]) - 1;
console.log("deleting, index", polygon.typeIndex);
}
}
const filteredMask = ( const filteredMask = (
Array.isArray(cameraConfig.motion.mask) Array.isArray(cameraConfig.motion.mask)
@ -118,11 +110,94 @@ export default function PolygonItem({
return `cameras.${polygon?.camera}.motion.mask=${coordinates}&`; return `cameras.${polygon?.camera}.motion.mask=${coordinates}&`;
}) })
.join(""); .join("");
if (!url) {
// deleting last mask
url = `cameras.${polygon?.camera}.motion.mask&`;
}
console.log(url); console.log(url);
// return; // return;
// url = `config/set?cameras.${polygon.camera}.motion.mask`; // url = `config/set?cameras.${polygon.camera}.motion.mask`;
} }
if (polygon.type == "object_mask") {
console.log("deleting", polygon.typeIndex, polygon);
let configObject;
let globalMask = false;
console.log("polygon objects", polygon.objects, !polygon.objects);
// global mask on camera for all objects
if (!polygon.objects.length) {
console.log("deleting global");
configObject = cameraConfig.objects.mask;
globalMask = true;
} else {
configObject = cameraConfig.objects.filters[polygon.objects[0]].mask;
}
if (!configObject) {
return;
}
const globalObjectMasksArray = Array.isArray(cameraConfig.objects.mask)
? cameraConfig.objects.mask
: cameraConfig.objects.mask
? [cameraConfig.objects.mask]
: [];
let filteredMask;
if (globalMask) {
filteredMask = (
Array.isArray(configObject) ? configObject : [configObject]
).filter((_, currentIndex) => currentIndex !== polygon.typeIndex);
} else {
console.log("not globals config object:", configObject);
filteredMask = (
Array.isArray(configObject) ? configObject : [configObject]
)
.filter((mask) => !globalObjectMasksArray.includes(mask))
.filter((_, currentIndex) => {
console.log(
"current index",
currentIndex,
"global length:",
globalObjectMasksArray.length,
"polygon typeindex",
polygon.typeIndex,
);
return currentIndex !== polygon.typeIndex;
});
}
console.log("filtered:", filteredMask);
url = filteredMask
.map((pointsArray) => {
const coordinates = flattenPoints(
parseCoordinates(pointsArray),
).join(",");
return globalMask
? `cameras.${polygon?.camera}.objects.mask=${coordinates}&`
: `cameras.${polygon?.camera}.objects.filters.${polygon.objects[0]}.mask=${coordinates}&`;
})
.join("");
if (!url) {
// deleting last mask
url = globalMask
? `cameras.${polygon?.camera}.objects.mask&`
: `cameras.${polygon?.camera}.objects.filters.${polygon.objects[0]}.mask`;
}
console.log("url:", url);
// return;
// url = `config/set?cameras.${polygon.camera}.motion.mask`;
}
await axios await axios
.put(`config/set?${url}`, { requires_restart: 0 }) .put(`config/set?${url}`, { requires_restart: 0 })
.then((res) => { .then((res) => {
@ -169,15 +244,16 @@ export default function PolygonItem({
}; };
const handleDelete = (type: string, typeIndex: number) => { const handleDelete = (type: string, typeIndex: number) => {
setAllPolygons((oldPolygons) => { // setAllPolygons((oldPolygons) => {
const filteredPolygons = oldPolygons.filter( // console.log("old polygons", oldPolygons);
(polygon) => // const filteredPolygons = oldPolygons.filter(
!(polygon.type === type && polygon.typeIndex === typeIndex), // (polygon) =>
); // !(polygon.type === type && polygon.typeIndex === typeIndex),
console.log("filtered", filteredPolygons); // );
// console.log("reindexed", reindexPolygons(filteredPolygons)); // console.log("filtered", filteredPolygons);
return filteredPolygons; // // console.log("reindexed", reindexPolygons(filteredPolygons));
}); // return filteredPolygons;
// });
setActivePolygonIndex(undefined); setActivePolygonIndex(undefined);
saveToConfig(polygon); saveToConfig(polygon);
}; };

View File

@ -14,11 +14,10 @@ import { Input } from "@/components/ui/input";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { ATTRIBUTE_LABELS, FrigateConfig } from "@/types/frigateConfig"; import { ATTRIBUTE_LABELS, FrigateConfig } from "@/types/frigateConfig";
import useSWR from "swr"; import useSWR from "swr";
import { isMobile } from "react-device-detect";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
import { FormValuesType, Polygon } from "@/types/canvas"; import { ZoneFormValuesType, Polygon } from "@/types/canvas";
import { reviewQueries } from "@/utils/zoneEdutUtil"; import { reviewQueries } from "@/utils/zoneEdutUtil";
import { Switch } from "../ui/switch"; import { Switch } from "../ui/switch";
import { Label } from "../ui/label"; import { Label } from "../ui/label";
@ -187,13 +186,13 @@ export default function ZoneEditPane({
const saveToConfig = useCallback( const saveToConfig = useCallback(
async ( async (
{ {
name, name: zoneName,
inertia, inertia,
loitering_time, loitering_time,
objects: form_objects, objects: form_objects,
review_alerts, review_alerts,
review_detections, review_detections,
}: FormValuesType, // values submitted via the form }: ZoneFormValuesType, // values submitted via the form
objects: string[], objects: string[],
) => { ) => {
if (!scaledWidth || !scaledHeight || !polygon) { if (!scaledWidth || !scaledHeight || !polygon) {
@ -206,7 +205,7 @@ export default function ZoneEditPane({
// config?.cameras[camera]?.review.detections.required_zones; // config?.cameras[camera]?.review.detections.required_zones;
let mutatedConfig = config; let mutatedConfig = config;
const renamingZone = name != polygon.name && polygon.name != ""; const renamingZone = zoneName != polygon.name && polygon.name != "";
if (renamingZone) { if (renamingZone) {
// rename - delete old zone and replace with new // rename - delete old zone and replace with new
@ -252,7 +251,7 @@ export default function ZoneEditPane({
let objectQueries = objects let objectQueries = objects
.map( .map(
(object) => (object) =>
`&cameras.${polygon?.camera}.zones.${name}.objects=${object}`, `&cameras.${polygon?.camera}.zones.${zoneName}.objects=${object}`,
) )
.join(""); .join("");
@ -265,11 +264,11 @@ export default function ZoneEditPane({
// deleting objects // deleting objects
if (!objectQueries && !same_objects && !renamingZone) { if (!objectQueries && !same_objects && !renamingZone) {
// console.log("deleting objects"); // console.log("deleting objects");
objectQueries = `&cameras.${polygon?.camera}.zones.${name}.objects`; objectQueries = `&cameras.${polygon?.camera}.zones.${zoneName}.objects`;
} }
const { alertQueries, detectionQueries } = reviewQueries( const { alertQueries, detectionQueries } = reviewQueries(
name, zoneName,
review_alerts, review_alerts,
review_detections, review_detections,
polygon.camera, polygon.camera,
@ -289,12 +288,12 @@ export default function ZoneEditPane({
axios axios
.put( .put(
`config/set?cameras.${polygon?.camera}.zones.${name}.coordinates=${coordinates}&cameras.${polygon?.camera}.zones.${name}.inertia=${inertia}&cameras.${polygon?.camera}.zones.${name}.loitering_time=${loitering_time}${objectQueries}${alertQueries}${detectionQueries}`, `config/set?cameras.${polygon?.camera}.zones.${zoneName}.coordinates=${coordinates}&cameras.${polygon?.camera}.zones.${zoneName}.inertia=${inertia}&cameras.${polygon?.camera}.zones.${zoneName}.loitering_time=${loitering_time}${objectQueries}${alertQueries}${detectionQueries}`,
{ requires_restart: 0 }, { requires_restart: 0 },
) )
.then((res) => { .then((res) => {
if (res.status === 200) { if (res.status === 200) {
toast.success(`Zone ${name} saved.`, { toast.success(`Zone (${zoneName}) has been saved.`, {
position: "top-center", position: "top-center",
}); });
// setChangedValue(false); // setChangedValue(false);
@ -343,7 +342,7 @@ export default function ZoneEditPane({
// console.log("active polygon", polygons[activePolygonIndex]); // console.log("active polygon", polygons[activePolygonIndex]);
saveToConfig( saveToConfig(
values as FormValuesType, values as ZoneFormValuesType,
polygons[activePolygonIndex].objects, polygons[activePolygonIndex].objects,
); );
@ -599,13 +598,13 @@ export function ZoneObjectSelector({
const labels = new Set<string>(); const labels = new Set<string>();
Object.values(config.cameras).forEach((camera) => { // Object.values(config.cameras).forEach((camera) => {
camera.objects.track.forEach((label) => { // camera.objects.track.forEach((label) => {
if (!ATTRIBUTE_LABELS.includes(label)) { // if (!ATTRIBUTE_LABELS.includes(label)) {
labels.add(label); // labels.add(label);
} // }
}); // });
}); // });
cameraConfig.objects.track.forEach((label) => { cameraConfig.objects.track.forEach((label) => {
if (!ATTRIBUTE_LABELS.includes(label)) { if (!ATTRIBUTE_LABELS.includes(label)) {

View File

@ -55,10 +55,8 @@ export default function Settings() {
const [filterZoneMask, setFilterZoneMask] = useState<PolygonType[]>(); const [filterZoneMask, setFilterZoneMask] = useState<PolygonType[]>();
useEffect(() => { useEffect(() => {
if (cameras) { if (cameras.length) {
// TODO: fixme
setSelectedCamera(cameras[0].name); setSelectedCamera(cameras[0].name);
console.log("setting selected cam");
} }
}, []); }, []);
@ -97,7 +95,6 @@ export default function Settings() {
updateZoneMaskFilter={setFilterZoneMask} updateZoneMaskFilter={setFilterZoneMask}
/> />
)} )}
{/* {isEditing && page == "masks / zones" && (<PolygonEditControls /)} */}
<CameraSelectButton <CameraSelectButton
allCameras={cameras} allCameras={cameras}
selectedCamera={selectedCamera} selectedCamera={selectedCamera}
@ -140,7 +137,7 @@ function CameraSelectButton({
}: CameraSelectButtonProps) { }: CameraSelectButtonProps) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
if (!allCameras) { if (!allCameras.length) {
return; return;
} }

View File

@ -12,7 +12,7 @@ export type Polygon = {
color: number[]; color: number[];
}; };
export type FormValuesType = { export type ZoneFormValuesType = {
name: string; name: string;
inertia: number; inertia: number;
loitering_time: number; loitering_time: number;
@ -21,3 +21,11 @@ export type FormValuesType = {
review_alerts: boolean; review_alerts: boolean;
review_detections: boolean; review_detections: boolean;
}; };
export type ObjectMaskFormValuesType = {
objects: string;
polygon: {
isFinished: boolean;
name: string;
};
};

View File

@ -56,8 +56,8 @@ export const interpolatePoints = (
const newPoints: number[][] = []; const newPoints: number[][] = [];
for (const [x, y] of points) { for (const [x, y] of points) {
const newX = (x * newWidth) / width; const newX = +((x * newWidth) / width).toFixed(3);
const newY = (y * newHeight) / height; const newY = +((y * newHeight) / height).toFixed(3);
newPoints.push([newX, newY]); newPoints.push([newX, newY]);
} }
@ -88,3 +88,15 @@ export const toRGBColorString = (color: number[], darkened: boolean) => {
return `rgba(${color[2]},${color[1]},${color[0]},${darkened ? "0.7" : "0.3"})`; return `rgba(${color[2]},${color[1]},${color[0]},${darkened ? "0.7" : "0.3"})`;
}; };
export const masksAreIdentical = (arr1: string[], arr2: string[]): boolean => {
if (arr1.length !== arr2.length) {
return false;
}
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
};