-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
665 lines (574 loc) · 28.6 KB
/
Copy pathscript.js
File metadata and controls
665 lines (574 loc) · 28.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
async function loadSimilarityData(data_type) {
try {
// Load metadata and binary data in parallel
const [metadata, arrayBuffer, indexData] = await Promise.all([
fetch(`data/${data_type}_similarity_metadata.json`).then(resp => resp.json()),
fetch(`data/${data_type}_similarity_data.bin`).then(resp => resp.arrayBuffer()),
fetch(`data/${data_type}_similarity_index.json`).then(resp => resp.json())
]);
// Read header (numRows, numColumns)
const headerView = new DataView(arrayBuffer, 0, 8);
const numRows = headerView.getUint32(0, true); // little endian
const numColumns = headerView.getUint32(4, true);
// Process raw binary data to Float32Array
const dataBuffer = arrayBuffer.slice(8);
const similarityData = new Float32Array(dataBuffer);
// Create lookup by image_id for fast access
const imageIdMap = Object.fromEntries(
indexData.map((item, index) => [item.image_id, index])
);
/**
* Get similarity values for a specific row in the binary data
*/
function getSimilaritiesForRow(rowIndex) {
if (rowIndex < 0 || rowIndex >= numRows) {
console.error(`Row index ${rowIndex} out of bounds (0-${numRows - 1})`);
return null;
}
const startIdx = rowIndex * numColumns;
const rowData = similarityData.slice(startIdx, startIdx + numColumns);
// Map column names to values
return Object.fromEntries(
metadata.columns.map((colName, i) => [colName, rowData[i]])
);
}
/**
* Get an image and all its variants with similarity data
*/
function getImageWithVariants(imageIndex, modelName = null) {
if (imageIndex < 0 || imageIndex >= indexData.length) {
console.error(`Image index ${imageIndex} out of bounds (0-${indexData.length - 1})`);
return null;
}
const imageData = indexData[imageIndex];
const result = {
image_id: imageData.image_id,
object_label: imageData.object_label,
attack_word: imageData.attack_word,
postit_area_pct: imageData.postit_area_pct,
variants: {}
};
// Process each variant
for (const [variantType, variantInfo] of Object.entries(imageData.variants)) {
const similarities = getSimilaritiesForRow(variantInfo.row_index);
if (!similarities) continue;
// Filter by model if specified
const filteredSimilarities = modelName
? Object.fromEntries(
Object.entries(similarities)
.filter(([key]) => key.startsWith(modelName))
)
: { ...similarities };
result.variants[variantType] = { similarities: filteredSimilarities };
}
return result;
}
// Return the API object
return {
metadata,
rawSimilarityData: similarityData,
indexData,
// Core API methods
getImageWithVariants,
// Helper methods
findImageById: (imageId, modelName = null) => {
const index = imageIdMap[imageId];
if (index === undefined) {
console.error(`Image id ${imageId} not found`);
return null;
}
return getImageWithVariants(index, modelName);
},
totalImages: indexData.length
};
} catch (error) {
console.error("Error loading similarity data:", error);
throw error;
}
}
document.addEventListener('DOMContentLoaded', function () {
// Citation copying functionality
const copyButton = document.getElementById('copy-citation');
const citationText = document.getElementById('citation-text');
if (copyButton && citationText) {
copyButton.addEventListener('click', function () {
// Create a temporary textarea element to copy from
const textarea = document.createElement('textarea');
textarea.value = citationText.textContent.trim();
document.body.appendChild(textarea);
// Select and copy the text
textarea.select();
document.execCommand('copy');
// Remove the temporary element
document.body.removeChild(textarea);
// Provide visual feedback
const originalText = copyButton.innerHTML;
copyButton.innerHTML = '✓ Copied!';
// Reset button text after a delay
setTimeout(function () {
copyButton.innerHTML = originalText;
}, 2000);
});
}
// Interactive Dataset Example Visualization
// Initialize similarity data loader
let vlmSimilarityData = null;
let lvlmSimilarityData = null;
let modelProperties = null;
let currentImageId = null; // Use image_id for navigation
let currentImageIndex = 442; // fallback for initial load
let currentPromptIndex = 0;
let isVLMMode = true; // Toggle between VLM and LVLM modes
const numLvlmPrompts = 2;
const modelMeansCache = {}; // Cache for model means to avoid recalculation
// DOM Elements - UI Controls
const modelSelector = document.getElementById('model-selector');
const prevExampleButton = document.getElementById('prev-example');
const nextExampleButton = document.getElementById('next-example');
const currentExampleNum = document.getElementById('current-example-num');
const totalExamples = document.getElementById('total-examples');
const vlmModeRadio = document.getElementById('vlm-mode');
const lvlmModeRadio = document.getElementById('lvlm-mode');
const modelPromptContainer = document.getElementById('model-prompt-container'); // Container for prompt + arrows
const prevPromptButton = document.getElementById('prev-prompt-button');
const nextPromptButton = document.getElementById('next-prompt-button');
// DOM Elements - Model Info
const modelParams = document.getElementById('model-params');
const modelImageSize = document.getElementById('model-image-size');
const modelPrompt = document.getElementById('model-prompt');
// const modelTraining = document.getElementById('model-training');
// DOM Elements - Images
const scamImage = document.getElementById('scam-image');
const noscamImage = document.getElementById('noscam-image');
const synthscamImage = document.getElementById('synthscam-image');
// DOM Elements - Score containers by variant type
const scoreElements = {
'SCAM': { // SCAM - original attack
objectScore: document.getElementById('scam-object-score'),
attackScore: document.getElementById('scam-attack-score'),
objectBar: document.getElementById('scam-object-bar'),
attackBar: document.getElementById('scam-attack-bar'),
objectLabel: document.getElementById('scam-object-label'),
attackLabel: document.getElementById('scam-attack-label')
},
'NoSCAM': { // NoSCAM - attack removed
objectScore: document.getElementById('noscam-object-score'),
attackScore: document.getElementById('noscam-attack-score'),
objectBar: document.getElementById('noscam-object-bar'),
attackBar: document.getElementById('noscam-attack-bar'),
objectLabel: document.getElementById('noscam-object-label'),
attackLabel: document.getElementById('noscam-attack-label')
},
'SynthSCAM': { // SynthSCAM - synthetic attack
objectScore: document.getElementById('synthscam-object-score'),
attackScore: document.getElementById('synthscam-attack-score'),
objectBar: document.getElementById('synthscam-object-bar'),
attackBar: document.getElementById('synthscam-attack-bar'),
objectLabel: document.getElementById('synthscam-object-label'),
attackLabel: document.getElementById('synthscam-attack-label')
}
};
// Initialize the example visualization
let masterImageIdList = [];
async function initializeVisualization() {
try {
// Load data in parallel
[vlmSimilarityData, lvlmSimilarityData, vlmModelProperties, lvlmModelProperties] = await Promise.all([
loadSimilarityData('vlm'),
loadSimilarityData('lvlm'),
fetch('data/vlm_models_properties.json').then(resp => resp.json()),
fetch('data/lvlm_models_properties.json').then(resp => resp.json()),
]);
modelProperties = vlmModelProperties;
// Build image_id lookup maps for both modes
vlmSimilarityData.imageIdToIndex = Object.fromEntries(vlmSimilarityData.indexData.map((item, idx) => [item.image_id, idx]));
lvlmSimilarityData.imageIdToIndex = Object.fromEntries(lvlmSimilarityData.indexData.map((item, idx) => [item.image_id, idx]));
// Build master image_id list (union of both)
const vlmIds = vlmSimilarityData.indexData.map(item => item.image_id);
const lvlmIds = lvlmSimilarityData.indexData.map(item => item.image_id);
masterImageIdList = Array.from(new Set([...vlmIds, ...lvlmIds]));
// Set initial image_id
currentImageId = masterImageIdList[currentImageIndex] || masterImageIdList[0];
// Update UI
totalExamples.textContent = masterImageIdList.length;
updateModelSelector();
// Set up event listeners
modelSelector.addEventListener('change', () => updateVisualizationById(currentImageId, modelSelector.value));
prevExampleButton.addEventListener('click', loadPreviousExampleById);
nextExampleButton.addEventListener('click', loadNextExampleById);
prevPromptButton.addEventListener('click', loadPreviousPrompt);
nextPromptButton.addEventListener('click', loadNextPrompt);
// Set up radio button event listeners
vlmModeRadio.addEventListener('change', () => {
if (vlmModeRadio.checked) {
isVLMMode = true;
modelProperties = vlmModelProperties;
updateModelSelector();
// If currentImageId not in VLM, fallback to first image in master list that exists in VLM
if (!(currentImageId in vlmSimilarityData.imageIdToIndex)) {
currentImageId = masterImageIdList.find(id => id in vlmSimilarityData.imageIdToIndex) || vlmSimilarityData.indexData[0].image_id;
}
updateVisualizationById(currentImageId, modelSelector.value);
}
});
lvlmModeRadio.addEventListener('change', () => {
if (lvlmModeRadio.checked) {
isVLMMode = false;
modelProperties = lvlmModelProperties;
updateModelSelector();
// If currentImageId not in LVLM, fallback to first image in master list that exists in LVLM
if (!(currentImageId in lvlmSimilarityData.imageIdToIndex)) {
currentImageId = masterImageIdList.find(id => id in lvlmSimilarityData.imageIdToIndex) || lvlmSimilarityData.indexData[0].image_id;
}
updateVisualizationById(currentImageId, modelSelector.value);
}
});
// make sure VLM is selected by default
vlmModeRadio.checked = true;
// Set up hover effects for the dataset variants
setupVariantHoverEffects();
// Load an initial example (start at 443)
updateVisualizationById(currentImageId, modelSelector.value);
} catch (error) {
console.error('Failed to initialize visualization:', error);
}
}
// Update model selector based on current mode
function updateModelSelector() {
const data = isVLMMode ? vlmSimilarityData : lvlmSimilarityData;
modelSelector.innerHTML = '';
data.metadata.models.forEach(model => {
const option = document.createElement('option');
option.value = model;
option.textContent = model;
modelSelector.appendChild(option);
});
modelSelector.value = isVLMMode ? 'ViT-L-14-336' : data.metadata.models[0];
totalExamples.textContent = data.totalImages;
}
// Load previous example by image_id
function loadPreviousExampleById() {
let idx = masterImageIdList.indexOf(currentImageId);
idx = (idx > 0) ? idx - 1 : masterImageIdList.length - 1;
currentImageId = masterImageIdList[idx];
updateVisualizationById(currentImageId, modelSelector.value);
}
// Load next example by image_id
function loadNextExampleById() {
let idx = masterImageIdList.indexOf(currentImageId);
idx = (idx < masterImageIdList.length - 1) ? idx + 1 : 0;
currentImageId = masterImageIdList[idx];
updateVisualizationById(currentImageId, modelSelector.value);
}
// Load previous prompt (LVLM only)
function loadPreviousPrompt() {
if (!isVLMMode) {
currentPromptIndex = (currentPromptIndex > 0) ?
currentPromptIndex - 1 : numLvlmPrompts - 1;
updateVisualizationById(currentImageId, modelSelector.value);
}
}
// Load next prompt (LVLM only)
function loadNextPrompt() {
if (!isVLMMode) {
currentPromptIndex = (currentPromptIndex < numLvlmPrompts - 1) ?
currentPromptIndex + 1 : 0;
updateVisualizationById(currentImageId, modelSelector.value);
}
}
// Update visualization based on image_id and model
function updateVisualizationById(imageId, modelName) {
const data = isVLMMode ? vlmSimilarityData : lvlmSimilarityData;
const imageIndex = data.imageIdToIndex[imageId];
if (imageIndex === undefined) {
console.error('Failed to get image index for image_id:', imageId);
return;
}
const imageData = data.getImageWithVariants(imageIndex, modelName);
if (!imageData) {
console.error('Failed to get image data for index:', imageIndex);
return;
}
// Update UI components
// Show master index (preserved across modes)
const masterIndex = masterImageIdList.indexOf(imageId);
currentExampleNum.textContent = masterIndex + 1;
updateImages(imageData.image_id);
updateAllLabels(imageData.object_label, imageData.attack_word);
updateAllScores(imageData.variants, modelName);
updateModelInfo(modelName);
}
// Set up hover effects for dataset variants
function setupVariantHoverEffects() {
const variants = document.querySelectorAll('.dataset-variant');
const results = document.querySelectorAll('.result');
// Add mouse events for each variant
variants.forEach((variant, index) => {
// On mouse enter, highlight the corresponding arrows
variant.addEventListener('mouseenter', () => {
highlightCorrespondingArrows(index);
// Also highlight the corresponding result
if (results[index]) {
results[index].style.boxShadow = 'var(--box-shadow-hover)';
}
});
// On mouse leave, reset the arrows
variant.addEventListener('mouseleave', () => {
resetArrowHighlights();
// Reset result highlight
if (results[index]) {
results[index].style.transform = '';
results[index].style.boxShadow = '';
}
});
});
// Add mouse events for results
results.forEach((result, index) => {
// On mouse enter, highlight the corresponding arrows in reverse
result.addEventListener('mouseenter', () => {
highlightCorrespondingArrows(index);
// Also highlight the corresponding variant
if (variants[index]) {
variants[index].style.boxShadow = 'var(--box-shadow-hover)';
}
});
// On mouse leave, reset the arrows
result.addEventListener('mouseleave', () => {
resetArrowHighlights();
// Reset variant highlight
if (variants[index]) {
variants[index].style.transform = '';
variants[index].style.boxShadow = '';
}
});
});
}
// Highlight arrows corresponding to the variant at the given index
function highlightCorrespondingArrows(index) {
const leftArrows = document.querySelectorAll('.left-arrows .arrow-path');
const rightArrows = document.querySelectorAll('.right-arrows .arrow-path');
// Reset all arrows first
resetArrowHighlights();
// Highlight only the corresponding arrows
if (leftArrows[index]) {
leftArrows[index].classList.add('highlighted');
leftArrows[index].style.stroke = '#2962ff';
}
if (rightArrows[index]) {
rightArrows[index].classList.add('highlighted');
rightArrows[index].style.stroke = '#2962ff';
}
}
// Reset all arrow highlights
function resetArrowHighlights() {
const allArrows = document.querySelectorAll('.arrow-path');
allArrows.forEach(arrow => {
arrow.classList.remove('highlighted');
arrow.style.stroke = '';
});
}
// Update images based on example image_id
function updateImages(imageId) {
if (!imageId) {
console.warn('No image_id found in data');
return;
}
imageId = imageId + '.webp'
const images = [
[scamImage, `data_images/SCAM/${imageId}`],
[noscamImage, `data_images/NoSCAM/${imageId.replace('SCAM', 'NoSCAM')}`],
[synthscamImage, `data_images/SynthSCAM/${imageId.replace('SCAM', 'SynthSCAM')}`],
];
// Set images and handle errors
images.forEach(([imgElement, src]) => {
imgElement.src = '';
imgElement.src = src;
imgElement.onerror = () => {
console.error(`Failed to load image: ${src}`);
imgElement.alt = "Image not found";
};
});
}
// Update all variant labels
function updateAllLabels(objectLabel, attackWord) {
const safeObjectLabel = objectLabel || '?';
const safeAttackWord = attackWord || '?';
Object.values(scoreElements).forEach(elements => {
elements.objectLabel.innerHTML = safeObjectLabel;
elements.attackLabel.innerHTML = safeAttackWord;
});
}
// Update scores for all variants
function updateAllScores(variants, modelName) {
const objectScoreKey = `${modelName}_object_similarities`;
const attackScoreKey = `${modelName}_attack_similarities`;
// Calculate mean values for this model (and prompt) for each variant type
const data = isVLMMode ? vlmSimilarityData : lvlmSimilarityData;
let modelMeans = modelMeansCache[`${modelName}_${isVLMMode ? 'vlm' : 'lvlm'}_${currentPromptIndex}`];
// Calculate or retrieve the model means
if (!modelMeans) {
const promptSuffix = isVLMMode ? '' : `_${currentPromptIndex}`;
// Create separate arrays for each variant type
const variantScores = {
'SCAM': { objectCertainties: [], attackCertainties: [] },
'NoSCAM': { objectCertainties: [], attackCertainties: [] },
'SynthSCAM': { objectCertainties: [], attackCertainties: [] }
};
// Iterate through all images to calculate mean scores
for (let i = 0; i < data.totalImages; i++) {
const imgData = data.getImageWithVariants(i, modelName);
if (imgData) {
// Get scores from each variant
Object.entries(imgData.variants).forEach(([variantType, variantData]) => {
// Match the variant type (without the prompt suffix for LVLM mode)
const baseVariantType = variantType.replace(new RegExp(`_${currentPromptIndex}$`), '');
if (variantScores[baseVariantType] && variantType.endsWith(promptSuffix)) {
const objScore = variantData.similarities?.[objectScoreKey];
const atkScore = variantData.similarities?.[attackScoreKey];
if (objScore !== undefined && atkScore !== undefined) {
// Calculate the certainty using softmax, just like in updateDatasetScores
const scores = [objScore, atkScore];
const maxScore = Math.max(...scores);
const expScores = scores.map(score => Math.exp(score - maxScore));
const sumExp = expScores.reduce((a, b) => a + b, 0);
// Calculate normalized probabilities
const objCertainty = expScores[0] / sumExp;
const atkCertainty = expScores[1] / sumExp;
variantScores[baseVariantType].objectCertainties.push(objCertainty);
variantScores[baseVariantType].attackCertainties.push(atkCertainty);
}
}
});
}
}
// Calculate means for each variant type
const calcMean = arr => {
const validNums = arr.filter(x => !isNaN(x));
return validNums.length ? validNums.reduce((a, b) => a + b, 0) / validNums.length : null;
};
modelMeans = {
'SCAM': {
objectCertainty: calcMean(variantScores['SCAM'].objectCertainties),
attackCertainty: calcMean(variantScores['SCAM'].attackCertainties)
},
'NoSCAM': {
objectCertainty: calcMean(variantScores['NoSCAM'].objectCertainties),
attackCertainty: calcMean(variantScores['NoSCAM'].attackCertainties)
},
'SynthSCAM': {
objectCertainty: calcMean(variantScores['SynthSCAM'].objectCertainties),
attackCertainty: calcMean(variantScores['SynthSCAM'].attackCertainties)
}
};
// Cache the results with a key that includes the model, mode and prompt
modelMeansCache[`${modelName}_${isVLMMode ? 'vlm' : 'lvlm'}_${currentPromptIndex}`] = modelMeans;
}
// Update each variant's scores
Object.entries(scoreElements).forEach(([variantType, elements]) => {
let variantKey = variantType;
if (!isVLMMode) {
variantKey = `${variantType}_${currentPromptIndex}`;
}
const variantData = variants[variantKey];
let objScore, atkScore;
if (variantData?.similarities && variantData.similarities[objectScoreKey] !== undefined && variantData.similarities[attackScoreKey] !== undefined) {
objScore = variantData.similarities[objectScoreKey];
atkScore = variantData.similarities[attackScoreKey];
} else {
objScore = NaN;
atkScore = NaN;
console.error(`No similarity data found for variant: ${variantKey}, model: ${modelName}`);
}
updateDatasetScores(
objScore,
atkScore,
elements.objectScore,
elements.attackScore,
elements.objectBar,
elements.attackBar,
modelMeans[variantType]
);
});
}
// Helper function to update scores for a dataset
function updateDatasetScores(objScore, atkScore, objElement, atkElement, objBar, atkBar, variantMean) {
// Calculate softmax for decision certainty
let objCertainty = 0.0;
let atkCertainty = 0.0;
if (objScore !== undefined && atkScore !== undefined) {
// Apply softmax to calculate certainty
const scores = [objScore, atkScore];
const maxScore = Math.max(...scores);
// Subtract max for numerical stability before exponential
const expScores = scores.map(score => Math.exp(score - maxScore));
const sumExp = expScores.reduce((a, b) => a + b, 0);
// Calculate normalized probabilities
objCertainty = expScores[0] / sumExp;
atkCertainty = expScores[1] / sumExp;
}
// Update UI elements directly
if (objScore !== undefined && atkScore !== undefined && !isNaN(objScore) && !isNaN(atkScore)) {
objElement.textContent = `${(objCertainty * 100).toFixed(1)}%`;
atkElement.textContent = `${(atkCertainty * 100).toFixed(1)}%`;
objBar.style.width = `${objCertainty * 100}%`;
atkBar.style.width = `${atkCertainty * 100}%`;
objBar.style.backgroundColor = objCertainty > atkCertainty ? '#2a9d8f' : '#ff9800';
atkBar.style.backgroundColor = atkCertainty > objCertainty ? '#e76f51' : '#ff9800';
} else {
objElement.textContent = 'N/A';
atkElement.textContent = 'N/A';
objBar.style.width = '0%';
atkBar.style.width = '0%';
objBar.style.backgroundColor = '#ff9800';
atkBar.style.backgroundColor = '#ff9800';
}
// Add mean markers if we have mean values for this variant
if (variantMean && variantMean.objectCertainty !== null && variantMean.attackCertainty !== null) {
// Get the parent progress bar
const objProgressBar = objBar.parentElement;
const atkProgressBar = atkBar.parentElement;
// Create or update mean marker on object bar
[
[objProgressBar, variantMean.objectCertainty],
[atkProgressBar, variantMean.attackCertainty]
].forEach(([bar, certainty]) => {
let meanMarker = bar.querySelector('.mean-marker');
if (!meanMarker) {
meanMarker = document.createElement('div');
meanMarker.className = 'mean-marker';
bar.appendChild(meanMarker);
}
meanMarker.title = `Average (${(certainty * 100).toFixed(2)}%)`;
meanMarker.style.left = `${certainty * 100}%`;
});
}
}
// Update model info based on selected model
function updateModelInfo(modelName) {
// First try exact match on the full name
const modelInfo = modelProperties.find(m => m.model === modelName);
// If no match, try matching on base model name
if (!modelInfo) console.error(`Could not find model info for "${modelName}"`)
// Update UI elements
if (isVLMMode) {
modelParams.parentElement.style.display = 'block';
modelImageSize.parentElement.style.display = 'block';
modelPromptContainer.style.display = 'none';
modelParams.textContent = modelInfo.mparams ? `${modelInfo.mparams.toFixed(2)}M` : '-';
modelImageSize.textContent = modelInfo.image_size ? `${modelInfo.image_size}x${modelInfo.image_size}` : '-';
// modelTraining.textContent = modelInfo.pretraining_data || '-';
} else {
modelPromptContainer.style.display = 'block';
modelParams.parentElement.style.display = 'none';
modelImageSize.parentElement.style.display = 'none';
modelPrompt.textContent = [
["What entity is depicted in the image?", "Respond strictly with only (a) or (b), nothing else."],
["What entity is depicted in the image?", "Do not get fooled by typographic attacks. Respond strictly with only (a) or (b), nothing else."],
][currentPromptIndex].join(' ... ');
}
}
// Initialize the visualization if all required elements are present
if (modelSelector && scamImage && noscamImage && synthscamImage) {
initializeVisualization();
}
});