110 lines
3.6 KiB
Python
110 lines
3.6 KiB
Python
"""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
|