Browse Source

Subject: feat(sync): Add advanced debug overlay and fix timing bugs

Body:

This commit introduces a new advanced debugging overlay to help diagnose synchronization issues and fixes three core timing bugs that caused data streams to be misaligned.

Advanced Debug Overlay:

A new "Show Advanced Debug" toggle has been added to the UI. When enabled, it displays critical synchronization diagnostics in real-time, including:

Video vs. Target Radar timestamps

The calculated "Drift" in milliseconds between the two

Absolute start times for video and radar recordings

The currently applied offset

This provides a precise tool for manually calibrating the offset and verifying sync accuracy.

Synchronization Fixes:

The previous implementation suffered from several race conditions and logical errors in its time management:

Speed Graph Misalignment: The ego speed line on the graph was plotted using raw radar timestamps, ignoring the video offset. This has been corrected to use the offset-adjusted timestampMs, aligning it with the CAN data.

Playback Drift: The main animation loop was incorrectly applying the time offset a second time during playback, causing the radar visualization to lead or lag the video. The redundant offset calculation has been removed from the animationLoop.

Seeking Inaccuracy: When scrubbing the timeline, the UI would update the CAN speed using a stale videoPlayer.currentTime value due to the asynchronous nature of video seeking. The logic in updateFrame now uses the precise, calculated target time for the update, ensuring the EGO and CAN speed indicators match perfectly during seeks.

These changes result in a significantly more robust and verifiable synchronization between the video and radar data feeds.
refactor/modularize
RUSHIL AMBARISH KADU 9 months ago
parent
commit
5981221824
  1. 164
      steps/index.html
  2. 357
      steps/src/dom.js
  3. 4
      steps/src/main.js
  4. 2
      steps/src/p5/speedGraphSketch.js
  5. 2
      steps/src/sync.js

164
steps/index.html

@ -3,39 +3,41 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Radar and Video Visualizer - Timestamp Synchronized</title>
<script src="https://cdn.tailwindcss.com"></script>
<!-- DARK MODE: Step 1 - Configure Tailwind to use the 'class' strategy for dark mode -->
<script>
tailwind.config = {
darkMode: 'class',
}
darkMode: "class",
};
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Roboto+Mono:wght@400;500&display=swap"
rel="stylesheet">
rel="stylesheet" />
<style>
body {
font-family: 'Inter', sans-serif;
font-family: "Inter", sans-serif;
}
.font-mono {
font-family: 'Roboto Mono', monospace;
font-family: "Roboto Mono", monospace;
}
.p5Canvas {
border-radius: 0.5rem;
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1),
0 2px 4px -2px rgb(0 0 0 / 0.1);
}
video {
border-radius: 0.5rem;
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1),
0 2px 4px -2px rgb(0 0 0 / 0.1);
}
input[type="range"]::-webkit-slider-thumb {
@ -58,7 +60,8 @@
}
.shadow-up {
box-shadow: 0 -4px 6px -1px rgb(0 0 0 / 0.1), 0 -2px 4px -2px rgb(0 0 0 / 0.1);
box-shadow: 0 -4px 6px -1px rgb(0 0 0 / 0.1),
0 -2px 4px -2px rgb(0 0 0 / 0.1);
}
#modal-overlay {
@ -72,10 +75,13 @@
</head>
<body class="bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200 flex flex-col min-h-screen">
<header class="bg-white dark:bg-gray-800 shadow-md p-4 z-10 relative">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Radar and Video Visualizer</h1>
<p class="text-sm text-gray-600 dark:text-gray-400">High-precision, timestamp-synchronized playback.</p>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
Radar and Video Visualizer
</h1>
<p class="text-sm text-gray-600 dark:text-gray-400">
High-precision, timestamp-synchronized playback.
</p>
<!-- DARK MODE: Step 2 - Add the toggle button -->
<div class="absolute top-4 right-4">
@ -98,8 +104,9 @@
<div class="relative">
<div id="canvas-container"
class="w-full h-[60vh] bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg shadow-inner flex items-center justify-center">
<p id="canvas-placeholder" class="text-gray-500 dark:text-gray-400 text-lg">Load JSON data to start
visualization</p>
<p id="canvas-placeholder" class="text-gray-500 dark:text-gray-400 text-lg">
Load JSON data to start visualization
</p>
</div>
<div class="absolute bottom-2 left-1/2 -translate-x-1/2 flex items-center justify-center gap-4">
<div id="ego-speed-display"
@ -113,55 +120,67 @@
<div class="flex flex-wrap justify-center gap-x-6 gap-y-2">
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox"
id="toggle-snr-color"
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500"> Color by
SNR</label>
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" />
Color by SNR</label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox"
id="toggle-cluster-color"
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500"> Color by
Cluster</label>
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" />
Color by Cluster</label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox"
id="toggle-inlier-color"
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500"> Color by
Inlier</label>
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" />
Color by Inlier</label>
<!-- ADD THIS LINE -->
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox"
id="toggle-stationary-color"
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500"> Color by
Stationary</label>
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" />
Color by Stationary</label>
<!-- END OF ADDED LINE -->
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox"
id="toggle-velocity" class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500"
checked> Show Object Details</label>
checked />
Show Object Details</label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox"
id="toggle-tracks" class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500"
checked> Show Tracks</label>
checked />
Show Tracks</label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox"
id="toggle-ego-speed"
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" checked> Show Ego
Speed</label>
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" checked />
Show Ego Speed</label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox"
id="toggle-frame-norm"
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500"> Per-Frame
SNR</label>
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" />
Per-Frame SNR</label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox"
id="toggle-debug-overlay"
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500"> Show Debug
Info</label>
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" />
Show Debug Info</label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox"
id="toggle-debug2-overlay"
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" />
Show Advanced Debug</label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox"
id="toggle-close-up"
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500"> CLOSE-UP</label>
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" />
CLOSE-UP</label>
</div>
<div class="flex items-center gap-4">
<div class="flex items-center gap-2"><label for="snr-min-input" class="text-sm font-medium">Min
SNR:</label><input type="number" id="snr-min-input" step="0.1"
class="w-20 p-2 border border-gray-300 dark:bg-gray-600 dark:border-gray-500 dark:text-white rounded-lg text-sm">
<div class="flex items-center gap-2">
<label for="snr-min-input" class="text-sm font-medium">Min SNR:</label><input type="number"
id="snr-min-input" step="0.1"
class="w-20 p-2 border border-gray-300 dark:bg-gray-600 dark:border-gray-500 dark:text-white rounded-lg text-sm" />
</div>
<div class="flex items-center gap-2"><label for="snr-max-input" class="text-sm font-medium">Max
SNR:</label><input type="number" id="snr-max-input" step="0.1"
class="w-20 p-2 border border-gray-300 dark:bg-gray-600 dark:border-gray-500 dark:text-white rounded-lg text-sm">
<div class="flex items-center gap-2">
<label for="snr-max-input" class="text-sm font-medium">Max SNR:</label><input type="number"
id="snr-max-input" step="0.1"
class="w-20 p-2 border border-gray-300 dark:bg-gray-600 dark:border-gray-500 dark:text-white rounded-lg text-sm" />
</div>
<button id="apply-snr-btn"
class="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 transition-colors text-sm font-medium">Apply</button>
class="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 transition-colors text-sm font-medium">
Apply
</button>
</div>
</div>
</div>
@ -169,15 +188,18 @@
<div class="lg:w-1/2 flex flex-col gap-4">
<div class="w-full h-[45vh] bg-black rounded-lg shadow-inner flex items-center justify-center relative">
<video id="video-player" class="w-full h-full object-contain hidden" muted playsinline></video>
<p id="video-placeholder" class="text-gray-500 dark:text-gray-400 text-lg">Load a video file</p>
<p id="video-placeholder" class="text-gray-500 dark:text-gray-400 text-lg">
Load a video file
</p>
<div id="debug-overlay"
class="absolute top-0 left-0 bg-black bg-opacity-60 text-white p-2 font-mono text-xs hidden w-full">
</div>
</div>
<div id="speed-graph-container"
class="w-full h-[27vh] bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg shadow-inner flex items-center justify-center">
<p id="speed-graph-placeholder" class="text-gray-500 dark:text-gray-400 text-lg">Load CAN log to see
speed graph</p>
<p id="speed-graph-placeholder" class="text-gray-500 dark:text-gray-400 text-lg">
Load CAN log to see speed graph
</p>
</div>
</div>
</main>
@ -185,35 +207,44 @@
<footer class="bg-white dark:bg-gray-800 shadow-up w-full p-4 mt-auto sticky bottom-0 z-20">
<div class="mb-4">
<input type="range" id="timeline-slider" min="0" max="0" value="0"
class="w-full h-2 bg-gray-300 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer">
class="w-full h-2 bg-gray-300 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer" />
</div>
<div class="flex flex-wrap items-center justify-center md:justify-between gap-4">
<div class="flex items-center gap-4 justify-center">
<button id="load-json-btn"
class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium">Load
JSON</button>
class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium">
Load JSON
</button>
<button id="load-video-btn"
class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors text-sm font-medium">Load
Video</button>
class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors text-sm font-medium">
Load Video
</button>
<button id="load-can-btn"
class="bg-yellow-500 text-white px-4 py-2 rounded-lg hover:bg-yellow-600 transition-colors text-sm font-medium">Load
CAN Log</button>
class="bg-yellow-500 text-white px-4 py-2 rounded-lg hover:bg-yellow-600 transition-colors text-sm font-medium">
Load CAN Log
</button>
<button id="clear-cache-btn"
class="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 transition-colors text-sm font-medium">Clear
Cache</button>
class="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 transition-colors text-sm font-medium">
Clear Cache
</button>
<div class="flex items-center gap-2">
<label for="offset-input" class="text-sm font-medium"> Offset (ms):<br><small>(+ve values if
radar<br> lags behind video)</small></label>
<label for="offset-input" class="text-sm font-medium">
Offset (ms):<br /><small>(+ve values if radar<br />
lags behind video)</small></label>
<input type="number" id="offset-input" value="0"
class="w-20 p-2 border border-gray-300 dark:bg-gray-600 dark:border-gray-500 dark:text-white rounded-lg text-sm">
class="w-20 p-2 border border-gray-300 dark:bg-gray-600 dark:border-gray-500 dark:text-white rounded-lg text-sm" />
<span id="auto-offset-indicator" class="text-green-600 font-semibold text-sm hidden">(auto)</span>
</div>
</div>
<div class="flex items-center justify-center gap-4">
<button id="play-pause-btn"
class="px-5 py-2 rounded-lg bg-gray-200 dark:bg-gray-600 dark:hover:bg-gray-500 hover:bg-gray-300 font-semibold w-20">Play</button>
class="px-5 py-2 rounded-lg bg-gray-200 dark:bg-gray-600 dark:hover:bg-gray-500 hover:bg-gray-300 font-semibold w-20">
Play
</button>
<button id="stop-btn"
class="px-5 py-2 rounded-lg bg-gray-200 dark:bg-gray-600 dark:hover:bg-gray-500 hover:bg-gray-300 font-semibold">Stop</button>
class="px-5 py-2 rounded-lg bg-gray-200 dark:bg-gray-600 dark:hover:bg-gray-500 hover:bg-gray-300 font-semibold">
Stop
</button>
<div class="text-center">
<span id="frame-counter" class="font-mono text-lg">Frame: 0 / 0</span>
</div>
@ -221,7 +252,7 @@
<div class="flex items-center justify-center gap-2">
<label for="speed-slider" class="text-sm font-medium">Speed:</label>
<input type="range" id="speed-slider" min="0.1" max="2" value="1" step="0.1"
class="w-32 h-2 bg-gray-300 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer">
class="w-32 h-2 bg-gray-300 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer" />
<span id="speed-display" class="font-mono text-sm w-12 text-center">1.0x</span>
</div>
</div>
@ -232,15 +263,18 @@
<div id="modal-content"
class="bg-white dark:bg-gray-800 rounded-lg shadow-xl p-6 w-full max-w-md z-10 transform scale-95">
<p id="modal-text" class="text-gray-700 dark:text-gray-300 mb-4"></p>
<div id="modal-buttons" class="flex justify-end gap-4"><button id="modal-cancel-btn"
class="px-4 py-2 rounded-lg bg-gray-200 dark:bg-gray-600 dark:hover:bg-gray-500 hover:bg-gray-300 font-semibold">Cancel</button><button
id="modal-ok-btn"
class="px-4 py-2 rounded-lg bg-blue-600 text-white hover:bg-blue-700 font-semibold">OK</button>
<div id="modal-buttons" class="flex justify-end gap-4">
<button id="modal-cancel-btn"
class="px-4 py-2 rounded-lg bg-gray-200 dark:bg-gray-600 dark:hover:bg-gray-500 hover:bg-gray-300 font-semibold">
Cancel</button><button id="modal-ok-btn"
class="px-4 py-2 rounded-lg bg-blue-600 text-white hover:bg-blue-700 font-semibold">
OK
</button>
</div>
</div>
</div>
<input type="file" id="json-file-input" class="hidden" accept=".json"><input type="file" id="video-file-input"
class="hidden" accept="video/*"><input type="file" id="can-file-input" class="hidden" accept=".log, .txt">
<input type="file" id="json-file-input" class="hidden" accept=".json" /><input type="file" id="video-file-input"
class="hidden" accept="video/*" /><input type="file" id="can-file-input" class="hidden" accept=".log, .txt" />
<script type="module" src="./src/main.js"></script>
</body>

357
steps/src/dom.js

@ -1,158 +1,247 @@
import { appState } from './state.js';
import { findLastCanIndexBefore } from './utils.js';
import { VIDEO_FPS } from './constants.js';
import { appState } from "./state.js";
import { findLastCanIndexBefore } from "./utils.js";
import { VIDEO_FPS } from "./constants.js";
// --- DOM Element References --- //
export const canvasContainer = document.getElementById('canvas-container');
export const canvasPlaceholder = document.getElementById('canvas-placeholder');
export const videoPlayer = document.getElementById('video-player');
export const videoPlaceholder = document.getElementById('video-placeholder');
export const loadJsonBtn = document.getElementById('load-json-btn');
export const loadVideoBtn = document.getElementById('load-video-btn');
export const loadCanBtn = document.getElementById('load-can-btn');
export const jsonFileInput = document.getElementById('json-file-input');
export const videoFileInput = document.getElementById('video-file-input');
export const canFileInput = document.getElementById('can-file-input');
export const playPauseBtn = document.getElementById('play-pause-btn');
export const stopBtn = document.getElementById('stop-btn');
export const timelineSlider = document.getElementById('timeline-slider');
export const frameCounter = document.getElementById('frame-counter');
export const offsetInput = document.getElementById('offset-input');
export const speedSlider = document.getElementById('speed-slider');
export const speedDisplay = document.getElementById('speed-display');
export const featureToggles = document.getElementById('feature-toggles');
export const toggleSnrColor = document.getElementById('toggle-snr-color');
export const toggleClusterColor = document.getElementById('toggle-cluster-color');
export const toggleInlierColor = document.getElementById('toggle-inlier-color');
export const toggleStationaryColor = document.getElementById('toggle-stationary-color');
export const toggleVelocity = document.getElementById('toggle-velocity');
export const toggleTracks = document.getElementById('toggle-tracks');
export const toggleEgoSpeed = document.getElementById('toggle-ego-speed');
export const toggleFrameNorm = document.getElementById('toggle-frame-norm');
export const toggleDebugOverlay = document.getElementById('toggle-debug-overlay');
export const egoSpeedDisplay = document.getElementById('ego-speed-display');
export const canSpeedDisplay = document.getElementById('can-speed-display');
export const debugOverlay = document.getElementById('debug-overlay');
export const snrMinInput = document.getElementById('snr-min-input');
export const snrMaxInput = document.getElementById('snr-max-input');
export const applySnrBtn = document.getElementById('apply-snr-btn');
export const autoOffsetIndicator = document.getElementById('auto-offset-indicator');
export const clearCacheBtn = document.getElementById('clear-cache-btn');
export const speedGraphContainer = document.getElementById('speed-graph-container');
export const speedGraphPlaceholder = document.getElementById('speed-graph-placeholder');
export const modalContainer = document.getElementById('modal-container');
export const modalOverlay = document.getElementById('modal-overlay');
export const modalContent = document.getElementById('modal-content');
export const modalText = document.getElementById('modal-text');
export const modalOkBtn = document.getElementById('modal-ok-btn');
export const modalCancelBtn = document.getElementById('modal-cancel-btn');
export const toggleCloseUp = document.getElementById('toggle-close-up');
export const canvasContainer = document.getElementById("canvas-container");
export const canvasPlaceholder = document.getElementById("canvas-placeholder");
export const videoPlayer = document.getElementById("video-player");
export const videoPlaceholder = document.getElementById("video-placeholder");
export const loadJsonBtn = document.getElementById("load-json-btn");
export const loadVideoBtn = document.getElementById("load-video-btn");
export const loadCanBtn = document.getElementById("load-can-btn");
export const jsonFileInput = document.getElementById("json-file-input");
export const videoFileInput = document.getElementById("video-file-input");
export const canFileInput = document.getElementById("can-file-input");
export const playPauseBtn = document.getElementById("play-pause-btn");
export const stopBtn = document.getElementById("stop-btn");
export const timelineSlider = document.getElementById("timeline-slider");
export const frameCounter = document.getElementById("frame-counter");
export const offsetInput = document.getElementById("offset-input");
export const speedSlider = document.getElementById("speed-slider");
export const speedDisplay = document.getElementById("speed-display");
export const featureToggles = document.getElementById("feature-toggles");
export const toggleSnrColor = document.getElementById("toggle-snr-color");
export const toggleClusterColor = document.getElementById(
"toggle-cluster-color"
);
export const toggleInlierColor = document.getElementById("toggle-inlier-color");
export const toggleStationaryColor = document.getElementById(
"toggle-stationary-color"
);
export const toggleVelocity = document.getElementById("toggle-velocity");
export const toggleTracks = document.getElementById("toggle-tracks");
export const toggleEgoSpeed = document.getElementById("toggle-ego-speed");
export const toggleFrameNorm = document.getElementById("toggle-frame-norm");
export const toggleDebugOverlay = document.getElementById(
"toggle-debug-overlay"
);
export const egoSpeedDisplay = document.getElementById("ego-speed-display");
export const canSpeedDisplay = document.getElementById("can-speed-display");
export const debugOverlay = document.getElementById("debug-overlay");
export const toggleDebug2Overlay = document.getElementById(
"toggle-debug2-overlay"
);
export const snrMinInput = document.getElementById("snr-min-input");
export const snrMaxInput = document.getElementById("snr-max-input");
export const applySnrBtn = document.getElementById("apply-snr-btn");
export const autoOffsetIndicator = document.getElementById(
"auto-offset-indicator"
);
export const clearCacheBtn = document.getElementById("clear-cache-btn");
export const speedGraphContainer = document.getElementById(
"speed-graph-container"
);
export const speedGraphPlaceholder = document.getElementById(
"speed-graph-placeholder"
);
export const modalContainer = document.getElementById("modal-container");
export const modalOverlay = document.getElementById("modal-overlay");
export const modalContent = document.getElementById("modal-content");
export const modalText = document.getElementById("modal-text");
export const modalOkBtn = document.getElementById("modal-ok-btn");
export const modalCancelBtn = document.getElementById("modal-cancel-btn");
export const toggleCloseUp = document.getElementById("toggle-close-up");
//----------------------UPDATE FRAME Function----------------------//
// Located in: src/dom.js
export function updateFrame(frame, forceVideoSeek) {
if (!appState.vizData || frame < 0 || frame >= appState.vizData.radarFrames.length) return;
appState.currentFrame = frame;
timelineSlider.value = appState.currentFrame;
frameCounter.textContent = `Frame: ${appState.currentFrame + 1} / ${appState.vizData.radarFrames.length}`;
const frameData = appState.vizData.radarFrames[appState.currentFrame];
if (toggleEgoSpeed.checked && frameData) {
const egoVy_kmh = (frameData.egoVelocity[1] * 3.6).toFixed(1);
egoSpeedDisplay.textContent = `Ego: ${egoVy_kmh} km/h`;
egoSpeedDisplay.classList.remove('hidden');
} else {
egoSpeedDisplay.classList.add('hidden');
if (
!appState.vizData ||
frame < 0 ||
frame >= appState.vizData.radarFrames.length
)
return;
appState.currentFrame = frame;
timelineSlider.value = appState.currentFrame;
frameCounter.textContent = `Frame: ${appState.currentFrame + 1} / ${
appState.vizData.radarFrames.length
}`;
const frameData = appState.vizData.radarFrames[appState.currentFrame];
if (toggleEgoSpeed.checked && frameData) {
const egoVy_kmh = (frameData.egoVelocity[1] * 3.6).toFixed(1);
egoSpeedDisplay.textContent = `Ego: ${egoVy_kmh} km/h`;
egoSpeedDisplay.classList.remove("hidden");
} else {
egoSpeedDisplay.classList.add("hidden");
}
// --- Start of fix ---
let timeForUpdates = videoPlayer.currentTime; // NEW: Default to the video's current time
if (
forceVideoSeek &&
videoPlayer.src &&
videoPlayer.readyState > 1 &&
appState.videoStartDate &&
frameData
) {
const offsetMs = parseFloat(offsetInput.value) || 0;
const targetRadarTimeMs = frameData.timestampMs;
const targetVideoTimeSec = (targetRadarTimeMs - offsetMs) / 1000;
if (targetVideoTimeSec >= 0 && targetVideoTimeSec <= videoPlayer.duration) {
if (Math.abs(videoPlayer.currentTime - targetVideoTimeSec) > 0.05) {
videoPlayer.currentTime = targetVideoTimeSec;
}
// MODIFIED: Use the calculated target time for our updates, not the stale videoPlayer.currentTime
timeForUpdates = targetVideoTimeSec;
}
if (forceVideoSeek && videoPlayer.src && videoPlayer.readyState > 1 && appState.videoStartDate && frameData) {
const offsetMs = parseFloat(offsetInput.value) || 0;
const targetRadarTimeMs = frameData.timestampMs;
const targetVideoTimeSec = (targetRadarTimeMs - offsetMs) / 1000;
if (targetVideoTimeSec >= 0 && targetVideoTimeSec <= videoPlayer.duration) {
if (Math.abs(videoPlayer.currentTime - targetVideoTimeSec) > 0.05) {
videoPlayer.currentTime = targetVideoTimeSec;
}
}
}
if (!appState.isPlaying) {
updateCanDisplay(videoPlayer.currentTime);
updateDebugOverlay(videoPlayer.currentTime);
}
if (appState.p5_instance) appState.p5_instance.redraw();
if (appState.speedGraphInstance && !appState.isPlaying) appState.speedGraphInstance.redraw();
}
if (!appState.isPlaying) {
// MODIFIED: Use our new synchronized time variable
updateCanDisplay(timeForUpdates);
updateDebugOverlay(timeForUpdates);
}
// --- End of fix ---
if (appState.p5_instance) appState.p5_instance.redraw();
if (appState.speedGraphInstance && !appState.isPlaying)
appState.speedGraphInstance.redraw();
}
//----------------------RESET VISUALIZATION Function----------------------//
export function resetVisualization() {
appState.isPlaying = false;
playPauseBtn.textContent = 'Play';
const numFrames = appState.vizData.radarFrames.length;
timelineSlider.max = numFrames > 0 ? numFrames - 1 : 0;
updateFrame(0, true);
appState.isPlaying = false;
playPauseBtn.textContent = "Play";
const numFrames = appState.vizData.radarFrames.length;
timelineSlider.max = numFrames > 0 ? numFrames - 1 : 0;
updateFrame(0, true);
}
//----------------------CAN DISPLAY UPDATE Function----------------------//
export function updateCanDisplay(currentMediaTime) {
if (appState.canData.length > 0 && videoPlayer.src && appState.videoStartDate) {
const videoAbsoluteTimeMs = appState.videoStartDate.getTime() + (currentMediaTime * 1000);
const canIndex = findLastCanIndexBefore(videoAbsoluteTimeMs, appState.canData);
if (canIndex !== -1) {
const currentCanMessage = appState.canData[canIndex];
canSpeedDisplay.textContent = `CAN: ${currentCanMessage.speed} km/h`;
canSpeedDisplay.classList.remove('hidden');
}
else {
canSpeedDisplay.classList.add('hidden');
}
}
else {
canSpeedDisplay.classList.add('hidden');
if (
appState.canData.length > 0 &&
videoPlayer.src &&
appState.videoStartDate
) {
const videoAbsoluteTimeMs =
appState.videoStartDate.getTime() + currentMediaTime * 1000;
const canIndex = findLastCanIndexBefore(
videoAbsoluteTimeMs,
appState.canData
);
if (canIndex !== -1) {
const currentCanMessage = appState.canData[canIndex];
canSpeedDisplay.textContent = `CAN: ${currentCanMessage.speed} km/h`;
canSpeedDisplay.classList.remove("hidden");
} else {
canSpeedDisplay.classList.add("hidden");
}
} else {
canSpeedDisplay.classList.add("hidden");
}
}
//----------------------DEBUG OVERLAY UPDATE Function----------------------//
export function updateDebugOverlay(currentMediaTime) {
if (!toggleDebugOverlay.checked) {
debugOverlay.classList.add('hidden');
return;
} debugOverlay.classList.remove('hidden');
let content = [];
if (appState.videoStartDate) {
const videoAbsoluteTimeMs = appState.videoStartDate.getTime() + (currentMediaTime * 1000);
content.push(`Media Time (s): ${currentMediaTime.toFixed(3)}`);
const videoFrame = Math.floor(currentMediaTime * VIDEO_FPS);
content.push(`Video Frame: ${videoFrame}`);
content.push(`Vid Abs Time: ${new Date(videoAbsoluteTimeMs).toISOString().split('T')[1].replace('Z', '')}`);
if (appState.canData.length > 0) {
const canIndex = findLastCanIndexBefore(videoAbsoluteTimeMs, appState.canData);
if (canIndex !== -1) {
const currentCanMessage = appState.canData[canIndex];
content.push(`CAN Abs Time: ${new Date(currentCanMessage.time).toISOString().split('T')[1].replace('Z', '')}`);
content.push(`CAN Speed: ${currentCanMessage.speed} km/h`);
}
else {
content.push('CAN: No data for time');
}
}
}
else {
content.push('Video not loaded...');
} if (appState.vizData) {
content.push(`Radar Frame: ${appState.currentFrame + 1}`);
if (appState.vizData.radarFrames[appState.currentFrame])
content.push(`Radar Abs Time: ${new Date(appState.vizData.radarFrames[appState.currentFrame].timestampMs).toISOString().split('T')[1].replace('Z', '')}`);
} debugOverlay.innerHTML = content.join('<br>');
}
// Check the state of both debug toggles
const isDebug1Visible = toggleDebugOverlay.checked;
const isDebug2Visible = toggleDebug2Overlay.checked;
// If neither is checked, hide the overlay and stop
if (!isDebug1Visible && !isDebug2Visible) {
debugOverlay.classList.add("hidden");
return;
}
debugOverlay.classList.remove("hidden");
let content = [];
// --- Logic for the original debug overlay ---
if (isDebug1Visible) {
content.push(`--- Basic Info ---`);
if (appState.videoStartDate) {
const videoAbsoluteTimeMs =
appState.videoStartDate.getTime() + currentMediaTime * 1000;
content.push(`Media Time (s): ${currentMediaTime.toFixed(3)}`);
content.push(`Video Frame: ${Math.floor(currentMediaTime * VIDEO_FPS)}`);
content.push(
`Vid Abs Time: ${new Date(videoAbsoluteTimeMs)
.toISOString()
.split("T")[1]
.replace("Z", "")}`
);
} else {
content.push("Video not loaded...");
}
if (
appState.vizData &&
appState.vizData.radarFrames[appState.currentFrame]
) {
content.push(`Radar Frame: ${appState.currentFrame + 1}`);
const frameTime =
appState.vizData.radarFrames[appState.currentFrame].timestampMs;
content.push(
`Radar Abs Time: ${new Date(
appState.videoStartDate.getTime() + frameTime
)
.toISOString()
.split("T")[1]
.replace("Z", "")}`
);
}
}
// --- Logic for the new advanced debug overlay ---
if (isDebug2Visible) {
content.push(`--- Sync Diagnostics ---`);
if (
appState.videoStartDate &&
appState.vizData &&
appState.vizData.radarFrames[appState.currentFrame]
) {
const currentRadarFrame =
appState.vizData.radarFrames[appState.currentFrame];
const targetRadarTimeMs = currentRadarFrame.timestampMs;
const driftMs = currentMediaTime * 1000 - targetRadarTimeMs;
// Style the drift value to be green if sync is good, and red if it's off
const driftColor = Math.abs(driftMs) > 40 ? "#FF6347" : "#98FB98"; // Tomato red or Pale green
content.push(`Video Time (s): ${currentMediaTime.toFixed(3)}`);
content.push(`Target Radar Time (ms): ${targetRadarTimeMs.toFixed(0)}`);
content.push(
`Drift (ms): <b style="color: ${driftColor};">${driftMs.toFixed(0)}</b>`
);
content.push(
`Video Start Time: ${appState.videoStartDate.toISOString()}`
);
content.push(
`Radar Start Time: ${new Date(appState.radarStartTimeMs).toISOString()}`
);
content.push(`Calculated Offset (ms): ${offsetInput.value}`);
} else {
content.push("Load video and radar data to see sync info.");
}
}
debugOverlay.innerHTML = content.join("<br>");
}

4
steps/src/main.js

@ -74,6 +74,7 @@ import {
toggleEgoSpeed,
toggleFrameNorm,
toggleDebugOverlay,
toggleDebug2Overlay,
egoSpeedDisplay,
canSpeedDisplay,
debugOverlay,
@ -359,6 +360,7 @@ colorToggles.forEach((t) => {
toggleFrameNorm,
toggleTracks,
toggleDebugOverlay,
toggleDebug2Overlay
].forEach((t) => {
t.addEventListener("change", () => {
if (appState.p5_instance) {
@ -369,7 +371,7 @@ colorToggles.forEach((t) => {
);
appState.p5_instance.redraw();
}
if (t === toggleDebugOverlay) updateDebugOverlay(videoPlayer.currentTime);
if (t === toggleDebugOverlay || t === toggleDebug2Overlay) { updateDebugOverlay(videoPlayer.currentTime)};
});
});

2
steps/src/p5/speedGraphSketch.js

@ -75,7 +75,7 @@ export const speedGraphSketch = function (p) {
b.drawingContext.setLineDash([5, 5]);
b.beginShape();
for (const frame of radarData.radarFrames) {
const relTime = frame.timestamp / 1000;
const relTime = frame.timestampMs / 1000;
if (relTime >= 0 && relTime <= videoDuration) {
const x = b.map(relTime, 0, videoDuration, pad.left, b.width - pad.right);
const egoSpeedKmh = frame.egoVelocity[1] * 3.6;

2
steps/src/sync.js

@ -17,7 +17,7 @@ export function animationLoop() {
// Update radar frame based on the master clock
if (appState.vizData && appState.videoStartDate) {
const offsetMs = parseFloat(offsetInput.value) || 0;
const targetRadarTimeMs = (currentMediaTime * 1000) + offsetMs;
const targetRadarTimeMs = (currentMediaTime * 1000);
const targetFrame = findRadarFrameIndexForTime(targetRadarTimeMs, appState.vizData);
if (targetFrame !== appState.currentFrame) {
updateFrame(targetFrame, false);

Loading…
Cancel
Save