Skip to content

Commit 73cd5f9

Browse files
authored
Diagnose ground truth GNSS (#81)
1 parent 2babf6f commit 73cd5f9

File tree

2 files changed

+94
-45
lines changed

2 files changed

+94
-45
lines changed

python/cli/diagnose/diagnose.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ def generateReport(args):
4444
'barometer': {"v": [], "t": [], "td": []},
4545
'cpu': {"v": [], "t": [], "td": [], "processes": {}},
4646
'gnss': {
47+
"name": "GNSS",
48+
"color": "darkred",
49+
"t": [],
50+
"td": [],
51+
"position": [], # ENU
52+
"altitude": [] # WGS-84
53+
},
54+
'globalGroundTruth': {
55+
"name": "Ground truth",
56+
"color": "tab:orange",
4757
"t": [],
4858
"td": [],
4959
"position": [], # ENU
@@ -85,7 +95,6 @@ def addMeasurement(type, t, v):
8595
time = measurement.get("time")
8696
sensor = measurement.get("sensor")
8797
barometer = measurement.get("barometer")
88-
gnss = measurement.get("gps")
8998
frames = measurement.get("frames")
9099
metrics = measurement.get("systemMetrics")
91100
vioOutput = measurement if "status" in measurement else None
@@ -94,13 +103,23 @@ def addMeasurement(type, t, v):
94103
frames = [measurement['frame']]
95104
frames[0]['cameraInd'] = 0
96105

106+
gnss = measurement.get("gps")
107+
groundTruth = measurement.get("groundTruth")
108+
externalPose = measurement.get("externalPose")
109+
if "pose" in measurement:
110+
if measurement["pose"]["name"] == "groundTruth": groundTruth = measurement["pose"]
111+
if measurement["pose"]["name"] == "gps": gnss = measurement["pose"]
112+
if measurement["pose"]["name"] == "externalPose": externalPose = measurement["pose"]
113+
if gnss is None: gnss = externalPose
114+
97115
if time is None: continue
98116

99117
if (sensor is None
100118
and frames is None
101119
and metrics is None
102120
and barometer is None
103121
and gnss is None
122+
and groundTruth is None
104123
and vioOutput is None
105124
and droppedFrame is None): continue
106125

@@ -113,6 +132,16 @@ def addMeasurement(type, t, v):
113132
nSkipped += 1
114133
continue
115134

135+
def convertGnss(gnss, gnssData):
136+
if len(gnssData["t"]) > 0:
137+
diff = t - gnssData["t"][-1]
138+
gnssData["td"].append(diff)
139+
gnssData["t"].append(t)
140+
if "latitude" not in gnss: return
141+
enu = gnssConverter.enu(gnss["latitude"], gnss["longitude"], gnss["altitude"])
142+
gnssData["position"].append([enu[c] for c in "xyz"])
143+
gnssData["altitude"].append(gnss["altitude"])
144+
116145
t = time - timeOffset
117146
if sensor is not None:
118147
measurementType = sensor["type"]
@@ -129,14 +158,11 @@ def addMeasurement(type, t, v):
129158
addMeasurement("barometer", t, barometer["pressureHectopascals"])
130159

131160
elif gnss is not None:
132-
gnssData = data["gnss"]
133-
if len(gnssData["t"]) > 0:
134-
diff = t - gnssData["t"][-1]
135-
gnssData["td"].append(diff)
136-
gnssData["t"].append(t)
137-
enu = gnssConverter.enu(gnss["latitude"], gnss["longitude"], gnss["altitude"])
138-
gnssData["position"].append([enu[c] for c in "xyz"])
139-
gnssData["altitude"].append(gnss["altitude"])
161+
convertGnss(gnss, data["gnss"])
162+
163+
elif groundTruth is not None:
164+
convertGnss(groundTruth, data["globalGroundTruth"])
165+
# Should also convert non-GNSS ground truth.
140166

141167
elif frames is not None:
142168
for f in frames:

python/cli/diagnose/sensors.py

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ def base64(fig):
1717
buf.seek(0)
1818
return base64.b64encode(buf.getvalue()).decode('utf-8')
1919

20+
def getGroundTruths(data):
21+
# Preferred ground truth type first.
22+
groundTruths = []
23+
for field in ["globalGroundTruth", "gnss"]:
24+
if len(data[field]["t"]) > 0:
25+
groundTruths.append(data[field])
26+
return groundTruths
27+
2028
def plotFrame(
2129
x,
2230
ys,
@@ -579,18 +587,18 @@ def computeVelocity(data, intervalSeconds=None):
579587

580588
plt.subplot(3, 1, 1)
581589
plt.plot(timestamps, velocity[:, 0], label="VIO")
582-
plt.plot(gtTime, gtVelocity[:, 0], label="GNSS")
590+
plt.plot(gtTime, gtVelocity[:, 0], label=groundTruth["name"], color=groundTruth["color"])
583591
plt.ylabel("East (m/s)")
584592
plt.legend()
585593

586594
plt.subplot(3, 1, 2)
587595
plt.plot(timestamps, velocity[:, 1], label="VIO")
588-
plt.plot(gtTime, gtVelocity[:, 1], label="GNSS")
596+
plt.plot(gtTime, gtVelocity[:, 1], label=groundTruth["name"], color=groundTruth["color"])
589597
plt.ylabel("North (m/s)")
590598

591599
plt.subplot(3, 1, 3)
592600
plt.plot(timestamps, velocity[:, 2], label="VIO (z)")
593-
plt.plot(gtTime, gtVelocity[:, 2], label="GNSS")
601+
plt.plot(gtTime, gtVelocity[:, 2], label=groundTruth["name"], color=groundTruth["color"])
594602
plt.ylabel("Up (m/s)")
595603

596604
fig.suptitle(f"VIO velocity in ENU coordinates")
@@ -603,8 +611,8 @@ def analyzeVIOPosition(
603611
altitudeWGS84,
604612
timestamps,
605613
trackingStatus,
606-
groundTruth):
607-
if groundTruth is None:
614+
groundTruths):
615+
if len(groundTruths) == 0:
608616
self.images.append(plotFrame(
609617
positionENU[:, 0],
610618
positionENU[:, 1],
@@ -630,16 +638,16 @@ def analyzeVIOPosition(
630638

631639
fig, _ = plt.subplots(2, 1, figsize=(8, 6))
632640

633-
gtPosition = np.array(groundTruth["position"])
634-
gtAltitudeWGS84 = np.array(groundTruth["altitude"])
635-
gtTime = np.array(groundTruth["t"])
636-
637641
# Get lost tracking indices
638642
lostIndices = [i for i, status in enumerate(trackingStatus) if status == "LOST_TRACKING"]
639643

640644
ax1 = plt.subplot(2, 1, 1)
641645
ax1.plot(positionENU[:, 0], positionENU[:, 1], label="VIO")
642-
ax1.plot(gtPosition[:, 0], gtPosition[:, 1], label="GNSS")
646+
647+
for groundTruth in groundTruths:
648+
gtTime = np.array(groundTruth["t"])
649+
gtPosition = np.array(groundTruth["position"])
650+
ax1.plot(gtPosition[:, 0], gtPosition[:, 1], label=groundTruth["name"], color=groundTruth["color"])
643651

644652
# Plot lost tracking points
645653
if lostIndices:
@@ -656,7 +664,12 @@ def analyzeVIOPosition(
656664

657665
ax2 = plt.subplot(2, 1, 2)
658666
ax2.plot(timestamps, altitudeWGS84, label="VIO")
659-
ax2.plot(gtTime, gtAltitudeWGS84, label="GNSS")
667+
668+
for groundTruth in groundTruths:
669+
gtTime = np.array(groundTruth["t"])
670+
gtAltitudeWGS84 = np.array(groundTruth["altitude"])
671+
ax2.plot(gtTime, gtAltitudeWGS84, label=groundTruth["name"], color=groundTruth["color"])
672+
660673
ax2.set_title("VIO altitude in WGS-84 coordinates")
661674
ax2.set_xlabel("Time (s)")
662675
ax2.set_ylabel("Altitude (m)")
@@ -994,8 +1007,6 @@ def diagnoseGNSS(data, output):
9941007
sensor = data["gnss"]
9951008
timestamps = np.array(sensor["t"])
9961009
deltaTimes = np.array(sensor["td"])
997-
positionEnu = np.array(sensor["position"])
998-
altitude = np.array(sensor["altitude"])
9991010

10001011
if len(timestamps) == 0: return
10011012

@@ -1011,32 +1022,43 @@ def diagnoseGNSS(data, output):
10111022
},
10121023
allowDataGaps=True,
10131024
isOptionalSensor=True)
1014-
status.analyzeSignalDuplicateValues(positionEnu)
1025+
status.analyzeSignalDuplicateValues(np.array(sensor["position"]))
1026+
1027+
groundTruths = getGroundTruths(data)
1028+
1029+
import matplotlib.pyplot as plt
1030+
fig, ax = plt.subplots(figsize=(8, 6))
1031+
for groundTruth in groundTruths:
1032+
p = np.array(groundTruth["position"])
1033+
linestyle = "-" if len(groundTruth["t"]) > 0 else "."
1034+
ax.plot(p[:, 0], p[:, 1], label=groundTruth["name"], color=groundTruth["color"], linestyle=linestyle)
1035+
ax.set_title("GNSS position")
1036+
ax.set_xlabel("East (m)")
1037+
ax.set_ylabel("North (m)")
1038+
ax.legend()
1039+
ax.set_xscale("linear")
1040+
ax.set_yscale("linear")
1041+
ax.set_aspect("equal", adjustable="datalim")
1042+
fig.tight_layout()
1043+
positionImage = base64(fig)
1044+
1045+
fig, ax = plt.subplots(figsize=(8, 6))
1046+
for groundTruth in groundTruths:
1047+
linestyle = "-" if len(groundTruth["t"]) > 0 else "."
1048+
ax.plot(groundTruth["t"], groundTruth["altitude"], label=groundTruth["name"], color=groundTruth["color"], linestyle=linestyle)
1049+
ax.set_title("GNSS altitude (WGS-84)")
1050+
ax.set_xlabel("Time (s)")
1051+
ax.set_ylabel("Altitude (m)")
1052+
ax.legend()
1053+
fig.tight_layout()
1054+
altitudeImage = base64(fig)
10151055

10161056
output["GNSS"] = {
10171057
"diagnosis": status.diagnosis.toString(),
10181058
"issues": status.serializeIssues(),
10191059
"frequency": computeSamplingRate(deltaTimes),
10201060
"count": len(timestamps),
1021-
"images": [
1022-
plotFrame(
1023-
positionEnu[:, 0],
1024-
positionEnu[:, 1],
1025-
"GNSS position",
1026-
xLabel="ENU x (m)",
1027-
yLabel="ENU y (m)",
1028-
style='-' if len(timestamps) > 1 else '.',
1029-
yScale="linear",
1030-
xScale="linear",
1031-
equalAxis=True),
1032-
plotFrame(
1033-
timestamps,
1034-
altitude,
1035-
"GNSS altitude (WGS-84)",
1036-
xLabel="Time (s)",
1037-
yLabel="Altitude (m)",
1038-
style='-' if len(timestamps) > 1 else '.')
1039-
] + status.images
1061+
"images": [positionImage, altitudeImage] + status.images,
10401062
}
10411063
if status.diagnosis == DiagnosisLevel.ERROR:
10421064
output["passed"] = False
@@ -1094,9 +1116,10 @@ def diagnoseVIO(data, output):
10941116

10951117
images = []
10961118
if hasGlobalOutput:
1097-
groundTruth = data["gnss"] # TODO: use groundTruth instead of gnss (if available)
1098-
if len(groundTruth["t"]) == 0: groundTruth = None
1099-
status.analyzeVIOPosition(positionENU, altitudeWGS84, timestamps, trackingStatus, groundTruth)
1119+
groundTruths = getGroundTruths(data)
1120+
status.analyzeVIOPosition(positionENU, altitudeWGS84, timestamps, trackingStatus, groundTruths)
1121+
1122+
groundTruth = groundTruths[0] if len(groundTruths) > 0 else None
11001123
status.analyzeVIOVelocity(velocityENU, timestamps, groundTruth)
11011124
else:
11021125
# TODO: improve relative positioning analysis

0 commit comments

Comments
 (0)