From b171863a482073a3cf8bdebd3b286917011b25fa Mon Sep 17 00:00:00 2001 From: xiaoyuxi Date: Wed, 9 Jul 2025 18:41:17 +0800 Subject: [PATCH] fix_md --- _viz/viz_template.html | 339 ++++++++++++++++++++++++++++++++++++++++- viz.html | 339 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 676 insertions(+), 2 deletions(-) diff --git a/_viz/viz_template.html b/_viz/viz_template.html index 218d0df..0d083e2 100644 --- a/_viz/viz_template.html +++ b/_viz/viz_template.html @@ -671,6 +671,38 @@ +
+

Keep History

+
+ + +
+
+ + +
+
+ +
+

Background

+
+ + +
+
+
@@ -739,7 +771,10 @@ showCameraFrustum: document.getElementById('show-camera-frustum'), frustumSize: document.getElementById('frustum-size'), hideSettingsBtn: document.getElementById('hide-settings-btn'), - showSettingsBtn: document.getElementById('show-settings-btn') + showSettingsBtn: document.getElementById('show-settings-btn'), + enableKeepHistory: document.getElementById('enable-keep-history'), + historyStride: document.getElementById('history-stride'), + whiteBackground: document.getElementById('white-background') }; this.scene = null; @@ -750,6 +785,12 @@ this.trajectories = []; this.cameraFrustum = null; + // Keep History functionality + this.historyPointClouds = []; + this.historyTrajectories = []; + this.historyFrames = []; + this.maxHistoryFrames = 20; + this.initThreeJS(); this.loadDefaultSettings().then(() => { this.initEventListeners(); @@ -977,6 +1018,28 @@ this.ui.showSettingsBtn.style.display = 'none'; }); } + + // Keep History event listeners + if (this.ui.enableKeepHistory) { + this.ui.enableKeepHistory.addEventListener('change', () => { + if (!this.ui.enableKeepHistory.checked) { + this.clearHistory(); + } + }); + } + + if (this.ui.historyStride) { + this.ui.historyStride.addEventListener('change', () => { + this.clearHistory(); + }); + } + + // Background toggle event listener + if (this.ui.whiteBackground) { + this.ui.whiteBackground.addEventListener('change', () => { + this.toggleBackground(); + }); + } } makeElementDraggable(element) { @@ -1296,6 +1359,9 @@ this.updateTrajectories(frameIndex); + // Keep History management + this.updateHistory(frameIndex); + const progress = (frameIndex + 1) / this.config.totalFrames; this.ui.progress.style.width = `${progress * 100}%`; @@ -1752,15 +1818,286 @@ this.updateCameraFrustum(this.currentFrame); } + // Keep History methods + updateHistory(frameIndex) { + if (!this.ui.enableKeepHistory.checked || !this.data) return; + + const stride = parseInt(this.ui.historyStride.value); + const newHistoryFrames = this.calculateHistoryFrames(frameIndex, stride); + + // Check if history frames changed + if (this.arraysEqual(this.historyFrames, newHistoryFrames)) return; + + this.clearHistory(); + this.historyFrames = newHistoryFrames; + + // Create history point clouds and trajectories + this.historyFrames.forEach(historyFrame => { + if (historyFrame !== frameIndex) { + this.createHistoryPointCloud(historyFrame); + this.createHistoryTrajectories(historyFrame); + } + }); + } + + calculateHistoryFrames(currentFrame, stride) { + const frames = []; + let frame = 1; // Start from frame 1 + + while (frame <= currentFrame && frames.length < this.maxHistoryFrames) { + frames.push(frame); + frame += stride; + } + + // Always include current frame + if (!frames.includes(currentFrame)) { + frames.push(currentFrame); + } + + return frames.sort((a, b) => a - b); + } + + createHistoryPointCloud(frameIndex) { + const numPoints = this.config.resolution[0] * this.config.resolution[1]; + const positions = new Float32Array(numPoints * 3); + const colors = new Float32Array(numPoints * 3); + + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); + + const material = new THREE.PointsMaterial({ + size: parseFloat(this.ui.pointSize.value), + vertexColors: true, + transparent: true, + opacity: 0.5, // Transparent for history + sizeAttenuation: true + }); + + const historyPointCloud = new THREE.Points(geometry, material); + this.scene.add(historyPointCloud); + this.historyPointClouds.push(historyPointCloud); + + // Update the history point cloud with data + this.updateHistoryPointCloud(historyPointCloud, frameIndex); + } + + updateHistoryPointCloud(pointCloud, frameIndex) { + const positions = pointCloud.geometry.attributes.position.array; + const colors = pointCloud.geometry.attributes.color.array; + + const rgbVideo = this.data.rgb_video; + const depthsRgb = this.data.depths_rgb; + const intrinsics = this.data.intrinsics; + const invExtrinsics = this.data.inv_extrinsics; + + const width = this.config.resolution[0]; + const height = this.config.resolution[1]; + const numPoints = width * height; + + const K = this.get3x3Matrix(intrinsics.data, intrinsics.shape, frameIndex); + const fx = K[0][0], fy = K[1][1], cx = K[0][2], cy = K[1][2]; + + const invExtrMat = this.get4x4Matrix(invExtrinsics.data, invExtrinsics.shape, frameIndex); + const transform = this.getTransformElements(invExtrMat); + + const rgbFrame = this.getFrame(rgbVideo.data, rgbVideo.shape, frameIndex); + const depthFrame = this.getFrame(depthsRgb.data, depthsRgb.shape, frameIndex); + + const maxDepth = parseFloat(this.ui.maxDepth.value) || 10.0; + + let validPointCount = 0; + + for (let i = 0; i < numPoints; i++) { + const xPix = i % width; + const yPix = Math.floor(i / width); + + const d0 = depthFrame[i * 3]; + const d1 = depthFrame[i * 3 + 1]; + const depthEncoded = d0 | (d1 << 8); + const depthValue = (depthEncoded / ((1 << 16) - 1)) * + (this.config.depthRange[1] - this.config.depthRange[0]) + + this.config.depthRange[0]; + + if (depthValue === 0 || depthValue > maxDepth) { + continue; + } + + const X = ((xPix - cx) * depthValue) / fx; + const Y = ((yPix - cy) * depthValue) / fy; + const Z = depthValue; + + const tx = transform.m11 * X + transform.m12 * Y + transform.m13 * Z + transform.m14; + const ty = transform.m21 * X + transform.m22 * Y + transform.m23 * Z + transform.m24; + const tz = transform.m31 * X + transform.m32 * Y + transform.m33 * Z + transform.m34; + + const index = validPointCount * 3; + positions[index] = tx; + positions[index + 1] = -ty; + positions[index + 2] = -tz; + + colors[index] = rgbFrame[i * 3] / 255; + colors[index + 1] = rgbFrame[i * 3 + 1] / 255; + colors[index + 2] = rgbFrame[i * 3 + 2] / 255; + + validPointCount++; + } + + pointCloud.geometry.setDrawRange(0, validPointCount); + pointCloud.geometry.attributes.position.needsUpdate = true; + pointCloud.geometry.attributes.color.needsUpdate = true; + } + + createHistoryTrajectories(frameIndex) { + if (!this.data.trajectories) return; + + const trajectoryData = this.data.trajectories.data; + const [totalFrames, numTrajectories] = this.data.trajectories.shape; + const palette = this.createColorPalette(numTrajectories); + + const historyTrajectoryGroup = new THREE.Group(); + + for (let i = 0; i < numTrajectories; i++) { + const ballSize = parseFloat(this.ui.trajectoryBallSize.value); + const sphereGeometry = new THREE.SphereGeometry(ballSize, 16, 16); + const sphereMaterial = new THREE.MeshBasicMaterial({ + color: palette[i], + transparent: true, + opacity: 0.3 // Transparent for history + }); + const positionMarker = new THREE.Mesh(sphereGeometry, sphereMaterial); + + const currentOffset = (frameIndex * numTrajectories + i) * 3; + positionMarker.position.set( + trajectoryData[currentOffset], + -trajectoryData[currentOffset + 1], + -trajectoryData[currentOffset + 2] + ); + + historyTrajectoryGroup.add(positionMarker); + } + + this.scene.add(historyTrajectoryGroup); + this.historyTrajectories.push(historyTrajectoryGroup); + } + + clearHistory() { + // Clear history point clouds + this.historyPointClouds.forEach(pointCloud => { + if (pointCloud.geometry) pointCloud.geometry.dispose(); + if (pointCloud.material) pointCloud.material.dispose(); + this.scene.remove(pointCloud); + }); + this.historyPointClouds = []; + + // Clear history trajectories + this.historyTrajectories.forEach(trajectoryGroup => { + trajectoryGroup.children.forEach(child => { + if (child.geometry) child.geometry.dispose(); + if (child.material) child.material.dispose(); + }); + this.scene.remove(trajectoryGroup); + }); + this.historyTrajectories = []; + + this.historyFrames = []; + } + + arraysEqual(a, b) { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; + } + + toggleBackground() { + const isWhiteBackground = this.ui.whiteBackground.checked; + + if (isWhiteBackground) { + // Switch to white background + document.body.style.backgroundColor = '#ffffff'; + this.scene.background = new THREE.Color(0xffffff); + + // Update UI elements for white background + document.documentElement.style.setProperty('--bg', '#ffffff'); + document.documentElement.style.setProperty('--text', '#333333'); + document.documentElement.style.setProperty('--text-secondary', '#666666'); + document.documentElement.style.setProperty('--border', '#cccccc'); + document.documentElement.style.setProperty('--surface', '#f5f5f5'); + document.documentElement.style.setProperty('--shadow', 'rgba(0, 0, 0, 0.1)'); + document.documentElement.style.setProperty('--shadow-hover', 'rgba(0, 0, 0, 0.2)'); + + // Update status bar and control panel backgrounds + this.ui.statusBar.style.background = 'rgba(245, 245, 245, 0.9)'; + this.ui.statusBar.style.color = '#333333'; + + const controlPanel = document.getElementById('control-panel'); + if (controlPanel) { + controlPanel.style.background = 'rgba(245, 245, 245, 0.95)'; + } + + const settingsPanel = document.getElementById('settings-panel'); + if (settingsPanel) { + settingsPanel.style.background = 'rgba(245, 245, 245, 0.98)'; + } + + } else { + // Switch back to dark background + document.body.style.backgroundColor = '#1a1a1a'; + this.scene.background = new THREE.Color(0x1a1a1a); + + // Restore original dark theme variables + document.documentElement.style.setProperty('--bg', '#1a1a1a'); + document.documentElement.style.setProperty('--text', '#e0e0e0'); + document.documentElement.style.setProperty('--text-secondary', '#a0a0a0'); + document.documentElement.style.setProperty('--border', '#444444'); + document.documentElement.style.setProperty('--surface', '#2c2c2c'); + document.documentElement.style.setProperty('--shadow', 'rgba(0, 0, 0, 0.2)'); + document.documentElement.style.setProperty('--shadow-hover', 'rgba(0, 0, 0, 0.3)'); + + // Restore original UI backgrounds + this.ui.statusBar.style.background = 'rgba(30, 30, 30, 0.9)'; + this.ui.statusBar.style.color = '#e0e0e0'; + + const controlPanel = document.getElementById('control-panel'); + if (controlPanel) { + controlPanel.style.background = 'rgba(44, 44, 44, 0.95)'; + } + + const settingsPanel = document.getElementById('settings-panel'); + if (settingsPanel) { + settingsPanel.style.background = 'rgba(44, 44, 44, 0.98)'; + } + } + + // Show status message + this.ui.statusBar.textContent = isWhiteBackground ? "Switched to white background" : "Switched to dark background"; + this.ui.statusBar.classList.remove('hidden'); + + setTimeout(() => { + this.ui.statusBar.classList.add('hidden'); + }, 2000); + } + resetSettings() { if (!this.defaultSettings) return; this.applyDefaultSettings(); + // Reset background to dark theme + if (this.ui.whiteBackground) { + this.ui.whiteBackground.checked = false; + this.toggleBackground(); + } + this.updatePointCloudSettings(); this.updateTrajectorySettings(); this.updateFrustumDimensions(); + // Clear history when resetting settings + this.clearHistory(); + this.ui.statusBar.textContent = "Settings reset to defaults"; this.ui.statusBar.classList.remove('hidden'); diff --git a/viz.html b/viz.html index 218d0df..0d083e2 100644 --- a/viz.html +++ b/viz.html @@ -671,6 +671,38 @@
+
+

Keep History

+
+ + +
+
+ + +
+
+ +
+

Background

+
+ + +
+
+
@@ -739,7 +771,10 @@ showCameraFrustum: document.getElementById('show-camera-frustum'), frustumSize: document.getElementById('frustum-size'), hideSettingsBtn: document.getElementById('hide-settings-btn'), - showSettingsBtn: document.getElementById('show-settings-btn') + showSettingsBtn: document.getElementById('show-settings-btn'), + enableKeepHistory: document.getElementById('enable-keep-history'), + historyStride: document.getElementById('history-stride'), + whiteBackground: document.getElementById('white-background') }; this.scene = null; @@ -750,6 +785,12 @@ this.trajectories = []; this.cameraFrustum = null; + // Keep History functionality + this.historyPointClouds = []; + this.historyTrajectories = []; + this.historyFrames = []; + this.maxHistoryFrames = 20; + this.initThreeJS(); this.loadDefaultSettings().then(() => { this.initEventListeners(); @@ -977,6 +1018,28 @@ this.ui.showSettingsBtn.style.display = 'none'; }); } + + // Keep History event listeners + if (this.ui.enableKeepHistory) { + this.ui.enableKeepHistory.addEventListener('change', () => { + if (!this.ui.enableKeepHistory.checked) { + this.clearHistory(); + } + }); + } + + if (this.ui.historyStride) { + this.ui.historyStride.addEventListener('change', () => { + this.clearHistory(); + }); + } + + // Background toggle event listener + if (this.ui.whiteBackground) { + this.ui.whiteBackground.addEventListener('change', () => { + this.toggleBackground(); + }); + } } makeElementDraggable(element) { @@ -1296,6 +1359,9 @@ this.updateTrajectories(frameIndex); + // Keep History management + this.updateHistory(frameIndex); + const progress = (frameIndex + 1) / this.config.totalFrames; this.ui.progress.style.width = `${progress * 100}%`; @@ -1752,15 +1818,286 @@ this.updateCameraFrustum(this.currentFrame); } + // Keep History methods + updateHistory(frameIndex) { + if (!this.ui.enableKeepHistory.checked || !this.data) return; + + const stride = parseInt(this.ui.historyStride.value); + const newHistoryFrames = this.calculateHistoryFrames(frameIndex, stride); + + // Check if history frames changed + if (this.arraysEqual(this.historyFrames, newHistoryFrames)) return; + + this.clearHistory(); + this.historyFrames = newHistoryFrames; + + // Create history point clouds and trajectories + this.historyFrames.forEach(historyFrame => { + if (historyFrame !== frameIndex) { + this.createHistoryPointCloud(historyFrame); + this.createHistoryTrajectories(historyFrame); + } + }); + } + + calculateHistoryFrames(currentFrame, stride) { + const frames = []; + let frame = 1; // Start from frame 1 + + while (frame <= currentFrame && frames.length < this.maxHistoryFrames) { + frames.push(frame); + frame += stride; + } + + // Always include current frame + if (!frames.includes(currentFrame)) { + frames.push(currentFrame); + } + + return frames.sort((a, b) => a - b); + } + + createHistoryPointCloud(frameIndex) { + const numPoints = this.config.resolution[0] * this.config.resolution[1]; + const positions = new Float32Array(numPoints * 3); + const colors = new Float32Array(numPoints * 3); + + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); + + const material = new THREE.PointsMaterial({ + size: parseFloat(this.ui.pointSize.value), + vertexColors: true, + transparent: true, + opacity: 0.5, // Transparent for history + sizeAttenuation: true + }); + + const historyPointCloud = new THREE.Points(geometry, material); + this.scene.add(historyPointCloud); + this.historyPointClouds.push(historyPointCloud); + + // Update the history point cloud with data + this.updateHistoryPointCloud(historyPointCloud, frameIndex); + } + + updateHistoryPointCloud(pointCloud, frameIndex) { + const positions = pointCloud.geometry.attributes.position.array; + const colors = pointCloud.geometry.attributes.color.array; + + const rgbVideo = this.data.rgb_video; + const depthsRgb = this.data.depths_rgb; + const intrinsics = this.data.intrinsics; + const invExtrinsics = this.data.inv_extrinsics; + + const width = this.config.resolution[0]; + const height = this.config.resolution[1]; + const numPoints = width * height; + + const K = this.get3x3Matrix(intrinsics.data, intrinsics.shape, frameIndex); + const fx = K[0][0], fy = K[1][1], cx = K[0][2], cy = K[1][2]; + + const invExtrMat = this.get4x4Matrix(invExtrinsics.data, invExtrinsics.shape, frameIndex); + const transform = this.getTransformElements(invExtrMat); + + const rgbFrame = this.getFrame(rgbVideo.data, rgbVideo.shape, frameIndex); + const depthFrame = this.getFrame(depthsRgb.data, depthsRgb.shape, frameIndex); + + const maxDepth = parseFloat(this.ui.maxDepth.value) || 10.0; + + let validPointCount = 0; + + for (let i = 0; i < numPoints; i++) { + const xPix = i % width; + const yPix = Math.floor(i / width); + + const d0 = depthFrame[i * 3]; + const d1 = depthFrame[i * 3 + 1]; + const depthEncoded = d0 | (d1 << 8); + const depthValue = (depthEncoded / ((1 << 16) - 1)) * + (this.config.depthRange[1] - this.config.depthRange[0]) + + this.config.depthRange[0]; + + if (depthValue === 0 || depthValue > maxDepth) { + continue; + } + + const X = ((xPix - cx) * depthValue) / fx; + const Y = ((yPix - cy) * depthValue) / fy; + const Z = depthValue; + + const tx = transform.m11 * X + transform.m12 * Y + transform.m13 * Z + transform.m14; + const ty = transform.m21 * X + transform.m22 * Y + transform.m23 * Z + transform.m24; + const tz = transform.m31 * X + transform.m32 * Y + transform.m33 * Z + transform.m34; + + const index = validPointCount * 3; + positions[index] = tx; + positions[index + 1] = -ty; + positions[index + 2] = -tz; + + colors[index] = rgbFrame[i * 3] / 255; + colors[index + 1] = rgbFrame[i * 3 + 1] / 255; + colors[index + 2] = rgbFrame[i * 3 + 2] / 255; + + validPointCount++; + } + + pointCloud.geometry.setDrawRange(0, validPointCount); + pointCloud.geometry.attributes.position.needsUpdate = true; + pointCloud.geometry.attributes.color.needsUpdate = true; + } + + createHistoryTrajectories(frameIndex) { + if (!this.data.trajectories) return; + + const trajectoryData = this.data.trajectories.data; + const [totalFrames, numTrajectories] = this.data.trajectories.shape; + const palette = this.createColorPalette(numTrajectories); + + const historyTrajectoryGroup = new THREE.Group(); + + for (let i = 0; i < numTrajectories; i++) { + const ballSize = parseFloat(this.ui.trajectoryBallSize.value); + const sphereGeometry = new THREE.SphereGeometry(ballSize, 16, 16); + const sphereMaterial = new THREE.MeshBasicMaterial({ + color: palette[i], + transparent: true, + opacity: 0.3 // Transparent for history + }); + const positionMarker = new THREE.Mesh(sphereGeometry, sphereMaterial); + + const currentOffset = (frameIndex * numTrajectories + i) * 3; + positionMarker.position.set( + trajectoryData[currentOffset], + -trajectoryData[currentOffset + 1], + -trajectoryData[currentOffset + 2] + ); + + historyTrajectoryGroup.add(positionMarker); + } + + this.scene.add(historyTrajectoryGroup); + this.historyTrajectories.push(historyTrajectoryGroup); + } + + clearHistory() { + // Clear history point clouds + this.historyPointClouds.forEach(pointCloud => { + if (pointCloud.geometry) pointCloud.geometry.dispose(); + if (pointCloud.material) pointCloud.material.dispose(); + this.scene.remove(pointCloud); + }); + this.historyPointClouds = []; + + // Clear history trajectories + this.historyTrajectories.forEach(trajectoryGroup => { + trajectoryGroup.children.forEach(child => { + if (child.geometry) child.geometry.dispose(); + if (child.material) child.material.dispose(); + }); + this.scene.remove(trajectoryGroup); + }); + this.historyTrajectories = []; + + this.historyFrames = []; + } + + arraysEqual(a, b) { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; + } + + toggleBackground() { + const isWhiteBackground = this.ui.whiteBackground.checked; + + if (isWhiteBackground) { + // Switch to white background + document.body.style.backgroundColor = '#ffffff'; + this.scene.background = new THREE.Color(0xffffff); + + // Update UI elements for white background + document.documentElement.style.setProperty('--bg', '#ffffff'); + document.documentElement.style.setProperty('--text', '#333333'); + document.documentElement.style.setProperty('--text-secondary', '#666666'); + document.documentElement.style.setProperty('--border', '#cccccc'); + document.documentElement.style.setProperty('--surface', '#f5f5f5'); + document.documentElement.style.setProperty('--shadow', 'rgba(0, 0, 0, 0.1)'); + document.documentElement.style.setProperty('--shadow-hover', 'rgba(0, 0, 0, 0.2)'); + + // Update status bar and control panel backgrounds + this.ui.statusBar.style.background = 'rgba(245, 245, 245, 0.9)'; + this.ui.statusBar.style.color = '#333333'; + + const controlPanel = document.getElementById('control-panel'); + if (controlPanel) { + controlPanel.style.background = 'rgba(245, 245, 245, 0.95)'; + } + + const settingsPanel = document.getElementById('settings-panel'); + if (settingsPanel) { + settingsPanel.style.background = 'rgba(245, 245, 245, 0.98)'; + } + + } else { + // Switch back to dark background + document.body.style.backgroundColor = '#1a1a1a'; + this.scene.background = new THREE.Color(0x1a1a1a); + + // Restore original dark theme variables + document.documentElement.style.setProperty('--bg', '#1a1a1a'); + document.documentElement.style.setProperty('--text', '#e0e0e0'); + document.documentElement.style.setProperty('--text-secondary', '#a0a0a0'); + document.documentElement.style.setProperty('--border', '#444444'); + document.documentElement.style.setProperty('--surface', '#2c2c2c'); + document.documentElement.style.setProperty('--shadow', 'rgba(0, 0, 0, 0.2)'); + document.documentElement.style.setProperty('--shadow-hover', 'rgba(0, 0, 0, 0.3)'); + + // Restore original UI backgrounds + this.ui.statusBar.style.background = 'rgba(30, 30, 30, 0.9)'; + this.ui.statusBar.style.color = '#e0e0e0'; + + const controlPanel = document.getElementById('control-panel'); + if (controlPanel) { + controlPanel.style.background = 'rgba(44, 44, 44, 0.95)'; + } + + const settingsPanel = document.getElementById('settings-panel'); + if (settingsPanel) { + settingsPanel.style.background = 'rgba(44, 44, 44, 0.98)'; + } + } + + // Show status message + this.ui.statusBar.textContent = isWhiteBackground ? "Switched to white background" : "Switched to dark background"; + this.ui.statusBar.classList.remove('hidden'); + + setTimeout(() => { + this.ui.statusBar.classList.add('hidden'); + }, 2000); + } + resetSettings() { if (!this.defaultSettings) return; this.applyDefaultSettings(); + // Reset background to dark theme + if (this.ui.whiteBackground) { + this.ui.whiteBackground.checked = false; + this.toggleBackground(); + } + this.updatePointCloudSettings(); this.updateTrajectorySettings(); this.updateFrustumDimensions(); + // Clear history when resetting settings + this.clearHistory(); + this.ui.statusBar.textContent = "Settings reset to defaults"; this.ui.statusBar.classList.remove('hidden');