Files
frigate/frigate/track/object_processing.py
T

868 lines
30 KiB
Python
Raw Normal View History

2025-03-23 14:30:48 -05:00
import base64
2020-11-04 06:31:25 -06:00
import datetime
import json
2020-11-03 21:26:39 -06:00
import logging
2020-08-02 08:46:36 -05:00
import queue
2020-11-04 06:31:25 -06:00
import threading
from collections import defaultdict
from enum import Enum
from multiprocessing import Queue as MpQueue
from multiprocessing.synchronize import Event as MpEvent
2025-05-13 16:27:20 +02:00
from typing import Any
2020-11-04 06:31:25 -06:00
2025-03-23 14:30:48 -05:00
import cv2
2020-11-04 06:31:25 -06:00
import numpy as np
2025-06-23 18:40:21 -05:00
from peewee import SQL, DoesNotExist
2020-11-04 06:31:25 -06:00
from frigate.camera.state import CameraState
from frigate.comms.detections_updater import DetectionPublisher, DetectionTypeEnum
from frigate.comms.dispatcher import Dispatcher
2025-03-10 16:29:29 -06:00
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 (
2025-02-18 07:46:29 -07:00
CameraMqttConfig,
FrigateConfig,
2023-05-29 12:31:17 +02:00
RecordConfig,
SnapshotsConfig,
)
2025-05-22 12:16:51 -06:00
from frigate.config.camera.updater import (
CameraConfigUpdateEnum,
CameraConfigUpdateSubscriber,
)
2025-06-23 18:40:21 -05:00
from frigate.const import (
FAST_QUEUE_TIMEOUT,
UPDATE_CAMERA_ACTIVITY,
UPSERT_REVIEW_SEGMENT,
)
from frigate.events.types import EventStateEnum, EventTypeEnum
2025-06-23 18:40:21 -05:00
from frigate.models import Event, ReviewSegment, Timeline
from frigate.ptz.autotrack import PtzAutoTrackerThread
2024-10-17 10:02:27 -06:00
from frigate.track.tracked_object import TrackedObject
from frigate.util.image import SharedMemoryFrameManager
2020-02-15 21:07:54 -06:00
2020-11-03 21:26:39 -06:00
logger = logging.getLogger(__name__)
2021-02-17 07:23:32 -06:00
class ManualEventState(str, Enum):
complete = "complete"
start = "start"
end = "end"
2025-02-18 09:13:28 -07:00
2021-02-17 07:23:32 -06:00
2020-02-15 21:07:54 -06:00
class TrackedObjectProcessor(threading.Thread):
2021-02-17 07:23:32 -06:00
def __init__(
self,
config: FrigateConfig,
dispatcher: Dispatcher,
tracked_objects_queue: MpQueue,
ptz_autotracker_thread: PtzAutoTrackerThread,
stop_event: MpEvent,
) -> None:
super().__init__(name="detected_frames_processor")
self.config = config
self.dispatcher = dispatcher
2020-02-15 21:07:54 -06:00
self.tracked_objects_queue = tracked_objects_queue
self.stop_event: MpEvent = stop_event
2022-04-16 17:38:07 +02:00
self.camera_states: dict[str, CameraState] = {}
self.camera_states_lock = threading.Lock()
self.frame_manager = SharedMemoryFrameManager()
2022-05-15 07:45:04 -05:00
self.last_motion_detected: dict[str, float] = {}
self.ptz_autotracker_thread = ptz_autotracker_thread
2025-06-11 11:25:30 -06:00
self.camera_config_subscriber = CameraConfigUpdateSubscriber(
self.config,
self.config.cameras,
2025-06-11 11:25:30 -06:00
[
CameraConfigUpdateEnum.add,
CameraConfigUpdateEnum.enabled,
2025-12-12 07:45:03 -06:00
CameraConfigUpdateEnum.motion,
CameraConfigUpdateEnum.objects,
2025-06-11 11:25:30 -06:00
CameraConfigUpdateEnum.remove,
2026-03-25 14:14:32 -05:00
CameraConfigUpdateEnum.timestamp_style,
2025-06-11 11:25:30 -06:00
CameraConfigUpdateEnum.zones,
],
2025-05-22 12:16:51 -06:00
)
2025-03-03 09:30:52 -06:00
self.requestor = InterProcessRequestor()
2025-08-08 06:08:37 -06:00
self.detection_publisher = DetectionPublisher(DetectionTypeEnum.all.value)
self.event_sender = EventUpdatePublisher()
self.event_end_subscriber = EventEndSubscriber()
self.sub_label_subscriber = EventMetadataSubscriber(EventMetadataTypeEnum.all)
2020-09-07 12:17:42 -05:00
2025-05-13 16:27:20 +02:00
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: dict[str, dict[str, Any]] = defaultdict(
lambda: defaultdict(dict)
)
self.active_zone_data: dict[str, dict[str, Any]] = defaultdict(
lambda: defaultdict(dict)
)
2025-06-11 11:25:30 -06:00
for camera in self.config.cameras.keys():
self.create_camera_state(camera)
def create_camera_state(self, camera: str) -> None:
"""Creates a new camera state."""
def start(camera: str, obj: TrackedObject, frame_name: str) -> None:
self.event_sender.publish(
(
EventTypeEnum.tracked_object,
EventStateEnum.start,
camera,
2024-11-19 11:20:04 -07:00
frame_name,
obj.to_dict(),
)
2023-04-30 11:07:14 -06:00
)
2020-09-07 12:17:42 -05:00
def update(camera: str, obj: TrackedObject, frame_name: str) -> None:
obj.has_snapshot = self.should_save_snapshot(camera, obj)
obj.has_clip = self.should_retain_recording(camera, obj)
2020-12-20 07:19:18 -06:00
after = obj.to_dict()
2021-02-17 07:23:32 -06:00
message = {
"before": obj.previous,
"after": after,
"type": "new" if obj.previous["false_positive"] else "update",
}
2022-11-25 19:10:09 -07:00
self.dispatcher.publish("events", json.dumps(message), retain=False)
2020-12-20 07:19:18 -06:00
obj.previous = after
self.event_sender.publish(
2023-04-30 11:07:14 -06:00
(
EventTypeEnum.tracked_object,
EventStateEnum.update,
2023-04-30 11:07:14 -06:00
camera,
2024-11-19 11:20:04 -07:00
frame_name,
2025-02-18 07:46:29 -07:00
obj.to_dict(),
2023-04-30 11:07:14 -06:00
)
)
2020-09-07 12:17:42 -05:00
def autotrack(camera: str, obj: TrackedObject, frame_name: str) -> None:
self.ptz_autotracker_thread.ptz_autotracker.autotrack_object(camera, obj)
def end(camera: str, obj: TrackedObject, frame_name: str) -> None:
2021-09-15 07:16:52 -05:00
# populate has_snapshot
obj.has_snapshot = self.should_save_snapshot(camera, obj)
obj.has_clip = self.should_retain_recording(camera, obj)
2025-02-18 07:46:29 -07:00
# 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()
2021-09-15 07:16:52 -05:00
# write the snapshot to disk
if obj.has_snapshot:
2025-02-18 07:46:29 -07:00
obj.write_snapshot_to_disk()
2021-09-15 07:16:52 -05:00
2020-11-25 20:22:54 -06:00
if not obj.false_positive:
2021-02-17 07:23:32 -06:00
message = {
"before": obj.previous,
"after": obj.to_dict(),
"type": "end",
}
2022-12-08 18:00:44 -07:00
self.dispatcher.publish("events", json.dumps(message), retain=False)
self.ptz_autotracker_thread.ptz_autotracker.end_object(camera, obj)
2021-06-19 08:38:42 -05:00
self.event_sender.publish(
2023-04-30 11:07:14 -06:00
(
EventTypeEnum.tracked_object,
EventStateEnum.end,
2023-04-30 11:07:14 -06:00
camera,
2024-11-19 11:20:04 -07:00
frame_name,
2025-02-18 07:46:29 -07:00
obj.to_dict(),
2023-04-30 11:07:14 -06:00
)
)
2021-02-17 07:23:32 -06:00
2025-06-23 18:40:21 -05:00
def snapshot(camera: str, obj: TrackedObject) -> bool:
2025-02-18 07:46:29 -07:00
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(
2025-02-18 07:46:29 -07:00
ext="jpg",
timestamp=mqtt_config.timestamp,
bounding_box=mqtt_config.bounding_box,
crop=mqtt_config.crop,
2021-02-17 07:23:32 -06:00
height=mqtt_config.height,
2021-07-02 07:47:03 -05:00
quality=mqtt_config.quality,
)
2021-02-09 07:35:41 -06:00
if jpg_bytes is None:
2021-02-17 07:23:32 -06:00
logger.warning(
f"Unable to send mqtt snapshot for {obj.obj_data['id']}."
)
2021-02-09 07:35:41 -06:00
else:
self.dispatcher.publish(
2022-11-25 19:10:09 -07:00
f"{camera}/{obj.obj_data['label']}/snapshot",
2021-02-17 07:23:32 -06:00
jpg_bytes,
retain=True,
)
2025-03-21 16:37:09 -06:00
if obj.obj_data.get("sub_label"):
sub_label = obj.obj_data["sub_label"][0]
if sub_label in self.config.all_attribute_logos:
2025-03-21 16:37:09 -06:00
self.dispatcher.publish(
f"{camera}/{sub_label}/snapshot",
jpg_bytes,
retain=True,
)
2025-06-23 18:40:21 -05:00
return True
return False
def camera_activity(camera: str, activity: dict[str, Any]) -> None:
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)
2025-06-11 11:25:30 -06:00
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)
with self.camera_states_lock:
self.camera_states[camera] = camera_state
2020-09-07 12:17:42 -05:00
def should_save_snapshot(self, camera: str, obj: TrackedObject) -> bool:
2021-09-15 07:16:52 -05:00
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
2021-09-15 07:16:52 -05:00
required_zones = snapshot_config.required_zones
if len(required_zones) > 0 and not set(obj.entered_zones) & set(required_zones):
2021-02-17 07:23:32 -06:00
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) -> bool:
2021-09-15 07:16:52 -05:00
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:
2021-09-15 07:16:52 -05:00
return False
return True
def should_mqtt_snapshot(self, camera: str, obj: TrackedObject) -> bool:
# object never changed position
2025-05-15 16:13:18 -06:00
if obj.is_stationary():
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):
2021-02-17 07:23:32 -06:00
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: str, frame_time: float, motion_boxes: list
) -> None:
# publish if motion is currently being detected
if motion_boxes:
2022-05-15 07:45:04 -05:00
# only send ON if motion isn't already active
if self.last_motion_detected.get(camera, 0) == 0:
self.dispatcher.publish(
2022-11-25 19:10:09 -07:00
f"{camera}/motion",
"ON",
retain=False,
)
# always updated latest motion
2022-05-15 07:45:04 -05:00
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
2022-05-15 07:45:04 -05:00
if frame_time - self.last_motion_detected.get(camera, 0) >= mqtt_delay:
self.dispatcher.publish(
2022-11-25 19:10:09 -07:00
f"{camera}/motion",
"OFF",
retain=False,
)
# reset the last_motion so redundant `off` commands aren't sent
2022-05-15 07:45:04 -05:00
self.last_motion_detected[camera] = 0
def get_camera_state(self, camera: str) -> CameraState | None:
"""Returns the state for a camera, or None if it does not exist."""
with self.camera_states_lock:
return self.camera_states.get(camera)
def get_camera_states(self) -> list[CameraState]:
"""Returns a snapshot of camera states that is safe to iterate."""
with self.camera_states_lock:
return list(self.camera_states.values())
def get_best(self, camera: str, label: str) -> dict[str, Any]:
camera_state = self.get_camera_state(camera)
if camera_state is None:
return {}
2020-11-11 17:44:51 -06:00
if label in camera_state.best_objects:
best_obj = camera_state.best_objects[label]
if not best_obj.thumbnail_data:
return {}
best = best_obj.thumbnail_data.copy()
2021-02-17 07:23:32 -06:00
best["frame"] = camera_state.frame_cache.get(
best_obj.thumbnail_data["frame_time"]
)
2020-11-11 17:44:51 -06:00
return best
2020-02-15 21:07:54 -06:00
else:
return {}
2021-01-11 09:09:43 -08:00
def get_current_frame(
2025-05-13 16:27:20 +02:00
self, camera: str, draw_options: dict[str, Any] = {}
) -> np.ndarray | None:
2022-12-31 07:54:10 -07:00
if camera == "birdseye":
return self.frame_manager.get(
"birdseye",
(self.config.birdseye.height * 3 // 2, self.config.birdseye.width),
)
camera_state = self.get_camera_state(camera)
if camera_state is None:
return None
return camera_state.get_current_frame(draw_options)
def get_current_frame_time(self, camera: str) -> float:
"""Returns the latest frame time for a given camera."""
camera_state = self.get_camera_state(camera)
if camera_state is None:
2026-05-18 22:52:40 -05:00
return 0.0
return camera_state.current_frame_time
2025-03-10 16:29:29 -06:00
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 = None
2025-03-10 16:29:29 -06:00
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 | None = Event.get(Event.id == event_id)
2025-03-10 16:29:29 -06:00
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
2025-03-10 16:29:29 -06:00
data = event.data
if sub_label is None:
data["sub_label_score"] = None
2025-03-10 16:29:29 -06:00
elif score is not None:
data["sub_label_score"] = score
2025-03-10 16:29:29 -06:00
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()
2025-06-23 18:40:21 -05:00
# only update ended review segments
# manually updating a sub_label from the UI is only possible for ended tracked objects
try:
review_segment = ReviewSegment.get(
(
SQL(
"json_extract(data, '$.detections') LIKE ?",
[f'%"{event_id}"%'],
)
)
& (ReviewSegment.end_time.is_null(False))
)
segment_data = review_segment.data
detection_ids = segment_data.get("detections", [])
# Rebuild objects list and sync sub_labels
objects_list = []
sub_labels = set()
events = Event.select(Event.id, Event.label, Event.sub_label).where(
Event.id.in_(detection_ids)
2025-06-23 18:40:21 -05:00
)
for det_event in events:
if det_event.sub_label:
sub_labels.add(det_event.sub_label)
objects_list.append(
f"{det_event.label}-verified"
) # eg, "bird-verified"
else:
objects_list.append(det_event.label) # eg, "bird"
segment_data["sub_labels"] = list(sub_labels)
segment_data["objects"] = objects_list
updated_data = {
ReviewSegment.id.name: review_segment.id,
ReviewSegment.camera.name: review_segment.camera,
ReviewSegment.start_time.name: review_segment.start_time,
ReviewSegment.end_time.name: review_segment.end_time,
ReviewSegment.severity.name: review_segment.severity,
ReviewSegment.thumb_path.name: review_segment.thumb_path,
ReviewSegment.data.name: segment_data,
}
self.requestor.send_data(UPSERT_REVIEW_SEGMENT, updated_data)
logger.debug(
f"Updated sub_label for event {event_id} in review segment {review_segment.id}"
)
except DoesNotExist:
2025-06-23 18:40:21 -05:00
logger.debug(
f"No review segment found with event ID {event_id} when updating sub_label"
)
2025-07-18 08:28:02 -06:00
def set_object_attribute(
self,
event_id: str,
field_name: str,
field_value: str | None,
score: float | None,
) -> None:
2025-07-18 08:28:02 -06:00
"""Update attribute for given event id."""
tracked_obj: TrackedObject | None = 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 | None = Event.get(Event.id == event_id)
except DoesNotExist:
event = None
if not tracked_obj and not event:
return
if tracked_obj:
2025-07-18 08:28:02 -06:00
tracked_obj.obj_data[field_name] = (
field_value,
2025-03-12 18:45:16 -05:00
score,
)
if event:
data = event.data
data[field_name] = field_value
2025-07-18 08:28:02 -06:00
if field_value is None:
data[f"{field_name}_score"] = None
elif score is not None:
data[f"{field_name}_score"] = score
event.data = data
event.save()
2025-03-23 14:30:48 -05:00
def save_lpr_snapshot(self, payload: tuple) -> None:
# save the snapshot image
(frame, event_id, camera) = payload
camera_state = self.camera_states.get(camera)
if camera_state is None:
logger.debug("Discarding LPR snapshot for unknown camera %s", camera)
return
2025-03-23 14:30:48 -05:00
img = cv2.imdecode(
np.frombuffer(base64.b64decode(frame), dtype=np.uint8),
cv2.IMREAD_COLOR,
)
camera_state.save_manual_event_image(img, event_id, "license_plate", {})
2025-03-23 14:30:48 -05:00
def create_manual_event(self, payload: tuple) -> None:
(
frame_time,
camera_name,
label,
event_id,
include_recording,
score,
sub_label,
duration,
source_type,
draw,
pre_capture,
) = payload
camera_state = self.camera_states.get(camera_name)
if camera_state is None:
logger.debug("Discarding manual event for unknown camera %s", camera_name)
return
# save the snapshot image
camera_state.save_manual_event_image(None, event_id, label, draw)
end_time = frame_time + duration if duration is not None else None
start_time = (
frame_time - camera_state.camera_config.record.event_pre_capture
if pre_capture is None
else frame_time - pre_capture
)
# 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": start_time,
"end_time": end_time,
"has_clip": camera_state.camera_config.record.enabled
and include_recording,
"has_snapshot": True,
"snapshot_clean": True,
"snapshot_frame_time": frame_time,
"type": source_type,
"draw": draw,
},
)
)
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,
)
2025-03-23 14:30:48 -05:00
def create_lpr_event(self, payload: tuple) -> None:
(
frame_time,
camera_name,
label,
event_id,
include_recording,
score,
sub_label,
plate,
) = payload
camera_state = self.camera_states.get(camera_name)
if camera_state is None:
logger.debug("Discarding LPR event for unknown camera %s", camera_name)
return
2025-03-23 14:30:48 -05:00
# 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
- camera_state.camera_config.record.event_pre_capture,
2025-03-23 14:30:48 -05:00
"end_time": None,
"has_clip": camera_state.camera_config.record.enabled
2025-03-23 14:30:48 -05:00
and include_recording,
"has_snapshot": True,
"snapshot_clean": True,
2025-03-23 14:30:48 -05:00
"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) -> None:
2025-03-03 09:30:52 -06:00
"""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) -> None:
2021-05-21 11:39:14 -04:00
while not self.stop_event.is_set():
2025-03-06 06:59:35 -07:00
# check for config updates
2025-06-11 11:25:30 -06:00
updated_topics = self.camera_config_subscriber.check_for_updates()
2025-03-06 06:59:35 -07:00
2026-07-17 09:00:15 -05:00
# a single drain can carry several topics at once, so add and
# remove are handled independently rather than as exclusive branches
for camera in updated_topics.get("add", []):
self.config.cameras[camera] = (
self.camera_config_subscriber.camera_configs[camera]
)
self.create_camera_state(camera)
if "remove" in updated_topics:
2025-06-11 11:25:30 -06:00
for camera in updated_topics["remove"]:
2026-07-17 09:00:15 -05:00
camera_state = self.camera_states.get(camera)
if camera_state is None:
continue
camera_state.shutdown()
with self.camera_states_lock:
self.camera_states.pop(camera)
2026-03-04 10:07:34 -06:00
self.camera_activity.pop(camera, None)
self.last_motion_detected.pop(camera, None)
self.requestor.send_data(UPDATE_CAMERA_ACTIVITY, self.camera_activity)
2025-03-06 06:59:35 -07:00
# manage camera disabled state
2025-03-03 09:30:52 -06:00
for camera, config in self.config.cameras.items():
if not config.enabled_in_config:
continue
2025-03-06 06:59:35 -07:00
current_enabled = config.enabled
2026-03-04 10:07:34 -06:00
camera_state = self.camera_states.get(camera)
if camera_state is None:
continue
2025-03-03 09:30:52 -06:00
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
2025-03-10 16:29:29 -06:00
# check for sub label updates
while True:
update = self.sub_label_subscriber.check_for_update(timeout=0)
2025-03-10 16:29:29 -06:00
if not update:
break
(raw_topic, payload) = update
if not raw_topic or not payload:
2025-03-10 16:29:29 -06:00
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)
2025-07-18 08:28:02 -06:00
if topic.endswith(EventMetadataTypeEnum.attribute.value):
(event_id, field_name, field_value, score) = payload
self.set_object_attribute(event_id, field_name, field_value, score)
2025-03-23 14:30:48 -05:00
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)
2025-03-10 16:29:29 -06:00
2020-08-02 08:46:36 -05:00
try:
2021-02-17 07:23:32 -06:00
(
camera,
2024-11-16 16:00:19 -07:00
frame_name,
2021-02-17 07:23:32 -06:00
frame_time,
current_tracked_objects,
motion_boxes,
regions,
2023-02-03 20:15:47 -06:00
) = self.tracked_objects_queue.get(True, 1)
2020-08-02 08:46:36 -05:00
except queue.Empty:
continue
2026-03-04 10:07:34 -06:00
camera_config = self.config.cameras.get(camera)
if camera_config is None:
continue
if not camera_config.enabled:
2025-03-03 09:30:52 -06:00
logger.debug(f"Camera {camera} disabled, skipping update")
continue
2026-05-07 13:23:02 -05:00
camera_state = self.camera_states.get(camera)
if camera_state is None:
continue
2020-07-09 06:57:16 -05:00
2021-02-17 07:23:32 -06:00
camera_state.update(
2024-11-16 16:00:19 -07:00
frame_name, frame_time, current_tracked_objects, motion_boxes, regions
2021-02-17 07:23:32 -06:00
)
2020-07-09 06:57:16 -05:00
2022-05-15 07:45:04 -05:00
self.update_mqtt_motion(camera, frame_time, motion_boxes)
2021-12-11 08:59:54 -06:00
tracked_objects = [
2026-02-18 10:48:45 -07:00
o.to_dict() for o in camera_state.tracked_objects.values()
2021-12-11 08:59:54 -06:00
]
# publish info on this frame
2024-06-21 17:30:19 -04:00
self.detection_publisher.publish(
2021-12-10 22:56:29 -06:00
(
camera,
2024-11-16 16:00:19 -07:00
frame_name,
2021-12-10 22:56:29 -06:00
frame_time,
2021-12-11 08:59:54 -06:00
tracked_objects,
2021-12-10 22:56:29 -06:00
motion_boxes,
regions,
),
DetectionTypeEnum.video.value,
2021-12-10 22:56:29 -06:00
)
2020-11-25 10:37:41 -06:00
# cleanup event finished queue
while not self.stop_event.is_set():
2025-04-30 08:42:53 -05:00
update = self.event_end_subscriber.check_for_update(
timeout=FAST_QUEUE_TIMEOUT
)
if not update:
break
2024-06-21 17:30:19 -04:00
event_id, camera, _ = update
camera_state = self.camera_states.get(camera)
# the camera may have been removed while its event was pending
if camera_state is not None:
camera_state.finished(event_id)
2021-05-21 11:39:14 -04:00
2025-02-18 09:13:28 -07:00
# 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()
2025-03-10 16:29:29 -06:00
self.sub_label_subscriber.stop()
2025-06-11 11:25:30 -06:00
self.camera_config_subscriber.stop()
2025-03-03 09:30:52 -06:00
2023-05-29 12:31:17 +02:00
logger.info("Exiting object processor...")