feat: draw calibration overlay on alarm snapshots
Before JPEG encoding and OTA upload, paint the configured [[zones]] polygons (yellow) and the [trash].roi (red) directly onto a copied Frame.rgb so uploaded alarm snapshots visually carry the calibrated regions. Normalized coordinates are clamped to image bounds, the source frame stays untouched for downstream runtime processing, and non-alert/disabled paths are unchanged. Adds stdlib-only polygon fill/outline helpers plus focused unit tests. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import unittest
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from cold_display_guard import alarm_snapshots
|
||||
from cold_display_guard.alarm_snapshots import (
|
||||
capture_alert_snapshot,
|
||||
load_alarm_snapshot_settings,
|
||||
@@ -127,6 +128,75 @@ class AlarmSnapshotTests(unittest.TestCase):
|
||||
self.assertEqual(result["object_key"], "uploads/alarms/test.jpg")
|
||||
self.assertEqual(result["batch_ids"], ["batch_1"])
|
||||
|
||||
def test_calibration_overlay_draws_zones_and_trash_roi_without_mutating_source(self) -> None:
|
||||
apply_overlay = getattr(alarm_snapshots, "apply_calibration_overlay", None)
|
||||
self.assertTrue(callable(apply_overlay), "apply_calibration_overlay should be available")
|
||||
frame = Frame(width=5, height=5, rgb=b"\x00\x00\x00" * 25)
|
||||
|
||||
annotated = apply_overlay(
|
||||
frame,
|
||||
{
|
||||
"zones": [
|
||||
{
|
||||
"id": "1",
|
||||
"label": "区域 1",
|
||||
"polygon": [[0.2, 0.2], [0.8, 0.2], [0.8, 0.8], [0.2, 0.8]],
|
||||
}
|
||||
],
|
||||
"trash": {"roi": [[0.0, 0.0], [0.4, 0.0], [0.0, 0.4]]},
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(frame.rgb, b"\x00\x00\x00" * 25)
|
||||
self.assertNotEqual(annotated.rgb, frame.rgb)
|
||||
self.assertNotEqual(annotated.pixel(1, 1), (0, 0, 0))
|
||||
self.assertNotEqual(annotated.pixel(2, 2), (0, 0, 0))
|
||||
self.assertNotEqual(annotated.pixel(0, 0), (0, 0, 0))
|
||||
|
||||
def test_capture_alert_snapshot_encodes_frame_with_configured_calibration_overlay(self) -> None:
|
||||
encoded_frames: list[Frame] = []
|
||||
|
||||
def fake_encode(frame: Frame, timeout_seconds: float) -> bytes:
|
||||
encoded_frames.append(frame)
|
||||
return b"jpeg-bytes"
|
||||
|
||||
def fake_upload(
|
||||
image_bytes: bytes,
|
||||
*,
|
||||
file_name: str,
|
||||
object_key_hint: str,
|
||||
settings,
|
||||
post_json_request=None,
|
||||
post_multipart_request=None,
|
||||
) -> dict[str, object]:
|
||||
return {"status": "uploaded", "object_key": "uploads/alarms/overlay.jpg", "file_name": file_name}
|
||||
|
||||
source_frame = Frame(width=5, height=5, rgb=b"\x00\x00\x00" * 25)
|
||||
result = capture_alert_snapshot(
|
||||
source_frame,
|
||||
[{"event": "time_alarm", "severity": "alarm", "batch_id": "batch_1", "camera_id": "cam_1", "zone_id": "1", "ts": "2026-06-09T09:00:00+00:00"}],
|
||||
{
|
||||
"alarm_snapshot_upload": {"enabled": True},
|
||||
"zones": [
|
||||
{
|
||||
"id": "1",
|
||||
"label": "区域 1",
|
||||
"polygon": [[0.2, 0.2], [0.8, 0.2], [0.8, 0.8], [0.2, 0.8]],
|
||||
}
|
||||
],
|
||||
"trash": {"roi": [[0.0, 0.0], [0.4, 0.0], [0.0, 0.4]]},
|
||||
},
|
||||
now=datetime(2026, 6, 9, 9, 0, tzinfo=UTC),
|
||||
jpeg_encoder=fake_encode,
|
||||
uploader=fake_upload,
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "uploaded")
|
||||
self.assertEqual(source_frame.rgb, b"\x00\x00\x00" * 25)
|
||||
self.assertEqual(len(encoded_frames), 1)
|
||||
self.assertNotEqual(encoded_frames[0].rgb, source_frame.rgb)
|
||||
self.assertNotEqual(encoded_frames[0].pixel(1, 1), (0, 0, 0))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user