diff --git a/frigate/app.py b/frigate/app.py index 6c75ebff7..85267e01c 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -17,7 +17,7 @@ from playhouse.sqliteq import SqliteQueueDatabase import frigate.util as util from frigate.api.auth import hash_password from frigate.api.fastapi_app import create_fastapi_app -from frigate.camera import CameraMetrics +from frigate.camera import CameraMetrics, PTZMetrics from frigate.comms.config_updater import ConfigPublisher from frigate.comms.dispatcher import Communicator, Dispatcher from frigate.comms.event_metadata_updater import ( @@ -67,7 +67,6 @@ from frigate.stats.emitter import StatsEmitter from frigate.stats.util import stats_init from frigate.storage import StorageMaintainer from frigate.timeline import TimelineProcessor -from frigate.types import PTZMetricsTypes from frigate.util.builtin import empty_and_close_queue from frigate.util.object import get_camera_regions_grid from frigate.version import VERSION @@ -87,7 +86,7 @@ class FrigateApp: self.detection_shms: list[mp.shared_memory.SharedMemory] = [] self.log_queue: Queue = mp.Queue() self.camera_metrics: dict[str, CameraMetrics] = {} - self.ptz_metrics: dict[str, PTZMetricsTypes] = {} + self.ptz_metrics: dict[str, PTZMetrics] = {} self.processes: dict[str, int] = {} self.region_grids: dict[str, list[list[dict[str, int]]]] = {} self.config = config @@ -111,36 +110,11 @@ class FrigateApp: # create camera_metrics for camera_name in self.config.cameras.keys(): self.camera_metrics[camera_name] = CameraMetrics() - self.ptz_metrics[camera_name] = { - "ptz_autotracker_enabled": mp.Value( # type: ignore[typeddict-item] - # issue https://github.com/python/typeshed/issues/8799 - # from mypy 0.981 onwards - "i", - self.config.cameras[camera_name].onvif.autotracking.enabled, - ), - "ptz_tracking_active": mp.Event(), - "ptz_motor_stopped": mp.Event(), - "ptz_reset": mp.Event(), - "ptz_start_time": mp.Value("d", 0.0), # type: ignore[typeddict-item] - # issue https://github.com/python/typeshed/issues/8799 - # from mypy 0.981 onwards - "ptz_stop_time": mp.Value("d", 0.0), # type: ignore[typeddict-item] - # issue https://github.com/python/typeshed/issues/8799 - # from mypy 0.981 onwards - "ptz_frame_time": mp.Value("d", 0.0), # type: ignore[typeddict-item] - # issue https://github.com/python/typeshed/issues/8799 - # from mypy 0.981 onwards - "ptz_zoom_level": mp.Value("d", 0.0), # type: ignore[typeddict-item] - # issue https://github.com/python/typeshed/issues/8799 - # from mypy 0.981 onwards - "ptz_max_zoom": mp.Value("d", 0.0), # type: ignore[typeddict-item] - # issue https://github.com/python/typeshed/issues/8799 - # from mypy 0.981 onwards - "ptz_min_zoom": mp.Value("d", 0.0), # type: ignore[typeddict-item] - # issue https://github.com/python/typeshed/issues/8799 - # from mypy 0.981 onwards - } - self.ptz_metrics[camera_name]["ptz_motor_stopped"].set() + self.ptz_metrics[camera_name] = PTZMetrics( + autotracker_enabled=self.config.cameras[ + camera_name + ].onvif.autotracking.enabled + ) def init_queues(self) -> None: # Queue for cameras to push tracked objects to diff --git a/frigate/camera/__init__.py b/frigate/camera/__init__.py index 9a7ce447d..456751c52 100644 --- a/frigate/camera/__init__.py +++ b/frigate/camera/__init__.py @@ -36,3 +36,33 @@ class CameraMetrics: self.capture_process = None self.ffmpeg_pid = mp.Value("i", 0) + +class PTZMetrics: + autotracker_enabled: Synchronized + + start_time: Synchronized + stop_time: Synchronized + frame_time: Synchronized + zoom_level: Synchronized + max_zoom: Synchronized + min_zoom: Synchronized + + tracking_active: Event + motor_stopped: Event + reset: Event + + def __init__(self, *, autotracker_enabled: bool): + self.autotracker_enabled = mp.Value("i", autotracker_enabled) + + self.start_time = mp.Value("d", 0) + self.stop_time = mp.Value("d", 0) + self.frame_time = mp.Value("d", 0) + self.zoom_level = mp.Value("d", 0) + self.max_zoom = mp.Value("d", 0) + self.min_zoom = mp.Value("d", 0) + + self.tracking_active = mp.Event() + self.motor_stopped = mp.Event() + self.reset = mp.Event() + + self.motor_stopped.set() diff --git a/frigate/comms/dispatcher.py b/frigate/comms/dispatcher.py index b55707ab5..a987f6a38 100644 --- a/frigate/comms/dispatcher.py +++ b/frigate/comms/dispatcher.py @@ -6,6 +6,7 @@ import logging from abc import ABC, abstractmethod from typing import Any, Callable, Optional +from frigate.camera import PTZMetrics from frigate.comms.config_updater import ConfigPublisher from frigate.config import BirdseyeModeEnum, FrigateConfig from frigate.const import ( @@ -19,7 +20,6 @@ from frigate.const import ( ) from frigate.models import Event, Previews, Recordings, ReviewSegment from frigate.ptz.onvif import OnvifCommandEnum, OnvifController -from frigate.types import PTZMetricsTypes from frigate.util.object import get_camera_regions_grid from frigate.util.services import restart_frigate @@ -53,7 +53,7 @@ class Dispatcher: config: FrigateConfig, config_updater: ConfigPublisher, onvif: OnvifController, - ptz_metrics: dict[str, PTZMetricsTypes], + ptz_metrics: dict[str, PTZMetrics], communicators: list[Communicator], ) -> None: self.config = config @@ -251,16 +251,16 @@ class Dispatcher: "Autotracking must be enabled in the config to be turned on via MQTT." ) return - if not self.ptz_metrics[camera_name]["ptz_autotracker_enabled"].value: + if not self.ptz_metrics[camera_name].autotracker_enabled.value: logger.info(f"Turning on ptz autotracker for {camera_name}") - self.ptz_metrics[camera_name]["ptz_autotracker_enabled"].value = True - self.ptz_metrics[camera_name]["ptz_start_time"].value = 0 + self.ptz_metrics[camera_name].autotracker_enabled.value = True + self.ptz_metrics[camera_name].start_time.value = 0 ptz_autotracker_settings.enabled = True elif payload == "OFF": - if self.ptz_metrics[camera_name]["ptz_autotracker_enabled"].value: + if self.ptz_metrics[camera_name].autotracker_enabled.value: logger.info(f"Turning off ptz autotracker for {camera_name}") - self.ptz_metrics[camera_name]["ptz_autotracker_enabled"].value = False - self.ptz_metrics[camera_name]["ptz_start_time"].value = 0 + self.ptz_metrics[camera_name].autotracker_enabled.value = False + self.ptz_metrics[camera_name].start_time.value = 0 ptz_autotracker_settings.enabled = False self.publish(f"{camera_name}/ptz_autotracker/state", payload, retain=True) diff --git a/frigate/ptz/autotrack.py b/frigate/ptz/autotrack.py index 80195f5d8..93231c92c 100644 --- a/frigate/ptz/autotrack.py +++ b/frigate/ptz/autotrack.py @@ -18,6 +18,7 @@ from norfair.camera_motion import ( TranslationTransformationGetter, ) +from frigate.camera import PTZMetrics from frigate.comms.dispatcher import Dispatcher from frigate.config import CameraConfig, FrigateConfig, ZoomingModeEnum from frigate.const import ( @@ -31,7 +32,6 @@ from frigate.const import ( CONFIG_DIR, ) from frigate.ptz.onvif import OnvifController -from frigate.types import PTZMetricsTypes from frigate.util.builtin import update_yaml_file from frigate.util.image import SharedMemoryFrameManager, intersection_over_union @@ -50,23 +50,23 @@ def ptz_moving_at_frame_time(frame_time, ptz_start_time, ptz_stop_time): class PtzMotionEstimator: def __init__( - self, config: CameraConfig, ptz_metrics: dict[str, PTZMetricsTypes] + self, config: CameraConfig, ptz_metrics: dict[str, PTZMetrics] ) -> None: self.frame_manager = SharedMemoryFrameManager() self.norfair_motion_estimator = None self.camera_config = config self.coord_transformations = None self.ptz_metrics = ptz_metrics - self.ptz_start_time = self.ptz_metrics["ptz_start_time"] - self.ptz_stop_time = self.ptz_metrics["ptz_stop_time"] + self.ptz_start_time = self.ptz_metrics.start_time + self.ptz_stop_time = self.ptz_metrics.stop_time - self.ptz_metrics["ptz_reset"].set() + self.ptz_metrics.reset.set() logger.debug(f"{config.name}: Motion estimator init") def motion_estimator(self, detections, frame_time, camera): # If we've just started up or returned to our preset, reset motion estimator for new tracking session - if self.ptz_metrics["ptz_reset"].is_set(): - self.ptz_metrics["ptz_reset"].clear() + if self.ptz_metrics.reset.is_set(): + self.ptz_metrics.reset.clear() # homography is nice (zooming) but slow, translation is pan/tilt only but fast. if ( @@ -148,7 +148,7 @@ class PtzAutoTrackerThread(threading.Thread): self, config: FrigateConfig, onvif: OnvifController, - ptz_metrics: dict[str, PTZMetricsTypes], + ptz_metrics: dict[str, PTZMetrics], dispatcher: Dispatcher, stop_event: MpEvent, ) -> None: @@ -182,7 +182,7 @@ class PtzAutoTracker: self, config: FrigateConfig, onvif: OnvifController, - ptz_metrics: PTZMetricsTypes, + ptz_metrics: PTZMetrics, dispatcher: Dispatcher, stop_event: MpEvent, ) -> None: @@ -248,7 +248,7 @@ class PtzAutoTracker: f"Disabling autotracking for {camera}: onvif connection failed" ) camera_config.onvif.autotracking.enabled = False - self.ptz_metrics[camera]["ptz_autotracker_enabled"].value = False + self.ptz_metrics[camera].autotracker_enabled.value = False return if not self.onvif.cams[camera]["init"]: @@ -257,7 +257,7 @@ class PtzAutoTracker: f"Disabling autotracking for {camera}: Unable to initialize onvif" ) camera_config.onvif.autotracking.enabled = False - self.ptz_metrics[camera]["ptz_autotracker_enabled"].value = False + self.ptz_metrics[camera].autotracker_enabled.value = False return if "pt-r-fov" not in self.onvif.cams[camera]["features"]: @@ -265,7 +265,7 @@ class PtzAutoTracker: f"Disabling autotracking for {camera}: FOV relative movement not supported" ) camera_config.onvif.autotracking.enabled = False - self.ptz_metrics[camera]["ptz_autotracker_enabled"].value = False + self.ptz_metrics[camera].autotracker_enabled.value = False return move_status_supported = self.onvif.get_service_capabilities(camera) @@ -275,7 +275,7 @@ class PtzAutoTracker: f"Disabling autotracking for {camera}: ONVIF MoveStatus not supported" ) camera_config.onvif.autotracking.enabled = False - self.ptz_metrics[camera]["ptz_autotracker_enabled"].value = False + self.ptz_metrics[camera].autotracker_enabled.value = False return if self.onvif.cams[camera]["init"]: @@ -295,12 +295,16 @@ class PtzAutoTracker: float(val) for val in camera_config.onvif.autotracking.movement_weights ] - self.ptz_metrics[camera][ - "ptz_min_zoom" - ].value = camera_config.onvif.autotracking.movement_weights[0] - self.ptz_metrics[camera][ - "ptz_max_zoom" - ].value = camera_config.onvif.autotracking.movement_weights[1] + self.ptz_metrics[ + camera + ].min_zoom.value = ( + camera_config.onvif.autotracking.movement_weights[0] + ) + self.ptz_metrics[ + camera + ].max_zoom.value = ( + camera_config.onvif.autotracking.movement_weights[1] + ) self.intercept[camera] = ( camera_config.onvif.autotracking.movement_weights[2] ) @@ -309,7 +313,7 @@ class PtzAutoTracker: ) else: camera_config.onvif.autotracking.enabled = False - self.ptz_metrics[camera]["ptz_autotracker_enabled"].value = False + self.ptz_metrics[camera].autotracker_enabled.value = False logger.warning( f"Autotracker recalibration is required for {camera}. Disabling autotracking." ) @@ -317,7 +321,7 @@ class PtzAutoTracker: if camera_config.onvif.autotracking.calibrate_on_startup: self._calibrate_camera(camera) - self.ptz_metrics[camera]["ptz_tracking_active"].clear() + self.ptz_metrics[camera].tracking_active.clear() self.dispatcher.publish(f"{camera}/ptz_autotracker/active", "OFF", retain=False) self.autotracker_init[camera] = True @@ -363,10 +367,10 @@ class PtzAutoTracker: 1, ) - while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set(): + while not self.ptz_metrics[camera].motor_stopped.is_set(): self.onvif.get_camera_status(camera) - zoom_out_values.append(self.ptz_metrics[camera]["ptz_zoom_level"].value) + zoom_out_values.append(self.ptz_metrics[camera].zoom_level.value) self.onvif._zoom_absolute( camera, @@ -374,10 +378,10 @@ class PtzAutoTracker: 1, ) - while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set(): + while not self.ptz_metrics[camera].motor_stopped.is_set(): self.onvif.get_camera_status(camera) - zoom_in_values.append(self.ptz_metrics[camera]["ptz_zoom_level"].value) + zoom_in_values.append(self.ptz_metrics[camera].zoom_level.value) if ( self.config.cameras[camera].onvif.autotracking.zooming @@ -392,12 +396,10 @@ class PtzAutoTracker: 1, ) - while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set(): + while not self.ptz_metrics[camera].motor_stopped.is_set(): self.onvif.get_camera_status(camera) - zoom_out_values.append( - self.ptz_metrics[camera]["ptz_zoom_level"].value - ) + zoom_out_values.append(self.ptz_metrics[camera].zoom_level.value) # relative move to 0.01 self.onvif._move_relative( @@ -408,33 +410,31 @@ class PtzAutoTracker: 1, ) - while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set(): + while not self.ptz_metrics[camera].motor_stopped.is_set(): self.onvif.get_camera_status(camera) - zoom_in_values.append( - self.ptz_metrics[camera]["ptz_zoom_level"].value - ) + zoom_in_values.append(self.ptz_metrics[camera].zoom_level.value) - self.ptz_metrics[camera]["ptz_max_zoom"].value = max(zoom_in_values) - self.ptz_metrics[camera]["ptz_min_zoom"].value = min(zoom_out_values) + self.ptz_metrics[camera].max_zoom.value = max(zoom_in_values) + self.ptz_metrics[camera].min_zoom.value = min(zoom_out_values) logger.debug( - f'{camera}: Calibration values: max zoom: {self.ptz_metrics[camera]["ptz_max_zoom"].value}, min zoom: {self.ptz_metrics[camera]["ptz_min_zoom"].value}' + f"{camera}: Calibration values: max zoom: {self.ptz_metrics[camera].max_zoom.value}, min zoom: {self.ptz_metrics[camera].min_zoom.value}" ) else: - self.ptz_metrics[camera]["ptz_max_zoom"].value = 1 - self.ptz_metrics[camera]["ptz_min_zoom"].value = 0 + self.ptz_metrics[camera].max_zoom.value = 1 + self.ptz_metrics[camera].min_zoom.value = 0 self.onvif._move_to_preset( camera, self.config.cameras[camera].onvif.autotracking.return_preset.lower(), ) - self.ptz_metrics[camera]["ptz_reset"].set() - self.ptz_metrics[camera]["ptz_motor_stopped"].clear() + self.ptz_metrics[camera].reset.set() + self.ptz_metrics[camera].motor_stopped.clear() # Wait until the camera finishes moving - while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set(): + while not self.ptz_metrics[camera].motor_stopped.is_set(): self.onvif.get_camera_status(camera) for step in range(num_steps): @@ -445,7 +445,7 @@ class PtzAutoTracker: self.onvif._move_relative(camera, pan, tilt, 0, 1) # Wait until the camera finishes moving - while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set(): + while not self.ptz_metrics[camera].motor_stopped.is_set(): self.onvif.get_camera_status(camera) stop_time = time.time() @@ -462,11 +462,11 @@ class PtzAutoTracker: camera, self.config.cameras[camera].onvif.autotracking.return_preset.lower(), ) - self.ptz_metrics[camera]["ptz_reset"].set() - self.ptz_metrics[camera]["ptz_motor_stopped"].clear() + self.ptz_metrics[camera].reset.set() + self.ptz_metrics[camera].motor_stopped.clear() # Wait until the camera finishes moving - while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set(): + while not self.ptz_metrics[camera].motor_stopped.is_set(): self.onvif.get_camera_status(camera) logger.info( @@ -512,8 +512,8 @@ class PtzAutoTracker: self.config.cameras[camera].onvif.autotracking.movement_weights = ", ".join( str(v) for v in [ - self.ptz_metrics[camera]["ptz_min_zoom"].value, - self.ptz_metrics[camera]["ptz_max_zoom"].value, + self.ptz_metrics[camera].min_zoom.value, + self.ptz_metrics[camera].max_zoom.value, self.intercept[camera], *self.move_coefficients[camera], ] @@ -649,8 +649,8 @@ class PtzAutoTracker: # if we're receiving move requests during a PTZ move, ignore them if ptz_moving_at_frame_time( frame_time, - self.ptz_metrics[camera]["ptz_start_time"].value, - self.ptz_metrics[camera]["ptz_stop_time"].value, + self.ptz_metrics[camera].start_time.value, + self.ptz_metrics[camera].stop_time.value, ): # instead of dequeueing this might be a good place to preemptively move based # on an estimate - for fast moving objects, etc. @@ -671,19 +671,17 @@ class PtzAutoTracker: self.onvif._move_relative(camera, pan, tilt, 0, 1) # Wait until the camera finishes moving - while not self.ptz_metrics[camera][ - "ptz_motor_stopped" - ].is_set(): + while not self.ptz_metrics[camera].motor_stopped.is_set(): self.onvif.get_camera_status(camera) if ( zoom > 0 - and self.ptz_metrics[camera]["ptz_zoom_level"].value != zoom + and self.ptz_metrics[camera].zoom_level.value != zoom ): self.onvif._zoom_absolute(camera, zoom, 1) # Wait until the camera finishes moving - while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set(): + while not self.ptz_metrics[camera].motor_stopped.is_set(): self.onvif.get_camera_status(camera) if self.config.cameras[camera].onvif.autotracking.movement_weights: @@ -691,7 +689,7 @@ class PtzAutoTracker: f"{camera}: Predicted movement time: {self._predict_movement_time(camera, pan, tilt)}" ) logger.debug( - f'{camera}: Actual movement time: {self.ptz_metrics[camera]["ptz_stop_time"].value-self.ptz_metrics[camera]["ptz_start_time"].value}' + f"{camera}: Actual movement time: {self.ptz_metrics[camera].stop_time.value-self.ptz_metrics[camera].start_time.value}" ) # save metrics for better estimate calculations @@ -709,12 +707,12 @@ class PtzAutoTracker: { "pan": pan, "tilt": tilt, - "start_timestamp": self.ptz_metrics[camera][ - "ptz_start_time" - ].value, - "end_timestamp": self.ptz_metrics[camera][ - "ptz_stop_time" - ].value, + "start_timestamp": self.ptz_metrics[ + camera + ].start_time.value, + "end_timestamp": self.ptz_metrics[ + camera + ].stop_time.value, } ) @@ -734,8 +732,8 @@ class PtzAutoTracker: return clipped, diff if ( - frame_time > self.ptz_metrics[camera]["ptz_start_time"].value - and frame_time > self.ptz_metrics[camera]["ptz_stop_time"].value + frame_time > self.ptz_metrics[camera].start_time.value + and frame_time > self.ptz_metrics[camera].stop_time.value and not self.move_queue_locks[camera].locked() ): # we can split up any large moves caused by velocity estimated movements if necessary @@ -954,12 +952,12 @@ class PtzAutoTracker: ) at_max_zoom = ( - self.ptz_metrics[camera]["ptz_zoom_level"].value - == self.ptz_metrics[camera]["ptz_max_zoom"].value + self.ptz_metrics[camera].zoom_level.value + == self.ptz_metrics[camera].max_zoom.value ) at_min_zoom = ( - self.ptz_metrics[camera]["ptz_zoom_level"].value - == self.ptz_metrics[camera]["ptz_min_zoom"].value + self.ptz_metrics[camera].zoom_level.value + == self.ptz_metrics[camera].min_zoom.value ) # debug zooming @@ -1100,7 +1098,7 @@ class PtzAutoTracker: zoom = 0 result = None - current_zoom_level = self.ptz_metrics[camera]["ptz_zoom_level"].value + current_zoom_level = self.ptz_metrics[camera].zoom_level.value target_box = max( obj.obj_data["box"][2] - obj.obj_data["box"][0], obj.obj_data["box"][3] - obj.obj_data["box"][1], @@ -1123,8 +1121,8 @@ class PtzAutoTracker: ) is not None: # divide zoom in 10 increments and always zoom out more than in level = ( - self.ptz_metrics[camera]["ptz_max_zoom"].value - - self.ptz_metrics[camera]["ptz_min_zoom"].value + self.ptz_metrics[camera].max_zoom.value + - self.ptz_metrics[camera].min_zoom.value ) / 20 if result: zoom = min(1.0, current_zoom_level + level) @@ -1219,7 +1217,7 @@ class PtzAutoTracker: logger.debug( f"{camera}: New object: {obj.obj_data['id']} {obj.obj_data['box']} {obj.obj_data['frame_time']}" ) - self.ptz_metrics[camera]["ptz_tracking_active"].set() + self.ptz_metrics[camera].tracking_active.set() self.dispatcher.publish( f"{camera}/ptz_autotracker/active", "ON", retain=False ) @@ -1243,8 +1241,8 @@ class PtzAutoTracker: if not ptz_moving_at_frame_time( obj.obj_data["frame_time"], - self.ptz_metrics[camera]["ptz_start_time"].value, - self.ptz_metrics[camera]["ptz_stop_time"].value, + self.ptz_metrics[camera].start_time.value, + self.ptz_metrics[camera].stop_time.value, ): if self.tracked_object_metrics[camera]["below_distance_threshold"]: logger.debug( @@ -1325,7 +1323,7 @@ class PtzAutoTracker: if not self.autotracker_init[camera]: self._autotracker_setup(self.config.cameras[camera], camera) # regularly update camera status - if not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set(): + if not self.ptz_metrics[camera].motor_stopped.is_set(): self.onvif.get_camera_status(camera) # return to preset if tracking is over @@ -1334,7 +1332,7 @@ class PtzAutoTracker: and self.tracked_object_history[camera] and ( # might want to use a different timestamp here? - self.ptz_metrics[camera]["ptz_frame_time"].value + self.ptz_metrics[camera].frame_time.value - self.tracked_object_history[camera][-1]["frame_time"] >= autotracker_config.timeout ) @@ -1348,9 +1346,9 @@ class PtzAutoTracker: while not self.move_queues[camera].empty(): self.move_queues[camera].get() - self.ptz_metrics[camera]["ptz_motor_stopped"].wait() + self.ptz_metrics[camera].motor_stopped.wait() logger.debug( - f"{camera}: Time is {self.ptz_metrics[camera]['ptz_frame_time'].value}, returning to preset: {autotracker_config.return_preset}" + f"{camera}: Time is {self.ptz_metrics[camera].frame_time.value}, returning to preset: {autotracker_config.return_preset}" ) self.onvif._move_to_preset( camera, @@ -1358,11 +1356,11 @@ class PtzAutoTracker: ) # update stored zoom level from preset - if not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set(): + if not self.ptz_metrics[camera].motor_stopped.is_set(): self.onvif.get_camera_status(camera) - self.ptz_metrics[camera]["ptz_tracking_active"].clear() + self.ptz_metrics[camera].tracking_active.clear() self.dispatcher.publish( f"{camera}/ptz_autotracker/active", "OFF", retain=False ) - self.ptz_metrics[camera]["ptz_reset"].set() + self.ptz_metrics[camera].reset.set() diff --git a/frigate/ptz/onvif.py b/frigate/ptz/onvif.py index dc7a00e78..bbcdf5188 100644 --- a/frigate/ptz/onvif.py +++ b/frigate/ptz/onvif.py @@ -10,8 +10,8 @@ from onvif import ONVIFCamera, ONVIFError from zeep.exceptions import Fault, TransportError from zeep.transports import Transport +from frigate.camera import PTZMetrics from frigate.config import FrigateConfig, ZoomingModeEnum -from frigate.types import PTZMetricsTypes from frigate.util.builtin import find_by_key logger = logging.getLogger(__name__) @@ -33,8 +33,10 @@ class OnvifCommandEnum(str, Enum): class OnvifController: + ptz_metrics: dict[str, PTZMetrics] + def __init__( - self, config: FrigateConfig, ptz_metrics: dict[str, PTZMetricsTypes] + self, config: FrigateConfig, ptz_metrics: dict[str, PTZMetrics] ) -> None: self.cams: dict[str, ONVIFCamera] = {} self.config = config @@ -389,14 +391,14 @@ class OnvifController: return self.cams[camera_name]["active"] = True - self.ptz_metrics[camera_name]["ptz_motor_stopped"].clear() + self.ptz_metrics[camera_name].motor_stopped.clear() logger.debug( - f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}" + f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name].frame_time.value}" ) - self.ptz_metrics[camera_name]["ptz_start_time"].value = self.ptz_metrics[ + self.ptz_metrics[camera_name].start_time.value = self.ptz_metrics[ camera_name - ]["ptz_frame_time"].value - self.ptz_metrics[camera_name]["ptz_stop_time"].value = 0 + ].frame_time.value + self.ptz_metrics[camera_name].stop_time.value = 0 onvif: ONVIFCamera = self.cams[camera_name]["onvif"] move_request = self.cams[camera_name]["relative_move_request"] @@ -464,9 +466,9 @@ class OnvifController: return self.cams[camera_name]["active"] = True - self.ptz_metrics[camera_name]["ptz_motor_stopped"].clear() - self.ptz_metrics[camera_name]["ptz_start_time"].value = 0 - self.ptz_metrics[camera_name]["ptz_stop_time"].value = 0 + self.ptz_metrics[camera_name].motor_stopped.clear() + self.ptz_metrics[camera_name].start_time.value = 0 + self.ptz_metrics[camera_name].stop_time.value = 0 move_request = self.cams[camera_name]["move_request"] onvif: ONVIFCamera = self.cams[camera_name]["onvif"] preset_token = self.cams[camera_name]["presets"][preset] @@ -515,14 +517,14 @@ class OnvifController: return self.cams[camera_name]["active"] = True - self.ptz_metrics[camera_name]["ptz_motor_stopped"].clear() + self.ptz_metrics[camera_name].motor_stopped.clear() logger.debug( - f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}" + f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name].frame_time.value}" ) - self.ptz_metrics[camera_name]["ptz_start_time"].value = self.ptz_metrics[ + self.ptz_metrics[camera_name].start_time.value = self.ptz_metrics[ camera_name - ]["ptz_frame_time"].value - self.ptz_metrics[camera_name]["ptz_stop_time"].value = 0 + ].frame_time.value + self.ptz_metrics[camera_name].stop_time.value = 0 onvif: ONVIFCamera = self.cams[camera_name]["onvif"] move_request = self.cams[camera_name]["absolute_move_request"] @@ -653,36 +655,36 @@ class OnvifController: if pan_tilt_status == "IDLE" and (zoom_status is None or zoom_status == "IDLE"): self.cams[camera_name]["active"] = False - if not self.ptz_metrics[camera_name]["ptz_motor_stopped"].is_set(): - self.ptz_metrics[camera_name]["ptz_motor_stopped"].set() + if not self.ptz_metrics[camera_name].motor_stopped.is_set(): + self.ptz_metrics[camera_name].motor_stopped.set() logger.debug( - f"{camera_name}: PTZ stop time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}" + f"{camera_name}: PTZ stop time: {self.ptz_metrics[camera_name].frame_time.value}" ) - self.ptz_metrics[camera_name]["ptz_stop_time"].value = self.ptz_metrics[ + self.ptz_metrics[camera_name].stop_time.value = self.ptz_metrics[ camera_name - ]["ptz_frame_time"].value + ].frame_time.value else: self.cams[camera_name]["active"] = True - if self.ptz_metrics[camera_name]["ptz_motor_stopped"].is_set(): - self.ptz_metrics[camera_name]["ptz_motor_stopped"].clear() + if self.ptz_metrics[camera_name].motor_stopped.is_set(): + self.ptz_metrics[camera_name].motor_stopped.clear() logger.debug( - f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}" + f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name].frame_time.value}" ) - self.ptz_metrics[camera_name][ - "ptz_start_time" - ].value = self.ptz_metrics[camera_name]["ptz_frame_time"].value - self.ptz_metrics[camera_name]["ptz_stop_time"].value = 0 + self.ptz_metrics[camera_name].start_time.value = self.ptz_metrics[ + camera_name + ].frame_time.value + self.ptz_metrics[camera_name].stop_time.value = 0 if ( self.config.cameras[camera_name].onvif.autotracking.zooming != ZoomingModeEnum.disabled ): # store absolute zoom level as 0 to 1 interpolated from the values of the camera - self.ptz_metrics[camera_name]["ptz_zoom_level"].value = numpy.interp( + self.ptz_metrics[camera_name].zoom_level.value = numpy.interp( round(status.Position.Zoom.x, 2), [0, 1], [ @@ -691,23 +693,23 @@ class OnvifController: ], ) logger.debug( - f'{camera_name}: Camera zoom level: {self.ptz_metrics[camera_name]["ptz_zoom_level"].value}' + f"{camera_name}: Camera zoom level: {self.ptz_metrics[camera_name].zoom_level.value}" ) # some hikvision cams won't update MoveStatus, so warn if it hasn't changed if ( - not self.ptz_metrics[camera_name]["ptz_motor_stopped"].is_set() - and not self.ptz_metrics[camera_name]["ptz_reset"].is_set() - and self.ptz_metrics[camera_name]["ptz_start_time"].value != 0 - and self.ptz_metrics[camera_name]["ptz_frame_time"].value - > (self.ptz_metrics[camera_name]["ptz_start_time"].value + 10) - and self.ptz_metrics[camera_name]["ptz_stop_time"].value == 0 + not self.ptz_metrics[camera_name].motor_stopped.is_set() + and not self.ptz_metrics[camera_name].reset.is_set() + and self.ptz_metrics[camera_name].start_time.value != 0 + and self.ptz_metrics[camera_name].frame_time.value + > (self.ptz_metrics[camera_name].start_time.value + 10) + and self.ptz_metrics[camera_name].stop_time.value == 0 ): logger.debug( - f'Start time: {self.ptz_metrics[camera_name]["ptz_start_time"].value}, Stop time: {self.ptz_metrics[camera_name]["ptz_stop_time"].value}, Frame time: {self.ptz_metrics[camera_name]["ptz_frame_time"].value}' + f"Start time: {self.ptz_metrics[camera_name].start_time.value}, Stop time: {self.ptz_metrics[camera_name].stop_time.value}, Frame time: {self.ptz_metrics[camera_name].frame_time.value}" ) # set the stop time so we don't come back into this again and spam the logs - self.ptz_metrics[camera_name]["ptz_stop_time"].value = self.ptz_metrics[ + self.ptz_metrics[camera_name].stop_time.value = self.ptz_metrics[ camera_name - ]["ptz_frame_time"].value + ].frame_time.value logger.warning(f"Camera {camera_name} is still in ONVIF 'MOVING' status.") diff --git a/frigate/track/norfair_tracker.py b/frigate/track/norfair_tracker.py index f3e5a0ac0..985a53165 100644 --- a/frigate/track/norfair_tracker.py +++ b/frigate/track/norfair_tracker.py @@ -12,10 +12,10 @@ from norfair import ( ) from norfair.drawing.drawer import Drawer +from frigate.camera import PTZMetrics from frigate.config import CameraConfig from frigate.ptz.autotrack import PtzMotionEstimator from frigate.track import ObjectTracker -from frigate.types import PTZMetricsTypes from frigate.util.image import intersection_over_union from frigate.util.object import average_boxes, median_of_boxes @@ -75,7 +75,7 @@ class NorfairTracker(ObjectTracker): def __init__( self, config: CameraConfig, - ptz_metrics: PTZMetricsTypes, + ptz_metrics: PTZMetrics, ): self.tracked_objects = {} self.untracked_object_boxes: list[list[int]] = [] @@ -85,7 +85,7 @@ class NorfairTracker(ObjectTracker): self.camera_config = config self.detect_config = config.detect self.ptz_metrics = ptz_metrics - self.ptz_autotracker_enabled = ptz_metrics["ptz_autotracker_enabled"] + self.ptz_autotracker_enabled = ptz_metrics.autotracker_enabled self.ptz_motion_estimator = {} self.camera_name = config.name self.track_id_map = {} diff --git a/frigate/types.py b/frigate/types.py index 9bc53dbf4..21f55e502 100644 --- a/frigate/types.py +++ b/frigate/types.py @@ -1,24 +1,9 @@ -from multiprocessing.sharedctypes import Synchronized -from multiprocessing.synchronize import Event from typing import TypedDict from frigate.camera import CameraMetrics from frigate.object_detection import ObjectDetectProcess -class PTZMetricsTypes(TypedDict): - ptz_autotracker_enabled: Synchronized - ptz_tracking_active: Event - ptz_motor_stopped: Event - ptz_reset: Event - ptz_start_time: Synchronized - ptz_stop_time: Synchronized - ptz_frame_time: Synchronized - ptz_zoom_level: Synchronized - ptz_max_zoom: Synchronized - ptz_min_zoom: Synchronized - - class StatsTrackingTypes(TypedDict): camera_metrics: dict[str, CameraMetrics] detectors: dict[str, ObjectDetectProcess] diff --git a/frigate/video.py b/frigate/video.py index 02d95864b..33e71a454 100755 --- a/frigate/video.py +++ b/frigate/video.py @@ -11,7 +11,7 @@ import time import cv2 from setproctitle import setproctitle -from frigate.camera import CameraMetrics +from frigate.camera import CameraMetrics, PTZMetrics from frigate.comms.config_updater import ConfigSubscriber from frigate.comms.inter_process import InterProcessRequestor from frigate.config import CameraConfig, DetectConfig, ModelConfig @@ -29,7 +29,6 @@ from frigate.object_detection import RemoteObjectDetector from frigate.ptz.autotrack import ptz_moving_at_frame_time from frigate.track import ObjectTracker from frigate.track.norfair_tracker import NorfairTracker -from frigate.types import PTZMetricsTypes from frigate.util.builtin import EventsPerSecond, get_tomorrow_at_time from frigate.util.image import ( FrameManager, @@ -424,7 +423,7 @@ def track_camera( result_connection, detected_objects_queue, camera_metrics: CameraMetrics, - ptz_metrics, + ptz_metrics: PTZMetrics, region_grid, ): stop_event = mp.Event() @@ -548,7 +547,7 @@ def process_frames( objects_to_track: list[str], object_filters, stop_event, - ptz_metrics: PTZMetricsTypes, + ptz_metrics: PTZMetrics, region_grid, exit_on_empty: bool = False, ): @@ -589,7 +588,7 @@ def process_frames( continue camera_metrics.detection_frame.value = frame_time - ptz_metrics["ptz_frame_time"].value = frame_time + ptz_metrics.frame_time.value = frame_time frame = frame_manager.get( f"{camera_name}{frame_time}", (frame_shape[0] * 3 // 2, frame_shape[1]) @@ -659,8 +658,8 @@ def process_frames( # ptz_moving_at_frame_time() always returns False for non-autotracking cameras if not motion_detector.is_calibrating() and not ptz_moving_at_frame_time( frame_time, - ptz_metrics["ptz_start_time"].value, - ptz_metrics["ptz_stop_time"].value, + ptz_metrics.start_time.value, + ptz_metrics.stop_time.value, ): # find motion boxes that are not inside tracked object regions standalone_motion_boxes = [