stereospace_web / js /comparison-widget.js
toshas's picture
wider component
96fc7d1
// Example hotspot configuration (percent coordinates)
const WS_HOTSPOTS = [
{ left: '25%', top: '60%' },
{ left: '60%', top: '40%' },
{ left: '75%', top: '70%' },
];
function wsRenderHotspots() {
const $layer = $('#ws-hotspots-layer');
if (!$layer.length) return;
$layer.empty();
WS_HOTSPOTS.forEach((pos) => {
const $hotspot = $('<div/>')
.addClass('ws-hotspot')
.css({
left: pos.left,
top: pos.top,
transform: 'translate(-50%, -50%)',
});
const $arrow = $('<div/>').addClass('ws-hotspot-arrow');
$hotspot.append($arrow);
$layer.append($hotspot);
});
}
// Scene-based image mapping
const WS_SCENE_IMAGES = {
215: {
target: '/images/teaser_subfigures/215/tgt.png',
stereospace: '/images/teaser_subfigures/215/stereospace.png',
lyra: '/images/teaser_subfigures/215/lyra.png',
genstereo: '/images/teaser_subfigures/215/genstereo.png',
stereodiffusion: '/images/teaser_subfigures/215/stereodiffusion.png',
zerostereo: '/images/teaser_subfigures/215/zerostereo.png',
},
249: {
target: '/images/teaser_subfigures/249/tgt.png',
stereospace: '/images/teaser_subfigures/249/stereospace.png',
lyra: '/images/teaser_subfigures/249/lyra.png',
genstereo: '/images/teaser_subfigures/249/genstereo.png',
stereodiffusion: '/images/teaser_subfigures/249/stereodiffusion.png',
zerostereo: '/images/teaser_subfigures/249/zerostereo.png',
},
252: {
target: '/images/teaser_subfigures/252/tgt.png',
stereospace: '/images/teaser_subfigures/252/stereospace.png',
lyra: '/images/teaser_subfigures/252/lyra.png',
genstereo: '/images/teaser_subfigures/252/genstereo.png',
stereodiffusion: '/images/teaser_subfigures/252/stereodiffusion.png',
zerostereo: '/images/teaser_subfigures/252/zerostereo.png',
},
285: {
target: '/images/teaser_subfigures/285/tgt.png',
stereospace: '/images/teaser_subfigures/285/stereospace.png',
lyra: '/images/teaser_subfigures/285/lyra.png',
genstereo: '/images/teaser_subfigures/285/genstereo.png',
stereodiffusion: '/images/teaser_subfigures/285/stereodiffusion.png',
zerostereo: '/images/teaser_subfigures/285/zerostereo.png',
},
297: {
target: '/images/teaser_subfigures/297/tgt.png',
stereospace: '/images/teaser_subfigures/297/stereospace.png',
lyra: '/images/teaser_subfigures/297/lyra.png',
genstereo: '/images/teaser_subfigures/297/genstereo.png',
stereodiffusion: '/images/teaser_subfigures/297/stereodiffusion.png',
zerostereo: '/images/teaser_subfigures/297/zerostereo.png',
},
};
$(document).ready(function() {
// Initialize state
let currentScene = '215';
let currentModel = 'stereospace';
let sliderInitialized = false;
// map model keys -> readable label shown on the slider
const WS_MODEL_LABELS = {
src: 'Input',
tgt: 'Ground Truth',
stereospace: 'StereoSpace (ours)',
genstereo: 'GenStereo',
lyra: 'Lyra',
stereodiffusion: 'StereoDiffusion',
zerostereo: 'ZeroStereo',
};
// preload helper
function wsPreloadImage(url) {
return new Promise((resolve, reject) => {
if (!url) return resolve();
// if already loaded by browser, resolve quickly
const cached = Array.from(document.images).find(img => img.src && img.src.endsWith(url));
const img = new Image();
img.onload = () => resolve(url);
img.onerror = () => reject(new Error('Failed to load ' + url));
img.src = url;
});
}
// Update labels on an already-initialized twentytwenty instance
function wsSetSliderLabels(beforeLabel, afterLabel) {
$('#ws-comparison-slider .twentytwenty-before-label').attr('data-content', beforeLabel);
$('#ws-comparison-slider .twentytwenty-after-label').attr('data-content', afterLabel);
const $slider = $('#ws-comparison-slider');
const $api = $slider.data('twentytwenty');
if ($api) {
$api.adjustSlider(0.5);
}
}
// Function to update images
function updateImages(scene, model) {
currentScene = scene;
currentModel = model;
const sceneData = WS_SCENE_IMAGES[scene];
if (!sceneData) return;
// Preload all images we will use, then swap and (re)initialize slider.
const leftUrl = sceneData.target;
const rightUrl = sceneData[model];
// show light loading state if you have CSS for it
$('#ws-comparison-container').addClass('ws-loading');
Promise.all([
wsPreloadImage(leftUrl),
wsPreloadImage(rightUrl),
]).then(() => {
$('#ws-image-left').attr('src', leftUrl);
$('#ws-image-right').attr('src', rightUrl);
const modelLabel = WS_MODEL_LABELS[model] || model;
setTimeout(() => {
wsSetSliderLabels('Ground Truth', modelLabel);
$('#ws-comparison-container').removeClass('ws-loading');
}, 60);
}).catch((err) => {
console.warn('Image preload failed:', err);
$('#ws-image-left').attr('src', leftUrl);
$('#ws-image-right').attr('src', rightUrl);
if (sliderInitialized) $('#ws-comparison-slider').trigger('destroy');
wsSetSliderLabels('Ground Truth', modelLabel);
$('#ws-comparison-container').removeClass('ws-loading');
});
}
// Gallery item clicks
$('.ws-gallery-item').on('click', function() {
$('.ws-gallery-item').removeClass('is-active');
$(this).addClass('is-active');
const scene = $(this).data('scene');
updateImages(scene, currentModel);
});
// Model selector clicks
$('.ws-model-pill[data-model]').on('click', function() {
$('.ws-model-pill[data-model]').removeClass('is-active');
$(this).addClass('is-active');
const model = $(this).data('model');
updateImages(currentScene, model);
});
// Initialize with default scene and model
updateImages(currentScene, currentModel);
});