Coverage for orchestr_ant_ion / pipeline / ui / viewmodel.py: 34%

79 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-19 08:36 +0000

1"""Shared view-model that formats raw metrics into display strings. 

2 

3Both :class:`DearPyGuiViewer` and :class:`WxPythonViewer` delegate to 

4this module so that formatting logic is defined in exactly one place. 

5""" 

6 

7from __future__ import annotations 

8 

9from dataclasses import dataclass 

10from typing import TYPE_CHECKING 

11 

12 

13if TYPE_CHECKING: 

14 from orchestr_ant_ion.pipeline.types import ( 

15 PerformanceMetrics, 

16 SystemStats, 

17 ) 

18 

19 

20@dataclass(slots=True) 

21class ViewLabels: 

22 """Pre-formatted label strings ready for display.""" 

23 

24 resolution: str = "" 

25 capture: str = "" 

26 backend: str = "" 

27 pipeline: str = "" 

28 cpu_model: str = "" 

29 ram_total: str = "" 

30 gpu_model: str = "" 

31 vram_total: str = "" 

32 detections: str = "" 

33 camera_fps: str = "" 

34 inference_ms: str = "" 

35 budget: str = "" 

36 headroom: str = "" 

37 sys_cpu: str = "" 

38 sys_ram: str = "" 

39 gpu: str = "" 

40 vram: str = "" 

41 power: str = "" 

42 energy: str = "" 

43 proc: str = "" 

44 classification: str = "" 

45 

46 

47def format_labels( # noqa: C901 

48 *, 

49 perf_metrics: PerformanceMetrics | None, 

50 sys_stats: SystemStats | None, 

51 proc_stats: dict | None, 

52 camera_info: dict | None, 

53 detections_count: int | None, 

54 hardware_info: dict | None, 

55 power_info: dict | None, 

56 classification: dict | None, 

57 frame_size: tuple[int, int], 

58) -> ViewLabels: 

59 """Return a :class:`ViewLabels` from the raw data sources.""" 

60 labels = ViewLabels() 

61 

62 if ( 

63 perf_metrics is None 

64 or sys_stats is None 

65 or proc_stats is None 

66 or camera_info is None 

67 ): 

68 return labels 

69 

70 w, h = frame_size 

71 labels.resolution = f"Resolution: {w}x{h}" 

72 

73 # Camera / backend 

74 backend_display = camera_info.get("backend", "unknown") 

75 labels.capture = f"Capture: {backend_display}" 

76 labels.backend = f"Backend: {backend_display}" 

77 pipeline = camera_info.get("pipeline", "") 

78 if pipeline: 

79 if len(pipeline) > 70: 

80 pipeline = pipeline[:67] + "..." 

81 labels.pipeline = f"Pipeline: {pipeline}" 

82 if detections_count is not None: 

83 labels.detections = f"Detections: {detections_count}" 

84 

85 # Hardware 

86 if hardware_info is not None: 

87 cpu_model = hardware_info.get("cpu_model", "N/A") 

88 ram_total = hardware_info.get("ram_total_gb", 0.0) 

89 gpu_model = hardware_info.get("gpu_model", "N/A") 

90 vram_total = hardware_info.get("vram_total_gb", 0.0) 

91 labels.cpu_model = f"CPU: {cpu_model}" 

92 labels.ram_total = f"RAM: {ram_total:.1f} GB" 

93 if gpu_model and gpu_model != "N/A": 

94 labels.gpu_model = f"GPU: {gpu_model}" 

95 labels.vram_total = f"VRAM: {vram_total:.1f} GB" 

96 

97 # Performance 

98 labels.camera_fps = f"Camera Input: {perf_metrics.camera_fps:.1f} FPS" 

99 labels.inference_ms = f"Inference Latency: {perf_metrics.inference_ms:.1f} ms/frame" 

100 labels.budget = f"Frame Budget Used: {perf_metrics.frame_budget_percent:.1f}%" 

101 headroom = 100 - perf_metrics.frame_budget_percent 

102 labels.headroom = ( 

103 f"GPU Headroom: {headroom:.0f}% " 

104 f"(capacity: {perf_metrics.inference_capacity_fps:.0f} FPS)" 

105 ) 

106 

107 # System 

108 labels.sys_cpu = f"System CPU: {sys_stats.cpu_percent:.1f}%" 

109 labels.sys_ram = ( 

110 f"System RAM: {sys_stats.ram_used_gb:.1f}/{sys_stats.ram_total_gb:.1f} GB " 

111 f"({sys_stats.ram_percent:.1f}%)" 

112 ) 

113 if sys_stats.gpu_name != "N/A": 

114 labels.gpu = ( 

115 f"GPU Load: {sys_stats.gpu_percent:.0f}% | " 

116 f"Temp: {sys_stats.gpu_temp_celsius:.0f}C | " 

117 f"{sys_stats.gpu_power_watts:.0f}W" 

118 ) 

119 labels.vram = ( 

120 f"VRAM: {sys_stats.gpu_memory_used_gb:.1f}/" 

121 f"{sys_stats.gpu_memory_total_gb:.1f} GB " 

122 f"({sys_stats.gpu_memory_percent:.0f}%)" 

123 ) 

124 

125 # Power 

126 if power_info is not None: 

127 system_power = power_info.get("system_power_watts", 0.0) 

128 cpu_power = power_info.get("cpu_power_watts", 0.0) 

129 gpu_power = power_info.get("gpu_power_watts", 0.0) 

130 energy_wh = power_info.get("energy_wh", 0.0) 

131 if system_power > 0.0: 

132 labels.power = ( 

133 f"Power: {system_power:.0f}W " 

134 f"(CPU {cpu_power:.0f}W, GPU {gpu_power:.0f}W)" 

135 ) 

136 elif gpu_power > 0.0: 

137 labels.power = f"Power: GPU {gpu_power:.0f}W" 

138 if energy_wh > 0.0: 

139 labels.energy = f"Energy: {energy_wh:.3f} Wh" 

140 

141 # Process 

142 labels.proc = ( 

143 f"CPU: {proc_stats['cpu_percent']:.1f}% | " 

144 f"RAM: {proc_stats['memory_mb']:.0f}MB | " 

145 f"Threads: {proc_stats['threads']}" 

146 ) 

147 

148 # Classification 

149 if classification is not None: 

150 class_label = classification.get("label", "unknown") 

151 class_score = classification.get("score", 0.0) 

152 labels.classification = f"Class: {class_label} ({class_score:.2f})" 

153 

154 return labels