mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-10 21:25:24 +03:00
object masks and deletion of all types
This commit is contained in:
parent
93b206d6b4
commit
848d881976
@ -7,7 +7,7 @@ import { Polygon, PolygonType } from "@/types/canvas";
|
||||
import { interpolatePoints, parseCoordinates } from "@/utils/canvasUtil";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||
import { LuExternalLink, LuInfo, LuPlus } from "react-icons/lu";
|
||||
import { LuExternalLink, LuPlus } from "react-icons/lu";
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
@ -25,12 +25,6 @@ import ObjectMaskEditPane from "./ObjectMaskEditPane";
|
||||
import PolygonItem from "./PolygonItem";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
// export type ZoneObjects = {
|
||||
// camera: string;
|
||||
// zoneName: string;
|
||||
// objects: string[];
|
||||
// };
|
||||
|
||||
type MasksAndZoneProps = {
|
||||
selectedCamera: string;
|
||||
selectedZoneMask?: PolygonType[];
|
||||
@ -209,12 +203,12 @@ export default function MasksAndZones({
|
||||
setActivePolygonIndex(allPolygons.length);
|
||||
|
||||
let polygonColor = [128, 128, 0];
|
||||
|
||||
if (type == "motion_mask") {
|
||||
polygonColor = [0, 0, 220];
|
||||
}
|
||||
if (type == "object_mask") {
|
||||
polygonColor = [128, 128, 128];
|
||||
// TODO - get this from config object after mutation so label can be set
|
||||
}
|
||||
|
||||
setEditingPolygons([
|
||||
@ -224,6 +218,7 @@ export default function MasksAndZones({
|
||||
isFinished: false,
|
||||
// isUnsaved: true,
|
||||
type,
|
||||
typeIndex: 9999,
|
||||
name: "",
|
||||
objects: [],
|
||||
camera: selectedCamera,
|
||||
@ -305,30 +300,49 @@ export default function MasksAndZones({
|
||||
}),
|
||||
);
|
||||
|
||||
// this can be an array or a string
|
||||
const motionMasks = Object.entries(
|
||||
Array.isArray(cameraConfig.motion.mask)
|
||||
? cameraConfig.motion.mask
|
||||
: [cameraConfig.motion.mask],
|
||||
).map(([, maskData], index) => ({
|
||||
type: "motion_mask" as PolygonType,
|
||||
typeIndex: index,
|
||||
camera: cameraConfig.name,
|
||||
name: `Motion Mask ${index + 1}`,
|
||||
objects: [],
|
||||
points: interpolatePoints(
|
||||
parseCoordinates(maskData),
|
||||
1,
|
||||
1,
|
||||
scaledWidth,
|
||||
scaledHeight,
|
||||
),
|
||||
isFinished: true,
|
||||
color: [0, 0, 255],
|
||||
}));
|
||||
let motionMasks: Polygon[] = [];
|
||||
let globalObjectMasks: Polygon[] = [];
|
||||
let objectMasks: Polygon[] = [];
|
||||
|
||||
const globalObjectMasks = Object.entries(cameraConfig.objects.mask).map(
|
||||
([, maskData], index) => ({
|
||||
if (
|
||||
cameraConfig.motion.mask !== null &&
|
||||
cameraConfig.motion.mask !== undefined
|
||||
) {
|
||||
// this can be an array or a string
|
||||
motionMasks = (
|
||||
Array.isArray(cameraConfig.motion.mask)
|
||||
? cameraConfig.motion.mask
|
||||
: cameraConfig.motion.mask
|
||||
? [cameraConfig.motion.mask]
|
||||
: []
|
||||
).map((maskData, index) => ({
|
||||
type: "motion_mask" as PolygonType,
|
||||
typeIndex: index,
|
||||
camera: cameraConfig.name,
|
||||
name: `Motion Mask ${index + 1}`,
|
||||
objects: [],
|
||||
points: interpolatePoints(
|
||||
parseCoordinates(maskData),
|
||||
1,
|
||||
1,
|
||||
scaledWidth,
|
||||
scaledHeight,
|
||||
),
|
||||
isFinished: true,
|
||||
color: [0, 0, 255],
|
||||
}));
|
||||
}
|
||||
|
||||
const globalObjectMasksArray = Array.isArray(cameraConfig.objects.mask)
|
||||
? 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,
|
||||
typeIndex: index,
|
||||
camera: cameraConfig.name,
|
||||
@ -342,41 +356,66 @@ export default function MasksAndZones({
|
||||
scaledHeight,
|
||||
),
|
||||
isFinished: true,
|
||||
// isUnsaved: false,
|
||||
color: [0, 0, 255],
|
||||
}),
|
||||
);
|
||||
color: [128, 128, 128],
|
||||
}));
|
||||
}
|
||||
|
||||
// if (globalObjectMasks && !Array.isArray(globalObjectMasks)) {
|
||||
// globalObjectMasks = [globalObjectMasks];
|
||||
// }
|
||||
|
||||
console.log("global", globalObjectMasks);
|
||||
|
||||
const globalObjectMasksCount = globalObjectMasks.length;
|
||||
|
||||
const objectMasks = Object.entries(cameraConfig.objects.filters).flatMap(
|
||||
([objectName, { mask }]): Polygon[] =>
|
||||
mask !== null && mask !== undefined
|
||||
? mask.flatMap((maskItem, subIndex) =>
|
||||
maskItem !== null && maskItem !== undefined
|
||||
? [
|
||||
{
|
||||
type: "object_mask" as PolygonType,
|
||||
typeIndex: subIndex,
|
||||
camera: cameraConfig.name,
|
||||
name: `Object Mask ${globalObjectMasksCount + subIndex + 1} (${objectName})`,
|
||||
objects: [objectName],
|
||||
points: interpolatePoints(
|
||||
parseCoordinates(maskItem),
|
||||
1,
|
||||
1,
|
||||
scaledWidth,
|
||||
scaledHeight,
|
||||
),
|
||||
isFinished: true,
|
||||
// isUnsaved: false,
|
||||
color: [128, 128, 128],
|
||||
},
|
||||
]
|
||||
: [],
|
||||
console.log("filters", cameraConfig.objects.filters);
|
||||
|
||||
let index = 0;
|
||||
objectMasks = Object.entries(cameraConfig.objects.filters)
|
||||
.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,
|
||||
typeIndex: subIndex,
|
||||
camera: cameraConfig.name,
|
||||
name: `Object Mask ${globalObjectMasksCount + index + 1} (${objectName})`,
|
||||
objects: [objectName],
|
||||
points: interpolatePoints(
|
||||
parseCoordinates(maskItem),
|
||||
1,
|
||||
1,
|
||||
scaledWidth,
|
||||
scaledHeight,
|
||||
),
|
||||
isFinished: true,
|
||||
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");
|
||||
setAllPolygons([
|
||||
@ -404,9 +443,9 @@ export default function MasksAndZones({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [cameraConfig, containerRef, scaledHeight, scaledWidth]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("editing polygons changed:", editingPolygons);
|
||||
}, [editingPolygons]);
|
||||
// useEffect(() => {
|
||||
// console.log("editing polygons changed:", editingPolygons);
|
||||
// }, [editingPolygons]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editPane === undefined) {
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
import axios from "axios";
|
||||
import { toast } from "sonner";
|
||||
import { Toaster } from "../ui/sonner";
|
||||
import ActivityIndicator from "../indicators/activity-indicator";
|
||||
|
||||
type MotionMaskEditPaneProps = {
|
||||
polygons?: Polygon[];
|
||||
@ -105,7 +106,9 @@ export default function MotionMaskEditPane({
|
||||
|
||||
let index = Array.isArray(cameraConfig.motion.mask)
|
||||
? cameraConfig.motion.mask.length
|
||||
: 1;
|
||||
: cameraConfig.motion.mask
|
||||
? 1
|
||||
: 0;
|
||||
|
||||
console.log("are we an array?", Array.isArray(cameraConfig.motion.mask));
|
||||
console.log("index", index);
|
||||
@ -114,20 +117,14 @@ export default function MotionMaskEditPane({
|
||||
// editing existing mask, not creating a new one
|
||||
if (editingMask) {
|
||||
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)
|
||||
? cameraConfig.motion.mask
|
||||
: [cameraConfig.motion.mask].filter(
|
||||
(_, currentIndex) => currentIndex !== index,
|
||||
);
|
||||
const filteredMask = (
|
||||
Array.isArray(cameraConfig.motion.mask)
|
||||
? cameraConfig.motion.mask
|
||||
: [cameraConfig.motion.mask]
|
||||
).filter((_, currentIndex) => currentIndex !== index);
|
||||
console.log("filtered", filteredMask);
|
||||
|
||||
// if (editingMask) {
|
||||
@ -163,7 +160,7 @@ export default function MotionMaskEditPane({
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
toast.success(`Zone ${name} saved.`, {
|
||||
toast.success(`${polygon.name || "Motion Mask"} has been saved.`, {
|
||||
position: "top-center",
|
||||
});
|
||||
// setChangedValue(false);
|
||||
@ -274,8 +271,20 @@ export default function MotionMaskEditPane({
|
||||
<Button className="flex flex-1" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="select" className="flex flex-1" type="submit">
|
||||
Save
|
||||
<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>
|
||||
</form>
|
||||
|
||||
@ -19,20 +19,33 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { useMemo } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { ATTRIBUTE_LABELS, FrigateConfig } from "@/types/frigateConfig";
|
||||
import useSWR from "swr";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { Polygon } from "@/types/canvas";
|
||||
import { ObjectMaskFormValuesType, Polygon } from "@/types/canvas";
|
||||
import PolygonEditControls from "./PolygonEditControls";
|
||||
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 = {
|
||||
polygons?: Polygon[];
|
||||
setPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
|
||||
activePolygonIndex?: number;
|
||||
scaledWidth?: number;
|
||||
scaledHeight?: number;
|
||||
isLoading: boolean;
|
||||
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
onSave?: () => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
@ -41,9 +54,15 @@ export default function ObjectMaskEditPane({
|
||||
polygons,
|
||||
setPolygons,
|
||||
activePolygonIndex,
|
||||
scaledWidth,
|
||||
scaledHeight,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
onSave,
|
||||
onCancel,
|
||||
}: ObjectMaskEditPaneProps) {
|
||||
const { data: config, mutate: updateConfig } =
|
||||
useSWR<FrigateConfig>("config");
|
||||
// const { data: config } = useSWR<FrigateConfig>("config");
|
||||
|
||||
// const cameras = useMemo(() => {
|
||||
@ -64,6 +83,12 @@ export default function ObjectMaskEditPane({
|
||||
}
|
||||
}, [polygons, activePolygonIndex]);
|
||||
|
||||
const cameraConfig = useMemo(() => {
|
||||
if (polygon?.camera && config) {
|
||||
return config.cameras[polygon.camera];
|
||||
}
|
||||
}, [polygon, config]);
|
||||
|
||||
const defaultName = useMemo(() => {
|
||||
if (!polygons) {
|
||||
return;
|
||||
@ -101,20 +126,157 @@ export default function ObjectMaskEditPane({
|
||||
},
|
||||
});
|
||||
|
||||
const saveToConfig = useCallback(
|
||||
async (
|
||||
{ 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;
|
||||
|
||||
if (!queryString) {
|
||||
console.log("no query string");
|
||||
return;
|
||||
}
|
||||
|
||||
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>) {
|
||||
console.log("form values", values);
|
||||
// if (activePolygonIndex === undefined || !polygons) {
|
||||
// return;
|
||||
// }
|
||||
if (activePolygonIndex === undefined || !values || !polygons) {
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
// polygons[activePolygonIndex].name = values.name;
|
||||
// console.log("form values", values);
|
||||
// console.log(
|
||||
// "string",
|
||||
|
||||
// const updatedPolygons = [...polygons];
|
||||
// const activePolygon = updatedPolygons[activePolygonIndex];
|
||||
// updatedPolygons[activePolygonIndex] = {
|
||||
// ...activePolygon,
|
||||
// name: defaultName ?? "foo",
|
||||
// };
|
||||
// setPolygons(updatedPolygons);
|
||||
// flattenPoints(
|
||||
// interpolatePoints(polygon.points, scaledWidth, scaledHeight, 1, 1),
|
||||
// ).join(","),
|
||||
// );
|
||||
// console.log("active polygon", polygons[activePolygonIndex]);
|
||||
|
||||
saveToConfig(
|
||||
values as ObjectMaskFormValuesType,
|
||||
polygons[activePolygonIndex].objects,
|
||||
);
|
||||
if (onSave) {
|
||||
onSave();
|
||||
}
|
||||
@ -126,6 +288,7 @@ export default function ObjectMaskEditPane({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toaster position="top-center" />
|
||||
<Heading as="h3" className="my-2">
|
||||
{polygon.name.length ? "Edit" : "New"} Object Mask
|
||||
</Heading>
|
||||
@ -211,8 +374,20 @@ export default function ObjectMaskEditPane({
|
||||
<Button className="flex flex-1" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="select" className="flex flex-1" type="submit">
|
||||
Save
|
||||
<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>
|
||||
</form>
|
||||
|
||||
@ -106,6 +106,7 @@ export default function PolygonDrawer({
|
||||
onMouseOver={isActive ? handleGroupMouseOver : undefined}
|
||||
onTouchStart={isActive ? handleGroupMouseOver : undefined}
|
||||
onMouseOut={isActive ? handleGroupMouseOut : undefined}
|
||||
zIndex={isActive ? 999 : 100}
|
||||
>
|
||||
<Line
|
||||
points={flattenedPoints}
|
||||
|
||||
@ -44,6 +44,10 @@ export default function PolygonEditControls({
|
||||
setPolygons(updatedPolygons);
|
||||
};
|
||||
|
||||
if (activePolygonIndex === undefined || !polygons) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-row justify-center gap-2">
|
||||
<Tooltip>
|
||||
@ -51,6 +55,7 @@ export default function PolygonEditControls({
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="size-6 p-1 rounded-md text-background bg-secondary-foreground"
|
||||
disabled={!polygons[activePolygonIndex].points.length}
|
||||
onClick={undo}
|
||||
>
|
||||
<MdUndo />
|
||||
@ -63,6 +68,7 @@ export default function PolygonEditControls({
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="size-6 p-1 rounded-md text-background bg-secondary-foreground"
|
||||
disabled={!polygons[activePolygonIndex].points.length}
|
||||
onClick={reset}
|
||||
>
|
||||
<MdOutlineRestartAlt />
|
||||
|
||||
@ -37,7 +37,6 @@ import { reviewQueries } from "@/utils/zoneEdutUtil";
|
||||
type PolygonItemProps = {
|
||||
polygon: Polygon;
|
||||
setAllPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
|
||||
setReindexPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
|
||||
index: number;
|
||||
activePolygonIndex: number | undefined;
|
||||
hoveredPolygonIndex: number | null;
|
||||
@ -95,13 +94,6 @@ export default function PolygonItem({
|
||||
}
|
||||
if (polygon.type == "motion_mask") {
|
||||
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 = (
|
||||
Array.isArray(cameraConfig.motion.mask)
|
||||
@ -118,11 +110,94 @@ export default function PolygonItem({
|
||||
return `cameras.${polygon?.camera}.motion.mask=${coordinates}&`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
if (!url) {
|
||||
// deleting last mask
|
||||
url = `cameras.${polygon?.camera}.motion.mask&`;
|
||||
}
|
||||
console.log(url);
|
||||
|
||||
// return;
|
||||
// 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
|
||||
.put(`config/set?${url}`, { requires_restart: 0 })
|
||||
.then((res) => {
|
||||
@ -169,15 +244,16 @@ export default function PolygonItem({
|
||||
};
|
||||
|
||||
const handleDelete = (type: string, typeIndex: number) => {
|
||||
setAllPolygons((oldPolygons) => {
|
||||
const filteredPolygons = oldPolygons.filter(
|
||||
(polygon) =>
|
||||
!(polygon.type === type && polygon.typeIndex === typeIndex),
|
||||
);
|
||||
console.log("filtered", filteredPolygons);
|
||||
// console.log("reindexed", reindexPolygons(filteredPolygons));
|
||||
return filteredPolygons;
|
||||
});
|
||||
// setAllPolygons((oldPolygons) => {
|
||||
// console.log("old polygons", oldPolygons);
|
||||
// const filteredPolygons = oldPolygons.filter(
|
||||
// (polygon) =>
|
||||
// !(polygon.type === type && polygon.typeIndex === typeIndex),
|
||||
// );
|
||||
// console.log("filtered", filteredPolygons);
|
||||
// // console.log("reindexed", reindexPolygons(filteredPolygons));
|
||||
// return filteredPolygons;
|
||||
// });
|
||||
setActivePolygonIndex(undefined);
|
||||
saveToConfig(polygon);
|
||||
};
|
||||
|
||||
@ -14,11 +14,10 @@ import { Input } from "@/components/ui/input";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { ATTRIBUTE_LABELS, FrigateConfig } from "@/types/frigateConfig";
|
||||
import useSWR from "swr";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { FormValuesType, Polygon } from "@/types/canvas";
|
||||
import { ZoneFormValuesType, Polygon } from "@/types/canvas";
|
||||
import { reviewQueries } from "@/utils/zoneEdutUtil";
|
||||
import { Switch } from "../ui/switch";
|
||||
import { Label } from "../ui/label";
|
||||
@ -187,13 +186,13 @@ export default function ZoneEditPane({
|
||||
const saveToConfig = useCallback(
|
||||
async (
|
||||
{
|
||||
name,
|
||||
name: zoneName,
|
||||
inertia,
|
||||
loitering_time,
|
||||
objects: form_objects,
|
||||
review_alerts,
|
||||
review_detections,
|
||||
}: FormValuesType, // values submitted via the form
|
||||
}: ZoneFormValuesType, // values submitted via the form
|
||||
objects: string[],
|
||||
) => {
|
||||
if (!scaledWidth || !scaledHeight || !polygon) {
|
||||
@ -206,7 +205,7 @@ export default function ZoneEditPane({
|
||||
// config?.cameras[camera]?.review.detections.required_zones;
|
||||
let mutatedConfig = config;
|
||||
|
||||
const renamingZone = name != polygon.name && polygon.name != "";
|
||||
const renamingZone = zoneName != polygon.name && polygon.name != "";
|
||||
|
||||
if (renamingZone) {
|
||||
// rename - delete old zone and replace with new
|
||||
@ -252,7 +251,7 @@ export default function ZoneEditPane({
|
||||
let objectQueries = objects
|
||||
.map(
|
||||
(object) =>
|
||||
`&cameras.${polygon?.camera}.zones.${name}.objects=${object}`,
|
||||
`&cameras.${polygon?.camera}.zones.${zoneName}.objects=${object}`,
|
||||
)
|
||||
.join("");
|
||||
|
||||
@ -265,11 +264,11 @@ export default function ZoneEditPane({
|
||||
// deleting objects
|
||||
if (!objectQueries && !same_objects && !renamingZone) {
|
||||
// console.log("deleting objects");
|
||||
objectQueries = `&cameras.${polygon?.camera}.zones.${name}.objects`;
|
||||
objectQueries = `&cameras.${polygon?.camera}.zones.${zoneName}.objects`;
|
||||
}
|
||||
|
||||
const { alertQueries, detectionQueries } = reviewQueries(
|
||||
name,
|
||||
zoneName,
|
||||
review_alerts,
|
||||
review_detections,
|
||||
polygon.camera,
|
||||
@ -289,12 +288,12 @@ export default function ZoneEditPane({
|
||||
|
||||
axios
|
||||
.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 },
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
toast.success(`Zone ${name} saved.`, {
|
||||
toast.success(`Zone (${zoneName}) has been saved.`, {
|
||||
position: "top-center",
|
||||
});
|
||||
// setChangedValue(false);
|
||||
@ -343,7 +342,7 @@ export default function ZoneEditPane({
|
||||
// console.log("active polygon", polygons[activePolygonIndex]);
|
||||
|
||||
saveToConfig(
|
||||
values as FormValuesType,
|
||||
values as ZoneFormValuesType,
|
||||
polygons[activePolygonIndex].objects,
|
||||
);
|
||||
|
||||
@ -599,13 +598,13 @@ export function ZoneObjectSelector({
|
||||
|
||||
const labels = new Set<string>();
|
||||
|
||||
Object.values(config.cameras).forEach((camera) => {
|
||||
camera.objects.track.forEach((label) => {
|
||||
if (!ATTRIBUTE_LABELS.includes(label)) {
|
||||
labels.add(label);
|
||||
}
|
||||
});
|
||||
});
|
||||
// Object.values(config.cameras).forEach((camera) => {
|
||||
// camera.objects.track.forEach((label) => {
|
||||
// if (!ATTRIBUTE_LABELS.includes(label)) {
|
||||
// labels.add(label);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
cameraConfig.objects.track.forEach((label) => {
|
||||
if (!ATTRIBUTE_LABELS.includes(label)) {
|
||||
|
||||
@ -55,10 +55,8 @@ export default function Settings() {
|
||||
const [filterZoneMask, setFilterZoneMask] = useState<PolygonType[]>();
|
||||
|
||||
useEffect(() => {
|
||||
if (cameras) {
|
||||
// TODO: fixme
|
||||
if (cameras.length) {
|
||||
setSelectedCamera(cameras[0].name);
|
||||
console.log("setting selected cam");
|
||||
}
|
||||
}, []);
|
||||
|
||||
@ -97,7 +95,6 @@ export default function Settings() {
|
||||
updateZoneMaskFilter={setFilterZoneMask}
|
||||
/>
|
||||
)}
|
||||
{/* {isEditing && page == "masks / zones" && (<PolygonEditControls /)} */}
|
||||
<CameraSelectButton
|
||||
allCameras={cameras}
|
||||
selectedCamera={selectedCamera}
|
||||
@ -140,7 +137,7 @@ function CameraSelectButton({
|
||||
}: CameraSelectButtonProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (!allCameras) {
|
||||
if (!allCameras.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ export type Polygon = {
|
||||
color: number[];
|
||||
};
|
||||
|
||||
export type FormValuesType = {
|
||||
export type ZoneFormValuesType = {
|
||||
name: string;
|
||||
inertia: number;
|
||||
loitering_time: number;
|
||||
@ -21,3 +21,11 @@ export type FormValuesType = {
|
||||
review_alerts: boolean;
|
||||
review_detections: boolean;
|
||||
};
|
||||
|
||||
export type ObjectMaskFormValuesType = {
|
||||
objects: string;
|
||||
polygon: {
|
||||
isFinished: boolean;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
@ -56,8 +56,8 @@ export const interpolatePoints = (
|
||||
const newPoints: number[][] = [];
|
||||
|
||||
for (const [x, y] of points) {
|
||||
const newX = (x * newWidth) / width;
|
||||
const newY = (y * newHeight) / height;
|
||||
const newX = +((x * newWidth) / width).toFixed(3);
|
||||
const newY = +((y * newHeight) / height).toFixed(3);
|
||||
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"})`;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user