Reality123b's picture
Add config.py
c5ea85f verified
"""
Configurable sensor and vehicle configuration for the FSD model.
Supports arbitrary sensor counts, types, and placements.
"""
import math
import json
from dataclasses import dataclass, field, asdict
from typing import List, Optional, Tuple, Dict, Any
from enum import Enum
class SensorType(Enum):
CAMERA = "camera"
ULTRASONIC = "ultrasonic"
LIDAR = "lidar"
RADAR = "radar"
class CameraPosition(Enum):
FRONT_LEFT = "front_left"
FRONT_RIGHT = "front_right"
REAR_LEFT = "rear_left"
REAR_RIGHT = "rear_right"
LEFT_MIRROR = "left_mirror"
RIGHT_MIRROR = "right_mirror"
FRONT_CENTER = "front_center"
REAR_CENTER = "rear_center"
ROOF_FRONT = "roof_front"
ROOF_REAR = "roof_rear"
BUMPER_LEFT = "bumper_left"
BUMPER_RIGHT = "bumper_right"
class UltrasonicZone(Enum):
FRONT_LEFT_CORNER = "front_left_corner"
FRONT_LEFT = "front_left"
FRONT_CENTER_LEFT = "front_center_left"
FRONT_CENTER = "front_center"
FRONT_CENTER_RIGHT = "front_center_right"
FRONT_RIGHT = "front_right"
FRONT_RIGHT_CORNER = "front_right_corner"
LEFT_FRONT = "left_front"
LEFT_CENTER = "left_center"
LEFT_REAR = "left_rear"
RIGHT_FRONT = "right_front"
RIGHT_CENTER = "right_center"
RIGHT_REAR = "right_rear"
REAR_LEFT_CORNER = "rear_left_corner"
REAR_LEFT = "rear_left"
REAR_CENTER_LEFT = "rear_center_left"
REAR_CENTER = "rear_center"
REAR_CENTER_RIGHT = "rear_center_right"
REAR_RIGHT = "rear_right"
REAR_RIGHT_CORNER = "rear_right_corner"
@dataclass
class SensorPlacement:
x: float
y: float
z: float
yaw: float = 0.0
pitch: float = 0.0
roll: float = 0.0
def to_transform_matrix(self):
import numpy as np
yaw_r = math.radians(self.yaw)
pitch_r = math.radians(self.pitch)
roll_r = math.radians(self.roll)
Rz = np.array([
[math.cos(yaw_r), -math.sin(yaw_r), 0],
[math.sin(yaw_r), math.cos(yaw_r), 0],
[0, 0, 1]
])
Ry = np.array([
[math.cos(pitch_r), 0, math.sin(pitch_r)],
[0, 1, 0],
[-math.sin(pitch_r), 0, math.cos(pitch_r)]
])
Rx = np.array([
[1, 0, 0],
[0, math.cos(roll_r), -math.sin(roll_r)],
[0, math.sin(roll_r), math.cos(roll_r)]
])
R = Rz @ Ry @ Rx
T = np.eye(4)
T[:3, :3] = R
T[:3, 3] = [self.x, self.y, self.z]
return T
@dataclass
class CameraSensorConfig:
name: str
position: CameraPosition
placement: SensorPlacement
resolution: Tuple[int, int] = (640, 480)
fov_horizontal: float = 120.0
fov_vertical: float = 90.0
fps: int = 30
encoding: str = "rgb"
distortion_model: str = "pinhole"
fx: Optional[float] = None
fy: Optional[float] = None
cx: Optional[float] = None
cy: Optional[float] = None
def __post_init__(self):
if self.fx is None:
self.fx = self.resolution[0] / (2 * math.tan(math.radians(self.fov_horizontal / 2)))
if self.fy is None:
self.fy = self.resolution[1] / (2 * math.tan(math.radians(self.fov_vertical / 2)))
if self.cx is None:
self.cx = self.resolution[0] / 2
if self.cy is None:
self.cy = self.resolution[1] / 2
def get_intrinsic_matrix(self):
import numpy as np
return np.array([
[self.fx, 0, self.cx],
[0, self.fy, self.cy],
[0, 0, 1]
])
@dataclass
class UltrasonicSensorConfig:
name: str
zone: UltrasonicZone
placement: SensorPlacement
max_range: float = 5.0
min_range: float = 0.02
beam_angle: float = 30.0
frequency: float = 40000.0
update_rate: float = 20.0
accuracy: float = 0.01
noise_std: float = 0.005
@dataclass
class SensorConfig:
cameras: List[CameraSensorConfig] = field(default_factory=list)
ultrasonics: List[UltrasonicSensorConfig] = field(default_factory=list)
@property
def num_cameras(self) -> int:
return len(self.cameras)
@property
def num_ultrasonics(self) -> int:
return len(self.ultrasonics)
@property
def total_sensors(self) -> int:
return self.num_cameras + self.num_ultrasonics
def validate(self):
issues = []
if self.num_cameras == 0:
issues.append("WARNING: No cameras configured")
if self.num_ultrasonics == 0:
issues.append("WARNING: No ultrasonic sensors configured")
yaw_angles = sorted([c.placement.yaw for c in self.cameras])
if len(yaw_angles) >= 2:
gaps = []
for i in range(len(yaw_angles)):
next_i = (i + 1) % len(yaw_angles)
gap = (yaw_angles[next_i] - yaw_angles[i]) % 360
if gap == 0 and next_i != 0:
continue
gaps.append(gap)
max_gap = max(gaps) if gaps else 360
if max_gap > 120:
issues.append(f"WARNING: Camera coverage gap of {max_gap:.0f} degrees")
return issues
def get_sensor_summary(self) -> str:
lines = [f"Sensor Configuration Summary:"]
lines.append(f" Total sensors: {self.total_sensors}")
lines.append(f" Cameras: {self.num_cameras}")
for cam in self.cameras:
lines.append(f" - {cam.name}: {cam.position.value} | {cam.resolution[0]}x{cam.resolution[1]} | FOV: {cam.fov_horizontal}")
lines.append(f" Ultrasonic sensors: {self.num_ultrasonics}")
for us in self.ultrasonics:
lines.append(f" - {us.name}: {us.zone.value} | Range: {us.min_range}-{us.max_range}m")
issues = self.validate()
if issues:
lines.append(" Validation issues:")
for issue in issues:
lines.append(f" {issue}")
return "\n".join(lines)
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
def save(self, path: str):
with open(path, 'w') as f:
json.dump(self.to_dict(), f, indent=2, default=str)
@dataclass
class VehicleConfig:
name: str = "FSD_Vehicle"
length: float = 4.5
width: float = 1.8
height: float = 1.5
wheelbase: float = 2.7
track_width: float = 1.5
max_speed_mph: float = 20.0
max_speed_ms: float = 8.94
max_steering_angle: float = 35.0
max_acceleration: float = 3.0
max_deceleration: float = 8.0
sensor_config: Optional[SensorConfig] = None
def __post_init__(self):
self.max_speed_ms = self.max_speed_mph * 0.44704
if self.sensor_config is None:
self.sensor_config = self._default_sensor_config()
def _default_sensor_config(self) -> SensorConfig:
cameras = self._create_default_cameras()
ultrasonics = self._create_default_ultrasonics()
return SensorConfig(cameras=cameras, ultrasonics=ultrasonics)
def _create_default_cameras(self) -> List[CameraSensorConfig]:
half_l = self.length / 2
half_w = self.width / 2
mirror_h = 1.1
cameras = [
CameraSensorConfig(
name="cam_front_left", position=CameraPosition.FRONT_LEFT,
placement=SensorPlacement(x=half_l - 0.3, y=half_w, z=0.8, yaw=-45, pitch=-5),
resolution=(640, 480), fov_horizontal=120,
),
CameraSensorConfig(
name="cam_front_right", position=CameraPosition.FRONT_RIGHT,
placement=SensorPlacement(x=half_l - 0.3, y=-half_w, z=0.8, yaw=45, pitch=-5),
resolution=(640, 480), fov_horizontal=120,
),
CameraSensorConfig(
name="cam_rear_left", position=CameraPosition.REAR_LEFT,
placement=SensorPlacement(x=-half_l + 0.3, y=half_w, z=0.8, yaw=-135, pitch=-5),
resolution=(640, 480), fov_horizontal=120,
),
CameraSensorConfig(
name="cam_rear_right", position=CameraPosition.REAR_RIGHT,
placement=SensorPlacement(x=-half_l + 0.3, y=-half_w, z=0.8, yaw=135, pitch=-5),
resolution=(640, 480), fov_horizontal=120,
),
CameraSensorConfig(
name="cam_left_mirror", position=CameraPosition.LEFT_MIRROR,
placement=SensorPlacement(x=0.8, y=half_w + 0.1, z=mirror_h, yaw=-90, pitch=-10),
resolution=(640, 480), fov_horizontal=90,
),
CameraSensorConfig(
name="cam_right_mirror", position=CameraPosition.RIGHT_MIRROR,
placement=SensorPlacement(x=0.8, y=-(half_w + 0.1), z=mirror_h, yaw=90, pitch=-10),
resolution=(640, 480), fov_horizontal=90,
),
]
return cameras
def _create_default_ultrasonics(self) -> List[UltrasonicSensorConfig]:
half_l = self.length / 2
half_w = self.width / 2
bumper_h = 0.4
zones_front = [
UltrasonicZone.FRONT_LEFT_CORNER, UltrasonicZone.FRONT_LEFT,
UltrasonicZone.FRONT_CENTER_LEFT, UltrasonicZone.FRONT_CENTER,
UltrasonicZone.FRONT_CENTER_RIGHT, UltrasonicZone.FRONT_RIGHT,
UltrasonicZone.FRONT_RIGHT_CORNER,
]
front_y = [half_w, half_w*0.66, half_w*0.33, 0.0, -half_w*0.33, -half_w*0.66, -half_w]
front_yaw = [-30, -15, -5, 0, 5, 15, 30]
ultrasonics = []
for i, (zone, y_pos, yaw) in enumerate(zip(zones_front, front_y, front_yaw)):
ultrasonics.append(UltrasonicSensorConfig(
name=f"us_front_{i}", zone=zone,
placement=SensorPlacement(x=half_l, y=y_pos, z=bumper_h, yaw=yaw),
max_range=5.0,
))
zones_rear = [
UltrasonicZone.REAR_LEFT_CORNER, UltrasonicZone.REAR_LEFT,
UltrasonicZone.REAR_CENTER_LEFT, UltrasonicZone.REAR_CENTER,
UltrasonicZone.REAR_CENTER_RIGHT, UltrasonicZone.REAR_RIGHT,
UltrasonicZone.REAR_RIGHT_CORNER,
]
rear_yaw = [-150, -165, -175, 180, 175, 165, 150]
for i, (zone, y_pos, yaw) in enumerate(zip(zones_rear, front_y, rear_yaw)):
ultrasonics.append(UltrasonicSensorConfig(
name=f"us_rear_{i}", zone=zone,
placement=SensorPlacement(x=-half_l, y=y_pos, z=bumper_h, yaw=yaw),
max_range=5.0,
))
zones_left = [UltrasonicZone.LEFT_FRONT, UltrasonicZone.LEFT_CENTER, UltrasonicZone.LEFT_REAR]
left_x = [half_l * 0.5, 0.0, -half_l * 0.5]
for i, (zone, x_pos) in enumerate(zip(zones_left, left_x)):
ultrasonics.append(UltrasonicSensorConfig(
name=f"us_left_{i}", zone=zone,
placement=SensorPlacement(x=x_pos, y=half_w, z=bumper_h + 0.2, yaw=-90),
max_range=3.0,
))
zones_right = [UltrasonicZone.RIGHT_FRONT, UltrasonicZone.RIGHT_CENTER, UltrasonicZone.RIGHT_REAR]
for i, (zone, x_pos) in enumerate(zip(zones_right, left_x)):
ultrasonics.append(UltrasonicSensorConfig(
name=f"us_right_{i}", zone=zone,
placement=SensorPlacement(x=x_pos, y=-half_w, z=bumper_h + 0.2, yaw=90),
max_range=3.0,
))
return ultrasonics
def create_custom_config(
num_cameras=6, num_ultrasonics=20,
camera_placements=None, ultrasonic_placements=None,
max_speed_mph=20.0, **vehicle_kwargs
) -> VehicleConfig:
config = VehicleConfig(max_speed_mph=max_speed_mph, **vehicle_kwargs)
if camera_placements is not None:
cameras = []
positions = list(CameraPosition)
for i, cp in enumerate(camera_placements):
pos = cp.get("position", positions[i % len(positions)])
if isinstance(pos, str):
pos = CameraPosition(pos)
placement = SensorPlacement(**cp.get("placement", {"x": 0, "y": 0, "z": 1.0}))
cameras.append(CameraSensorConfig(
name=cp.get("name", f"cam_{i}"), position=pos, placement=placement,
resolution=cp.get("resolution", (640, 480)),
fov_horizontal=cp.get("fov_horizontal", 120),
fov_vertical=cp.get("fov_vertical", 90),
))
config.sensor_config.cameras = cameras
if ultrasonic_placements is not None:
ultrasonics = []
zones = list(UltrasonicZone)
for i, up in enumerate(ultrasonic_placements):
zone = up.get("zone", zones[i % len(zones)])
if isinstance(zone, str):
zone = UltrasonicZone(zone)
placement = SensorPlacement(**up.get("placement", {"x": 0, "y": 0, "z": 0.4}))
ultrasonics.append(UltrasonicSensorConfig(
name=up.get("name", f"us_{i}"), zone=zone, placement=placement,
max_range=up.get("max_range", 5.0),
beam_angle=up.get("beam_angle", 30.0),
))
config.sensor_config.ultrasonics = ultrasonics
return config