mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-10 00:57:38 +03:00
Add zone field to custom_classification MQTT events
Co-authored-by: Teagan42 <2989925+Teagan42@users.noreply.github.com>
This commit is contained in:
parent
41b6891fa0
commit
a2ad45e357
@ -173,7 +173,8 @@ Message published when [object classification](/configuration/custom_classificat
|
|||||||
"timestamp": 1607123958.748393,
|
"timestamp": 1607123958.748393,
|
||||||
"model": "person_classifier",
|
"model": "person_classifier",
|
||||||
"sub_label": "delivery_person",
|
"sub_label": "delivery_person",
|
||||||
"score": 0.87
|
"score": 0.87,
|
||||||
|
"zones": ["front_yard", "driveway"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -187,10 +188,15 @@ Message published when [object classification](/configuration/custom_classificat
|
|||||||
"timestamp": 1607123958.748393,
|
"timestamp": 1607123958.748393,
|
||||||
"model": "helmet_detector",
|
"model": "helmet_detector",
|
||||||
"attribute": "yes",
|
"attribute": "yes",
|
||||||
"score": 0.92
|
"score": 0.92,
|
||||||
|
"zones": ["front_yard"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::note
|
||||||
|
The `zones` field is only included if the tracked object is currently in one or more zones.
|
||||||
|
:::
|
||||||
|
|
||||||
### `frigate/reviews`
|
### `frigate/reviews`
|
||||||
|
|
||||||
Message published for each changed review item. The first message is published when the `detection` or `alert` is initiated.
|
Message published for each changed review item. The first message is published when the `detection` or `alert` is initiated.
|
||||||
|
|||||||
@ -613,19 +613,20 @@ class CustomObjectClassificationProcessor(RealTimeProcessorApi):
|
|||||||
(object_id, consensus_label, consensus_score),
|
(object_id, consensus_label, consensus_score),
|
||||||
EventMetadataTypeEnum.sub_label,
|
EventMetadataTypeEnum.sub_label,
|
||||||
)
|
)
|
||||||
|
classification_data = {
|
||||||
|
"type": TrackedObjectUpdateTypesEnum.classification,
|
||||||
|
"id": object_id,
|
||||||
|
"camera": camera,
|
||||||
|
"timestamp": now,
|
||||||
|
"model": self.model_config.name,
|
||||||
|
"sub_label": consensus_label,
|
||||||
|
"score": consensus_score,
|
||||||
|
}
|
||||||
|
if obj_data.get("current_zones"):
|
||||||
|
classification_data["zones"] = obj_data["current_zones"]
|
||||||
self.requestor.send_data(
|
self.requestor.send_data(
|
||||||
"tracked_object_update",
|
"tracked_object_update",
|
||||||
json.dumps(
|
json.dumps(classification_data),
|
||||||
{
|
|
||||||
"type": TrackedObjectUpdateTypesEnum.classification,
|
|
||||||
"id": object_id,
|
|
||||||
"camera": camera,
|
|
||||||
"timestamp": now,
|
|
||||||
"model": self.model_config.name,
|
|
||||||
"sub_label": consensus_label,
|
|
||||||
"score": consensus_score,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
elif (
|
elif (
|
||||||
self.model_config.object_config.classification_type
|
self.model_config.object_config.classification_type
|
||||||
@ -640,19 +641,20 @@ class CustomObjectClassificationProcessor(RealTimeProcessorApi):
|
|||||||
),
|
),
|
||||||
EventMetadataTypeEnum.attribute.value,
|
EventMetadataTypeEnum.attribute.value,
|
||||||
)
|
)
|
||||||
|
classification_data = {
|
||||||
|
"type": TrackedObjectUpdateTypesEnum.classification,
|
||||||
|
"id": object_id,
|
||||||
|
"camera": camera,
|
||||||
|
"timestamp": now,
|
||||||
|
"model": self.model_config.name,
|
||||||
|
"attribute": consensus_label,
|
||||||
|
"score": consensus_score,
|
||||||
|
}
|
||||||
|
if obj_data.get("current_zones"):
|
||||||
|
classification_data["zones"] = obj_data["current_zones"]
|
||||||
self.requestor.send_data(
|
self.requestor.send_data(
|
||||||
"tracked_object_update",
|
"tracked_object_update",
|
||||||
json.dumps(
|
json.dumps(classification_data),
|
||||||
{
|
|
||||||
"type": TrackedObjectUpdateTypesEnum.classification,
|
|
||||||
"id": object_id,
|
|
||||||
"camera": camera,
|
|
||||||
"timestamp": now,
|
|
||||||
"model": self.model_config.name,
|
|
||||||
"attribute": consensus_label,
|
|
||||||
"score": consensus_score,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_request(self, topic, request_data):
|
def handle_request(self, topic, request_data):
|
||||||
|
|||||||
166
frigate/test/test_custom_classification.py
Normal file
166
frigate/test/test_custom_classification.py
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import json
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
|
||||||
|
class TestCustomObjectClassificationZones(unittest.TestCase):
|
||||||
|
"""Test that zone information is correctly added to custom classification MQTT messages"""
|
||||||
|
|
||||||
|
def test_sub_label_message_includes_zones_when_present(self):
|
||||||
|
"""Test that zones are included in sub_label classification messages when object is in zones"""
|
||||||
|
# Create a simple mock requestor
|
||||||
|
requestor = MagicMock()
|
||||||
|
|
||||||
|
# Create mock obj_data with zones
|
||||||
|
obj_data = {
|
||||||
|
"id": "test_object_123",
|
||||||
|
"camera": "front_door",
|
||||||
|
"current_zones": ["driveway", "front_yard"],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Simulate what the processor does when publishing sub_label classification
|
||||||
|
classification_data = {
|
||||||
|
"type": "classification",
|
||||||
|
"id": obj_data["id"],
|
||||||
|
"camera": obj_data["camera"],
|
||||||
|
"timestamp": 1234567890.0,
|
||||||
|
"model": "test_classifier",
|
||||||
|
"sub_label": "person_walking",
|
||||||
|
"score": 0.89,
|
||||||
|
}
|
||||||
|
if obj_data.get("current_zones"):
|
||||||
|
classification_data["zones"] = obj_data["current_zones"]
|
||||||
|
|
||||||
|
requestor.send_data("tracked_object_update", json.dumps(classification_data))
|
||||||
|
|
||||||
|
# Verify that send_data was called
|
||||||
|
requestor.send_data.assert_called_once()
|
||||||
|
|
||||||
|
# Get the actual call arguments
|
||||||
|
call_args = requestor.send_data.call_args
|
||||||
|
topic = call_args[0][0]
|
||||||
|
data_json = call_args[0][1]
|
||||||
|
|
||||||
|
# Verify the topic
|
||||||
|
self.assertEqual(topic, "tracked_object_update")
|
||||||
|
|
||||||
|
# Parse and verify the data
|
||||||
|
data = json.loads(data_json)
|
||||||
|
self.assertEqual(data["type"], "classification")
|
||||||
|
self.assertEqual(data["id"], "test_object_123")
|
||||||
|
self.assertEqual(data["camera"], "front_door")
|
||||||
|
self.assertEqual(data["model"], "test_classifier")
|
||||||
|
self.assertEqual(data["sub_label"], "person_walking")
|
||||||
|
self.assertIn("zones", data)
|
||||||
|
self.assertEqual(data["zones"], ["driveway", "front_yard"])
|
||||||
|
|
||||||
|
def test_sub_label_message_excludes_zones_when_empty(self):
|
||||||
|
"""Test that zones are not included when object is not in any zones"""
|
||||||
|
requestor = MagicMock()
|
||||||
|
|
||||||
|
# Create mock obj_data without zones
|
||||||
|
obj_data = {
|
||||||
|
"id": "test_object_456",
|
||||||
|
"camera": "back_door",
|
||||||
|
"current_zones": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Simulate what the processor does when publishing sub_label classification
|
||||||
|
classification_data = {
|
||||||
|
"type": "classification",
|
||||||
|
"id": obj_data["id"],
|
||||||
|
"camera": obj_data["camera"],
|
||||||
|
"timestamp": 1234567890.0,
|
||||||
|
"model": "test_classifier",
|
||||||
|
"sub_label": "person_running",
|
||||||
|
"score": 0.87,
|
||||||
|
}
|
||||||
|
if obj_data.get("current_zones"):
|
||||||
|
classification_data["zones"] = obj_data["current_zones"]
|
||||||
|
|
||||||
|
requestor.send_data("tracked_object_update", json.dumps(classification_data))
|
||||||
|
|
||||||
|
# Get the actual call arguments
|
||||||
|
call_args = requestor.send_data.call_args
|
||||||
|
data_json = call_args[0][1]
|
||||||
|
|
||||||
|
# Parse and verify the data
|
||||||
|
data = json.loads(data_json)
|
||||||
|
self.assertNotIn("zones", data)
|
||||||
|
|
||||||
|
def test_attribute_message_includes_zones_when_present(self):
|
||||||
|
"""Test that zones are included in attribute classification messages when object is in zones"""
|
||||||
|
requestor = MagicMock()
|
||||||
|
|
||||||
|
# Create mock obj_data with zones
|
||||||
|
obj_data = {
|
||||||
|
"id": "test_object_789",
|
||||||
|
"camera": "construction_site",
|
||||||
|
"current_zones": ["site_entrance"],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Simulate what the processor does when publishing attribute classification
|
||||||
|
classification_data = {
|
||||||
|
"type": "classification",
|
||||||
|
"id": obj_data["id"],
|
||||||
|
"camera": obj_data["camera"],
|
||||||
|
"timestamp": 1234567890.0,
|
||||||
|
"model": "helmet_detector",
|
||||||
|
"attribute": "wearing_helmet",
|
||||||
|
"score": 0.92,
|
||||||
|
}
|
||||||
|
if obj_data.get("current_zones"):
|
||||||
|
classification_data["zones"] = obj_data["current_zones"]
|
||||||
|
|
||||||
|
requestor.send_data("tracked_object_update", json.dumps(classification_data))
|
||||||
|
|
||||||
|
# Get the actual call arguments
|
||||||
|
call_args = requestor.send_data.call_args
|
||||||
|
data_json = call_args[0][1]
|
||||||
|
|
||||||
|
# Parse and verify the data
|
||||||
|
data = json.loads(data_json)
|
||||||
|
self.assertEqual(data["type"], "classification")
|
||||||
|
self.assertEqual(data["id"], "test_object_789")
|
||||||
|
self.assertEqual(data["camera"], "construction_site")
|
||||||
|
self.assertEqual(data["model"], "helmet_detector")
|
||||||
|
self.assertEqual(data["attribute"], "wearing_helmet")
|
||||||
|
self.assertIn("zones", data)
|
||||||
|
self.assertEqual(data["zones"], ["site_entrance"])
|
||||||
|
|
||||||
|
def test_attribute_message_excludes_zones_when_missing(self):
|
||||||
|
"""Test that zones are not included when current_zones key is missing"""
|
||||||
|
requestor = MagicMock()
|
||||||
|
|
||||||
|
# Create mock obj_data without current_zones key
|
||||||
|
obj_data = {
|
||||||
|
"id": "test_object_999",
|
||||||
|
"camera": "parking_lot",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Simulate what the processor does when publishing attribute classification
|
||||||
|
classification_data = {
|
||||||
|
"type": "classification",
|
||||||
|
"id": obj_data["id"],
|
||||||
|
"camera": obj_data["camera"],
|
||||||
|
"timestamp": 1234567890.0,
|
||||||
|
"model": "vehicle_type",
|
||||||
|
"attribute": "sedan",
|
||||||
|
"score": 0.95,
|
||||||
|
}
|
||||||
|
if obj_data.get("current_zones"):
|
||||||
|
classification_data["zones"] = obj_data["current_zones"]
|
||||||
|
|
||||||
|
requestor.send_data("tracked_object_update", json.dumps(classification_data))
|
||||||
|
|
||||||
|
# Get the actual call arguments
|
||||||
|
call_args = requestor.send_data.call_args
|
||||||
|
data_json = call_args[0][1]
|
||||||
|
|
||||||
|
# Parse and verify the data
|
||||||
|
data = json.loads(data_json)
|
||||||
|
self.assertNotIn("zones", data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Reference in New Issue
Block a user