From 8cf418cf1e32c721204e56ab0b0113ba63fb6dd5 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 22 May 2025 14:20:15 -0600 Subject: [PATCH] Support zone and mask reset --- frigate/api/app.py | 5 ++-- frigate/config/base.py | 24 +++++++++++++++++++ frigate/motion/__init__.py | 12 +++++++++- frigate/motion/improved_motion.py | 15 +++++++----- frigate/video.py | 3 +++ .../settings/MotionMaskEditPane.tsx | 1 + 6 files changed, 50 insertions(+), 10 deletions(-) diff --git a/frigate/api/app.py b/frigate/api/app.py index a384718cb..38116f6d6 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -394,9 +394,8 @@ def config_set(request: Request, body: AppConfigSetBody): if body.update_topic and body.update_topic.startswith("config/cameras/"): _, _, camera, field = body.update_topic.split("/") - settings = config.model_dump( - mode="json", warnings="none", exclude_none=True - )["cameras"][camera][field] + + settings = config.get_nested_object(body.update_topic) request.app.config_publisher.publish_update( CameraConfigUpdateTopic(CameraConfigUpdateEnum[field], camera), settings, diff --git a/frigate/config/base.py b/frigate/config/base.py index 068a68acd..1e369e293 100644 --- a/frigate/config/base.py +++ b/frigate/config/base.py @@ -1,5 +1,29 @@ +from typing import Any + from pydantic import BaseModel, ConfigDict class FrigateBaseModel(BaseModel): model_config = ConfigDict(extra="forbid", protected_namespaces=()) + + def get_nested_object(self, path: str) -> Any: + parts = path.split("/") + obj = self + for part in parts: + if part == "config": + continue + + if isinstance(obj, BaseModel): + try: + obj = getattr(obj, part) + except AttributeError: + return None + elif isinstance(obj, dict): + try: + obj = obj[part] + except KeyError: + return None + else: + return None + + return obj diff --git a/frigate/motion/__init__.py b/frigate/motion/__init__.py index db5f25879..1f6785d5d 100644 --- a/frigate/motion/__init__.py +++ b/frigate/motion/__init__.py @@ -1,6 +1,8 @@ from abc import ABC, abstractmethod from typing import Tuple +from numpy import ndarray + from frigate.config import MotionConfig @@ -18,13 +20,21 @@ class MotionDetector(ABC): pass @abstractmethod - def detect(self, frame): + def detect(self, frame: ndarray) -> list: + """Detect motion and return motion boxes.""" pass @abstractmethod def is_calibrating(self): + """Return if motion is recalibrating.""" + pass + + @abstractmethod + def update_mask(self) -> None: + """Update the motion mask after a config change.""" pass @abstractmethod def stop(self): + """Stop any ongoing work and processes.""" pass diff --git a/frigate/motion/improved_motion.py b/frigate/motion/improved_motion.py index 10818ea70..77eae26a9 100644 --- a/frigate/motion/improved_motion.py +++ b/frigate/motion/improved_motion.py @@ -35,12 +35,7 @@ class ImprovedMotionDetector(MotionDetector): self.avg_frame = np.zeros(self.motion_frame_size, np.float32) self.motion_frame_count = 0 self.frame_counter = 0 - resized_mask = cv2.resize( - config.mask, - dsize=(self.motion_frame_size[1], self.motion_frame_size[0]), - interpolation=cv2.INTER_AREA, - ) - self.mask = np.where(resized_mask == [0]) + self.update_mask() self.save_images = False self.calibrating = True self.blur_radius = blur_radius @@ -236,6 +231,14 @@ class ImprovedMotionDetector(MotionDetector): return motion_boxes + def update_mask(self) -> None: + resized_mask = cv2.resize( + self.config.mask, + dsize=(self.motion_frame_size[1], self.motion_frame_size[0]), + interpolation=cv2.INTER_AREA, + ) + self.mask = np.where(resized_mask == [0]) + def stop(self) -> None: """stop the motion detector.""" pass diff --git a/frigate/video.py b/frigate/video.py index 2efabfd93..8ee974345 100755 --- a/frigate/video.py +++ b/frigate/video.py @@ -650,6 +650,9 @@ def process_frames( prev_enabled = camera_enabled camera_enabled = camera_config.enabled + if "motion" in updated_configs: + motion_detector.update_mask() + if ( not camera_enabled and prev_enabled != camera_enabled diff --git a/web/src/components/settings/MotionMaskEditPane.tsx b/web/src/components/settings/MotionMaskEditPane.tsx index 7aaeed70c..e5c1d9b21 100644 --- a/web/src/components/settings/MotionMaskEditPane.tsx +++ b/web/src/components/settings/MotionMaskEditPane.tsx @@ -161,6 +161,7 @@ export default function MotionMaskEditPane({ axios .put(`config/set?${queryString}`, { requires_restart: 0, + update_topic: `config/cameras/${polygon.camera}/motion`, }) .then((res) => { if (res.status === 200) {