Support manual detections as review items

This commit is contained in:
Nicolas Mowen 2024-04-01 17:07:35 -06:00
parent d75756164a
commit df58b15793
3 changed files with 101 additions and 5 deletions

View File

@ -13,6 +13,7 @@ SOCKET_SUB = "ipc:///tmp/cache/detect_sun"
class DetectionTypeEnum(str, Enum):
all = ""
api = "api"
video = "video"
audio = "audio"

View File

@ -6,10 +6,12 @@ import logging
import os
import random
import string
from enum import Enum
from typing import Optional
import cv2
from frigate.comms.detections_updater import DetectionPublisher, DetectionTypeEnum
from frigate.comms.events_updater import EventUpdatePublisher
from frigate.config import CameraConfig, FrigateConfig
from frigate.const import CLIPS_DIR
@ -19,11 +21,19 @@ from frigate.util.image import draw_box_with_label
logger = logging.getLogger(__name__)
class ManualEventState(str, Enum):
complete = "complete"
start = "start"
end = "end"
class ExternalEventProcessor:
def __init__(self, config: FrigateConfig) -> None:
self.config = config
self.default_thumbnail = None
self.event_sender = EventUpdatePublisher()
self.detection_updater = DetectionPublisher(DetectionTypeEnum.api)
self.event_camera = {}
def create_manual_event(
self,
@ -47,6 +57,11 @@ class ExternalEventProcessor:
thumbnail = self._write_images(
camera_config, label, event_id, draw, snapshot_frame
)
end = (
now + duration + camera_config.record.events.post_capture
if duration is not None
else None
)
self.event_sender.publish(
(
@ -60,11 +75,7 @@ class ExternalEventProcessor:
"score": score,
"camera": camera,
"start_time": now - camera_config.record.events.pre_capture,
"end_time": now
+ duration
+ camera_config.record.events.post_capture
if duration is not None
else None,
"end_time": end,
"thumbnail": thumbnail,
"has_clip": camera_config.record.enabled and include_recording,
"has_snapshot": True,
@ -73,6 +84,23 @@ class ExternalEventProcessor:
)
)
if source_type == "api":
self.event_camera[event_id] = camera
self.detection_updater.send_data(
(
camera,
now,
{
"state": (
ManualEventState.complete if end else ManualEventState.start
),
"label": f"{label}: {sub_label}" if sub_label else label,
"event_id": event_id,
"end_time": end,
},
)
)
return event_id
def finish_manual_event(self, event_id: str, end_time: float) -> None:
@ -86,6 +114,16 @@ class ExternalEventProcessor:
)
)
if event_id in self.event_camera:
self.detection_updater.send_data(
(
self.event_camera[event_id],
end_time,
{"state": ManualEventState.end, "event_id": event_id},
)
)
self.event_camera.pop(event_id)
def _write_images(
self,
camera_config: CameraConfig,
@ -143,3 +181,4 @@ class ExternalEventProcessor:
def stop(self):
self.event_sender.stop()
self.detection_updater.stop()

View File

@ -5,6 +5,7 @@ import logging
import os
import random
import string
import sys
import threading
from enum import Enum
from multiprocessing.synchronize import Event as MpEvent
@ -18,6 +19,7 @@ from frigate.comms.detections_updater import DetectionSubscriber, DetectionTypeE
from frigate.comms.inter_process import InterProcessRequestor
from frigate.config import CameraConfig, FrigateConfig
from frigate.const import ALL_ATTRIBUTE_LABELS, CLIPS_DIR, UPSERT_REVIEW_SEGMENT
from frigate.events.external import ManualEventState
from frigate.models import ReviewSegment
from frigate.object_processing import TrackedObject
from frigate.util.image import SharedMemoryFrameManager, calculate_16_9_crop
@ -134,6 +136,9 @@ class ReviewSegmentMaintainer(threading.Thread):
self.config_subscriber = ConfigSubscriber("config/record/")
self.detection_subscriber = DetectionSubscriber(DetectionTypeEnum.all)
# manual events
self.indefinite_events: dict[str, dict[str, any]] = {}
self.stop_event = stop_event
def end_segment(self, segment: PendingReviewSegment) -> None:
@ -304,6 +309,15 @@ class ReviewSegmentMaintainer(threading.Thread):
dBFS,
audio_detections,
) = data
elif topic == DetectionTypeEnum.api:
(
camera,
frame_time,
manual_info,
) = data
if camera not in self.indefinite_events:
self.indefinite_events[camera] = {}
if not self.config.cameras[camera].record.enabled:
continue
@ -323,6 +337,27 @@ class ReviewSegmentMaintainer(threading.Thread):
current_segment.last_update = frame_time
current_segment.audio.update(audio_detections)
elif topic == DetectionTypeEnum.api:
if manual_info["state"] == ManualEventState.complete:
current_segment.detections[manual_info["event_id"]] = (
manual_info["label"]
)
current_segment.severity = SeverityEnum.alert
current_segment.last_update = manual_info["end_time"]
elif manual_info["state"] == ManualEventState.start:
self.indefinite_events[camera][manual_info["event_id"]] = (
manual_info["label"]
)
current_segment.detections[manual_info["event_id"]] = (
manual_info["label"]
)
current_segment.severity = SeverityEnum.alert
# temporarily make it so this event can not end
current_segment.last_update = sys.maxsize
elif manual_info["state"] == ManualEventState.end:
self.indefinite_events[camera].pop(manual_info["event_id"])
current_segment.last_update = manual_info["end_time"]
else:
if topic == DetectionTypeEnum.video:
self.check_if_new_segment(
@ -341,6 +376,27 @@ class ReviewSegmentMaintainer(threading.Thread):
set(audio_detections),
[],
)
elif topic == DetectionTypeEnum.api:
self.active_review_segments[camera] = PendingReviewSegment(
camera,
frame_time,
SeverityEnum.alert,
{manual_info["event_id"]: manual_info["label"]},
set(),
set(),
[],
)
if manual_info["state"] == ManualEventState.start:
self.indefinite_events[camera][manual_info["event_id"]] = (
manual_info["label"]
)
# temporarily make it so this event can not end
self.active_review_segments[camera] = sys.maxsize
elif manual_info["state"] == ManualEventState.complete:
self.active_review_segments[camera].last_update = manual_info[
"end_time"
]
def get_active_objects(