Skip to content

Commit 75833b1

Browse files
feat: implement local corruption thumbnail bar and update corruption handling logic for experiments
1 parent 9edc8cf commit 75833b1

File tree

2 files changed

+79
-122
lines changed

2 files changed

+79
-122
lines changed

css/style.css

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -442,12 +442,13 @@ body.coming-soon-active #coming-soon-overlay .coming-soon-content {
442442
display: none !important;
443443
}
444444

445-
/* Sticky thumbnail bar when scrolling through experiments */
446-
.thumbnail-container.is-stuck {
447-
position: fixed;
448-
z-index: 1050;
449-
background: var(--header-bg);
450-
box-shadow: 0 2px 6px var(--shadow-color);
451-
padding: 0.5rem 1rem;
452-
border-radius: 0 0 8px 8px;
445+
/* Per-experiment local corruption thumbnail bar */
446+
.corruption-bar-local {
447+
gap: 0.5rem;
448+
margin: 0.75rem 0 0.25rem 0;
449+
}
450+
451+
.corruption-thumb-small {
452+
width: 40px;
453+
height: 40px;
453454
}

js/main.js

Lines changed: 70 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@
7272
return imagesManifest;
7373
}
7474

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+
7584
const plotCorruptionNameMap = {
7685
'ImageFlip': 'Flip'
7786
};
@@ -191,16 +200,11 @@
191200

192201
function buildCorruptionGallery() {
193202
topThumbnailContainer.innerHTML = '';
194-
// Build sequentially; createThumbnail is async to read manifest safely
195203
(async () => {
196204
for (const corr of corruptions) {
197205
const thumb = await createThumbnail(corr, () => {
198206
currentCorruption = corr;
199207
updateLargeImageView();
200-
document.querySelectorAll('.experiment').forEach(expDiv => {
201-
const ev = new Event('corruptionChanged');
202-
expDiv.dispatchEvent(ev);
203-
});
204208
});
205209
topThumbnailContainer.appendChild(thumb);
206210
}
@@ -217,16 +221,10 @@
217221
thumbContainer.classList.add('thumbnail-container');
218222
container.appendChild(thumbContainer);
219223

220-
// Options (filter toggle)
224+
// Options (context label only)
221225
const plotOptions = document.createElement('div');
222226
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>`;
230228
container.appendChild(plotOptions);
231229

232230
// Plots
@@ -254,6 +252,14 @@
254252
}
255253
container.appendChild(plots);
256254

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+
257263
initializeSharedExperimentLogic(expKey, basePath);
258264
}
259265

@@ -262,55 +268,69 @@
262268
const accImg = document.getElementById(`${expKey}-acc-plot`);
263269
const flipImg = document.getElementById(`${expKey}-flip-plot`);
264270
const corrImg = document.getElementById(`${expKey}-corr-plot`);
265-
const filterToggle = document.getElementById(`${expKey}-filter-by-corruption`);
266271
const plotContextLabelLocal = document.getElementById(`${expKey}-plot-context-label`);
267272
const plotsContainerLocal = document.getElementById(`${expKey}-plots-container`);
268273
const secondPlotTitleEl = document.getElementById(`${expKey}-second-plot-title`);
274+
const corruptionBar = document.getElementById(`${expKey}-corruption-bar`);
269275

270276
// Determine which use cases to show for this experiment
271277
const ucList = (expKey === 'specialists')
272278
? ['MedicalDiagnosis', 'SatelliteImaging']
273279
: useCases;
274280

275281
let currentUC = ucList[0];
282+
let selectedCorruption = null; // null = combined view
276283

277284
// build thumbnails for use cases
278285
ucList.forEach(uc => {
279286
const thumb = createUseCaseThumbnail(uc, () => {
280287
currentUC = uc;
288+
selectedCorruption = null;
289+
updateCorruptionThumbnails();
281290
updatePlots();
282291
});
283292
thumbs.appendChild(thumb);
284293
});
285294

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);
293298
});
294299
}
295300

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+
})();
300321
}
301322

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);
305327
});
306328
}
307329

308330
function updatePlots() {
309-
const corr = currentCorruption;
310-
const corrForFile = plotCorruptionNameMap[corr] || corr;
331+
updateUseCaseSelection();
311332

312333
if (expKey === 'correlation') {
313-
// Correlation plot lives under main experiment assets path
314334
const corrPath = `assets/experiments/main/correlation_${currentUC}.png`;
315335
if (corrImg) {
316336
corrImg.onerror = null;
@@ -321,23 +341,26 @@
321341
return;
322342
}
323343

344+
const corr = selectedCorruption;
345+
const corrForFile = corr ? (plotCorruptionNameMap[corr] || corr) : null;
346+
324347
// Configure second plot depending on experiment
325348
const secondPrefix = (expKey === 'pretraining') ? 'mce' : 'flip';
326349
const secondTitle = (expKey === 'pretraining') ? 'Mean Corruption Error (MCE)' : 'Label Flip Probability';
327350
if (secondPlotTitleEl) secondPlotTitleEl.textContent = secondTitle;
328351

329352
const combinedAcc = `${basePath}/acc_${currentUC}.png`;
330-
const perCorrAcc = `${basePath}/acc_${currentUC}_${corrForFile}.png`;
353+
const perCorrAcc = corrForFile ? `${basePath}/acc_${currentUC}_${corrForFile}.png` : null;
331354

332355
const combinedSecond = `${basePath}/${secondPrefix}_${currentUC}.png`;
333-
const perCorrSecond = `${basePath}/${secondPrefix}_${currentUC}_${corrForFile}.png`;
356+
const perCorrSecond = corrForFile ? `${basePath}/${secondPrefix}_${currentUC}_${corrForFile}.png` : null;
334357

335358
// Optional alternate prefix fallback (e.g., use flip when mce missing)
336359
const altSecondPrefix = (secondPrefix === 'mce') ? 'flip' : null;
337360
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;
339362

340-
const showPerCorruption = !!(filterToggle && filterToggle.checked);
363+
const showPerCorruption = selectedCorruption !== null;
341364
let anyFallbackUsed = false;
342365

343366
// Toggle side-by-side layout when showing per-corruption
@@ -413,7 +436,6 @@
413436
accImg.alt = `Balanced accuracy for ${humanizeCamelCase(currentUC)} (combined)`;
414437

415438
if (expKey === 'pretraining') {
416-
// Try combined MCE then combined flip as fallback
417439
const sources = [combinedSecond];
418440
const alts = [`${secondTitle} for ${humanizeCamelCase(currentUC)} (combined)`];
419441
if (combinedSecondAlt) {
@@ -435,9 +457,9 @@
435457
if (!plotContextLabelLocal) return;
436458
if (showPerCorruption) {
437459
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.`;
439461
} else {
440-
plotContextLabelLocal.textContent = `Showing: ${humanizeCamelCase(currentUC)}${humanizeCamelCase(currentCorruption)}`;
462+
plotContextLabelLocal.textContent = `Showing: ${humanizeCamelCase(currentUC)}${humanizeCamelCase(selectedCorruption)}`;
441463
}
442464
} else {
443465
plotContextLabelLocal.textContent = '';
@@ -446,6 +468,7 @@
446468

447469
// initial
448470
updateUseCaseSelection();
471+
updateCorruptionThumbnails();
449472
updatePlots();
450473
}
451474

@@ -473,8 +496,8 @@
473496
createExperimentSection({ expKey, container: expDiv, basePath });
474497
});
475498

476-
// Activate sticky thumbnails spanning all experiments
477-
setupStickyThumbnailsAcrossExperiments();
499+
// Header shrink on scroll
500+
setupHeaderScrollBehavior();
478501

479502
// Toggle Coming Soon via URL or global flag
480503
setupComingSoonAutoToggle();
@@ -868,85 +891,18 @@ document.addEventListener('DOMContentLoaded', () => {
868891
renderSources();
869892
initialize();
870893
});
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() {
876895
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;
907897

908898
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`);
940903
}
941-
const onResize = () => {
942-
if (bar.classList.contains('is-stuck')) updateBarWidthPosition();
943-
updateScrollOffsetsVars(bar.classList.contains('is-stuck'));
944-
onScroll();
945-
};
946904

947905
window.addEventListener('scroll', onScroll, { passive: true });
948-
window.addEventListener('resize', onResize);
949-
// initial
906+
window.addEventListener('resize', () => onScroll());
950907
onScroll();
951-
updateScrollOffsetsVars(false);
952908
}

0 commit comments

Comments
 (0)