fix(v1): use ASCII screenshot annotation to avoid garbled text

cv2.putText only supports ASCII (Hershey fonts); the middle-dot in the
label rendered as garbage on saved screenshots. screenshot_label builds
an ASCII-only 'ID STATE' string for the annotation; the Qt live-view
label is unchanged. 72 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ila
2026-07-21 23:38:53 +08:00
co-authored by Claude Opus 4.8
parent 7029468037
commit b35082a643
5 changed files with 45 additions and 4 deletions
+13 -1
View File
@@ -46,6 +46,18 @@ class AlertSink:
pass
def screenshot_label(track_id: str, state_value: str) -> str:
"""ASCII-only label for ``cv2.putText``.
OpenCV's Hershey fonts render only ASCII; non-ASCII characters (such as the
middle dot used in the live-view label) come out garbled, so the screenshot
label is built from the id and state and stripped to ASCII.
"""
text = "{0} {1}".format(track_id, state_value)
return text.encode("ascii", "ignore").decode("ascii")
def annotate_frame(image: np.ndarray, view: MonitorViewState) -> np.ndarray:
"""Return a copy of ``image`` with boxes, skeleton, labels and a state border."""
@@ -67,7 +79,7 @@ def annotate_frame(image: np.ndarray, view: MonitorViewState) -> np.ndarray:
cv2.circle(canvas, (int(round(point.x)), int(round(point.y))), 3, color, -1)
cv2.putText(
canvas,
overlay.label,
screenshot_label(overlay.track_id, overlay.state.value),
(left, max(0, top - 6)),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
+21 -1
View File
@@ -4,7 +4,13 @@ from datetime import datetime, timezone
import numpy as np
import pytest
from v1.alerts import AlertDispatcher, AlertSink, EventArtifactWriter, annotate_frame
from v1.alerts import (
AlertDispatcher,
AlertSink,
EventArtifactWriter,
annotate_frame,
screenshot_label,
)
from v1.fall_state import FallEvent, FallState
from v1.view_model import MonitorViewState, PersonOverlay, Point, StatusColor
@@ -115,6 +121,20 @@ def test_distinct_events_produce_two_log_lines(tmp_path):
assert len(lines) == 2
def test_screenshot_label_is_ascii_only():
label = screenshot_label("P-0001", "CONFIRMED")
assert label == "P-0001 CONFIRMED"
assert all(ord(ch) < 128 for ch in label)
def test_screenshot_label_strips_non_ascii_separator():
# The live-view label uses a middle dot; the screenshot label must not.
label = screenshot_label("P-0001", "CONFIRMED")
assert "·" not in label
def test_annotate_draws_red_border_only_for_confirmed():
image = np.full((60, 80, 3), 30, dtype=np.uint8)
confirmed = annotate_frame(image, _confirmed_view())