mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-09 00:02:48 +03:00
Masks and zones improvements (#22163)
* migrator and runtime config changes * component changes to use rasterized_mask * frontend * convert none to empty string for config save * i18n * update tests * add enabled config to zones * zones frontend * i18n * docs * tweaks * use dashed stroke to indicate disabled * allow toggle from icon * use filelock to ensure atomic config updates from endpoint * enforce atomic config update in the frontend * toggle via mqtt * fix global object masks * correctly handle global object masks in dispatcher * ws hooks * render masks and zones based on ws enabled state * use enabled_in_config for zones and masks * frontend for enabled_in_config * tweaks * i18n * publish websocket on config save * i18n tweaks * pydantic title and description * i18n generation * tweaks * fix typing
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"""Mask configuration for motion and object masks."""
|
||||
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from pydantic import Field, field_serializer
|
||||
|
||||
from ..base import FrigateBaseModel
|
||||
|
||||
__all__ = ["MotionMaskConfig", "ObjectMaskConfig"]
|
||||
|
||||
|
||||
class MotionMaskConfig(FrigateBaseModel):
|
||||
"""Configuration for a single motion mask."""
|
||||
|
||||
friendly_name: Optional[str] = Field(
|
||||
default=None,
|
||||
title="Friendly name",
|
||||
description="A friendly name for this motion mask used in the Frigate UI",
|
||||
)
|
||||
enabled: bool = Field(
|
||||
default=True,
|
||||
title="Enabled",
|
||||
description="Enable or disable this motion mask",
|
||||
)
|
||||
coordinates: Union[str, list[str]] = Field(
|
||||
default="",
|
||||
title="Coordinates",
|
||||
description="Ordered x,y coordinates defining the motion mask polygon used to include/exclude areas.",
|
||||
)
|
||||
raw_coordinates: Union[str, list[str]] = ""
|
||||
enabled_in_config: Optional[bool] = Field(
|
||||
default=None, title="Keep track of original state of motion mask."
|
||||
)
|
||||
|
||||
def get_formatted_name(self, mask_id: str) -> str:
|
||||
"""Return the friendly name if set, otherwise return a formatted version of the mask ID."""
|
||||
if self.friendly_name:
|
||||
return self.friendly_name
|
||||
return mask_id.replace("_", " ").title()
|
||||
|
||||
@field_serializer("coordinates", when_used="json")
|
||||
def serialize_coordinates(self, value: Any, info):
|
||||
return self.raw_coordinates if self.raw_coordinates else value
|
||||
|
||||
@field_serializer("raw_coordinates", when_used="json")
|
||||
def serialize_raw_coordinates(self, value: Any, info):
|
||||
return None
|
||||
|
||||
|
||||
class ObjectMaskConfig(FrigateBaseModel):
|
||||
"""Configuration for a single object mask."""
|
||||
|
||||
friendly_name: Optional[str] = Field(
|
||||
default=None,
|
||||
title="Friendly name",
|
||||
description="A friendly name for this object mask used in the Frigate UI",
|
||||
)
|
||||
enabled: bool = Field(
|
||||
default=True,
|
||||
title="Enabled",
|
||||
description="Enable or disable this object mask",
|
||||
)
|
||||
coordinates: Union[str, list[str]] = Field(
|
||||
default="",
|
||||
title="Coordinates",
|
||||
description="Ordered x,y coordinates defining the object mask polygon used to include/exclude areas.",
|
||||
)
|
||||
raw_coordinates: Union[str, list[str]] = ""
|
||||
enabled_in_config: Optional[bool] = Field(
|
||||
default=None, title="Keep track of original state of object mask."
|
||||
)
|
||||
|
||||
@field_serializer("coordinates", when_used="json")
|
||||
def serialize_coordinates(self, value: Any, info):
|
||||
return self.raw_coordinates if self.raw_coordinates else value
|
||||
|
||||
@field_serializer("raw_coordinates", when_used="json")
|
||||
def serialize_raw_coordinates(self, value: Any, info):
|
||||
return None
|
||||
|
||||
def get_formatted_name(self, mask_id: str) -> str:
|
||||
"""Return the friendly name if set, otherwise return a formatted version of the mask ID."""
|
||||
if self.friendly_name:
|
||||
return self.friendly_name
|
||||
return mask_id.replace("_", " ").title()
|
||||
@@ -1,8 +1,9 @@
|
||||
from typing import Any, Optional, Union
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import Field, field_serializer
|
||||
|
||||
from ..base import FrigateBaseModel
|
||||
from .mask import MotionMaskConfig
|
||||
|
||||
__all__ = ["MotionConfig"]
|
||||
|
||||
@@ -52,8 +53,8 @@ class MotionConfig(FrigateBaseModel):
|
||||
title="Frame height",
|
||||
description="Height in pixels to scale frames to when computing motion.",
|
||||
)
|
||||
mask: Union[str, list[str]] = Field(
|
||||
default="",
|
||||
mask: dict[str, Optional[MotionMaskConfig]] = Field(
|
||||
default_factory=dict,
|
||||
title="Mask coordinates",
|
||||
description="Ordered x,y coordinates defining the motion mask polygon used to include/exclude areas.",
|
||||
)
|
||||
@@ -67,11 +68,15 @@ class MotionConfig(FrigateBaseModel):
|
||||
title="Original motion state",
|
||||
description="Indicates whether motion detection was enabled in the original static configuration.",
|
||||
)
|
||||
raw_mask: Union[str, list[str]] = ""
|
||||
raw_mask: dict[str, Optional[MotionMaskConfig]] = Field(
|
||||
default_factory=dict, exclude=True
|
||||
)
|
||||
|
||||
@field_serializer("mask", when_used="json")
|
||||
def serialize_mask(self, value: Any, info):
|
||||
return self.raw_mask
|
||||
if self.raw_mask:
|
||||
return self.raw_mask
|
||||
return value
|
||||
|
||||
@field_serializer("raw_mask", when_used="json")
|
||||
def serialize_raw_mask(self, value: Any, info):
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Any, Optional, Union
|
||||
from pydantic import Field, PrivateAttr, field_serializer, field_validator
|
||||
|
||||
from ..base import FrigateBaseModel
|
||||
from .mask import ObjectMaskConfig
|
||||
|
||||
__all__ = ["ObjectConfig", "GenAIObjectConfig", "FilterConfig"]
|
||||
|
||||
@@ -41,16 +42,20 @@ class FilterConfig(FrigateBaseModel):
|
||||
title="Minimum confidence",
|
||||
description="Minimum single-frame detection confidence required for the object to be counted.",
|
||||
)
|
||||
mask: Optional[Union[str, list[str]]] = Field(
|
||||
default=None,
|
||||
mask: dict[str, Optional[ObjectMaskConfig]] = Field(
|
||||
default_factory=dict,
|
||||
title="Filter mask",
|
||||
description="Polygon coordinates defining where this filter applies within the frame.",
|
||||
)
|
||||
raw_mask: Union[str, list[str]] = ""
|
||||
raw_mask: dict[str, Optional[ObjectMaskConfig]] = Field(
|
||||
default_factory=dict, exclude=True
|
||||
)
|
||||
|
||||
@field_serializer("mask", when_used="json")
|
||||
def serialize_mask(self, value: Any, info):
|
||||
return self.raw_mask
|
||||
if self.raw_mask:
|
||||
return self.raw_mask
|
||||
return value
|
||||
|
||||
@field_serializer("raw_mask", when_used="json")
|
||||
def serialize_raw_mask(self, value: Any, info):
|
||||
@@ -139,11 +144,14 @@ class ObjectConfig(FrigateBaseModel):
|
||||
title="Object filters",
|
||||
description="Filters applied to detected objects to reduce false positives (area, ratio, confidence).",
|
||||
)
|
||||
mask: Union[str, list[str]] = Field(
|
||||
default="",
|
||||
mask: dict[str, Optional[ObjectMaskConfig]] = Field(
|
||||
default_factory=dict,
|
||||
title="Object mask",
|
||||
description="Mask polygon used to prevent object detection in specified areas.",
|
||||
)
|
||||
raw_mask: dict[str, Optional[ObjectMaskConfig]] = Field(
|
||||
default_factory=dict, exclude=True
|
||||
)
|
||||
genai: GenAIObjectConfig = Field(
|
||||
default_factory=GenAIObjectConfig,
|
||||
title="GenAI object config",
|
||||
@@ -166,3 +174,13 @@ class ObjectConfig(FrigateBaseModel):
|
||||
enabled_labels.update(camera.objects.track)
|
||||
|
||||
self._all_objects = list(enabled_labels)
|
||||
|
||||
@field_serializer("mask", when_used="json")
|
||||
def serialize_mask(self, value: Any, info):
|
||||
if self.raw_mask:
|
||||
return self.raw_mask
|
||||
return value
|
||||
|
||||
@field_serializer("raw_mask", when_used="json")
|
||||
def serialize_raw_mask(self, value: Any, info):
|
||||
return None
|
||||
|
||||
@@ -18,6 +18,14 @@ class ZoneConfig(BaseModel):
|
||||
title="Zone name",
|
||||
description="A user-friendly name for the zone, displayed in the Frigate UI. If not set, a formatted version of the zone name will be used.",
|
||||
)
|
||||
enabled: bool = Field(
|
||||
default=True,
|
||||
title="Enabled",
|
||||
description="Enable or disable this zone. Disabled zones are ignored at runtime.",
|
||||
)
|
||||
enabled_in_config: Optional[bool] = Field(
|
||||
default=None, title="Keep track of original state of zone."
|
||||
)
|
||||
filters: dict[str, FilterConfig] = Field(
|
||||
default_factory=dict,
|
||||
title="Zone filters",
|
||||
|
||||
Reference in New Issue
Block a user