from __future__ import annotations
from typing import Annotated
import numpy as np
from numpy.typing import NDArray
from perda.core_data_structures import SingleRunData
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
[docs]
class ArcData(BaseModel):
"""Data model for arc geometry. Stores parameters needed to render an arc segment of a track."""
center: tuple[float, float] = Field(
description="(x, y) coordinates of the arc's center in meters"
)
width: float = Field(description="Width of the arc ellipse (2 * radius) in meters")
height: float = Field(description="Height of the arc ellipse (2 * radius) in meters")
angle: float = Field(description="Rotation angle of the arc ellipse in degrees")
theta1: float = Field(
description="Starting angle of the arc in degrees (sorted, theta1 < theta2)"
)
theta2: float = Field(
description="Ending angle of the arc in degrees (sorted, theta1 < theta2)"
)
theta1_original: float = Field(
description="Original starting angle before sorting (used to detect reversal)"
)
theta2_original: float = Field(
description="Original ending angle before sorting (used to detect reversal)"
)
n_points: int = Field(
description="Number of interpolation points for NewArc (for smooth rendering)"
)
[docs]
class ContinuousTrackData(BaseModel):
"""Serializable data for a continuous (B-spline based) Track."""
model_config = ConfigDict(arbitrary_types_allowed=True)
dx: NDArray[np.float64] = Field(description="Array of distance steps")
radius: NDArray[np.float64] = Field(description="Array of radius of curvature values")
cumulative_dist: NDArray[np.float64] = Field(
description="Array of cumulative distances along the track"
)
x_m: NDArray[np.float64] = Field(description="Array of x coordinates in meters")
y_m: NDArray[np.float64] = Field(description="Array of y coordinates in meters")
distance_step: float = Field(description="Length of each step in meters")
tck: tuple[NDArray[np.float64], list[NDArray[np.float64]], int] = Field(
description="B-spline representation from scipy: knots, coefficients, and degree"
)
seed_idx: NDArray[np.int32] = Field(description="Array of simulation seed indices")
[docs]
class DiscreteTrackData(BaseModel):
"""Serializable data for a discrete (corner based) Track."""
model_config = ConfigDict(arbitrary_types_allowed=True)
dx: NDArray[np.float64] = Field(description="Array of distance steps")
radius: NDArray[np.float64] = Field(description="Array of radius of curvature values")
cumulative_dist: NDArray[np.float64] = Field(
description="Array of cumulative distances along the track"
)
x_m: NDArray[np.float64] = Field(description="Array of x coordinates in meters")
y_m: NDArray[np.float64] = Field(description="Array of y coordinates in meters")
distance_step: float = Field(description="Length of each step in meters")
original_corners: list[list[float]] = Field(description="Original corner specifications")
arcs: list[ArcData] = Field(description="Arc geometry data for each corner")
seed_idx: NDArray[np.int32] = Field(description="Array of simulation seed indices")
TrackData = ContinuousTrackData | DiscreteTrackData
# Keys required in SingleRunData for from_perda_logs, mapped to the function that produces them.
REQUIRED_PERDA_FIELDS: dict[str, str] = {
"body.curvature": "add_curvature() from suboptimumg.log_analysis.kinematics",
"groundSpeed": "add_groundspeed() from suboptimumg.log_analysis.kinematics",
"posX": "preprocess_gps_data_instances() from suboptimumg.log_analysis.preprocess_gps",
"posY": "preprocess_gps_data_instances() from suboptimumg.log_analysis.preprocess_gps",
}