File size: 6,573 Bytes
3e5fb54 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | """
Visualization utilities for FSD model outputs.
Generates visual representations of:
- Sensor placement on vehicle
- BEV perception outputs
- Planned trajectory
- Control commands
"""
import torch
import numpy as np
from typing import Dict, Optional, Tuple, List
import math
def visualize_sensor_layout_ascii(vehicle_config) -> str:
"""Generate ASCII art of sensor placement on vehicle."""
sc = vehicle_config.sensor_config
half_l = vehicle_config.length / 2
half_w = vehicle_config.width / 2
# Create grid
grid_h, grid_w = 30, 50
grid = [[' '] * grid_w for _ in range(grid_h)]
# Scale factors
scale_x = (grid_w - 10) / (vehicle_config.length + 2)
scale_y = (grid_h - 6) / (vehicle_config.width + 2)
cx, cy = grid_w // 2, grid_h // 2
# Draw vehicle outline
vw = int(vehicle_config.length * scale_x / 2)
vh = int(vehicle_config.width * scale_y / 2)
for x in range(cx - vw, cx + vw + 1):
if 0 <= x < grid_w:
if 0 <= cy - vh < grid_h:
grid[cy - vh][x] = 'β'
if 0 <= cy + vh < grid_h:
grid[cy + vh][x] = 'β'
for y in range(cy - vh, cy + vh + 1):
if 0 <= y < grid_h:
if 0 <= cx - vw < grid_w:
grid[y][cx - vw] = 'β'
if 0 <= cx + vw < grid_w:
grid[y][cx + vw] = 'β'
# Corners
for (dy, dx), ch in [
((-vh, -vw), 'β'), ((-vh, vw), 'β'),
((vh, -vw), 'β'), ((vh, vw), 'β')
]:
gy, gx = cy + dy, cx + dx
if 0 <= gy < grid_h and 0 <= gx < grid_w:
grid[gy][gx] = ch
# Direction arrow
if 0 <= cy < grid_h and cx + vw + 1 < grid_w:
grid[cy][cx + vw + 1] = 'βΊ'
grid[cy][cx] = '+' # center
# Place cameras
for i, cam in enumerate(sc.cameras):
gx = cx + int(cam.placement.x * scale_x)
gy = cy - int(cam.placement.y * scale_y)
if 0 <= gy < grid_h and 0 <= gx < grid_w:
grid[gy][gx] = 'C'
# Place ultrasonics
for i, us in enumerate(sc.ultrasonics):
gx = cx + int(us.placement.x * scale_x)
gy = cy - int(us.placement.y * scale_y)
if 0 <= gy < grid_h and 0 <= gx < grid_w:
if grid[gy][gx] == ' ' or grid[gy][gx] in 'ββ':
grid[gy][gx] = 'U'
# Add labels
result = "Vehicle Sensor Layout (Top View)\n"
result += "C = Camera, U = Ultrasonic, + = Center, βΊ = Forward\n"
result += "=" * grid_w + "\n"
for row in grid:
result += ''.join(row) + "\n"
result += "=" * grid_w + "\n"
return result
def format_model_output(output: Dict[str, torch.Tensor]) -> str:
"""Format model output as human-readable text."""
lines = []
lines.append("ββββββββββββββββββββββββββββββββββββββββββββ")
lines.append("β FSD Model Output Summary β")
lines.append("β βββββββββββββββββββββββββββββββββββββββββββ£")
# Control outputs
if "control/steering_deg" in output:
steer = output["control/steering_deg"].mean().item()
throttle = output["control/throttle"].mean().item()
brake = output["control/brake"].mean().item()
lines.append(f"β Steering: {steer:+.2f}Β°")
lines.append(f"β Throttle: {throttle:.3f}")
lines.append(f"β Brake: {brake:.3f}")
# Safety
if "planning/collision_risk" in output:
risk = output["planning/collision_risk"].mean().item()
emergency = output["planning/emergency_brake"].mean().item()
lines.append(f"β Collision Risk: {risk:.3f}")
lines.append(f"β Emergency Brake: {emergency:.3f}")
# Behavior
if "planning/behavior_logits" in output:
behaviors = [
"keep_lane", "turn_left", "turn_right", "change_left",
"change_right", "stop", "yield", "park", "reverse", "emergency"
]
probs = torch.softmax(output["planning/behavior_logits"], dim=-1)
top_prob, top_idx = probs.mean(0).topk(3)
lines.append("β Top behaviors:")
for p, i in zip(top_prob, top_idx):
lines.append(f"β {behaviors[i.item()]}: {p.item():.3f}")
# Waypoints
if "planning/safe_waypoints" in output:
wp = output["planning/safe_waypoints"]
lines.append(f"β Planned waypoints: {wp.shape[1]}")
lines.append(f"β First: ({wp[0,0,0]:.2f}, {wp[0,0,1]:.2f})")
lines.append(f"β Last: ({wp[0,-1,0]:.2f}, {wp[0,-1,1]:.2f})")
# Controller weights
if "control/controller_weights" in output:
w = output["control/controller_weights"].mean(0)
lines.append(f"β Controller mix: Neural={w[0]:.2f} Stanley={w[1]:.2f} PID={w[2]:.2f}")
lines.append("ββββββββββββββββββββββββββββββββββββββββββββ")
return "\n".join(lines)
def format_parameter_count(counts: Dict[str, int]) -> str:
"""Format parameter counts as a nice table."""
lines = []
lines.append("βββββββββββββββββββ¬βββββββββββββββ¬ββββββββββββ")
lines.append("β Module β Parameters β Size (MB) β")
lines.append("βββββββββββββββββββΌβββββββββββββββΌββββββββββββ€")
for name, count in counts.items():
if name in ["total", "total_trainable"]:
continue
size_mb = count * 4 / (1024 * 1024) # float32
lines.append(f"β {name:<15} β {count:>12,} β {size_mb:>8.2f} β")
lines.append("βββββββββββββββββββΌβββββββββββββββΌββββββββββββ€")
total = counts.get("total", 0)
trainable = counts.get("total_trainable", 0)
total_mb = total * 4 / (1024 * 1024)
lines.append(f"β {'TOTAL':<15} β {total:>12,} β {total_mb:>8.2f} β")
lines.append(f"β {'Trainable':<15} β {trainable:>12,} β β")
lines.append("βββββββββββββββββββ΄βββββββββββββββ΄ββββββββββββ")
return "\n".join(lines)
|