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): class DetectionTypeEnum(str, Enum):
all = "" all = ""
api = "api"
video = "video" video = "video"
audio = "audio" audio = "audio"

View File

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

View File

@ -5,6 +5,7 @@ import logging
import os import os
import random import random
import string import string
import sys
import threading import threading
from enum import Enum from enum import Enum
from multiprocessing.synchronize import Event as MpEvent 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.comms.inter_process import InterProcessRequestor
from frigate.config import CameraConfig, FrigateConfig from frigate.config import CameraConfig, FrigateConfig
from frigate.const import ALL_ATTRIBUTE_LABELS, CLIPS_DIR, UPSERT_REVIEW_SEGMENT 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.models import ReviewSegment
from frigate.object_processing import TrackedObject from frigate.object_processing import TrackedObject
from frigate.util.image import SharedMemoryFrameManager, calculate_16_9_crop 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.config_subscriber = ConfigSubscriber("config/record/")
self.detection_subscriber = DetectionSubscriber(DetectionTypeEnum.all) self.detection_subscriber = DetectionSubscriber(DetectionTypeEnum.all)
# manual events
self.indefinite_events: dict[str, dict[str, any]] = {}
self.stop_event = stop_event self.stop_event = stop_event
def end_segment(self, segment: PendingReviewSegment) -> None: def end_segment(self, segment: PendingReviewSegment) -> None:
@ -304,6 +309,15 @@ class ReviewSegmentMaintainer(threading.Thread):
dBFS, dBFS,
audio_detections, audio_detections,
) = data ) = 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: if not self.config.cameras[camera].record.enabled:
continue continue
@ -323,6 +337,27 @@ class ReviewSegmentMaintainer(threading.Thread):
current_segment.last_update = frame_time current_segment.last_update = frame_time
current_segment.audio.update(audio_detections) 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: else:
if topic == DetectionTypeEnum.video: if topic == DetectionTypeEnum.video:
self.check_if_new_segment( self.check_if_new_segment(
@ -341,6 +376,27 @@ class ReviewSegmentMaintainer(threading.Thread):
set(audio_detections), 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( def get_active_objects(