"""TelemetryOverlay — draws the performance metrics box on the camera view.""" from __future__ import annotations from PySide6.QtCore import QRect, Qt, Slot from PySide6.QtGui import QColor, QFont, QPainter, QPen from app.config import ( OVERLAY_BG_COLOR, OVERLAY_FONT_SIZE, OVERLAY_MARGIN, OVERLAY_PADDING, OVERLAY_TEXT_COLOR, ) from app.overlay.overlay_layer import IOverlayLayer from app.telemetry.telemetry_collector import TelemetrySnapshot class TelemetryOverlay(IOverlayLayer): """ Renders a semi-transparent metrics box in the top-left corner. Usage: overlay = TelemetryOverlay() camera_view.add_overlay_layer(overlay) telemetry_collector.metrics_updated.connect(overlay.on_metrics_updated) Display format: FPS req 60.0 ← what was requested from camera FPS got 30.2 ← what camera actually delivered Frame 33.1 ms Drop 0 CPU sys 14.8 % ← normalised by cpu_count (matches Task Manager) CPU core 118.4 % ← per single core (can exceed 100%) Mem 68 MB """ def __init__(self) -> None: super().__init__() self._snapshot: TelemetrySnapshot | None = None self._font = QFont("Monospace") self._font.setStyleHint(QFont.StyleHint.TypeWriter) self._font.setPointSize(OVERLAY_FONT_SIZE) self._font.setBold(False) @Slot(object) def on_metrics_updated(self, snapshot: TelemetrySnapshot) -> None: """Receive a new snapshot from TelemetryCollector.""" self._snapshot = snapshot # ------------------------------------------------------------------ # IOverlayLayer # ------------------------------------------------------------------ def paint(self, painter: QPainter, video_rect: QRect) -> None: if self._snapshot is None: return lines = self._format_lines(self._snapshot) if not lines: return painter.setFont(self._font) fm = painter.fontMetrics() line_height = fm.height() max_width = max(fm.horizontalAdvance(line) for line in lines) box_w = max_width + OVERLAY_PADDING * 2 box_h = line_height * len(lines) + OVERLAY_PADDING * 2 x = video_rect.left() + OVERLAY_MARGIN y = video_rect.top() + OVERLAY_MARGIN # Background painter.setBrush(QColor(*OVERLAY_BG_COLOR)) painter.setPen(Qt.PenStyle.NoPen) painter.drawRoundedRect(QRect(x, y, box_w, box_h), 6, 6) # Text painter.setPen(QPen(QColor(*OVERLAY_TEXT_COLOR))) text_x = x + OVERLAY_PADDING text_y = y + OVERLAY_PADDING + fm.ascent() for line in lines: painter.drawText(text_x, text_y, line) text_y += line_height # ------------------------------------------------------------------ # Private # ------------------------------------------------------------------ @staticmethod def _format_lines(snap: TelemetrySnapshot) -> list[str]: lines: list[str] = [] # FPS — show target if known, then actual if snap.target_fps is not None: lines.append(f"FPS req {snap.target_fps:>6.1f}") lines.append(f"FPS got {snap.fps:>6.1f}") lines.append(f"Frame {snap.frame_time_ms:>6.1f} ms") lines.append(f"Drop {snap.dropped_frames:>6d}") lines.append(f"CPU sys {snap.cpu_percent_sys:>5.1f} %") lines.append(f"CPU core {snap.cpu_percent_core:>5.1f} %") if snap.memory_mb is not None: lines.append(f"Mem {snap.memory_mb:>5.0f} MB") return lines