From f560d5fa5ef8fe0bc11904195c1dd5e67eb60c46 Mon Sep 17 00:00:00 2001 From: rakadu1 Date: Wed, 3 Sep 2025 12:02:07 +0530 Subject: [PATCH] OH MY GOD. What happened over here. Everything is back to its original state now, also we are using CAN speed data from JSON. CAN LOAD button removed. --- steps/src/dom.js | 75 +++++++------ steps/src/drawUtils.js | 34 +++++- steps/src/fileParsers.js | 70 ------------ steps/src/main.js | 147 ++++++------------------- steps/src/p5/speedGraphSketch.js | 179 ++++++++++--------------------- steps/src/state.js | 6 -- steps/src/sync.js | 3 - steps/src/theme.js | 19 ++-- steps/src/utils.js | 21 ---- 9 files changed, 165 insertions(+), 389 deletions(-) diff --git a/steps/src/dom.js b/steps/src/dom.js index 8864b93..e867ddb 100644 --- a/steps/src/dom.js +++ b/steps/src/dom.js @@ -1,5 +1,4 @@ import { appState } from "./state.js"; -import { findLastCanIndexBefore } from "./utils.js"; import { VIDEO_FPS } from "./constants.js"; // Import VIDEO_FPS for debug overlay calculations // --- DOM Element References --- // @@ -23,25 +22,39 @@ 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 toggleClusterColor = document.getElementById( + "toggle-cluster-color" +); export const toggleInlierColor = document.getElementById("toggle-inlier-color"); -export const toggleStationaryColor = document.getElementById("toggle-stationary-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 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 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 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 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"); @@ -49,7 +62,9 @@ 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 togglePredictedPos = document.getElementById("toggle-predicted-pos"); +export const togglePredictedPos = document.getElementById( + "toggle-predicted-pos" +); export const toggleCovariance = document.getElementById("toggle-covariance"); //----------------------UPDATE FRAME Function----------------------// @@ -77,8 +92,22 @@ export function updateFrame(frame, forceVideoSeek) { egoSpeedDisplay.classList.add("hidden"); // Hide ego speed display. } - // --- Start of fix --- - let timeForUpdates = videoPlayer.currentTime; // NEW: Default to the video's current time + // --- ADD THIS NEW BLOCK --- + if ( + frameData && + frameData.canVehSpeed_kmph !== null && + !isNaN(frameData.canVehSpeed_kmph) + ) { + canSpeedDisplay.textContent = `CAN: ${frameData.canVehSpeed_kmph.toFixed( + 1 + )} km/h`; + canSpeedDisplay.classList.remove("hidden"); + } else { + canSpeedDisplay.classList.add("hidden"); + } + // --- END OF NEW BLOCK --- + + let timeForUpdates = videoPlayer.currentTime; // NEW: Default to the video's current time if ( forceVideoSeek && @@ -103,7 +132,6 @@ export function updateFrame(frame, forceVideoSeek) { if (!appState.isPlaying) { // MODIFIED: Use our new synchronized time variable - updateCanDisplay(timeForUpdates); updateDebugOverlay(timeForUpdates); } // --- End of fix --- @@ -126,29 +154,6 @@ export function resetVisualization() { //----------------------CAN DISPLAY UPDATE Function----------------------// // Updates the CAN speed display based on the current media time. -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]; // Get the CAN message at the found index - canSpeedDisplay.textContent = `CAN: ${currentCanMessage.speed} km/h`; // Display CAN speed - canSpeedDisplay.classList.remove("hidden"); - } else { - canSpeedDisplay.classList.add("hidden"); // Hide CAN speed display - } - } else { - canSpeedDisplay.classList.add("hidden"); // Hide CAN speed display. - } -} //----------------------DEBUG OVERLAY UPDATE Function----------------------// // Updates the debug overlay with various synchronization and time information. diff --git a/steps/src/drawUtils.js b/steps/src/drawUtils.js index fdf591e..58e5302 100644 --- a/steps/src/drawUtils.js +++ b/steps/src/drawUtils.js @@ -525,9 +525,25 @@ export function drawCovarianceEllipse(p, position, covarianceP, plotScales) { const trace = a + d; const determinant = a * d - b * b; - const lambda1 = trace / 2 + Math.sqrt(Math.pow(trace, 2) / 4 - determinant); - const lambda2 = trace / 2 - Math.sqrt(Math.pow(trace, 2) / 4 - determinant); + //const lambda1 = trace / 2 + Math.sqrt(Math.pow(trace, 2) / 4 - determinant); + //const lambda2 = trace / 2 - Math.sqrt(Math.pow(trace, 2) / 4 - determinant); + // --- START: New robust calculation with logging --- + let sqrtTermVal = Math.pow(trace, 2) / 4 - determinant; + + // Check for a negative value, which causes NaN errors + if (sqrtTermVal < 0) { + // Log a warning so we know it happened, as you suggested + console.warn( + `Clamping negative sqrtTermVal in frame ${appState.currentFrame} to prevent NaN. Original value: ${sqrtTermVal}` + ); + // Clamp the value to 0. This allows drawing to continue instead of breaking. + sqrtTermVal = 0; + } + const sqrtTerm = Math.sqrt(sqrtTermVal); + const lambda1 = trace / 2 + sqrtTerm; + const lambda2 = trace / 2 - sqrtTerm; + // --- END: New robust calculation with logging --- const chi2 = 5.991; const majorAxis = Math.sqrt(chi2 * lambda1); const minorAxis = Math.sqrt(chi2 * lambda2); @@ -542,8 +558,16 @@ export function drawCovarianceEllipse(p, position, covarianceP, plotScales) { p.noFill(); p.stroke(255, 0, 0, 150); p.strokeWeight(1); - p.translate(position[0] * plotScales.plotScaleX, position[1] * plotScales.plotScaleY); + p.translate( + position[0] * plotScales.plotScaleX, + position[1] * plotScales.plotScaleY + ); p.rotate(angle); - p.ellipse(0, 0, majorAxis * 2 * plotScales.plotScaleX, minorAxis * 2 * plotScales.plotScaleY); + p.ellipse( + 0, + 0, + majorAxis * 2 * plotScales.plotScaleX, + minorAxis * 2 * plotScales.plotScaleY + ); p.pop(); -} \ No newline at end of file +} diff --git a/steps/src/fileParsers.js b/steps/src/fileParsers.js index c17c147..27dc08e 100644 --- a/steps/src/fileParsers.js +++ b/steps/src/fileParsers.js @@ -37,76 +37,6 @@ export function parseJsonWithOboe(fileURL, onComplete, onError) { } -//--------------------CAN-LOG PARSER------------------------// - -export function processCanLog(logContent, videoStartDate) { - // The function now receives all necessary data (logContent, videoStartDate) as arguments, - // making it a pure function that doesn't rely on global state. - if (!videoStartDate) { - // If videoStartDate is not provided, it means the video file hasn't been loaded yet. - // The CAN log cannot be synchronized without it, so an error is returned. - return { - // Error message to be displayed to the user. - error: "Please load the video file first to synchronize the CAN log.", - // The raw log content is returned so it can be stored and processed later - // once the videoStartDate becomes available. - rawCanLogText: logContent, - }; - } - - // This is a NEW, LOCAL variable, only for this function. - const canData = []; - const lines = logContent.split("\n"); - // Regular expression to parse CAN log lines. - // It captures time components (HH:MM:SS:ms), CAN ID, and data bytes. - const logRegex = - /(\d{2}):(\d{2}):(\d{2}):(\d{4})\s+Rx\s+\d+\s+0x([0-9a-fA-F]+)\s+s\s+\d+((?:\s+[0-9a-fA-F]{2})+)/; - // The specific CAN ID (0x30F) we are interested in for speed data. - const canIdToDecode = "30F"; - - for (const line of lines) { - const match = line.match(logRegex); - // Check if the line matches the regex and if the CAN ID is the one we want. - if (match && match[5].toUpperCase() === canIdToDecode) { - // Extract time components from the regex match. - const [h, m, s, ms] = [ - parseInt(match[1]), - parseInt(match[2]), - parseInt(match[3]), - parseInt(match[4].substring(0, 3)), - ]; - // Create a Date object for the CAN message timestamp. - // It uses the video's start date and then sets the time components from the log. - const msgDate = new Date(videoStartDate); - msgDate.setUTCHours(h, m, s, ms); - // Extract and parse data bytes from the regex match. - const dataBytes = match[6] - .trim() - .split(/\s+/) - .map((hex) => parseInt(hex, 16)); - // Check if there are enough data bytes to extract speed information. - if (dataBytes.length >= 2) { - // Decode the raw speed value from the first two data bytes. - // This specific decoding logic is based on the CAN message format. - const rawVal = (dataBytes[0] << 3) | (dataBytes[1] >> 5); - // Convert the raw value to km/h and format it to one decimal place. - const speed = (rawVal * 0.1).toFixed(1); - canData.push({ time: msgDate.getTime(), speed: speed }); - } - } - } - // Sort the processed CAN data points by their timestamp. - canData.sort((a, b) => a.time - b.time); - - console.log( - `Processed ${canData.length} CAN messages for ID ${canIdToDecode}.` - ); - - // It returns the finished product in a structured object. - // The processed CAN data is returned under the 'data' key. - return { data: canData }; -} - //--------------------JSON POST-PROCESSOR (ASYNCHRONOUS & SAFE)------------------------// // Helper function to process large arrays in chunks without blocking diff --git a/steps/src/main.js b/steps/src/main.js index 296457e..8440e2e 100644 --- a/steps/src/main.js +++ b/steps/src/main.js @@ -19,11 +19,7 @@ import { animationLoop } from "./sync.js"; import { radarSketch } from "./p5/radarSketch.js"; import { speedGraphSketch } from "./p5/speedGraphSketch.js"; -import { - processCanLog, - parseVisualizationJson, - parseJsonWithOboe, -} from "./fileParsers.js"; +import { parseVisualizationJson, parseJsonWithOboe } from "./fileParsers.js"; import { MAX_TRAJECTORY_LENGTH, VIDEO_FPS, @@ -34,7 +30,6 @@ import { } from "./constants.js"; import { findRadarFrameIndexForTime, - findLastCanIndexBefore, extractTimestampInfo, parseTimestamp, throttle, @@ -47,10 +42,8 @@ import { videoPlaceholder, loadJsonBtn, loadVideoBtn, - loadCanBtn, jsonFileInput, videoFileInput, - canFileInput, playPauseBtn, stopBtn, timelineSlider, @@ -70,7 +63,6 @@ import { toggleDebugOverlay, toggleDebug2Overlay, egoSpeedDisplay, - canSpeedDisplay, debugOverlay, snrMinInput, snrMaxInput, @@ -82,7 +74,6 @@ import { toggleCloseUp, updateFrame, resetVisualization, - updateCanDisplay, updateDebugOverlay, } from "./dom.js"; import { showModal } from "./modal.js"; @@ -111,11 +102,10 @@ clearCacheBtn.addEventListener("click", async () => { }); jsonFileInput.addEventListener("change", (event) => { - const file = event.target.files[0]; if (!file) return; - + appState.jsonFilename = file.name; localStorage.setItem("jsonFilename", appState.jsonFilename); @@ -123,55 +113,49 @@ jsonFileInput.addEventListener("change", (event) => { calculateAndSetOffset(); saveFileToDB("json", file); // Save the file object for the next session - + // 1. Show a loading modal immediately. showModal("Parsing large JSON file, please wait..."); - + // 2. Create a temporary URL for the streaming parser. const fileURL = URL.createObjectURL(file); - + // 3. Use the robust streaming parser. parseJsonWithOboe( - fileURL, async (parsedData) => { - // This is the success callback, running after the file is parsed. // We make it async so we can `await` the next step. - - const result = await parseVisualizationJson( + const result = await parseVisualizationJson( parsedData, appState.radarStartTimeMs, appState.videoStartDate - ); - + // Revoke the temporary URL to free up memory. URL.revokeObjectURL(fileURL); - - if (result.error) { + if (result.error) { showModal(result.error); return; - } - + appState.vizData = result.data; appState.globalMinSnr = result.minSnr; appState.globalMaxSnr = result.maxSnr; - + // Update UI with the correct, awaited data. snrMinInput.value = appState.globalMinSnr.toFixed(1); @@ -183,47 +167,38 @@ jsonFileInput.addEventListener("change", (event) => { canvasPlaceholder.style.display = "none"; featureToggles.classList.remove("hidden"); - - if (!appState.p5_instance) { + if (!appState.p5_instance) { appState.p5_instance = new p5(radarSketch); - } - - if (appState.speedGraphInstance) { - - appState.speedGraphInstance.setData( - - appState.canData, - - appState.vizData, - - videoPlayer.duration - - ); + if (appState.vizData) { + speedGraphPlaceholder.classList.add("hidden"); + if (!appState.speedGraphInstance) { + appState.speedGraphInstance = new p5(speedGraphSketch); + } + if (videoPlayer.duration) { + appState.speedGraphInstance.setData( + appState.vizData, + videoPlayer.duration + ); + } } - + // Close the loading modal. document.getElementById("modal-ok-btn").click(); - }, (error) => { - // This is the error callback for the streaming parser. showModal(error); URL.revokeObjectURL(fileURL); - } - ); - }); - // Event listener for video file input change. videoFileInput.addEventListener("change", (event) => { @@ -235,17 +210,6 @@ videoFileInput.addEventListener("change", (event) => { calculateAndSetOffset(); - if (appState.rawCanLogText) { - const result = processCanLog( - appState.rawCanLogText, - appState.videoStartDate - ); - if (!result.error) { - appState.canData = result.data; - appState.rawCanLogText = null; - } - } - if (appState.vizData) { console.log("DEBUG: Video loaded after JSON. Re-calculating timestamps."); appState.vizData.radarFrames.forEach((frame) => { @@ -263,7 +227,6 @@ videoFileInput.addEventListener("change", (event) => { videoPlayer.onloadedmetadata = () => { if (appState.speedGraphInstance) { appState.speedGraphInstance.setData( - appState.canData, appState.vizData, videoPlayer.duration ); @@ -271,46 +234,6 @@ videoFileInput.addEventListener("change", (event) => { }; }); -// Event listener for CAN file input change. - - appState.canLogFilename = file.name; - localStorage.setItem("canLogFilename", appState.canLogFilename); - - const reader = new FileReader(); - reader.onload = (e) => { - const logContent = e.target.result; - saveFileToDB("canLogText", logContent); - - const result = processCanLog(logContent, appState.videoStartDate); - - if (result.error) { - showModal(result.error); - appState.rawCanLogText = result.rawCanLogText; - return; - } - - appState.canData = result.data; - appState.rawCanLogText = null; - - if (appState.canData.length > 0 || appState.vizData) { - speedGraphPlaceholder.classList.add("hidden"); - if (!appState.speedGraphInstance) { - appState.speedGraphInstance = new p5(speedGraphSketch); - } - if (videoPlayer.duration) { - appState.speedGraphInstance.setData( - appState.canData, - appState.vizData, - videoPlayer.duration - ); - } - } else { - showModal(`No CAN messages with ID 0x30F found.`); - } - }; - reader.readAsText(file); - - // Event listener for offset input change. offsetInput.addEventListener("input", () => { autoOffsetIndicator.classList.add("hidden"); @@ -525,7 +448,6 @@ document.addEventListener("DOMContentLoaded", () => { } appState.videoFilename = localStorage.getItem("videoFilename"); appState.jsonFilename = localStorage.getItem("jsonFilename"); - appState.canLogFilename = localStorage.getItem("canLogFilename"); calculateAndSetOffset(); @@ -535,14 +457,11 @@ document.addEventListener("DOMContentLoaded", () => { const jsonPromise = new Promise((resolve) => loadFileFromDB("json", resolve) ); - const canLogPromise = new Promise((resolve) => - loadFileFromDB("canLogText", resolve) - ); // At the end of main.js, inside the DOMContentLoaded listener - Promise.all([videoPromise, jsonPromise, canLogPromise]) - .then(([videoBlob, jsonBlob, canLogText]) => { + Promise.all([videoPromise, jsonPromise]) + .then(([videoBlob, jsonBlob]) => { // Renamed jsonString to jsonBlob console.log("DEBUG: All data fetched from IndexedDB."); @@ -565,10 +484,6 @@ document.addEventListener("DOMContentLoaded", () => { } } - if (canLogText && appState.videoStartDate) { - // ... (process CAN log) - } - // Final UI updates if (appState.vizData) { resetVisualization(); @@ -578,9 +493,17 @@ document.addEventListener("DOMContentLoaded", () => { appState.p5_instance = new p5(radarSketch); } } - if (appState.canData.length > 0 || appState.vizData) { + if (appState.vizData) { speedGraphPlaceholder.classList.add("hidden"); - // ... (rest of the UI update logic) + if (!appState.speedGraphInstance) { + appState.speedGraphInstance = new p5(speedGraphSketch); + } + if (videoPlayer.duration) { + appState.speedGraphInstance.setData( + appState.vizData, + videoPlayer.duration + ); + } } }; diff --git a/steps/src/p5/speedGraphSketch.js b/steps/src/p5/speedGraphSketch.js index 2cccd23..d1b3f83 100644 --- a/steps/src/p5/speedGraphSketch.js +++ b/steps/src/p5/speedGraphSketch.js @@ -1,37 +1,20 @@ -//---Import APPSTATE VIDEOPLAYER and FindLastCanIndex---// - import { appState } from "../state.js"; import { videoPlayer, speedGraphContainer } from "../dom.js"; -import { findLastCanIndexBefore } from "../utils.js"; export const speedGraphSketch = function (p) { - // Declare variables for the static buffer, min/max speed for scaling, and video duration. let staticBuffer, minSpeed, maxSpeed, videoDuration; - // Define padding for the graph to ensure elements are not drawn at the edges. const pad = { top: 20, right: 130, bottom: 30, left: 50 }; - /** - * Draws the static elements of the speed graph (axes, grid, labels, and data lines) - * to an off-screen buffer. This optimizes performance by not redrawing these elements - * every frame. - * @param {Array} canSpeedData - Array of CAN speed data points. - * @param {Object} radarData - Object containing radar frames with ego velocity. - */ - // This function is now attached to the p5 instance, making it public - // It's responsible for drawing the static background and data lines - p.drawStaticGraphToBuffer = function (canSpeedData, radarData) { + p.drawStaticGraphToBuffer = function (radarData) { const b = staticBuffer; b.clear(); const isDark = document.documentElement.classList.contains("dark"); b.background(isDark ? [55, 65, 81] : 255); const gridColor = isDark ? 100 : 200; - const textColor = isDark ? 200 : 100; // Determine text color based on theme. + const textColor = isDark ? 200 : 100; - // Push current drawing style settings onto a stack. b.push(); - // Set stroke for grid lines. b.stroke(gridColor); - // Set stroke weight for grid lines. b.strokeWeight(1); b.line(pad.left, pad.top, pad.left, b.height - pad.bottom); b.line( @@ -39,14 +22,11 @@ export const speedGraphSketch = function (p) { b.height - pad.bottom, b.width - pad.right, b.height - pad.bottom - ); // Draw Y and X axes. - // Set text alignment for Y-axis labels. + ); b.textAlign(b.RIGHT, b.CENTER); b.noStroke(); b.fill(textColor); - // Set text size for labels. b.textSize(10); - // Draw horizontal grid lines and speed labels. for (let s = minSpeed; s <= maxSpeed; s += 10) { const y = b.map(s, minSpeed, maxSpeed, b.height - pad.bottom, pad.top); b.text(s, pad.left - 8, y); @@ -60,14 +40,11 @@ export const speedGraphSketch = function (p) { b.line(pad.left + 1, y, b.width - pad.right, y); b.noStroke(); } - // Draw Y-axis unit label. b.fill(textColor); b.text("km/h", pad.left - 8, pad.top - 8); - // Set text alignment for X-axis labels. b.textAlign(b.CENTER, b.TOP); b.noStroke(); b.fill(isDark ? 180 : 150); - // Calculate time interval for X-axis labels. const tInt = Math.max(1, Math.floor(videoDuration / 10)); for (let t = 0; t <= videoDuration; t += tInt) { const x = b.map(t, 0, videoDuration, pad.left, b.width - pad.right); @@ -75,39 +52,27 @@ export const speedGraphSketch = function (p) { } b.fill(textColor); b.text("Time (s)", b.width / 2, b.height - pad.bottom + 18); - // Restore previous drawing style settings. b.pop(); - // Draw CAN speed data line if available. - if (canSpeedData && canSpeedData.length > 0) { - b.noFill(); // Do not fill the shape. - b.stroke(0, 150, 255); + if (radarData && radarData.radarFrames) { + b.noFill(); + b.stroke(0, 150, 255); // Blue for CAN speed b.strokeWeight(1.5); b.beginShape(); - for (const d of canSpeedData) { - const relTime = (d.time - appState.videoStartDate.getTime()) / 1000; + for (const frame of radarData.radarFrames) { + if (frame.canVehSpeed_kmph === null || isNaN(frame.canVehSpeed_kmph)) { + continue; + } + const relTime = frame.timestampMs / 1000; if (relTime >= 0 && relTime <= videoDuration) { - const x = b.map( - relTime, - 0, - videoDuration, - pad.left, - b.width - pad.right - ); - const y = b.map( - d.speed, - minSpeed, - maxSpeed, - b.height - pad.bottom, - pad.top - ); + const x = b.map(relTime, 0, videoDuration, pad.left, b.width - pad.right); + const y = b.map(frame.canVehSpeed_kmph, minSpeed, maxSpeed, b.height - pad.bottom, pad.top); b.vertex(x, y); } } b.endShape(); - } // End of CAN speed data drawing. + } - // Draw radar ego speed data line if available. if (radarData && radarData.radarFrames) { b.stroke(0, 200, 100); b.drawingContext.setLineDash([5, 5]); @@ -115,29 +80,16 @@ export const speedGraphSketch = function (p) { for (const frame of radarData.radarFrames) { const relTime = frame.timestampMs / 1000; if (relTime >= 0 && relTime <= videoDuration) { - const x = b.map( - relTime, - 0, - videoDuration, - pad.left, - b.width - pad.right - ); + const x = b.map(relTime, 0, videoDuration, pad.left, b.width - pad.right); const egoSpeedKmh = frame.egoVelocity[1] * 3.6; - const y = b.map( - egoSpeedKmh, - minSpeed, - maxSpeed, - b.height - pad.bottom, - pad.top - ); + const y = b.map(egoSpeedKmh, minSpeed, maxSpeed, b.height - pad.bottom, pad.top); b.vertex(x, y); } } b.endShape(); - b.drawingContext.setLineDash([]); // Reset line dash to solid. - } // End of radar ego speed data drawing. + b.drawingContext.setLineDash([]); + } - // Draw legend for the graph lines. b.push(); b.strokeWeight(2); b.noStroke(); @@ -155,111 +107,88 @@ export const speedGraphSketch = function (p) { b.text("Ego Speed", b.width - 95, pad.top + 30); b.pop(); }; - /** - * p5.js setup function. Initializes the canvas and static buffer. - */ + p.setup = function () { let canvas = p.createCanvas( speedGraphContainer.offsetWidth, speedGraphContainer.offsetHeight ); canvas.parent("speed-graph-container"); - // Create an off-screen graphics buffer for static elements. staticBuffer = p.createGraphics(p.width, p.height); - // Disable continuous looping; draw will be called manually. p.noLoop(); }; - /** - * Sets the data for the speed graph and recalculates min/max speed for scaling. - * @param {Array} canSpeedData - Array of CAN speed data points. - * @param {Object} radarData - Object containing radar frames with ego velocity. - * @param {number} duration - The total duration of the video in seconds. - */ - p.setData = function (canSpeedData, radarData, duration) { - if ((!canSpeedData || canSpeedData.length === 0) && !radarData) return; // Exit if no data. + + p.setData = function (radarData, duration) { + if (!radarData || !radarData.radarFrames) return; videoDuration = duration; let speeds = []; - if (canSpeedData) { - speeds.push(...canSpeedData.map((d) => parseFloat(d.speed))); - } if (radarData && radarData.radarFrames) { const egoSpeeds = radarData.radarFrames.map( (frame) => frame.egoVelocity[1] * 3.6 ); speeds.push(...egoSpeeds); + + const canSpeeds = radarData.radarFrames + .map((frame) => frame.canVehSpeed_kmph) + .filter((speed) => speed !== null && !isNaN(speed)); + speeds.push(...canSpeeds); } - // Calculate min and max speeds for Y-axis scaling, rounding to nearest 10. - minSpeed = - speeds.length > 0 ? Math.floor(Math.min(...speeds) / 10) * 10 : 0; - maxSpeed = - speeds.length > 0 ? Math.ceil(Math.max(...speeds) / 10) * 10 : 10; - // Ensure maxSpeed is at least 10 if all speeds are non-positive. + minSpeed = speeds.length > 0 ? Math.floor(Math.min(...speeds) / 10) * 10 : 0; + maxSpeed = speeds.length > 0 ? Math.ceil(Math.max(...speeds) / 10) * 10 : 10; if (maxSpeed <= 0) maxSpeed = 10; - // Ensure minSpeed is 0 if all speeds are non-negative. if (minSpeed >= 0) minSpeed = 0; - // Redraw the static graph elements to the buffer with new data. - p.drawStaticGraphToBuffer(canSpeedData, radarData); - // Request a redraw of the main canvas. + p.drawStaticGraphToBuffer(radarData); p.redraw(); }; - /** - * p5.js draw function. Draws the static buffer and the dynamic time indicator. - */ + p.draw = function () { - if (!videoDuration) return; // Only draw if video duration is set. + if (!videoDuration) return; p.image(staticBuffer, 0, 0); drawTimeIndicator(); }; function drawTimeIndicator() { - const currentTime = videoPlayer.currentTime; + // Get the current frame's data as the single source of truth + const frameData = appState.vizData.radarFrames[appState.currentFrame]; + if (!frameData) return; // Exit if data isn't ready + + // Calculate the X position from the current frame's precise timestamp + const currentTimeSec = frameData.timestampMs / 1000.0; const x = p.map( - currentTime, + currentTimeSec, 0, videoDuration, pad.left, p.width - pad.right - ); // Map current time to X-coordinate. - // Draw the red time indicator line. + ); + + // Draw the red time indicator line at the accurate X position p.stroke(255, 0, 0, 150); p.strokeWeight(1.5); p.line(x, pad.top, x, p.height - pad.bottom); - // Draw a circle on the CAN speed line at the current time. - const videoAbsTimeMs = - appState.videoStartDate.getTime() + currentTime * 1000; - const canIndex = findLastCanIndexBefore(videoAbsTimeMs, appState.canData); - if (canIndex !== -1) { - const canMsg = appState.canData[canIndex]; - const y = p.map( - canMsg.speed, - minSpeed, - maxSpeed, - p.height - pad.bottom, - pad.top - ); - p.fill(255, 0, 0); - p.noStroke(); // No stroke for the ellipse. - p.ellipse(x, y, 8, 8); + // Now, draw the circle using the same frame data + if (frameData.canVehSpeed_kmph !== null && !isNaN(frameData.canVehSpeed_kmph)) { + const canSpeed = frameData.canVehSpeed_kmph; + const y = p.map(canSpeed, minSpeed, maxSpeed, p.height - pad.bottom, pad.top); + p.fill(255, 0, 0); + p.noStroke(); + p.ellipse(x, y, 8, 8); } - } - /** - * Handles window resizing. Resizes the canvas and recreates/redraws the static buffer. - */ +} + p.windowResized = function () { p.resizeCanvas( speedGraphContainer.offsetWidth, speedGraphContainer.offsetHeight ); - // Instead of resizing the buffer, we re-create it staticBuffer = p.createGraphics(p.width, p.height); - // And we must re-draw the static content to the new buffer - if ((appState.canData.length > 0 || appState.vizData) && videoDuration) { - p.drawStaticGraphToBuffer(appState.canData, appState.vizData); + if (appState.vizData && videoDuration) { + p.drawStaticGraphToBuffer(appState.vizData); } p.redraw(); }; -}; +}; \ No newline at end of file diff --git a/steps/src/state.js b/steps/src/state.js index bc2c5e2..f50a142 100644 --- a/steps/src/state.js +++ b/steps/src/state.js @@ -2,10 +2,6 @@ export const appState = { // Stores the parsed visualization data (radar frames, tracks, etc.) vizData: null, // Stores the processed CAN bus data (speed, time) - canData: [], - // Temporarily holds raw CAN log text if video start date is not yet available for processing - rawCanLogText: null, - // The Date object representing the start time of the video videoStartDate: null, // The timestamp (in milliseconds) of the first radar frame, extracted from the JSON filename radarStartTimeMs: 0, @@ -26,8 +22,6 @@ export const appState = { // The filename of the loaded video file videoFilename: "", // The filename of the loaded CAN log file - canLogFilename: "", - // Boolean indicating if the close-up interaction mode is active isCloseUpMode: false, // Timestamp (from performance.now()) when the master clock started for synchronized playback masterClockStart: 0, diff --git a/steps/src/sync.js b/steps/src/sync.js index cee3cde..a06a936 100644 --- a/steps/src/sync.js +++ b/steps/src/sync.js @@ -5,7 +5,6 @@ import { offsetInput, stopBtn, updateFrame, - updateCanDisplay, updateDebugOverlay, } from "./dom.js"; import { findRadarFrameIndexForTime } from "./utils.js"; @@ -64,8 +63,6 @@ export function animationLoop() { return; } - // Update CAN bus data display - updateCanDisplay(currentMediaTime); // Update debug overlay information updateDebugOverlay(currentMediaTime); // Redraw the speed graph if an instance exists diff --git a/steps/src/theme.js b/steps/src/theme.js index 1930f08..a84c80a 100644 --- a/steps/src/theme.js +++ b/steps/src/theme.js @@ -22,20 +22,15 @@ function setTheme(theme) { // Redraw the speed graph to apply theme changes if (appState.speedGraphInstance) { - // Check if there's data available to draw on the speed graph - if ( - (appState.canData.length > 0 || appState.vizData) && - videoPlayer.duration - ) { - // If data exists, redraw the static parts of the graph to a buffer - // This ensures the background and static elements reflect the new theme - appState.speedGraphInstance.drawStaticGraphToBuffer( - appState.canData, - appState.vizData + // Check if there's data available to redraw + if (appState.vizData && videoPlayer.duration) { + // Re-run setData. This is the most reliable way to redraw the graph + // with the new theme, as it recalculates and redraws everything. + appState.speedGraphInstance.setData( + appState.vizData, + videoPlayer.duration ); } - // Request a redraw of the speed graph to display the updated buffer - appState.speedGraphInstance.redraw(); } } diff --git a/steps/src/utils.js b/steps/src/utils.js index 4be0daf..5fe4f49 100644 --- a/steps/src/utils.js +++ b/steps/src/utils.js @@ -24,28 +24,7 @@ export function findRadarFrameIndexForTime(targetTimeMs, vizData) { return ans; } -export function findLastCanIndexBefore(targetTime, canData) { - // Check for empty or invalid CAN data - if (!canData || canData.length === 0) return -1; - // Initialize low, high, and answer variables for binary search - // 'ans' will store the index of the last CAN data point found before the target time - // 'low' and 'high' define the search range - let low = 0, - high = canData.length - 1, - ans = -1; // Initialize ans to -1, indicating no suitable frame found yet. - while (low <= high) { - let mid = Math.floor((low + high) / 2); - if (canData[mid].time <= targetTime) { - ans = mid; - low = mid + 1; - } else { - high = mid - 1; - } - } - // Return the index of the found CAN data point. - return ans; -} export function extractTimestampInfo(filename) { // Return null if filename is not provided