CHang commited on
Commit
40b2909
·
verified ·
1 Parent(s): 936a876

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1042 -19
index.html CHANGED
@@ -1,19 +1,1042 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Dataset Image Viewer</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ padding: 10px;
19
+ }
20
+
21
+ .viewer-container {
22
+ background: rgba(255, 255, 255, 0.95);
23
+ backdrop-filter: blur(10px);
24
+ border-radius: 15px;
25
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
26
+ padding: 20px;
27
+ width: calc(100vw - 20px);
28
+ max-height: calc(100vh - 20px);
29
+ overflow-y: auto;
30
+ }
31
+
32
+ .header {
33
+ text-align: center;
34
+ margin-bottom: 20px;
35
+ }
36
+
37
+ .header h1 {
38
+ color: #333;
39
+ font-size: 1.8rem;
40
+ font-weight: 700;
41
+ margin-bottom: 8px;
42
+ background: linear-gradient(45deg, #667eea, #764ba2);
43
+ -webkit-background-clip: text;
44
+ -webkit-text-fill-color: transparent;
45
+ background-clip: text;
46
+ }
47
+
48
+ .loading-screen {
49
+ text-align: center;
50
+ padding: 60px 20px;
51
+ color: #333;
52
+ }
53
+
54
+ .loading-title {
55
+ font-size: 1.2rem;
56
+ font-weight: 600;
57
+ margin-bottom: 20px;
58
+ color: #555;
59
+ }
60
+
61
+ .loading-progress {
62
+ background: rgba(102, 126, 234, 0.1);
63
+ border-radius: 25px;
64
+ padding: 20px;
65
+ margin: 20px auto;
66
+ max-width: 500px;
67
+ border: 2px solid rgba(102, 126, 234, 0.2);
68
+ }
69
+
70
+ .progress-bar {
71
+ width: 100%;
72
+ height: 8px;
73
+ background: #e9ecef;
74
+ border-radius: 10px;
75
+ overflow: hidden;
76
+ margin: 15px 0;
77
+ }
78
+
79
+ .progress-fill {
80
+ height: 100%;
81
+ background: linear-gradient(45deg, #667eea, #764ba2);
82
+ border-radius: 10px;
83
+ transition: width 0.3s ease;
84
+ width: 0%;
85
+ }
86
+
87
+ .progress-text {
88
+ font-size: 1rem;
89
+ font-weight: 600;
90
+ color: #667eea;
91
+ margin-bottom: 10px;
92
+ }
93
+
94
+ .progress-details {
95
+ font-size: 0.9rem;
96
+ color: #666;
97
+ line-height: 1.5;
98
+ }
99
+
100
+ .spinner-large {
101
+ border: 4px solid #f3f3f3;
102
+ border-top: 4px solid #667eea;
103
+ border-radius: 50%;
104
+ width: 50px;
105
+ height: 50px;
106
+ animation: spin 1s linear infinite;
107
+ margin: 0 auto 20px;
108
+ }
109
+
110
+ .max-demo-notice {
111
+ background: rgba(255, 193, 7, 0.1);
112
+ border: 2px solid #ffc107;
113
+ border-radius: 15px;
114
+ padding: 15px;
115
+ margin: 15px 0;
116
+ text-align: center;
117
+ color: #856404;
118
+ }
119
+
120
+ .navigation {
121
+ display: flex;
122
+ justify-content: center;
123
+ align-items: center;
124
+ gap: 20px;
125
+ margin-bottom: 20px;
126
+ flex-wrap: wrap;
127
+ }
128
+
129
+ .nav-btn, .toggle-btn {
130
+ background: linear-gradient(45deg, #667eea, #764ba2);
131
+ color: white;
132
+ border: none;
133
+ padding: 10px 20px;
134
+ border-radius: 25px;
135
+ cursor: pointer;
136
+ font-size: 14px;
137
+ font-weight: 600;
138
+ transition: all 0.3s ease;
139
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
140
+ }
141
+
142
+ .nav-btn:hover, .toggle-btn:hover {
143
+ transform: translateY(-2px);
144
+ box-shadow: 0 6px 25px rgba(102, 126, 234, 0.4);
145
+ }
146
+
147
+ .nav-btn:disabled {
148
+ opacity: 0.5;
149
+ cursor: not-allowed;
150
+ transform: none;
151
+ }
152
+
153
+ .toggle-btn.active {
154
+ background: linear-gradient(45deg, #28a745, #20c997);
155
+ }
156
+
157
+ .toggle-btn.inactive {
158
+ background: linear-gradient(45deg, #dc3545, #fd7e14);
159
+ }
160
+
161
+ .image-counter {
162
+ background: rgba(102, 126, 234, 0.1);
163
+ padding: 8px 16px;
164
+ border-radius: 20px;
165
+ font-weight: 600;
166
+ color: #333;
167
+ }
168
+
169
+ .loading-indicator {
170
+ background: rgba(255, 193, 7, 0.1);
171
+ padding: 5px 12px;
172
+ border-radius: 15px;
173
+ font-size: 12px;
174
+ color: #856404;
175
+ font-weight: 500;
176
+ }
177
+
178
+ .main-content {
179
+ display: grid;
180
+ grid-template-columns: 1fr 350px;
181
+ gap: 20px;
182
+ margin-bottom: 20px;
183
+ }
184
+
185
+ .image-section {
186
+ background: #f8f9fa;
187
+ border-radius: 15px;
188
+ overflow: hidden;
189
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
190
+ position: relative;
191
+ }
192
+
193
+ .image-container {
194
+ position: relative;
195
+ width: 100%;
196
+ height: 70vh;
197
+ overflow: hidden;
198
+ display: flex;
199
+ align-items: center;
200
+ justify-content: center;
201
+ background: #f0f0f0;
202
+ }
203
+
204
+ .main-image {
205
+ max-width: 100%;
206
+ max-height: 100%;
207
+ width: auto;
208
+ height: auto;
209
+ display: block;
210
+ object-fit: contain;
211
+ }
212
+
213
+ .image-overlay {
214
+ position: absolute;
215
+ top: 10px;
216
+ right: 10px;
217
+ background: rgba(0, 0, 0, 0.7);
218
+ color: white;
219
+ padding: 5px 10px;
220
+ border-radius: 5px;
221
+ font-size: 12px;
222
+ z-index: 10;
223
+ }
224
+
225
+ .bounding-box {
226
+ position: absolute;
227
+ border: 3px solid;
228
+ background: transparent;
229
+ pointer-events: none;
230
+ transition: all 0.3s ease;
231
+ z-index: 5;
232
+ }
233
+
234
+ .bounding-box.active {
235
+ opacity: 1;
236
+ }
237
+
238
+ .bounding-box.inactive {
239
+ opacity: 0;
240
+ }
241
+
242
+ .box-label {
243
+ position: absolute;
244
+ color: white;
245
+ padding: 4px 8px;
246
+ font-size: 12px;
247
+ font-weight: 700;
248
+ border-radius: 4px;
249
+ max-width: 200px;
250
+ overflow: hidden;
251
+ text-overflow: ellipsis;
252
+ white-space: nowrap;
253
+ line-height: 1.4;
254
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
255
+ min-height: 20px;
256
+ display: flex;
257
+ align-items: center;
258
+ z-index: 15;
259
+ }
260
+
261
+ .box-label.active {
262
+ opacity: 1;
263
+ }
264
+
265
+ .box-label.inactive {
266
+ opacity: 0;
267
+ }
268
+
269
+ .metadata-panel {
270
+ background: #f8f9fa;
271
+ border-radius: 15px;
272
+ padding: 15px;
273
+ overflow-y: auto;
274
+ max-height: 70vh;
275
+ }
276
+
277
+ .metadata-section {
278
+ margin-bottom: 15px;
279
+ }
280
+
281
+ .metadata-title {
282
+ font-weight: 700;
283
+ color: #333;
284
+ margin-bottom: 8px;
285
+ font-size: 1rem;
286
+ border-bottom: 2px solid #667eea;
287
+ padding-bottom: 3px;
288
+ display: flex;
289
+ justify-content: space-between;
290
+ align-items: center;
291
+ }
292
+
293
+ .metadata-content {
294
+ color: #666;
295
+ line-height: 1.4;
296
+ font-size: 13px;
297
+ }
298
+
299
+ .labels-grid {
300
+ display: grid;
301
+ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
302
+ gap: 6px;
303
+ max-height: 300px;
304
+ overflow-y: auto;
305
+ align-items: stretch;
306
+ }
307
+
308
+ .label-tag {
309
+ padding: 10px 14px;
310
+ border-radius: 15px;
311
+ font-size: 15px;
312
+ text-align: center;
313
+ font-weight: 600;
314
+ cursor: pointer;
315
+ transition: all 0.3s ease;
316
+ border: 2px solid transparent;
317
+ display: flex;
318
+ align-items: center;
319
+ justify-content: center;
320
+ min-height: 40px;
321
+ word-wrap: break-word;
322
+ hyphens: auto;
323
+ line-height: 1.2;
324
+ }
325
+
326
+ .label-tag.active {
327
+ background: linear-gradient(45deg, #198754, #20c997);
328
+ color: white;
329
+ border-color: #198754;
330
+ box-shadow: 0 2px 8px rgba(25, 135, 84, 0.3);
331
+ }
332
+
333
+ .label-tag.inactive {
334
+ background: #e9ecef;
335
+ color: #6c757d;
336
+ border-color: #dee2e6;
337
+ }
338
+
339
+ .label-tag:hover {
340
+ transform: translateY(-1px);
341
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
342
+ }
343
+
344
+ .source-meta-section {
345
+ margin-top: 10px;
346
+ padding-top: 10px;
347
+ border-top: 1px solid #dee2e6;
348
+ }
349
+
350
+ .source-meta-content {
351
+ max-height: 200px;
352
+ overflow-y: auto;
353
+ font-size: 13px;
354
+ line-height: 1.4;
355
+ color: #666;
356
+ }
357
+
358
+ .captions-section {
359
+ background: white;
360
+ border-radius: 15px;
361
+ padding: 20px;
362
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
363
+ grid-column: 1 / -1;
364
+ }
365
+
366
+ .caption-item {
367
+ margin-bottom: 15px;
368
+ padding: 15px;
369
+ background: rgba(102, 126, 234, 0.05);
370
+ border-left: 4px solid #667eea;
371
+ border-radius: 0 10px 10px 0;
372
+ }
373
+
374
+ .caption-label {
375
+ font-weight: 600;
376
+ color: #333;
377
+ margin-bottom: 5px;
378
+ }
379
+
380
+ .caption-text {
381
+ color: #555;
382
+ line-height: 1.6;
383
+ font-size: 14px;
384
+ }
385
+
386
+ .error-message {
387
+ background: #ff4757;
388
+ color: white;
389
+ padding: 15px;
390
+ border-radius: 10px;
391
+ text-align: center;
392
+ margin: 20px 0;
393
+ }
394
+
395
+ .loading {
396
+ text-align: center;
397
+ padding: 40px;
398
+ color: #666;
399
+ font-size: 16px;
400
+ }
401
+
402
+ .image-loading {
403
+ position: absolute;
404
+ top: 50%;
405
+ left: 50%;
406
+ transform: translate(-50%, -50%);
407
+ background: rgba(255, 255, 255, 0.9);
408
+ padding: 20px;
409
+ border-radius: 10px;
410
+ text-align: center;
411
+ z-index: 20;
412
+ }
413
+
414
+ .spinner {
415
+ border: 3px solid #f3f3f3;
416
+ border-top: 3px solid #667eea;
417
+ border-radius: 50%;
418
+ width: 30px;
419
+ height: 30px;
420
+ animation: spin 1s linear infinite;
421
+ margin: 0 auto 10px;
422
+ }
423
+
424
+ @keyframes spin {
425
+ 0% { transform: rotate(0deg); }
426
+ 100% { transform: rotate(360deg); }
427
+ }
428
+
429
+ @media (max-width: 1024px) {
430
+ .main-content {
431
+ grid-template-columns: 1fr;
432
+ }
433
+
434
+ .metadata-panel {
435
+ max-height: none;
436
+ }
437
+
438
+ .image-container {
439
+ height: 50vh;
440
+ }
441
+ }
442
+
443
+ @media (max-width: 768px) {
444
+ .navigation {
445
+ flex-direction: column;
446
+ gap: 10px;
447
+ }
448
+
449
+ .header h1 {
450
+ font-size: 1.5rem;
451
+ }
452
+
453
+ .viewer-container {
454
+ padding: 15px;
455
+ }
456
+
457
+ .loading-progress {
458
+ margin: 20px 10px;
459
+ padding: 15px;
460
+ }
461
+ }
462
+ </style>
463
+ </head>
464
+ <body>
465
+ <div class="viewer-container">
466
+ <div class="header">
467
+ <h1>📊 ROVI Example Viewer</h1>
468
+ </div>
469
+
470
+ <div id="loadingScreen" class="loading-screen">
471
+ <div class="spinner-large"></div>
472
+ <div class="loading-title">Loading Examples</div>
473
+ <div class="loading-progress">
474
+ <div class="progress-text" id="progressText">Loading json annotation file...</div>
475
+ <div class="progress-bar">
476
+ <div class="progress-fill" id="progressFill"></div>
477
+ </div>
478
+ <div class="progress-details" id="progressDetails">
479
+ Please wait while we load and validate images
480
+ </div>
481
+ </div>
482
+ </div>
483
+
484
+ <div id="errorMessage" class="error-message" style="display: none;">
485
+ ❌ Failed to load annotation file. Please ensure the JSON path is accessible.
486
+ </div>
487
+
488
+ <div id="mainViewer" style="display: none;">
489
+ <div id="maxDemoNotice" class="max-demo-notice" style="display: none;">
490
+ 🎯 <strong>Demo Limit Reached:</strong> Displaying maximum of 100 images for optimal performance
491
+ </div>
492
+
493
+ <div class="navigation">
494
+ <button class="nav-btn" id="prevBtn" onclick="navigatePrevious()">← Previous</button>
495
+ <div class="image-counter">
496
+ <span id="currentIndex">1</span> / <span id="totalImages">0</span>
497
+ </div>
498
+ <button class="nav-btn" id="nextBtn" onclick="navigateNext()">Next →</button>
499
+ <button class="toggle-btn active" id="globalToggle" onclick="toggleAllBoxes()">
500
+ Hide All Boxes
501
+ </button>
502
+ <div id="cacheStatus" class="loading-indicator" style="display: none;">
503
+ 📥 Caching images...
504
+ </div>
505
+ </div>
506
+
507
+ <div class="main-content">
508
+ <div class="image-section">
509
+ <div class="image-container" id="imageContainer">
510
+ <div id="imageLoading" class="image-loading" style="display: none;">
511
+ <div class="spinner"></div>
512
+ <div>Loading image...</div>
513
+ </div>
514
+ <img id="mainImage" class="main-image" alt="Dataset image" />
515
+ <div class="image-overlay">
516
+ <span id="imageId"></span>
517
+ </div>
518
+ </div>
519
+ </div>
520
+
521
+ <div class="metadata-panel">
522
+ <div class="metadata-section">
523
+ <div class="metadata-title">📏 Dimensions</div>
524
+ <div class="metadata-content" id="dimensions"></div>
525
+ </div>
526
+
527
+ <div class="metadata-section">
528
+ <div class="metadata-title">
529
+ 🏷️ Labels
530
+ <small style="font-size: 10px; color: #999;">Click to toggle boxes</small>
531
+ </div>
532
+ <div class="labels-grid" id="labelsContainer"></div>
533
+ </div>
534
+
535
+ <div class="metadata-section">
536
+ <div class="metadata-title">📊 Details</div>
537
+ <div class="metadata-content" id="sourceInfo"></div>
538
+ </div>
539
+
540
+ <div class="metadata-section source-meta-section">
541
+ <div class="metadata-title">🔍 Source Meta</div>
542
+ <div class="source-meta-content" id="sourceMeta"></div>
543
+ </div>
544
+ </div>
545
+
546
+ <div class="captions-section">
547
+ <div class="metadata-title">💬 Captions</div>
548
+ <div class="caption-item">
549
+ <div class="caption-label">VLM Description</div>
550
+ <div class="caption-text" id="vlmCaption"></div>
551
+ </div>
552
+ <div class="caption-item">
553
+ <div class="caption-label">Web Caption</div>
554
+ <div class="caption-text" id="webCaption"></div>
555
+ </div>
556
+ </div>
557
+ </div>
558
+ </div>
559
+ </div>
560
+
561
+ <script>
562
+ class DatasetViewer {
563
+ constructor() {
564
+ this.dataset = {};
565
+ this.validImages = [];
566
+ this.imageCache = new Map();
567
+ this.allImageIds = [];
568
+
569
+ this.currentIndex = 0;
570
+ this.currentImageData = null;
571
+ this.boxStates = {};
572
+
573
+ this.MAX_IMAGES = 100;
574
+ this.INITIAL_CACHE_SIZE = 5;
575
+ this.LOOKAHEAD_CACHE_SIZE = 10;
576
+
577
+ this.boxColors = [
578
+ '#FF0066', '#00FF66', '#6600FF', '#FF6600', '#00FFFF',
579
+ '#FF0099', '#99FF00', '#0099FF', '#FF9900', '#9900FF',
580
+ '#00FF99', '#FF3300', '#3300FF', '#FFFF00', '#FF00FF',
581
+ '#00CCFF', '#FF6699', '#66FF99', '#9966FF', '#FFCC00'
582
+ ];
583
+
584
+ this.isBackgroundLoading = false;
585
+ this.nextImageIndex = 0;
586
+ this.maxReached = false;
587
+ }
588
+
589
+ shuffleArray(array) {
590
+ const shuffled = [...array];
591
+ for (let i = shuffled.length - 1; i > 0; i--) {
592
+ const j = Math.floor(Math.random() * (i + 1));
593
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
594
+ }
595
+ return shuffled;
596
+ }
597
+
598
+ updateProgress(current, total, message, details) {
599
+ const progressText = document.getElementById('progressText');
600
+ const progressFill = document.getElementById('progressFill');
601
+ const progressDetails = document.getElementById('progressDetails');
602
+
603
+ const percentage = total > 0 ? (current / total) * 100 : 0;
604
+
605
+ progressText.textContent = message;
606
+ progressFill.style.width = `${percentage}%`;
607
+ progressDetails.textContent = details;
608
+ }
609
+
610
+ async loadDataset() {
611
+ try {
612
+ this.updateProgress(0, 100, 'Loading dataset file...', 'Fetching JSON data from server');
613
+
614
+ const response = await fetch('https://huggingface.co/datasets/CHang/ROVI/raw/main/sampled_ROVI_val_1000.json');
615
+ if (!response.ok) {
616
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
617
+ }
618
+
619
+ this.updateProgress(30, 100, 'Parsing dataset...', 'Processing JSON data');
620
+
621
+ this.dataset = await response.json();
622
+ this.allImageIds = this.shuffleArray(Object.keys(this.dataset));
623
+
624
+ if (this.allImageIds.length === 0) {
625
+ throw new Error('Dataset is empty');
626
+ }
627
+
628
+ this.updateProgress(60, 100, 'Caching initial images...', 'Loading first batch for display');
629
+
630
+ await this.loadInitialImages();
631
+
632
+ if (this.validImages.length === 0) {
633
+ throw new Error('No valid images found');
634
+ }
635
+
636
+ this.updateProgress(100, 100, 'Ready!', `Loaded ${this.validImages.length} images`);
637
+
638
+ setTimeout(() => this.initializeViewer(), 800);
639
+
640
+ } catch (error) {
641
+ console.error('Error loading dataset:', error);
642
+ this.showError(`Failed to load dataset: ${error.message}`);
643
+ }
644
+ }
645
+
646
+ async loadInitialImages() {
647
+ this.validImages = [];
648
+ this.nextImageIndex = 0;
649
+ let attempts = 0;
650
+ const maxAttempts = Math.min(50, this.allImageIds.length);
651
+
652
+ while (this.validImages.length < this.INITIAL_CACHE_SIZE && attempts < maxAttempts) {
653
+ const imageId = this.allImageIds[this.nextImageIndex];
654
+ const imageData = this.dataset[imageId];
655
+
656
+ this.updateProgress(
657
+ this.validImages.length,
658
+ this.INITIAL_CACHE_SIZE,
659
+ `Validating images... (${this.validImages.length}/${this.INITIAL_CACHE_SIZE})`,
660
+ `Testing: ${imageId.substring(0, 30)}... | Valid: ${this.validImages.length} | Failed: ${attempts - this.validImages.length}`
661
+ );
662
+
663
+ try {
664
+ await this.preloadImage(imageData.url);
665
+ this.validImages.push(imageId);
666
+ } catch (error) {
667
+ console.warn(`Image validation failed for ${imageId}:`, error.message);
668
+ }
669
+
670
+ this.nextImageIndex++;
671
+ attempts++;
672
+
673
+ if (this.nextImageIndex >= this.allImageIds.length) {
674
+ console.warn('Reached end of dataset during initial load');
675
+ break;
676
+ }
677
+ }
678
+
679
+ if (this.validImages.length === 0) {
680
+ throw new Error('No valid images found in dataset');
681
+ }
682
+ }
683
+
684
+ async startBackgroundCaching() {
685
+ if (this.isBackgroundLoading || this.validImages.length >= this.MAX_IMAGES) return;
686
+
687
+ this.isBackgroundLoading = true;
688
+ const cacheStatus = document.getElementById('cacheStatus');
689
+
690
+ while (this.validImages.length < this.MAX_IMAGES && this.nextImageIndex < this.allImageIds.length) {
691
+ const currentCacheAhead = Math.min(this.LOOKAHEAD_CACHE_SIZE, this.MAX_IMAGES - this.validImages.length);
692
+ const targetSize = this.validImages.length + currentCacheAhead;
693
+
694
+ cacheStatus.style.display = 'block';
695
+ let loaded = 0;
696
+ let attempts = 0;
697
+ const maxBackgroundAttempts = Math.min(30, this.allImageIds.length - this.nextImageIndex);
698
+
699
+ while (this.validImages.length < targetSize && attempts < maxBackgroundAttempts && this.nextImageIndex < this.allImageIds.length) {
700
+ const imageId = this.allImageIds[this.nextImageIndex];
701
+ const imageData = this.dataset[imageId];
702
+
703
+ try {
704
+ await this.preloadImage(imageData.url);
705
+ this.validImages.push(imageId);
706
+ loaded++;
707
+
708
+ cacheStatus.textContent = `📥 Cached ${loaded} images (${this.validImages.length} total)`;
709
+ this.updateNavigationButtons();
710
+
711
+ } catch (error) {
712
+ console.warn(`Background validation failed for ${imageId}:`, error.message);
713
+ }
714
+
715
+ this.nextImageIndex++;
716
+ attempts++;
717
+
718
+ if (loaded % 2 === 0) {
719
+ await new Promise(resolve => setTimeout(resolve, 10));
720
+ }
721
+ }
722
+
723
+ if (this.validImages.length >= this.MAX_IMAGES && !this.maxReached) {
724
+ this.maxReached = true;
725
+ document.getElementById('maxDemoNotice').style.display = 'block';
726
+ cacheStatus.textContent = '🎯 Maximum demo images reached';
727
+ setTimeout(() => cacheStatus.style.display = 'none', 3000);
728
+ break;
729
+ }
730
+
731
+ if (this.nextImageIndex >= this.allImageIds.length) {
732
+ cacheStatus.textContent = '✅ All available images processed';
733
+ setTimeout(() => cacheStatus.style.display = 'none', 2000);
734
+ break;
735
+ }
736
+
737
+ cacheStatus.style.display = 'none';
738
+ await new Promise(resolve => setTimeout(resolve, 1000));
739
+ }
740
+
741
+ this.isBackgroundLoading = false;
742
+ }
743
+
744
+ async preloadImage(url) {
745
+ if (this.imageCache.has(url)) {
746
+ return this.imageCache.get(url);
747
+ }
748
+
749
+ return new Promise((resolve, reject) => {
750
+ const img = new Image();
751
+ let isResolved = false;
752
+
753
+ img.onload = () => {
754
+ if (isResolved) return;
755
+ isResolved = true;
756
+
757
+ if (img.naturalWidth === 0 || img.naturalHeight === 0) {
758
+ reject(new Error(`Invalid image dimensions: ${url}`));
759
+ return;
760
+ }
761
+
762
+ this.imageCache.set(url, img);
763
+ resolve(img);
764
+ };
765
+
766
+ img.onerror = () => {
767
+ if (isResolved) return;
768
+ isResolved = true;
769
+ reject(new Error(`Failed to load: ${url}`));
770
+ };
771
+
772
+ img.onabort = () => {
773
+ if (isResolved) return;
774
+ isResolved = true;
775
+ reject(new Error(`Load aborted: ${url}`));
776
+ };
777
+
778
+ const timeoutId = setTimeout(() => {
779
+ if (isResolved) return;
780
+ isResolved = true;
781
+ img.src = '';
782
+ reject(new Error(`Timeout: ${url}`));
783
+ }, 3000);
784
+
785
+ img.src = url;
786
+
787
+ img.onload = () => {
788
+ clearTimeout(timeoutId);
789
+ if (isResolved) return;
790
+ isResolved = true;
791
+
792
+ if (img.naturalWidth === 0 || img.naturalHeight === 0) {
793
+ reject(new Error(`Invalid dimensions: ${url}`));
794
+ return;
795
+ }
796
+
797
+ this.imageCache.set(url, img);
798
+ resolve(img);
799
+ };
800
+ });
801
+ }
802
+
803
+ initializeViewer() {
804
+ document.getElementById('loadingScreen').style.display = 'none';
805
+ document.getElementById('mainViewer').style.display = 'block';
806
+ document.getElementById('totalImages').textContent = this.validImages.length;
807
+
808
+ this.currentIndex = 0;
809
+ this.displayImage(this.currentIndex);
810
+
811
+ setTimeout(() => this.startBackgroundCaching(), 1000);
812
+ }
813
+
814
+ displayImage(index) {
815
+ if (index < 0 || index >= this.validImages.length) {
816
+ console.error(`Invalid index ${index}, valid range: 0-${this.validImages.length - 1}`);
817
+ return;
818
+ }
819
+
820
+ const imageId = this.validImages[index];
821
+ this.currentImageData = this.dataset[imageId];
822
+ this.currentIndex = index;
823
+
824
+ this.boxStates = {};
825
+ this.currentImageData.labels.forEach((_, i) => {
826
+ this.boxStates[i] = true;
827
+ });
828
+
829
+ this.updateNavigationButtons();
830
+
831
+ const img = document.getElementById('mainImage');
832
+ const cachedImage = this.imageCache.get(this.currentImageData.url);
833
+ img.src = cachedImage.src;
834
+
835
+ this.updateMetadata(imageId);
836
+ this.updateLabels();
837
+ this.drawBoundingBoxes();
838
+ this.updateGlobalToggleButton();
839
+
840
+ if (index >= this.validImages.length - 5 && !this.isBackgroundLoading && this.validImages.length < this.MAX_IMAGES) {
841
+ this.startBackgroundCaching();
842
+ }
843
+ }
844
+
845
+ updateNavigationButtons() {
846
+ document.getElementById('currentIndex').textContent = this.currentIndex + 1;
847
+ document.getElementById('totalImages').textContent = this.validImages.length;
848
+ document.getElementById('prevBtn').disabled = this.currentIndex === 0;
849
+ document.getElementById('nextBtn').disabled = this.currentIndex >= this.validImages.length - 1;
850
+ }
851
+
852
+ updateMetadata(imageId) {
853
+ document.getElementById('imageId').textContent = imageId;
854
+ document.getElementById('dimensions').textContent =
855
+ `${this.currentImageData.width} × ${this.currentImageData.height}px`;
856
+
857
+ const sourceInfo = document.getElementById('sourceInfo');
858
+ sourceInfo.innerHTML = `
859
+ <strong>Source:</strong> ${this.currentImageData.source}<br>
860
+ <strong>PHash:</strong> ${this.currentImageData.phash}<br>
861
+ <strong>Bounding Boxes:</strong> ${this.currentImageData.box_num}<br>
862
+ <strong>Categories:</strong> ${this.currentImageData.category_num}<br>
863
+ <strong>VLM caption tokens (CLIP):</strong> ${this.currentImageData.vlm_clip_tok_num}<br>
864
+ <strong>Web caption tokens (CLIP):</strong> ${this.currentImageData.web_clip_tok_num}
865
+ `;
866
+
867
+ const sourceMeta = document.getElementById('sourceMeta');
868
+ if (this.currentImageData.source_meta && typeof this.currentImageData.source_meta === 'object') {
869
+ let metaHTML = '';
870
+ Object.entries(this.currentImageData.source_meta).forEach(([key, value]) => {
871
+ let displayValue = value;
872
+ if (typeof value === 'number') {
873
+ displayValue = Number.isInteger(value) ? value : value.toFixed(3);
874
+ } else if (typeof value === 'object') {
875
+ displayValue = JSON.stringify(value, null, 2);
876
+ }
877
+ metaHTML += `<strong>${key}:</strong> ${displayValue}<br>`;
878
+ });
879
+ sourceMeta.innerHTML = metaHTML;
880
+ } else {
881
+ sourceMeta.innerHTML = '<em>No source meta available</em>';
882
+ }
883
+
884
+ document.getElementById('vlmCaption').textContent = this.currentImageData.vlm_description;
885
+ document.getElementById('webCaption').textContent = this.currentImageData.web_caption;
886
+ }
887
+
888
+ updateLabels() {
889
+ const labelsContainer = document.getElementById('labelsContainer');
890
+ labelsContainer.innerHTML = '';
891
+
892
+ this.currentImageData.labels.forEach((label, index) => {
893
+ const labelTag = document.createElement('div');
894
+ labelTag.className = `label-tag ${this.boxStates[index] ? 'active' : 'inactive'}`;
895
+ labelTag.textContent = label;
896
+ labelTag.onclick = () => this.toggleBox(index);
897
+ labelTag.style.borderColor = this.boxColors[index % this.boxColors.length];
898
+ labelsContainer.appendChild(labelTag);
899
+ });
900
+ }
901
+
902
+ drawBoundingBoxes() {
903
+ const existingBoxes = document.querySelectorAll('.bounding-box, .box-label');
904
+ existingBoxes.forEach(element => element.remove());
905
+
906
+ const container = document.getElementById('imageContainer');
907
+ const img = document.getElementById('mainImage');
908
+
909
+ setTimeout(() => {
910
+ const containerRect = container.getBoundingClientRect();
911
+ const imgRect = img.getBoundingClientRect();
912
+
913
+ const displayedWidth = imgRect.width;
914
+ const displayedHeight = imgRect.height;
915
+
916
+ const scaleX = displayedWidth / this.currentImageData.width;
917
+ const scaleY = displayedHeight / this.currentImageData.height;
918
+
919
+ const offsetX = imgRect.left - containerRect.left;
920
+ const offsetY = imgRect.top - containerRect.top;
921
+
922
+ this.currentImageData.bboxes.forEach((bbox, index) => {
923
+ const [x1, y1, x2, y2] = bbox;
924
+ const color = this.boxColors[index % this.boxColors.length];
925
+
926
+ const boxDiv = document.createElement('div');
927
+ boxDiv.className = `bounding-box ${this.boxStates[index] ? 'active' : 'inactive'}`;
928
+ boxDiv.style.left = `${offsetX + (x1 * scaleX)}px`;
929
+ boxDiv.style.top = `${offsetY + (y1 * scaleY)}px`;
930
+ boxDiv.style.width = `${(x2 - x1) * scaleX}px`;
931
+ boxDiv.style.height = `${(y2 - y1) * scaleY}px`;
932
+ boxDiv.style.borderColor = color;
933
+ container.appendChild(boxDiv);
934
+
935
+ const labelDiv = document.createElement('div');
936
+ labelDiv.className = `box-label ${this.boxStates[index] ? 'active' : 'inactive'}`;
937
+ labelDiv.textContent = this.currentImageData.labels[index];
938
+ labelDiv.style.backgroundColor = color;
939
+ labelDiv.style.left = `${offsetX + (x1 * scaleX) + 4}px`;
940
+ labelDiv.style.top = `${offsetY + (y1 * scaleY) + 4}px`;
941
+ container.appendChild(labelDiv);
942
+ });
943
+ }, 50);
944
+ }
945
+
946
+ toggleBox(index) {
947
+ this.boxStates[index] = !this.boxStates[index];
948
+ this.updateLabels();
949
+ this.drawBoundingBoxes();
950
+ this.updateGlobalToggleButton();
951
+ }
952
+
953
+ updateGlobalToggleButton() {
954
+ const toggleBtn = document.getElementById('globalToggle');
955
+ const visibleBoxes = Object.values(this.boxStates).filter(state => state).length;
956
+
957
+ if (visibleBoxes === 0) {
958
+ toggleBtn.className = 'toggle-btn inactive';
959
+ toggleBtn.textContent = 'Show All Boxes';
960
+ } else {
961
+ toggleBtn.className = 'toggle-btn active';
962
+ toggleBtn.textContent = 'Hide All Boxes';
963
+ }
964
+ }
965
+
966
+ toggleAllBoxes() {
967
+ const visibleBoxes = Object.values(this.boxStates).filter(state => state).length;
968
+ const shouldShowAll = visibleBoxes === 0;
969
+
970
+ Object.keys(this.boxStates).forEach(key => {
971
+ this.boxStates[key] = shouldShowAll;
972
+ });
973
+
974
+ this.updateLabels();
975
+ this.drawBoundingBoxes();
976
+ this.updateGlobalToggleButton();
977
+ }
978
+
979
+ navigatePrevious() {
980
+ if (this.currentIndex > 0) {
981
+ this.displayImage(this.currentIndex - 1);
982
+ }
983
+ }
984
+
985
+ navigateNext() {
986
+ if (this.currentIndex < this.validImages.length - 1) {
987
+ this.displayImage(this.currentIndex + 1);
988
+ }
989
+ }
990
+
991
+ showError(message) {
992
+ document.getElementById('loadingScreen').style.display = 'none';
993
+ document.getElementById('errorMessage').style.display = 'block';
994
+ document.getElementById('errorMessage').innerHTML = `
995
+ ❌ ${message}<br>
996
+ <small>Please check the console for more details</small>
997
+ `;
998
+ }
999
+ }
1000
+
1001
+ let viewer = null;
1002
+
1003
+ function navigatePrevious() {
1004
+ if (viewer) viewer.navigatePrevious();
1005
+ }
1006
+
1007
+ function navigateNext() {
1008
+ if (viewer) viewer.navigateNext();
1009
+ }
1010
+
1011
+ function toggleAllBoxes() {
1012
+ if (viewer) viewer.toggleAllBoxes();
1013
+ }
1014
+
1015
+ document.addEventListener('keydown', function(e) {
1016
+ if (!viewer) return;
1017
+
1018
+ if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') {
1019
+ e.preventDefault();
1020
+ viewer.navigatePrevious();
1021
+ } else if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') {
1022
+ e.preventDefault();
1023
+ viewer.navigateNext();
1024
+ } else if (e.key === ' ') {
1025
+ e.preventDefault();
1026
+ viewer.toggleAllBoxes();
1027
+ }
1028
+ });
1029
+
1030
+ window.addEventListener('resize', () => {
1031
+ if (viewer && viewer.currentImageData) {
1032
+ setTimeout(() => viewer.drawBoundingBoxes(), 100);
1033
+ }
1034
+ });
1035
+
1036
+ document.addEventListener('DOMContentLoaded', () => {
1037
+ viewer = new DatasetViewer();
1038
+ viewer.loadDataset();
1039
+ });
1040
+ </script>
1041
+ </body>
1042
+ </html>