173 lines
4.7 KiB
Python
173 lines
4.7 KiB
Python
"""绘制横向误差和航向误差随时间变化图。"""
|
||||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import argparse
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
import matplotlib
|
|||
|
|
matplotlib.use("Agg")
|
|||
|
|
import matplotlib.pyplot as plt
|
|||
|
|
import numpy as np
|
|||
|
|
|
|||
|
|
from plot_trajectory_comparison import (
|
|||
|
|
build_reference,
|
|||
|
|
configure_matplotlib,
|
|||
|
|
discover_csv_files,
|
|||
|
|
load_and_resample,
|
|||
|
|
output_path,
|
|||
|
|
shade_localization_jump_windows,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def plot_errors(
|
|||
|
|
csv_path: Path,
|
|||
|
|
frequency_hz: float,
|
|||
|
|
filter_window_seconds: float,
|
|||
|
|
output_directory: str | None,
|
|||
|
|
show: bool,
|
|||
|
|
) -> Path:
|
|||
|
|
"""生成单份CSV的横向/航向误差图。"""
|
|||
|
|
frame, metadata = load_and_resample(
|
|||
|
|
csv_path,
|
|||
|
|
frequency_hz,
|
|||
|
|
filter_window_seconds,
|
|||
|
|
)
|
|||
|
|
reference = build_reference(frame, metadata)
|
|||
|
|
time = frame["TimeSeconds"].to_numpy()
|
|||
|
|
lateral = np.asarray(reference["lateral_error_mm"])
|
|||
|
|
heading = np.asarray(reference["heading_error_degrees"])
|
|||
|
|
invalid = frame[
|
|||
|
|
"InvalidNearLocalizationJump"
|
|||
|
|
].to_numpy(dtype=bool)
|
|||
|
|
lateral_for_statistics = lateral.copy()
|
|||
|
|
heading_for_statistics = heading.copy()
|
|||
|
|
lateral_for_statistics[invalid] = np.nan
|
|||
|
|
heading_for_statistics[invalid] = np.nan
|
|||
|
|
|
|||
|
|
lateral_rmse = float(
|
|||
|
|
np.sqrt(np.nanmean(lateral_for_statistics**2))
|
|||
|
|
)
|
|||
|
|
heading_rmse = float(
|
|||
|
|
np.sqrt(np.nanmean(heading_for_statistics**2))
|
|||
|
|
)
|
|||
|
|
lateral_max = float(
|
|||
|
|
np.nanmax(np.abs(lateral_for_statistics))
|
|||
|
|
)
|
|||
|
|
heading_max = float(
|
|||
|
|
np.nanmax(np.abs(heading_for_statistics))
|
|||
|
|
)
|
|||
|
|
is_in_place_rotation = (
|
|||
|
|
reference["kind"] == "in_place_rotation"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
fig, axes = plt.subplots(
|
|||
|
|
2,
|
|||
|
|
1,
|
|||
|
|
figsize=(10.0, 7.0),
|
|||
|
|
sharex=True,
|
|||
|
|
)
|
|||
|
|
axes[0].plot(time, lateral, linewidth=1.5)
|
|||
|
|
axes[0].axhline(0.0, color="black", linewidth=0.8)
|
|||
|
|
if is_in_place_rotation:
|
|||
|
|
axes[0].set_ylabel("旋转中心位置漂移 / mm")
|
|||
|
|
axes[0].set_title(
|
|||
|
|
f"原地自转位置漂移:RMS={lateral_rmse:.2f} mm,"
|
|||
|
|
f"最大值={lateral_max:.2f} mm"
|
|||
|
|
)
|
|||
|
|
else:
|
|||
|
|
axes[0].set_ylabel("横向误差 / mm")
|
|||
|
|
axes[0].set_title(
|
|||
|
|
f"横向误差:RMSE={lateral_rmse:.2f} mm,"
|
|||
|
|
f"最大绝对值={lateral_max:.2f} mm"
|
|||
|
|
)
|
|||
|
|
shade_localization_jump_windows(axes[0], metadata)
|
|||
|
|
axes[0].grid(True, alpha=0.3)
|
|||
|
|
|
|||
|
|
axes[1].plot(
|
|||
|
|
time,
|
|||
|
|
heading,
|
|||
|
|
color="tab:orange",
|
|||
|
|
linewidth=1.5,
|
|||
|
|
)
|
|||
|
|
axes[1].axhline(0.0, color="black", linewidth=0.8)
|
|||
|
|
axes[1].set_xlabel("时间 / s")
|
|||
|
|
axes[1].set_ylabel(
|
|||
|
|
"目标角度剩余误差 / °"
|
|||
|
|
if is_in_place_rotation
|
|||
|
|
else "航向误差 / °"
|
|||
|
|
)
|
|||
|
|
axes[1].set_title(
|
|||
|
|
(
|
|||
|
|
f"目标角度剩余误差:RMSE={heading_rmse:.2f}°,"
|
|||
|
|
f"最大绝对值={heading_max:.2f}°"
|
|||
|
|
)
|
|||
|
|
if is_in_place_rotation
|
|||
|
|
else (
|
|||
|
|
f"航向误差:RMSE={heading_rmse:.2f}°,"
|
|||
|
|
f"最大绝对值={heading_max:.2f}°"
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
shade_localization_jump_windows(axes[1], metadata)
|
|||
|
|
axes[1].grid(True, alpha=0.3)
|
|||
|
|
if metadata["localization_jump_events"]:
|
|||
|
|
axes[1].legend(loc="best")
|
|||
|
|
|
|||
|
|
fig.suptitle(
|
|||
|
|
f"横向/航向误差随时间变化\n"
|
|||
|
|
f"{metadata['controller_name']} - "
|
|||
|
|
f"{metadata['trajectory_name']}"
|
|||
|
|
)
|
|||
|
|
fig.tight_layout()
|
|||
|
|
|
|||
|
|
destination = output_path(
|
|||
|
|
csv_path,
|
|||
|
|
output_directory,
|
|||
|
|
"tracking_errors",
|
|||
|
|
)
|
|||
|
|
fig.savefig(destination, dpi=300, bbox_inches="tight")
|
|||
|
|
if show:
|
|||
|
|
plt.show()
|
|||
|
|
plt.close(fig)
|
|||
|
|
|
|||
|
|
if is_in_place_rotation:
|
|||
|
|
print(
|
|||
|
|
f"{csv_path.name}: position drift RMS="
|
|||
|
|
f"{lateral_rmse:.3f} mm, "
|
|||
|
|
f"target-angle error RMS={heading_rmse:.3f} deg"
|
|||
|
|
)
|
|||
|
|
else:
|
|||
|
|
print(
|
|||
|
|
f"{csv_path.name}: lateral RMSE="
|
|||
|
|
f"{lateral_rmse:.3f} mm, "
|
|||
|
|
f"heading RMSE={heading_rmse:.3f} deg"
|
|||
|
|
)
|
|||
|
|
return destination
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main() -> None:
|
|||
|
|
configure_matplotlib()
|
|||
|
|
parser = argparse.ArgumentParser(
|
|||
|
|
description="绘制横向误差和航向误差随时间变化图。"
|
|||
|
|
)
|
|||
|
|
parser.add_argument("files", nargs="*", help="一个或多个CSV文件")
|
|||
|
|
parser.add_argument("--frequency", type=float, default=20.0)
|
|||
|
|
parser.add_argument("--window", type=float, default=0.55)
|
|||
|
|
parser.add_argument("--output-dir")
|
|||
|
|
parser.add_argument("--show", action="store_true")
|
|||
|
|
args = parser.parse_args()
|
|||
|
|
|
|||
|
|
for csv_path in discover_csv_files(args.files):
|
|||
|
|
destination = plot_errors(
|
|||
|
|
csv_path,
|
|||
|
|
args.frequency,
|
|||
|
|
args.window,
|
|||
|
|
args.output_dir,
|
|||
|
|
args.show,
|
|||
|
|
)
|
|||
|
|
print(f"已生成:{destination}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|