mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-31 07:27:57 +00:00
Categorize manual events as alerts when their label is an alert label (#23981)
* Categorize manual events as alerts when their label is an alert label * tweak docs
This commit is contained in:
parent
6816050a46
commit
fd98977506
@ -121,6 +121,31 @@ cameras:
|
|||||||
</TabItem>
|
</TabItem>
|
||||||
</ConfigTabs>
|
</ConfigTabs>
|
||||||
|
|
||||||
|
## Categorizing manual events
|
||||||
|
|
||||||
|
Events created with the [create manual event API](../integrations/api/create-event-events-camera-name-label-create-post.api.mdx) are categorized with the same label lists, using the label from the request path:
|
||||||
|
|
||||||
|
1. If alerts are enabled and the label is listed in `review -> alerts -> labels`, the review item is an alert.
|
||||||
|
2. Otherwise, if detections are enabled and the label is listed in `review -> detections -> labels`, the review item is a detection.
|
||||||
|
3. If the label is in neither list, the review item is an alert, or no review item is created if alerts are disabled.
|
||||||
|
|
||||||
|
This means manual events are alerts unless you explicitly list their label as a detection label. For example, to have PIR sensors create detections instead of alerts, post to `/api/events/front_door/pir_sensor/create` with the following config:
|
||||||
|
|
||||||
|
```yaml {5-7}
|
||||||
|
cameras:
|
||||||
|
front_door:
|
||||||
|
review:
|
||||||
|
detections:
|
||||||
|
labels:
|
||||||
|
- pir_sensor
|
||||||
|
```
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
Required zones do not apply to manual events, since they are created through the API rather than by the object tracker. Setting `review -> alerts -> labels` to an empty list also does not stop manual events from becoming alerts, as a label in neither list still falls back to an alert.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## Restricting review items to specific zones
|
## Restricting review items to specific zones
|
||||||
|
|
||||||
By default a review item will be created if any `review -> alerts -> labels` and `review -> detections -> labels` are detected anywhere in the camera frame. You will likely want to configure review items to only be created when the object enters an area of interest, [see the zone docs for more information](./zones.md#restricting-alerts-and-detections-to-specific-zones)
|
By default a review item will be created if any `review -> alerts -> labels` and `review -> detections -> labels` are detected anywhere in the camera frame. You will likely want to configure review items to only be created when the object enters an area of interest, [see the zone docs for more information](./zones.md#restricting-alerts-and-detections-to-specific-zones)
|
||||||
|
|||||||
1
docs/static/frigate-api.yaml
vendored
1
docs/static/frigate-api.yaml
vendored
@ -5093,6 +5093,7 @@ paths:
|
|||||||
NOTES:
|
NOTES:
|
||||||
- Creating a manual event does not trigger an update to /events MQTT topic.
|
- Creating a manual event does not trigger an update to /events MQTT topic.
|
||||||
- If a duration is set to null, the event will need to be ended manually by calling /events/{event_id}/end.
|
- If a duration is set to null, the event will need to be ended manually by calling /events/{event_id}/end.
|
||||||
|
- The review item is an alert unless the label is listed in the camera's review -> detections -> labels config.
|
||||||
operationId: create_event_events__camera_name___label__create_post
|
operationId: create_event_events__camera_name___label__create_post
|
||||||
parameters:
|
parameters:
|
||||||
- name: camera_name
|
- name: camera_name
|
||||||
|
|||||||
@ -1748,6 +1748,7 @@ async def delete_events(request: Request, body: EventsDeleteBody):
|
|||||||
NOTES:
|
NOTES:
|
||||||
- Creating a manual event does not trigger an update to /events MQTT topic.
|
- Creating a manual event does not trigger an update to /events MQTT topic.
|
||||||
- If a duration is set to null, the event will need to be ended manually by calling /events/{event_id}/end.
|
- If a duration is set to null, the event will need to be ended manually by calling /events/{event_id}/end.
|
||||||
|
- The review item is an alert unless the label is listed in the camera's review -> detections -> labels config.
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
def create_event(
|
def create_event(
|
||||||
|
|||||||
@ -392,6 +392,32 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
return self._publish_segment_end(segment, prev_data)
|
return self._publish_segment_end(segment, prev_data)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_manual_event_severity(self, camera: str, label: str) -> SeverityEnum | None:
|
||||||
|
"""Determine the review severity for a manual event label.
|
||||||
|
|
||||||
|
Alert labels take precedence over detection labels, matching how
|
||||||
|
tracked objects are categorized. Labels in neither list default to
|
||||||
|
alerts so manual events keep their historical severity.
|
||||||
|
"""
|
||||||
|
review_config = self.config.cameras[camera].review
|
||||||
|
# label contains 'label: sub_label', only the label is categorized
|
||||||
|
label = label.split(": ")[0]
|
||||||
|
|
||||||
|
if review_config.alerts.enabled and label in review_config.alerts.labels:
|
||||||
|
return SeverityEnum.alert
|
||||||
|
|
||||||
|
if (
|
||||||
|
review_config.detections.enabled
|
||||||
|
and review_config.detections.labels is not None
|
||||||
|
and label in review_config.detections.labels
|
||||||
|
):
|
||||||
|
return SeverityEnum.detection
|
||||||
|
|
||||||
|
if review_config.alerts.enabled:
|
||||||
|
return SeverityEnum.alert
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def update_existing_segment(
|
def update_existing_segment(
|
||||||
self,
|
self,
|
||||||
segment: PendingReviewSegment,
|
segment: PendingReviewSegment,
|
||||||
@ -734,24 +760,19 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
manual_info["label"]
|
manual_info["label"]
|
||||||
)
|
)
|
||||||
if topic == DetectionTypeEnum.api:
|
if topic == DetectionTypeEnum.api:
|
||||||
# manual_info["label"] contains 'label: sub_label'
|
severity = self.get_manual_event_severity(
|
||||||
# so split out the label without modifying manual_info
|
camera, manual_info["label"]
|
||||||
det_labels = self.config.cameras[
|
)
|
||||||
camera
|
|
||||||
].review.detections.labels
|
if severity == SeverityEnum.alert:
|
||||||
if (
|
|
||||||
self.config.cameras[camera].review.detections.enabled
|
|
||||||
and det_labels is not None
|
|
||||||
and manual_info["label"].split(": ")[0] in det_labels
|
|
||||||
):
|
|
||||||
current_segment.last_detection_time = manual_info[
|
|
||||||
"end_time"
|
|
||||||
]
|
|
||||||
elif self.config.cameras[camera].review.alerts.enabled:
|
|
||||||
current_segment.severity = SeverityEnum.alert
|
current_segment.severity = SeverityEnum.alert
|
||||||
current_segment.last_alert_time = manual_info[
|
current_segment.last_alert_time = manual_info[
|
||||||
"end_time"
|
"end_time"
|
||||||
]
|
]
|
||||||
|
elif severity == SeverityEnum.detection:
|
||||||
|
current_segment.last_detection_time = manual_info[
|
||||||
|
"end_time"
|
||||||
|
]
|
||||||
elif (
|
elif (
|
||||||
topic == DetectionTypeEnum.lpr
|
topic == DetectionTypeEnum.lpr
|
||||||
and self.config.cameras[camera].review.detections.enabled
|
and self.config.cameras[camera].review.detections.enabled
|
||||||
@ -765,21 +786,12 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
current_segment.detections[manual_info["event_id"]] = (
|
current_segment.detections[manual_info["event_id"]] = (
|
||||||
manual_info["label"]
|
manual_info["label"]
|
||||||
)
|
)
|
||||||
if (
|
if topic == DetectionTypeEnum.api:
|
||||||
topic == DetectionTypeEnum.api
|
|
||||||
and self.config.cameras[camera].review.alerts.enabled
|
|
||||||
):
|
|
||||||
# manual_info["label"] contains 'label: sub_label'
|
|
||||||
# so split out the label without modifying manual_info
|
|
||||||
det_labels = self.config.cameras[
|
|
||||||
camera
|
|
||||||
].review.detections.labels
|
|
||||||
if (
|
if (
|
||||||
not self.config.cameras[
|
self.get_manual_event_severity(
|
||||||
camera
|
camera, manual_info["label"]
|
||||||
].review.detections.enabled
|
)
|
||||||
or det_labels is None
|
== SeverityEnum.alert
|
||||||
or manual_info["label"].split(": ")[0] not in det_labels
|
|
||||||
):
|
):
|
||||||
current_segment.severity = SeverityEnum.alert
|
current_segment.severity = SeverityEnum.alert
|
||||||
elif (
|
elif (
|
||||||
@ -853,18 +865,9 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
detections,
|
detections,
|
||||||
)
|
)
|
||||||
elif topic == DetectionTypeEnum.api:
|
elif topic == DetectionTypeEnum.api:
|
||||||
severity = None
|
severity = self.get_manual_event_severity(
|
||||||
# manual_info["label"] contains 'label: sub_label'
|
camera, manual_info["label"]
|
||||||
# so split out the label without modifying manual_info
|
)
|
||||||
det_labels = self.config.cameras[camera].review.detections.labels
|
|
||||||
if (
|
|
||||||
self.config.cameras[camera].review.detections.enabled
|
|
||||||
and det_labels is not None
|
|
||||||
and manual_info["label"].split(": ")[0] in det_labels
|
|
||||||
):
|
|
||||||
severity = SeverityEnum.detection
|
|
||||||
elif self.config.cameras[camera].review.alerts.enabled:
|
|
||||||
severity = SeverityEnum.alert
|
|
||||||
|
|
||||||
if severity:
|
if severity:
|
||||||
api_segment = PendingReviewSegment(
|
api_segment = PendingReviewSegment(
|
||||||
|
|||||||
154
frigate/test/test_review_manual_event_severity.py
Normal file
154
frigate/test/test_review_manual_event_severity.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
"""Tests for manual event severity categorization.
|
||||||
|
|
||||||
|
Regression coverage for manual events created via the events API being
|
||||||
|
categorized as detections when their label appears in both the alerts and
|
||||||
|
detections label lists. Alert labels must win, matching how tracked objects
|
||||||
|
are categorized, and labels in neither list must default to alerts so the
|
||||||
|
historical behavior of the API is preserved.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from frigate.config import FrigateConfig
|
||||||
|
from frigate.review.maintainer import ReviewSegmentMaintainer
|
||||||
|
from frigate.review.types import SeverityEnum
|
||||||
|
|
||||||
|
BASE_CONFIG = """
|
||||||
|
mqtt:
|
||||||
|
enabled: False
|
||||||
|
cameras:
|
||||||
|
front_door:
|
||||||
|
ffmpeg:
|
||||||
|
inputs:
|
||||||
|
- path: rtsp://10.0.0.1:554/video
|
||||||
|
roles:
|
||||||
|
- detect
|
||||||
|
detect:
|
||||||
|
width: 1920
|
||||||
|
height: 1080
|
||||||
|
fps: 5
|
||||||
|
%s
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TestManualEventSeverity(unittest.TestCase):
|
||||||
|
def _make_maintainer(self, review_config: str = "") -> ReviewSegmentMaintainer:
|
||||||
|
"""Build a maintainer without invoking __init__ (avoids needing ZMQ
|
||||||
|
sockets, shared memory, and clip dirs). Only the config is read when
|
||||||
|
categorizing a manual event label."""
|
||||||
|
maintainer = ReviewSegmentMaintainer.__new__(ReviewSegmentMaintainer)
|
||||||
|
maintainer.config = FrigateConfig.parse_yaml(BASE_CONFIG % review_config)
|
||||||
|
return maintainer
|
||||||
|
|
||||||
|
def test_defaults_to_alert(self) -> None:
|
||||||
|
maintainer = self._make_maintainer()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
maintainer.get_manual_event_severity("front_door", "person"),
|
||||||
|
SeverityEnum.alert,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_unlisted_label_defaults_to_alert(self) -> None:
|
||||||
|
maintainer = self._make_maintainer(
|
||||||
|
"""
|
||||||
|
review:
|
||||||
|
detections:
|
||||||
|
labels:
|
||||||
|
- dog
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
maintainer.get_manual_event_severity("front_door", "pir_sensor"),
|
||||||
|
SeverityEnum.alert,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_detection_label_is_detection(self) -> None:
|
||||||
|
maintainer = self._make_maintainer(
|
||||||
|
"""
|
||||||
|
review:
|
||||||
|
alerts:
|
||||||
|
labels:
|
||||||
|
- person
|
||||||
|
detections:
|
||||||
|
labels:
|
||||||
|
- pir_sensor
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
maintainer.get_manual_event_severity("front_door", "pir_sensor"),
|
||||||
|
SeverityEnum.detection,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_alert_label_wins_over_detection_label(self) -> None:
|
||||||
|
maintainer = self._make_maintainer(
|
||||||
|
"""
|
||||||
|
review:
|
||||||
|
alerts:
|
||||||
|
labels:
|
||||||
|
- person
|
||||||
|
detections:
|
||||||
|
labels:
|
||||||
|
- person
|
||||||
|
- dog
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
maintainer.get_manual_event_severity("front_door", "person"),
|
||||||
|
SeverityEnum.alert,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sub_label_is_stripped_before_categorizing(self) -> None:
|
||||||
|
maintainer = self._make_maintainer(
|
||||||
|
"""
|
||||||
|
review:
|
||||||
|
alerts:
|
||||||
|
labels:
|
||||||
|
- person
|
||||||
|
detections:
|
||||||
|
labels:
|
||||||
|
- person
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
maintainer.get_manual_event_severity("front_door", "person: Bob"),
|
||||||
|
SeverityEnum.alert,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_alert_label_is_detection_when_alerts_disabled(self) -> None:
|
||||||
|
maintainer = self._make_maintainer(
|
||||||
|
"""
|
||||||
|
review:
|
||||||
|
alerts:
|
||||||
|
enabled: False
|
||||||
|
labels:
|
||||||
|
- person
|
||||||
|
detections:
|
||||||
|
labels:
|
||||||
|
- person
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
maintainer.get_manual_event_severity("front_door", "person"),
|
||||||
|
SeverityEnum.detection,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_no_severity_when_alerts_disabled_and_label_not_a_detection(self) -> None:
|
||||||
|
maintainer = self._make_maintainer(
|
||||||
|
"""
|
||||||
|
review:
|
||||||
|
alerts:
|
||||||
|
enabled: False
|
||||||
|
detections:
|
||||||
|
labels:
|
||||||
|
- dog
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsNone(
|
||||||
|
maintainer.get_manual_event_severity("front_door", "pir_sensor")
|
||||||
|
)
|
||||||
Loading…
x
Reference in New Issue
Block a user