mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	* config options * processing in maintainer * detect and process dedicated lpr plates * create camera type, add manual event and save snapshot * use const * ensure lpr events are always detections, typing fixes * docs * docs tweaks * add preprocessing and penalization for low confidence chars
		
			
				
	
	
		
			705 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			705 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import base64
 | |
| import datetime
 | |
| import json
 | |
| import logging
 | |
| import queue
 | |
| import threading
 | |
| from collections import defaultdict
 | |
| from enum import Enum
 | |
| from multiprocessing.synchronize import Event as MpEvent
 | |
| 
 | |
| import cv2
 | |
| import numpy as np
 | |
| from peewee import DoesNotExist
 | |
| 
 | |
| from frigate.camera.state import CameraState
 | |
| from frigate.comms.config_updater import ConfigSubscriber
 | |
| from frigate.comms.detections_updater import DetectionPublisher, DetectionTypeEnum
 | |
| from frigate.comms.dispatcher import Dispatcher
 | |
| from frigate.comms.event_metadata_updater import (
 | |
|     EventMetadataSubscriber,
 | |
|     EventMetadataTypeEnum,
 | |
| )
 | |
| from frigate.comms.events_updater import EventEndSubscriber, EventUpdatePublisher
 | |
| from frigate.comms.inter_process import InterProcessRequestor
 | |
| from frigate.config import (
 | |
|     CameraMqttConfig,
 | |
|     FrigateConfig,
 | |
|     RecordConfig,
 | |
|     SnapshotsConfig,
 | |
| )
 | |
| from frigate.const import UPDATE_CAMERA_ACTIVITY
 | |
| from frigate.events.types import EventStateEnum, EventTypeEnum
 | |
| from frigate.models import Event, Timeline
 | |
| from frigate.track.tracked_object import TrackedObject
 | |
| from frigate.util.image import SharedMemoryFrameManager
 | |
| 
 | |
| logger = logging.getLogger(__name__)
 | |
| 
 | |
| 
 | |
| class ManualEventState(str, Enum):
 | |
|     complete = "complete"
 | |
|     start = "start"
 | |
|     end = "end"
 | |
| 
 | |
| 
 | |
| class TrackedObjectProcessor(threading.Thread):
 | |
|     def __init__(
 | |
|         self,
 | |
|         config: FrigateConfig,
 | |
|         dispatcher: Dispatcher,
 | |
|         tracked_objects_queue,
 | |
|         ptz_autotracker_thread,
 | |
|         stop_event,
 | |
|     ):
 | |
|         super().__init__(name="detected_frames_processor")
 | |
|         self.config = config
 | |
|         self.dispatcher = dispatcher
 | |
|         self.tracked_objects_queue = tracked_objects_queue
 | |
|         self.stop_event: MpEvent = stop_event
 | |
|         self.camera_states: dict[str, CameraState] = {}
 | |
|         self.frame_manager = SharedMemoryFrameManager()
 | |
|         self.last_motion_detected: dict[str, float] = {}
 | |
|         self.ptz_autotracker_thread = ptz_autotracker_thread
 | |
| 
 | |
|         self.config_enabled_subscriber = ConfigSubscriber("config/enabled/")
 | |
| 
 | |
|         self.requestor = InterProcessRequestor()
 | |
|         self.detection_publisher = DetectionPublisher(DetectionTypeEnum.all)
 | |
|         self.event_sender = EventUpdatePublisher()
 | |
|         self.event_end_subscriber = EventEndSubscriber()
 | |
|         self.sub_label_subscriber = EventMetadataSubscriber(EventMetadataTypeEnum.all)
 | |
| 
 | |
|         self.camera_activity: dict[str, dict[str, any]] = {}
 | |
|         self.ongoing_manual_events: dict[str, str] = {}
 | |
| 
 | |
|         # {
 | |
|         #   'zone_name': {
 | |
|         #       'person': {
 | |
|         #           'camera_1': 2,
 | |
|         #           'camera_2': 1
 | |
|         #       }
 | |
|         #   }
 | |
|         # }
 | |
|         self.zone_data = defaultdict(lambda: defaultdict(dict))
 | |
|         self.active_zone_data = defaultdict(lambda: defaultdict(dict))
 | |
| 
 | |
|         def start(camera: str, obj: TrackedObject, frame_name: str):
 | |
|             self.event_sender.publish(
 | |
|                 (
 | |
|                     EventTypeEnum.tracked_object,
 | |
|                     EventStateEnum.start,
 | |
|                     camera,
 | |
|                     frame_name,
 | |
|                     obj.to_dict(),
 | |
|                 )
 | |
|             )
 | |
| 
 | |
|         def update(camera: str, obj: TrackedObject, frame_name: str):
 | |
|             obj.has_snapshot = self.should_save_snapshot(camera, obj)
 | |
|             obj.has_clip = self.should_retain_recording(camera, obj)
 | |
|             after = obj.to_dict()
 | |
|             message = {
 | |
|                 "before": obj.previous,
 | |
|                 "after": after,
 | |
|                 "type": "new" if obj.previous["false_positive"] else "update",
 | |
|             }
 | |
|             self.dispatcher.publish("events", json.dumps(message), retain=False)
 | |
|             obj.previous = after
 | |
|             self.event_sender.publish(
 | |
|                 (
 | |
|                     EventTypeEnum.tracked_object,
 | |
|                     EventStateEnum.update,
 | |
|                     camera,
 | |
|                     frame_name,
 | |
|                     obj.to_dict(),
 | |
|                 )
 | |
|             )
 | |
| 
 | |
|         def autotrack(camera: str, obj: TrackedObject, frame_name: str):
 | |
|             self.ptz_autotracker_thread.ptz_autotracker.autotrack_object(camera, obj)
 | |
| 
 | |
|         def end(camera: str, obj: TrackedObject, frame_name: str):
 | |
|             # populate has_snapshot
 | |
|             obj.has_snapshot = self.should_save_snapshot(camera, obj)
 | |
|             obj.has_clip = self.should_retain_recording(camera, obj)
 | |
| 
 | |
|             # write thumbnail to disk if it will be saved as an event
 | |
|             if obj.has_snapshot or obj.has_clip:
 | |
|                 obj.write_thumbnail_to_disk()
 | |
| 
 | |
|             # write the snapshot to disk
 | |
|             if obj.has_snapshot:
 | |
|                 obj.write_snapshot_to_disk()
 | |
| 
 | |
|             if not obj.false_positive:
 | |
|                 message = {
 | |
|                     "before": obj.previous,
 | |
|                     "after": obj.to_dict(),
 | |
|                     "type": "end",
 | |
|                 }
 | |
|                 self.dispatcher.publish("events", json.dumps(message), retain=False)
 | |
|                 self.ptz_autotracker_thread.ptz_autotracker.end_object(camera, obj)
 | |
| 
 | |
|             self.event_sender.publish(
 | |
|                 (
 | |
|                     EventTypeEnum.tracked_object,
 | |
|                     EventStateEnum.end,
 | |
|                     camera,
 | |
|                     frame_name,
 | |
|                     obj.to_dict(),
 | |
|                 )
 | |
|             )
 | |
| 
 | |
|         def snapshot(camera, obj: TrackedObject, frame_name: str):
 | |
|             mqtt_config: CameraMqttConfig = self.config.cameras[camera].mqtt
 | |
|             if mqtt_config.enabled and self.should_mqtt_snapshot(camera, obj):
 | |
|                 jpg_bytes = obj.get_img_bytes(
 | |
|                     ext="jpg",
 | |
|                     timestamp=mqtt_config.timestamp,
 | |
|                     bounding_box=mqtt_config.bounding_box,
 | |
|                     crop=mqtt_config.crop,
 | |
|                     height=mqtt_config.height,
 | |
|                     quality=mqtt_config.quality,
 | |
|                 )
 | |
| 
 | |
|                 if jpg_bytes is None:
 | |
|                     logger.warning(
 | |
|                         f"Unable to send mqtt snapshot for {obj.obj_data['id']}."
 | |
|                     )
 | |
|                 else:
 | |
|                     self.dispatcher.publish(
 | |
|                         f"{camera}/{obj.obj_data['label']}/snapshot",
 | |
|                         jpg_bytes,
 | |
|                         retain=True,
 | |
|                     )
 | |
| 
 | |
|                     if obj.obj_data.get("sub_label"):
 | |
|                         sub_label = obj.obj_data["sub_label"][0]
 | |
| 
 | |
|                         if sub_label in self.config.model.all_attribute_logos:
 | |
|                             self.dispatcher.publish(
 | |
|                                 f"{camera}/{sub_label}/snapshot",
 | |
|                                 jpg_bytes,
 | |
|                                 retain=True,
 | |
|                             )
 | |
| 
 | |
|         def camera_activity(camera, activity):
 | |
|             last_activity = self.camera_activity.get(camera)
 | |
| 
 | |
|             if not last_activity or activity != last_activity:
 | |
|                 self.camera_activity[camera] = activity
 | |
|                 self.requestor.send_data(UPDATE_CAMERA_ACTIVITY, self.camera_activity)
 | |
| 
 | |
|         for camera in self.config.cameras.keys():
 | |
|             camera_state = CameraState(
 | |
|                 camera, self.config, self.frame_manager, self.ptz_autotracker_thread
 | |
|             )
 | |
|             camera_state.on("start", start)
 | |
|             camera_state.on("autotrack", autotrack)
 | |
|             camera_state.on("update", update)
 | |
|             camera_state.on("end", end)
 | |
|             camera_state.on("snapshot", snapshot)
 | |
|             camera_state.on("camera_activity", camera_activity)
 | |
|             self.camera_states[camera] = camera_state
 | |
| 
 | |
|     def should_save_snapshot(self, camera, obj: TrackedObject):
 | |
|         if obj.false_positive:
 | |
|             return False
 | |
| 
 | |
|         snapshot_config: SnapshotsConfig = self.config.cameras[camera].snapshots
 | |
| 
 | |
|         if not snapshot_config.enabled:
 | |
|             return False
 | |
| 
 | |
|         # object never changed position
 | |
|         if obj.obj_data["position_changes"] == 0:
 | |
|             return False
 | |
| 
 | |
|         # if there are required zones and there is no overlap
 | |
|         required_zones = snapshot_config.required_zones
 | |
|         if len(required_zones) > 0 and not set(obj.entered_zones) & set(required_zones):
 | |
|             logger.debug(
 | |
|                 f"Not creating snapshot for {obj.obj_data['id']} because it did not enter required zones"
 | |
|             )
 | |
|             return False
 | |
| 
 | |
|         return True
 | |
| 
 | |
|     def should_retain_recording(self, camera: str, obj: TrackedObject):
 | |
|         if obj.false_positive:
 | |
|             return False
 | |
| 
 | |
|         record_config: RecordConfig = self.config.cameras[camera].record
 | |
| 
 | |
|         # Recording is disabled
 | |
|         if not record_config.enabled:
 | |
|             return False
 | |
| 
 | |
|         # object never changed position
 | |
|         if obj.obj_data["position_changes"] == 0:
 | |
|             return False
 | |
| 
 | |
|         # If the object is not considered an alert or detection
 | |
|         if obj.max_severity is None:
 | |
|             return False
 | |
| 
 | |
|         return True
 | |
| 
 | |
|     def should_mqtt_snapshot(self, camera, obj: TrackedObject):
 | |
|         # object never changed position
 | |
|         if obj.obj_data["position_changes"] == 0:
 | |
|             return False
 | |
| 
 | |
|         # if there are required zones and there is no overlap
 | |
|         required_zones = self.config.cameras[camera].mqtt.required_zones
 | |
|         if len(required_zones) > 0 and not set(obj.entered_zones) & set(required_zones):
 | |
|             logger.debug(
 | |
|                 f"Not sending mqtt for {obj.obj_data['id']} because it did not enter required zones"
 | |
|             )
 | |
|             return False
 | |
| 
 | |
|         return True
 | |
| 
 | |
|     def update_mqtt_motion(self, camera, frame_time, motion_boxes):
 | |
|         # publish if motion is currently being detected
 | |
|         if motion_boxes:
 | |
|             # only send ON if motion isn't already active
 | |
|             if self.last_motion_detected.get(camera, 0) == 0:
 | |
|                 self.dispatcher.publish(
 | |
|                     f"{camera}/motion",
 | |
|                     "ON",
 | |
|                     retain=False,
 | |
|                 )
 | |
| 
 | |
|             # always updated latest motion
 | |
|             self.last_motion_detected[camera] = frame_time
 | |
|         elif self.last_motion_detected.get(camera, 0) > 0:
 | |
|             mqtt_delay = self.config.cameras[camera].motion.mqtt_off_delay
 | |
| 
 | |
|             # If no motion, make sure the off_delay has passed
 | |
|             if frame_time - self.last_motion_detected.get(camera, 0) >= mqtt_delay:
 | |
|                 self.dispatcher.publish(
 | |
|                     f"{camera}/motion",
 | |
|                     "OFF",
 | |
|                     retain=False,
 | |
|                 )
 | |
|                 # reset the last_motion so redundant `off` commands aren't sent
 | |
|                 self.last_motion_detected[camera] = 0
 | |
| 
 | |
|     def get_best(self, camera, label):
 | |
|         # TODO: need a lock here
 | |
|         camera_state = self.camera_states[camera]
 | |
|         if label in camera_state.best_objects:
 | |
|             best_obj = camera_state.best_objects[label]
 | |
|             best = best_obj.thumbnail_data.copy()
 | |
|             best["frame"] = camera_state.frame_cache.get(
 | |
|                 best_obj.thumbnail_data["frame_time"]
 | |
|             )
 | |
|             return best
 | |
|         else:
 | |
|             return {}
 | |
| 
 | |
|     def get_current_frame(
 | |
|         self, camera: str, draw_options: dict[str, any] = {}
 | |
|     ) -> np.ndarray | None:
 | |
|         if camera == "birdseye":
 | |
|             return self.frame_manager.get(
 | |
|                 "birdseye",
 | |
|                 (self.config.birdseye.height * 3 // 2, self.config.birdseye.width),
 | |
|             )
 | |
| 
 | |
|         if camera not in self.camera_states:
 | |
|             return None
 | |
| 
 | |
|         return self.camera_states[camera].get_current_frame(draw_options)
 | |
| 
 | |
|     def get_current_frame_time(self, camera) -> int:
 | |
|         """Returns the latest frame time for a given camera."""
 | |
|         return self.camera_states[camera].current_frame_time
 | |
| 
 | |
|     def set_sub_label(
 | |
|         self, event_id: str, sub_label: str | None, score: float | None
 | |
|     ) -> None:
 | |
|         """Update sub label for given event id."""
 | |
|         tracked_obj: TrackedObject = None
 | |
| 
 | |
|         for state in self.camera_states.values():
 | |
|             tracked_obj = state.tracked_objects.get(event_id)
 | |
| 
 | |
|             if tracked_obj is not None:
 | |
|                 break
 | |
| 
 | |
|         try:
 | |
|             event: Event = Event.get(Event.id == event_id)
 | |
|         except DoesNotExist:
 | |
|             event = None
 | |
| 
 | |
|         if not tracked_obj and not event:
 | |
|             return
 | |
| 
 | |
|         if tracked_obj:
 | |
|             tracked_obj.obj_data["sub_label"] = (sub_label, score)
 | |
| 
 | |
|         if event:
 | |
|             event.sub_label = sub_label
 | |
|             data = event.data
 | |
|             if sub_label is None:
 | |
|                 data["sub_label_score"] = None
 | |
|             elif score is not None:
 | |
|                 data["sub_label_score"] = score
 | |
|             event.data = data
 | |
|             event.save()
 | |
| 
 | |
|             # update timeline items
 | |
|             Timeline.update(
 | |
|                 data=Timeline.data.update({"sub_label": (sub_label, score)})
 | |
|             ).where(Timeline.source_id == event_id).execute()
 | |
| 
 | |
|         return True
 | |
| 
 | |
|     def set_recognized_license_plate(
 | |
|         self, event_id: str, recognized_license_plate: str | None, score: float | None
 | |
|     ) -> None:
 | |
|         """Update recognized license plate for given event id."""
 | |
|         tracked_obj: TrackedObject = None
 | |
| 
 | |
|         for state in self.camera_states.values():
 | |
|             tracked_obj = state.tracked_objects.get(event_id)
 | |
| 
 | |
|             if tracked_obj is not None:
 | |
|                 break
 | |
| 
 | |
|         try:
 | |
|             event: Event = Event.get(Event.id == event_id)
 | |
|         except DoesNotExist:
 | |
|             event = None
 | |
| 
 | |
|         if not tracked_obj and not event:
 | |
|             return
 | |
| 
 | |
|         if tracked_obj:
 | |
|             tracked_obj.obj_data["recognized_license_plate"] = (
 | |
|                 recognized_license_plate,
 | |
|                 score,
 | |
|             )
 | |
| 
 | |
|         if event:
 | |
|             data = event.data
 | |
|             data["recognized_license_plate"] = recognized_license_plate
 | |
|             if recognized_license_plate is None:
 | |
|                 data["recognized_license_plate_score"] = None
 | |
|             elif score is not None:
 | |
|                 data["recognized_license_plate_score"] = score
 | |
|             event.data = data
 | |
|             event.save()
 | |
| 
 | |
|         return True
 | |
| 
 | |
|     def save_lpr_snapshot(self, payload: tuple) -> None:
 | |
|         # save the snapshot image
 | |
|         (frame, event_id, camera) = payload
 | |
| 
 | |
|         img = cv2.imdecode(
 | |
|             np.frombuffer(base64.b64decode(frame), dtype=np.uint8),
 | |
|             cv2.IMREAD_COLOR,
 | |
|         )
 | |
| 
 | |
|         self.camera_states[camera].save_manual_event_image(
 | |
|             img, event_id, "license_plate", {}
 | |
|         )
 | |
| 
 | |
|     def create_manual_event(self, payload: tuple) -> None:
 | |
|         (
 | |
|             frame_time,
 | |
|             camera_name,
 | |
|             label,
 | |
|             event_id,
 | |
|             include_recording,
 | |
|             score,
 | |
|             sub_label,
 | |
|             duration,
 | |
|             source_type,
 | |
|             draw,
 | |
|         ) = payload
 | |
| 
 | |
|         # save the snapshot image
 | |
|         self.camera_states[camera_name].save_manual_event_image(
 | |
|             None, event_id, label, draw
 | |
|         )
 | |
|         end_time = frame_time + duration if duration is not None else None
 | |
| 
 | |
|         # send event to event maintainer
 | |
|         self.event_sender.publish(
 | |
|             (
 | |
|                 EventTypeEnum.api,
 | |
|                 EventStateEnum.start,
 | |
|                 camera_name,
 | |
|                 "",
 | |
|                 {
 | |
|                     "id": event_id,
 | |
|                     "label": label,
 | |
|                     "sub_label": sub_label,
 | |
|                     "score": score,
 | |
|                     "camera": camera_name,
 | |
|                     "start_time": frame_time
 | |
|                     - self.config.cameras[camera_name].record.event_pre_capture,
 | |
|                     "end_time": end_time,
 | |
|                     "has_clip": self.config.cameras[camera_name].record.enabled
 | |
|                     and include_recording,
 | |
|                     "has_snapshot": True,
 | |
|                     "type": source_type,
 | |
|                 },
 | |
|             )
 | |
|         )
 | |
| 
 | |
|         if source_type == "api":
 | |
|             self.ongoing_manual_events[event_id] = camera_name
 | |
|             self.detection_publisher.publish(
 | |
|                 (
 | |
|                     camera_name,
 | |
|                     frame_time,
 | |
|                     {
 | |
|                         "state": (
 | |
|                             ManualEventState.complete
 | |
|                             if end_time
 | |
|                             else ManualEventState.start
 | |
|                         ),
 | |
|                         "label": f"{label}: {sub_label}" if sub_label else label,
 | |
|                         "event_id": event_id,
 | |
|                         "end_time": end_time,
 | |
|                     },
 | |
|                 ),
 | |
|                 DetectionTypeEnum.api.value,
 | |
|             )
 | |
| 
 | |
|     def create_lpr_event(self, payload: tuple) -> None:
 | |
|         (
 | |
|             frame_time,
 | |
|             camera_name,
 | |
|             label,
 | |
|             event_id,
 | |
|             include_recording,
 | |
|             score,
 | |
|             sub_label,
 | |
|             plate,
 | |
|         ) = payload
 | |
| 
 | |
|         # send event to event maintainer
 | |
|         self.event_sender.publish(
 | |
|             (
 | |
|                 EventTypeEnum.api,
 | |
|                 EventStateEnum.start,
 | |
|                 camera_name,
 | |
|                 "",
 | |
|                 {
 | |
|                     "id": event_id,
 | |
|                     "label": label,
 | |
|                     "sub_label": sub_label,
 | |
|                     "score": score,
 | |
|                     "camera": camera_name,
 | |
|                     "start_time": frame_time
 | |
|                     - self.config.cameras[camera_name].record.event_pre_capture,
 | |
|                     "end_time": None,
 | |
|                     "has_clip": self.config.cameras[camera_name].record.enabled
 | |
|                     and include_recording,
 | |
|                     "has_snapshot": True,
 | |
|                     "type": "api",
 | |
|                     "recognized_license_plate": plate,
 | |
|                     "recognized_license_plate_score": score,
 | |
|                 },
 | |
|             )
 | |
|         )
 | |
| 
 | |
|         self.ongoing_manual_events[event_id] = camera_name
 | |
|         self.detection_publisher.publish(
 | |
|             (
 | |
|                 camera_name,
 | |
|                 frame_time,
 | |
|                 {
 | |
|                     "state": ManualEventState.start,
 | |
|                     "label": f"{label}: {sub_label}" if sub_label else label,
 | |
|                     "event_id": event_id,
 | |
|                     "end_time": None,
 | |
|                 },
 | |
|             ),
 | |
|             DetectionTypeEnum.lpr.value,
 | |
|         )
 | |
| 
 | |
|     def end_manual_event(self, payload: tuple) -> None:
 | |
|         (event_id, end_time) = payload
 | |
| 
 | |
|         self.event_sender.publish(
 | |
|             (
 | |
|                 EventTypeEnum.api,
 | |
|                 EventStateEnum.end,
 | |
|                 None,
 | |
|                 "",
 | |
|                 {"id": event_id, "end_time": end_time},
 | |
|             )
 | |
|         )
 | |
| 
 | |
|         if event_id in self.ongoing_manual_events:
 | |
|             self.detection_publisher.publish(
 | |
|                 (
 | |
|                     self.ongoing_manual_events[event_id],
 | |
|                     end_time,
 | |
|                     {
 | |
|                         "state": ManualEventState.end,
 | |
|                         "event_id": event_id,
 | |
|                         "end_time": end_time,
 | |
|                     },
 | |
|                 ),
 | |
|                 DetectionTypeEnum.api.value,
 | |
|             )
 | |
|             self.ongoing_manual_events.pop(event_id)
 | |
| 
 | |
|     def force_end_all_events(self, camera: str, camera_state: CameraState):
 | |
|         """Ends all active events on camera when disabling."""
 | |
|         last_frame_name = camera_state.previous_frame_id
 | |
|         for obj_id, obj in list(camera_state.tracked_objects.items()):
 | |
|             if "end_time" not in obj.obj_data:
 | |
|                 logger.debug(f"Camera {camera} disabled, ending active event {obj_id}")
 | |
|                 obj.obj_data["end_time"] = datetime.datetime.now().timestamp()
 | |
|                 # end callbacks
 | |
|                 for callback in camera_state.callbacks["end"]:
 | |
|                     callback(camera, obj, last_frame_name)
 | |
| 
 | |
|                 # camera activity callbacks
 | |
|                 for callback in camera_state.callbacks["camera_activity"]:
 | |
|                     callback(
 | |
|                         camera,
 | |
|                         {"enabled": False, "motion": 0, "objects": []},
 | |
|                     )
 | |
| 
 | |
|     def run(self):
 | |
|         while not self.stop_event.is_set():
 | |
|             # check for config updates
 | |
|             while True:
 | |
|                 (
 | |
|                     updated_enabled_topic,
 | |
|                     updated_enabled_config,
 | |
|                 ) = self.config_enabled_subscriber.check_for_update()
 | |
| 
 | |
|                 if not updated_enabled_topic:
 | |
|                     break
 | |
| 
 | |
|                 camera_name = updated_enabled_topic.rpartition("/")[-1]
 | |
|                 self.config.cameras[
 | |
|                     camera_name
 | |
|                 ].enabled = updated_enabled_config.enabled
 | |
| 
 | |
|                 if self.camera_states[camera_name].prev_enabled is None:
 | |
|                     self.camera_states[
 | |
|                         camera_name
 | |
|                     ].prev_enabled = updated_enabled_config.enabled
 | |
| 
 | |
|             # manage camera disabled state
 | |
|             for camera, config in self.config.cameras.items():
 | |
|                 if not config.enabled_in_config:
 | |
|                     continue
 | |
| 
 | |
|                 current_enabled = config.enabled
 | |
|                 camera_state = self.camera_states[camera]
 | |
| 
 | |
|                 if camera_state.prev_enabled and not current_enabled:
 | |
|                     logger.debug(f"Not processing objects for disabled camera {camera}")
 | |
|                     self.force_end_all_events(camera, camera_state)
 | |
| 
 | |
|                 camera_state.prev_enabled = current_enabled
 | |
| 
 | |
|                 if not current_enabled:
 | |
|                     continue
 | |
| 
 | |
|             # check for sub label updates
 | |
|             while True:
 | |
|                 (raw_topic, payload) = self.sub_label_subscriber.check_for_update(
 | |
|                     timeout=0
 | |
|                 )
 | |
| 
 | |
|                 if not raw_topic:
 | |
|                     break
 | |
| 
 | |
|                 topic = str(raw_topic)
 | |
| 
 | |
|                 if topic.endswith(EventMetadataTypeEnum.sub_label.value):
 | |
|                     (event_id, sub_label, score) = payload
 | |
|                     self.set_sub_label(event_id, sub_label, score)
 | |
|                 if topic.endswith(EventMetadataTypeEnum.recognized_license_plate.value):
 | |
|                     (event_id, recognized_license_plate, score) = payload
 | |
|                     self.set_recognized_license_plate(
 | |
|                         event_id, recognized_license_plate, score
 | |
|                     )
 | |
|                 elif topic.endswith(EventMetadataTypeEnum.lpr_event_create.value):
 | |
|                     self.create_lpr_event(payload)
 | |
|                 elif topic.endswith(EventMetadataTypeEnum.save_lpr_snapshot.value):
 | |
|                     self.save_lpr_snapshot(payload)
 | |
|                 elif topic.endswith(EventMetadataTypeEnum.manual_event_create.value):
 | |
|                     self.create_manual_event(payload)
 | |
|                 elif topic.endswith(EventMetadataTypeEnum.manual_event_end.value):
 | |
|                     self.end_manual_event(payload)
 | |
| 
 | |
|             try:
 | |
|                 (
 | |
|                     camera,
 | |
|                     frame_name,
 | |
|                     frame_time,
 | |
|                     current_tracked_objects,
 | |
|                     motion_boxes,
 | |
|                     regions,
 | |
|                 ) = self.tracked_objects_queue.get(True, 1)
 | |
|             except queue.Empty:
 | |
|                 continue
 | |
| 
 | |
|             if not self.config.cameras[camera].enabled:
 | |
|                 logger.debug(f"Camera {camera} disabled, skipping update")
 | |
|                 continue
 | |
| 
 | |
|             camera_state = self.camera_states[camera]
 | |
| 
 | |
|             camera_state.update(
 | |
|                 frame_name, frame_time, current_tracked_objects, motion_boxes, regions
 | |
|             )
 | |
| 
 | |
|             self.update_mqtt_motion(camera, frame_time, motion_boxes)
 | |
| 
 | |
|             tracked_objects = [
 | |
|                 o.to_dict() for o in camera_state.tracked_objects.values()
 | |
|             ]
 | |
| 
 | |
|             # publish info on this frame
 | |
|             self.detection_publisher.publish(
 | |
|                 (
 | |
|                     camera,
 | |
|                     frame_name,
 | |
|                     frame_time,
 | |
|                     tracked_objects,
 | |
|                     motion_boxes,
 | |
|                     regions,
 | |
|                 ),
 | |
|                 DetectionTypeEnum.video.value,
 | |
|             )
 | |
| 
 | |
|             # cleanup event finished queue
 | |
|             while not self.stop_event.is_set():
 | |
|                 update = self.event_end_subscriber.check_for_update(timeout=0.01)
 | |
| 
 | |
|                 if not update:
 | |
|                     break
 | |
| 
 | |
|                 event_id, camera, _ = update
 | |
|                 self.camera_states[camera].finished(event_id)
 | |
| 
 | |
|         # shut down camera states
 | |
|         for state in self.camera_states.values():
 | |
|             state.shutdown()
 | |
| 
 | |
|         self.requestor.stop()
 | |
|         self.detection_publisher.stop()
 | |
|         self.event_sender.stop()
 | |
|         self.event_end_subscriber.stop()
 | |
|         self.sub_label_subscriber.stop()
 | |
|         self.config_enabled_subscriber.stop()
 | |
| 
 | |
|         logger.info("Exiting object processor...")
 |