|
72 | 72 | return imagesManifest; |
73 | 73 | } |
74 | 74 |
|
| 75 | + const corruptionsPerUseCase = { |
| 76 | + 'AutonomousDriving': ['Brightness','Contrast','ImageFlip','GaussianNoise','GlobalColourShift','ImageRotation','MotionBlur','PerspectiveTransformation','Rain','Shadow'], |
| 77 | + 'Handheld': ['Brightness','Contrast','ImageFlip','GaussianNoise','GlobalColourShift','GridElasticDeformation','ImageRotation','MotionBlur','PerspectiveTransformation','Shadow'], |
| 78 | + 'ManufacturingQuality': ['Brightness','Contrast','ImageFlip','GaussianBlur','GaussianNoise','GridDistortion','GridElasticDeformation','ImageRotation','MotionBlur','PerspectiveTransformation','SaltPepperNoise','Shadow'], |
| 79 | + 'MedicalDiagnosis': ['Brightness','Contrast','ImageFlip','GaussianBlur','GaussianNoise','GridElasticDeformation','ImageRotation','MotionBlur','PerspectiveTransformation','SaltPepperNoise'], |
| 80 | + 'PeopleRecognition': ['Brightness','Contrast','ImageFlip','GaussianNoise','GridElasticDeformation','ImageRotation','MotionBlur','PerspectiveTransformation','SaltPepperNoise','Shadow'], |
| 81 | + 'SatelliteImaging': ['Brightness','CloudGenerator','Contrast','ImageFlip','GaussianNoise','ImageRotation','MotionBlur','PerspectiveTransformation','Shadow'] |
| 82 | + }; |
| 83 | + |
75 | 84 | const plotCorruptionNameMap = { |
76 | 85 | 'ImageFlip': 'Flip' |
77 | 86 | }; |
|
191 | 200 |
|
192 | 201 | function buildCorruptionGallery() { |
193 | 202 | topThumbnailContainer.innerHTML = ''; |
194 | | - // Build sequentially; createThumbnail is async to read manifest safely |
195 | 203 | (async () => { |
196 | 204 | for (const corr of corruptions) { |
197 | 205 | const thumb = await createThumbnail(corr, () => { |
198 | 206 | currentCorruption = corr; |
199 | 207 | updateLargeImageView(); |
200 | | - document.querySelectorAll('.experiment').forEach(expDiv => { |
201 | | - const ev = new Event('corruptionChanged'); |
202 | | - expDiv.dispatchEvent(ev); |
203 | | - }); |
204 | 208 | }); |
205 | 209 | topThumbnailContainer.appendChild(thumb); |
206 | 210 | } |
|
217 | 221 | thumbContainer.classList.add('thumbnail-container'); |
218 | 222 | container.appendChild(thumbContainer); |
219 | 223 |
|
220 | | - // Options (filter toggle) |
| 224 | + // Options (context label only) |
221 | 225 | const plotOptions = document.createElement('div'); |
222 | 226 | plotOptions.className = 'plot-options'; |
223 | | - // For correlation experiment, there is no per-corruption toggle |
224 | | - plotOptions.innerHTML = (expKey === 'correlation') |
225 | | - ? `<span id="${expKey}-plot-context-label" class="plot-context-label"></span>` |
226 | | - : ` |
227 | | - <label><input type="checkbox" id="${expKey}-filter-by-corruption"> Show only selected corruption</label> |
228 | | - <span id="${expKey}-plot-context-label" class="plot-context-label"></span> |
229 | | - `; |
| 227 | + plotOptions.innerHTML = `<span id="${expKey}-plot-context-label" class="plot-context-label"></span>`; |
230 | 228 | container.appendChild(plotOptions); |
231 | 229 |
|
232 | 230 | // Plots |
|
254 | 252 | } |
255 | 253 | container.appendChild(plots); |
256 | 254 |
|
| 255 | + // Local corruption thumbnail bar (skip for correlation) |
| 256 | + if (expKey !== 'correlation') { |
| 257 | + const corrBar = document.createElement('div'); |
| 258 | + corrBar.id = `${expKey}-corruption-bar`; |
| 259 | + corrBar.classList.add('thumbnail-container', 'corruption-bar-local'); |
| 260 | + container.appendChild(corrBar); |
| 261 | + } |
| 262 | + |
257 | 263 | initializeSharedExperimentLogic(expKey, basePath); |
258 | 264 | } |
259 | 265 |
|
|
262 | 268 | const accImg = document.getElementById(`${expKey}-acc-plot`); |
263 | 269 | const flipImg = document.getElementById(`${expKey}-flip-plot`); |
264 | 270 | const corrImg = document.getElementById(`${expKey}-corr-plot`); |
265 | | - const filterToggle = document.getElementById(`${expKey}-filter-by-corruption`); |
266 | 271 | const plotContextLabelLocal = document.getElementById(`${expKey}-plot-context-label`); |
267 | 272 | const plotsContainerLocal = document.getElementById(`${expKey}-plots-container`); |
268 | 273 | const secondPlotTitleEl = document.getElementById(`${expKey}-second-plot-title`); |
| 274 | + const corruptionBar = document.getElementById(`${expKey}-corruption-bar`); |
269 | 275 |
|
270 | 276 | // Determine which use cases to show for this experiment |
271 | 277 | const ucList = (expKey === 'specialists') |
272 | 278 | ? ['MedicalDiagnosis', 'SatelliteImaging'] |
273 | 279 | : useCases; |
274 | 280 |
|
275 | 281 | let currentUC = ucList[0]; |
| 282 | + let selectedCorruption = null; // null = combined view |
276 | 283 |
|
277 | 284 | // build thumbnails for use cases |
278 | 285 | ucList.forEach(uc => { |
279 | 286 | const thumb = createUseCaseThumbnail(uc, () => { |
280 | 287 | currentUC = uc; |
| 288 | + selectedCorruption = null; |
| 289 | + updateCorruptionThumbnails(); |
281 | 290 | updatePlots(); |
282 | 291 | }); |
283 | 292 | thumbs.appendChild(thumb); |
284 | 293 | }); |
285 | 294 |
|
286 | | - // Restore persisted toggle if any (non-correlation only) |
287 | | - if (filterToggle) { |
288 | | - const saved = localStorage.getItem(`${expKey}_filterByCorruption`); |
289 | | - if (saved === '1' || saved === '0') filterToggle.checked = saved === '1'; |
290 | | - filterToggle.addEventListener('change', () => { |
291 | | - localStorage.setItem(`${expKey}_filterByCorruption`, filterToggle.checked ? '1' : '0'); |
292 | | - updatePlots(); |
| 295 | + function updateUseCaseSelection() { |
| 296 | + thumbs.querySelectorAll('.thumbnail').forEach(t => { |
| 297 | + t.classList.toggle('active', t.dataset.usecase === currentUC); |
293 | 298 | }); |
294 | 299 | } |
295 | 300 |
|
296 | | - // react when global corruption changes (listen only on own experiment div) |
297 | | - const ownExpDiv = document.querySelector(`.experiment[data-exp="${expKey}"]`); |
298 | | - if (ownExpDiv) { |
299 | | - ownExpDiv.addEventListener('corruptionChanged', () => { updatePlots(); }); |
| 301 | + function updateCorruptionThumbnails() { |
| 302 | + if (!corruptionBar) return; |
| 303 | + corruptionBar.innerHTML = ''; |
| 304 | + const availableCorruptions = corruptionsPerUseCase[currentUC] || []; |
| 305 | + (async () => { |
| 306 | + for (const corr of availableCorruptions) { |
| 307 | + const thumb = await createThumbnail(corr, () => { |
| 308 | + if (selectedCorruption === corr) { |
| 309 | + selectedCorruption = null; |
| 310 | + } else { |
| 311 | + selectedCorruption = corr; |
| 312 | + } |
| 313 | + updateCorruptionSelection(); |
| 314 | + updatePlots(); |
| 315 | + }); |
| 316 | + thumb.classList.add('corruption-thumb-small'); |
| 317 | + corruptionBar.appendChild(thumb); |
| 318 | + } |
| 319 | + updateCorruptionSelection(); |
| 320 | + })(); |
300 | 321 | } |
301 | 322 |
|
302 | | - function updateUseCaseSelection() { |
303 | | - thumbs.querySelectorAll('.thumbnail').forEach(t => { |
304 | | - t.classList.toggle('active', t.dataset.usecase === currentUC); |
| 323 | + function updateCorruptionSelection() { |
| 324 | + if (!corruptionBar) return; |
| 325 | + corruptionBar.querySelectorAll('.thumbnail').forEach(t => { |
| 326 | + t.classList.toggle('active', t.dataset.corruption === selectedCorruption); |
305 | 327 | }); |
306 | 328 | } |
307 | 329 |
|
308 | 330 | function updatePlots() { |
309 | | - const corr = currentCorruption; |
310 | | - const corrForFile = plotCorruptionNameMap[corr] || corr; |
| 331 | + updateUseCaseSelection(); |
311 | 332 |
|
312 | 333 | if (expKey === 'correlation') { |
313 | | - // Correlation plot lives under main experiment assets path |
314 | 334 | const corrPath = `assets/experiments/main/correlation_${currentUC}.png`; |
315 | 335 | if (corrImg) { |
316 | 336 | corrImg.onerror = null; |
|
321 | 341 | return; |
322 | 342 | } |
323 | 343 |
|
| 344 | + const corr = selectedCorruption; |
| 345 | + const corrForFile = corr ? (plotCorruptionNameMap[corr] || corr) : null; |
| 346 | + |
324 | 347 | // Configure second plot depending on experiment |
325 | 348 | const secondPrefix = (expKey === 'pretraining') ? 'mce' : 'flip'; |
326 | 349 | const secondTitle = (expKey === 'pretraining') ? 'Mean Corruption Error (MCE)' : 'Label Flip Probability'; |
327 | 350 | if (secondPlotTitleEl) secondPlotTitleEl.textContent = secondTitle; |
328 | 351 |
|
329 | 352 | const combinedAcc = `${basePath}/acc_${currentUC}.png`; |
330 | | - const perCorrAcc = `${basePath}/acc_${currentUC}_${corrForFile}.png`; |
| 353 | + const perCorrAcc = corrForFile ? `${basePath}/acc_${currentUC}_${corrForFile}.png` : null; |
331 | 354 |
|
332 | 355 | const combinedSecond = `${basePath}/${secondPrefix}_${currentUC}.png`; |
333 | | - const perCorrSecond = `${basePath}/${secondPrefix}_${currentUC}_${corrForFile}.png`; |
| 356 | + const perCorrSecond = corrForFile ? `${basePath}/${secondPrefix}_${currentUC}_${corrForFile}.png` : null; |
334 | 357 |
|
335 | 358 | // Optional alternate prefix fallback (e.g., use flip when mce missing) |
336 | 359 | const altSecondPrefix = (secondPrefix === 'mce') ? 'flip' : null; |
337 | 360 | const combinedSecondAlt = altSecondPrefix ? `${basePath}/${altSecondPrefix}_${currentUC}.png` : null; |
338 | | - const perCorrSecondAlt = altSecondPrefix ? `${basePath}/${altSecondPrefix}_${currentUC}_${corrForFile}.png` : null; |
| 361 | + const perCorrSecondAlt = (altSecondPrefix && corrForFile) ? `${basePath}/${altSecondPrefix}_${currentUC}_${corrForFile}.png` : null; |
339 | 362 |
|
340 | | - const showPerCorruption = !!(filterToggle && filterToggle.checked); |
| 363 | + const showPerCorruption = selectedCorruption !== null; |
341 | 364 | let anyFallbackUsed = false; |
342 | 365 |
|
343 | 366 | // Toggle side-by-side layout when showing per-corruption |
|
413 | 436 | accImg.alt = `Balanced accuracy for ${humanizeCamelCase(currentUC)} (combined)`; |
414 | 437 |
|
415 | 438 | if (expKey === 'pretraining') { |
416 | | - // Try combined MCE then combined flip as fallback |
417 | 439 | const sources = [combinedSecond]; |
418 | 440 | const alts = [`${secondTitle} for ${humanizeCamelCase(currentUC)} (combined)`]; |
419 | 441 | if (combinedSecondAlt) { |
|
435 | 457 | if (!plotContextLabelLocal) return; |
436 | 458 | if (showPerCorruption) { |
437 | 459 | if (usedFallback) { |
438 | | - plotContextLabelLocal.textContent = `Per-corruption plot not available for ${humanizeCamelCase(currentCorruption)} in ${humanizeCamelCase(currentUC)} — showing combined.`; |
| 460 | + plotContextLabelLocal.textContent = `Per-corruption plot not available for ${humanizeCamelCase(selectedCorruption)} in ${humanizeCamelCase(currentUC)} — showing combined.`; |
439 | 461 | } else { |
440 | | - plotContextLabelLocal.textContent = `Showing: ${humanizeCamelCase(currentUC)} — ${humanizeCamelCase(currentCorruption)}`; |
| 462 | + plotContextLabelLocal.textContent = `Showing: ${humanizeCamelCase(currentUC)} — ${humanizeCamelCase(selectedCorruption)}`; |
441 | 463 | } |
442 | 464 | } else { |
443 | 465 | plotContextLabelLocal.textContent = ''; |
|
446 | 468 |
|
447 | 469 | // initial |
448 | 470 | updateUseCaseSelection(); |
| 471 | + updateCorruptionThumbnails(); |
449 | 472 | updatePlots(); |
450 | 473 | } |
451 | 474 |
|
|
473 | 496 | createExperimentSection({ expKey, container: expDiv, basePath }); |
474 | 497 | }); |
475 | 498 |
|
476 | | - // Activate sticky thumbnails spanning all experiments |
477 | | - setupStickyThumbnailsAcrossExperiments(); |
| 499 | + // Header shrink on scroll |
| 500 | + setupHeaderScrollBehavior(); |
478 | 501 |
|
479 | 502 | // Toggle Coming Soon via URL or global flag |
480 | 503 | setupComingSoonAutoToggle(); |
@@ -868,85 +891,18 @@ document.addEventListener('DOMContentLoaded', () => { |
868 | 891 | renderSources(); |
869 | 892 | initialize(); |
870 | 893 | }); |
871 | | -function setupStickyThumbnailsAcrossExperiments() { |
872 | | - const startEl = document.getElementById('corruptions-gallery'); |
873 | | - const endEl = document.getElementById('experiments-correlation'); |
874 | | - const bar = topThumbnailContainer; |
875 | | - const mainEl = document.querySelector('main'); |
| 894 | +function setupHeaderScrollBehavior() { |
876 | 895 | const headerEl = document.querySelector('header'); |
877 | | - if (!startEl || !endEl || !bar || !mainEl) return; |
878 | | - |
879 | | - let spacer = null; |
880 | | - function addSpacer() { |
881 | | - if (!spacer) { |
882 | | - spacer = document.createElement('div'); |
883 | | - spacer.style.width = '100%'; |
884 | | - bar.parentNode.insertBefore(spacer, bar); |
885 | | - } |
886 | | - spacer.style.height = `${bar.offsetHeight}px`; |
887 | | - } |
888 | | - function removeSpacer() { |
889 | | - if (spacer && spacer.parentNode) spacer.parentNode.removeChild(spacer); |
890 | | - spacer = null; |
891 | | - } |
892 | | - function updateBarWidthPosition() { |
893 | | - const mainRect = mainEl.getBoundingClientRect(); |
894 | | - bar.style.width = `${mainRect.width}px`; |
895 | | - bar.style.left = `${mainRect.left}px`; |
896 | | - bar.style.transform = 'none'; |
897 | | - // Offset below header height |
898 | | - const h = headerEl ? headerEl.getBoundingClientRect().height : 0; |
899 | | - bar.style.top = `${Math.max(0, h)}px`; |
900 | | - } |
901 | | - function updateScrollOffsetsVars(isStuck) { |
902 | | - const h = headerEl ? headerEl.getBoundingClientRect().height : 0; |
903 | | - document.documentElement.style.setProperty('--header-offset', `${Math.round(h)}px`); |
904 | | - const tb = isStuck ? bar.offsetHeight : 0; |
905 | | - document.documentElement.style.setProperty('--thumbbar-offset', `${Math.round(tb)}px`); |
906 | | - } |
| 896 | + if (!headerEl) return; |
907 | 897 |
|
908 | 898 | function onScroll() { |
909 | | - // Toggle header shrink state |
910 | | - if (headerEl) { |
911 | | - if (window.scrollY > 20) headerEl.classList.add('scrolled'); |
912 | | - else headerEl.classList.remove('scrolled'); |
913 | | - } |
914 | | - const startRect = startEl.getBoundingClientRect(); |
915 | | - const endRect = endEl.getBoundingClientRect(); |
916 | | - const shouldStick = startRect.top <= 0 && endRect.bottom > 0; |
917 | | - if (shouldStick) { |
918 | | - if (!bar.classList.contains('is-stuck')) { |
919 | | - addSpacer(); |
920 | | - bar.classList.add('is-stuck'); |
921 | | - bar.style.position = 'fixed'; |
922 | | - updateBarWidthPosition(); |
923 | | - } else { |
924 | | - // keep spacer height updated if images/layout changed |
925 | | - if (spacer) spacer.style.height = `${bar.offsetHeight}px`; |
926 | | - updateBarWidthPosition(); |
927 | | - } |
928 | | - } else { |
929 | | - if (bar.classList.contains('is-stuck')) { |
930 | | - bar.classList.remove('is-stuck'); |
931 | | - bar.style.position = ''; |
932 | | - bar.style.width = ''; |
933 | | - bar.style.left = ''; |
934 | | - bar.style.transform = ''; |
935 | | - bar.style.top = ''; |
936 | | - removeSpacer(); |
937 | | - } |
938 | | - } |
939 | | - updateScrollOffsetsVars(bar.classList.contains('is-stuck')); |
| 899 | + if (window.scrollY > 20) headerEl.classList.add('scrolled'); |
| 900 | + else headerEl.classList.remove('scrolled'); |
| 901 | + const h = headerEl.getBoundingClientRect().height; |
| 902 | + document.documentElement.style.setProperty('--header-offset', `${Math.round(h)}px`); |
940 | 903 | } |
941 | | - const onResize = () => { |
942 | | - if (bar.classList.contains('is-stuck')) updateBarWidthPosition(); |
943 | | - updateScrollOffsetsVars(bar.classList.contains('is-stuck')); |
944 | | - onScroll(); |
945 | | - }; |
946 | 904 |
|
947 | 905 | window.addEventListener('scroll', onScroll, { passive: true }); |
948 | | - window.addEventListener('resize', onResize); |
949 | | - // initial |
| 906 | + window.addEventListener('resize', () => onScroll()); |
950 | 907 | onScroll(); |
951 | | - updateScrollOffsetsVars(false); |
952 | 908 | } |
0 commit comments