-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathplot_results.py
More file actions
154 lines (130 loc) · 5 KB
/
Copy pathplot_results.py
File metadata and controls
154 lines (130 loc) · 5 KB
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
#!/usr/bin/env python3
import os
import glob
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def load_csv_flexible(path, expected_cols=15):
"""
Loads a CSV that may:
- have a header row (strings like 'quat_1')
- have an extra first column (timestamp/index)
Returns numpy array (N, expected_cols).
"""
# First try reading with header=None
df = pd.read_csv(path, header=None)
# If the first row contains strings (e.g. 'quat_1'), re-read treating first row as header
if df.shape[0] > 0 and any(isinstance(x, str) for x in df.iloc[0].values):
df = pd.read_csv(path, header=0) # use header row
else:
# no header row; keep header=None data
pass
# Coerce all to numeric (non-numeric become NaN)
df = df.apply(pd.to_numeric, errors="coerce")
# Drop fully empty rows (in case)
df = df.dropna(how="all")
# If there's an extra first column, drop it
if df.shape[1] == expected_cols + 1:
df = df.iloc[:, 1:]
elif df.shape[1] != expected_cols:
raise ValueError(
f"{path}: Expected {expected_cols} cols (or {expected_cols+1} with an extra first col), "
f"got {df.shape[1]} columns after parsing."
)
# If any NaNs remain, complain with a useful hint
if df.isna().any().any():
bad = df.isna().sum().sum()
raise ValueError(f"{path}: Found {bad} non-numeric / missing entries after conversion to float.")
return df.to_numpy(dtype=float)
def rotm_to_rpy(R):
"""
Convert rotation matrices to roll-pitch-yaw (XYZ / roll-pitch-yaw) in radians.
R: array (N, 3, 3)
Returns rpy: array (N, 3) = [roll, pitch, yaw]
Uses a common convention:
roll = atan2(R32, R33)
pitch = asin(-R31)
yaw = atan2(R21, R11)
"""
r11 = R[:, 0, 0]; r21 = R[:, 1, 0]; r31 = R[:, 2, 0]
r32 = R[:, 2, 1]; r33 = R[:, 2, 2]
pitch = np.arcsin(np.clip(-r31, -1.0, 1.0))
roll = np.arctan2(r32, r33)
yaw = np.arctan2(r21, r11)
return np.stack([roll, pitch, yaw], axis=1)
def main():
# Paths (edit if your filenames differ)
est_path = os.path.join("log", "result.csv")
# Ground truth: pick the first CSV inside data/ by default.
# If you have multiple, either rename the GT file or set gt_path directly.
gt_candidates = sorted(glob.glob(os.path.join("data", "*.csv")))
if not gt_candidates:
raise FileNotFoundError("No CSV found in data/. Put your GT csv there or update gt_path.")
gt_path = gt_candidates[0]
print(f"Estimate: {est_path}")
print(f"Ground truth:{gt_path}")
est = load_csv_flexible(est_path, expected_cols=15)
gt = load_csv_flexible(gt_path, expected_cols=15)
# Align length (if they differ)
N = min(len(est), len(gt))
est = est[:N]
gt = gt[:N]
t = np.arange(N)
# Split into components
# Estimate: [R(9), p(3), v(3)]
est_R = est[:, 0:9].reshape(N, 3, 3)
est_p = est[:, 9:12]
est_v = est[:, 12:15]
# GT: columns quat_1..quat_9 are interpreted as the 9 elements of a 3x3 rotation matrix (not quaternion components).
gt_R = gt[:, 0:9].reshape(N, 3, 3)
gt_p = gt[:, 9:12]
gt_v = gt[:, 12:15]
# Orientation comparison as RPY (roll/pitch/yaw)
est_rpy = rotm_to_rpy(est_R)
gt_rpy = rotm_to_rpy(gt_R)
# ---- Plot 1: Orientation (RPY) ----
fig1, ax = plt.subplots(3, 1, sharex=True, figsize=(10, 8))
labels = ["roll [rad]", "pitch [rad]", "yaw [rad]"]
for i in range(3):
ax[i].plot(t, gt_rpy[:, i], label="GT")
ax[i].plot(t, est_rpy[:, i], label="Estimate", linestyle="--")
ax[i].set_ylabel(labels[i])
ax[i].grid(True)
ax[0].legend()
ax[-1].set_xlabel("sample")
fig1.suptitle("Orientation comparison (RPY from rotation matrix)")
# ---- Plot 2: Position ----
fig2, ax = plt.subplots(3, 1, sharex=True, figsize=(10, 8))
labels = ["x [m]", "y [m]", "z [m]"]
for i in range(3):
ax[i].plot(t, gt_p[:, i], label="GT")
ax[i].plot(t, est_p[:, i], label="Estimate", linestyle="--")
ax[i].set_ylabel(labels[i])
ax[i].grid(True)
ax[0].legend()
ax[-1].set_xlabel("sample")
fig2.suptitle("Position comparison")
# ---- Plot 3: Linear velocity ----
fig3, ax = plt.subplots(3, 1, sharex=True, figsize=(10, 8))
labels = ["vx [m/s]", "vy [m/s]", "vz [m/s]"]
for i in range(3):
ax[i].plot(t, gt_v[:, i], label="GT")
ax[i].plot(t, est_v[:, i], label="Estimate", linestyle="--")
ax[i].set_ylabel(labels[i])
ax[i].grid(True)
ax[0].legend()
ax[-1].set_xlabel("sample")
fig3.suptitle("Velocity comparison")
# ---- Plot 4: XY trajectory ----
plt.figure(figsize=(8, 8))
plt.plot(gt_p[:, 0], gt_p[:, 1], label="GT")
plt.plot(est_p[:, 0], est_p[:, 1], label="Estimate", linestyle="--")
plt.xlabel("x [m]")
plt.ylabel("y [m]")
plt.title("Trajectory (XY)")
plt.axis("equal")
plt.grid(True)
plt.legend()
plt.show()
if __name__ == "__main__":
main()