Skip to content

Commit 9096468

Browse files
committed
Detect large difference in frame and gyroscope arrival times
1 parent b8808c3 commit 9096468

File tree

2 files changed

+88
-1
lines changed

2 files changed

+88
-1
lines changed

python/cli/diagnose/diagnose.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,11 @@ def addMeasurement(type, t, v):
6868

6969
startTime = None
7070
timeOffset = 0
71+
latestGyroTime = None
7172
gnssConverter = GnssConverter()
7273

74+
framesMissingNextGyroTime = []
75+
7376
with open(jsonlFile) as f:
7477
nSkipped = 0
7578
for line in f.readlines():
@@ -113,8 +116,15 @@ def addMeasurement(type, t, v):
113116
if measurementType in ["accelerometer", "gyroscope", "magnetometer"]:
114117
v = [sensor["values"][i] for i in range(3)]
115118
addMeasurement(measurementType, t, v)
119+
if measurementType == "gyroscope":
120+
latestGyroTime = t
121+
for f in framesMissingNextGyroTime:
122+
gyroTimeDeltas["next"] = abs(latestGyroTime - gyroTimeDeltas["t"])
123+
framesMissingNextGyroTime.clear()
124+
116125
elif barometer is not None:
117126
addMeasurement("barometer", t, barometer["pressureHectopascals"])
127+
118128
elif gnss is not None:
119129
gnssData = data["gnss"]
120130
if len(gnssData["t"]) > 0:
@@ -124,18 +134,27 @@ def addMeasurement(type, t, v):
124134
enu = gnssConverter.enu(gnss["latitude"], gnss["longitude"], gnss["altitude"])
125135
gnssData["position"].append([enu[c] for c in "xyz"])
126136
gnssData["altitude"].append(gnss["altitude"])
137+
127138
elif frames is not None:
128139
for f in frames:
129140
if f.get("missingBitmap", False): continue
130141
cameras = data['cameras']
131142
ind = f["cameraInd"]
132143
if cameras.get(ind) is None:
133-
cameras[ind] = {"td": [], "t": [], "features": []}
144+
cameras[ind] = {"td": [], "t": [], "features": [], "gyroTimeDeltas": []}
134145
else:
135146
diff = t - cameras[ind]["t"][-1]
136147
cameras[ind]["td"].append(diff)
137148
if "features" in f: cameras[ind]["features"].append(0 if not f["features"] else len(f["features"]))
138149
cameras[ind]["t"].append(t)
150+
gyroTimeDeltas = {
151+
"prev": abs(latestGyroTime - t) if latestGyroTime != None else None,
152+
"next": None,
153+
"t": t
154+
}
155+
cameras[ind]["gyroTimeDeltas"].append(gyroTimeDeltas)
156+
framesMissingNextGyroTime.append(gyroTimeDeltas)
157+
139158
elif metrics is not None and 'cpu' in metrics:
140159
addMeasurement("cpu", t, metrics['cpu'].get('systemTotalUsagePercent', 0))
141160
usedProcessNames = {} # Track duplicate process names
@@ -148,6 +167,7 @@ def addMeasurement(type, t, v):
148167
processData = data['cpu']["processes"].setdefault(uniqueName, {"v": [], "t": []})
149168
processData['v'].append(process['usagePercent'])
150169
processData['t'].append(t)
170+
151171
elif vioOutput is not None:
152172
vio = data["vio"]
153173
if len(vio["t"]) > 0:

python/cli/diagnose/sensors.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,67 @@ def toPercent(value):
210210
f"Found {invalidTimestamps} ({toPercent(invalidTimestamps)}) "
211211
"timestamps that don't overlap with IMU")
212212

213+
def analyzeArrivalTimes(
214+
self,
215+
timestamps,
216+
gyroTimeDeltas,
217+
plotArgs,
218+
dataName="frames"):
219+
220+
deltaTimes = []
221+
for d in gyroTimeDeltas:
222+
if d["prev"] == None or d["next"] == None:
223+
deltaTimes.append(None)
224+
else:
225+
deltaTimes.append(min(d["prev"], d["next"]))
226+
227+
deltaTimes = np.array(deltaTimes).astype(float)
228+
229+
total = len(deltaTimes)
230+
if total == 0: return
231+
232+
medianDeltaTime = np.nanmedian(deltaTimes)
233+
warningThreshold = min(.5, medianDeltaTime * 4)
234+
errorThreshold = 1
235+
236+
COLOR_OK = (0, 1, 0) # Green
237+
COLOR_WARNING = (1, 0.65, 0) # Orange
238+
COLOR_ERROR = (1, 0, 0) # Red
239+
deltaTimePlotColors = []
240+
badDeltaTimes = 0
241+
maxError = None
242+
for td in deltaTimes:
243+
error = max(0, (td - medianDeltaTime))
244+
if maxError == None or maxError < error: maxError = error
245+
if error > warningThreshold:
246+
badDeltaTimes += 1
247+
if error > errorThreshold:
248+
deltaTimePlotColors.append(COLOR_ERROR)
249+
else:
250+
deltaTimePlotColors.append(COLOR_WARNING)
251+
else:
252+
deltaTimePlotColors.append(COLOR_OK)
253+
254+
if badDeltaTimes > 0:
255+
self.images.append(plotFrame(
256+
timestamps,
257+
deltaTimes * SECONDS_TO_MILLISECONDS,
258+
color=deltaTimePlotColors,
259+
plottype="scatter",
260+
xLabel="Time (s)",
261+
yLabel="Time diff (ms)",
262+
yScale="symlog",
263+
s=10,
264+
**plotArgs))
265+
266+
self.__addIssue(DiagnosisLevel.ERROR if maxError > errorThreshold else DiagnosisLevel.WARNING,
267+
f"Found {badDeltaTimes} {dataName} where the neibhgouring gyroscope samples "
268+
f"have time difference (expected {medianDeltaTime*SECONDS_TO_MILLISECONDS:.1f}ms) "
269+
f"larger than {warningThreshold*SECONDS_TO_MILLISECONDS:.1f}ms, up to {maxError*SECONDS_TO_MILLISECONDS:.1f}ms. "
270+
f"This indicates that gyroscope and {dataName} for same moment of time are fed "
271+
f"large time appart which may lead to data being dropped and higher latency."
272+
)
273+
213274
def analyzeSignalDuplicateValues(
214275
self,
215276
signal,
@@ -608,6 +669,12 @@ def diagnoseCamera(data, output):
608669
plotArgs={
609670
"title": f"Camera #{ind} frame time diff"
610671
})
672+
status.analyzeArrivalTimes(
673+
timestamps,
674+
camera["gyroTimeDeltas"],
675+
plotArgs={
676+
"title": f"Camera #{ind} nearest gyro (in file, not time) time diff"
677+
})
611678
cameraOutput = {
612679
"diagnosis": status.diagnosis.toString(),
613680
"issues": status.serializeIssues(),

0 commit comments

Comments
 (0)