Spaces:
Runtime error
Runtime error
Commit
·
d9d5d76
1
Parent(s):
567b5f3
Update templates/index.html
Browse filesréécriture complète du front pour permettre l'upload de deux images et l'affichage de euclid
- templates/index.html +72 -560
templates/index.html
CHANGED
@@ -1,577 +1,89 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
flex-wrap: wrap;
|
15 |
-
justify-content: space-between;
|
16 |
-
padding: 4px;
|
17 |
-
margin: 2px 0;
|
18 |
-
font-family: monospace;
|
19 |
-
font-size: 12px;
|
20 |
-
align-items: center;
|
21 |
-
}
|
22 |
-
.metric-label {
|
23 |
-
flex: 1 1 auto;
|
24 |
-
text-align: left;
|
25 |
-
padding-right: 5px;
|
26 |
-
min-width: 150px;
|
27 |
-
}
|
28 |
-
.metric-value {
|
29 |
-
flex: 0 0 60px;
|
30 |
-
text-align: right;
|
31 |
-
font-weight: 500;
|
32 |
-
}
|
33 |
-
.result-card {
|
34 |
-
margin-bottom: 20px;
|
35 |
-
padding: 15px;
|
36 |
-
border: 1px solid #ddd;
|
37 |
-
border-radius: 8px;
|
38 |
-
}
|
39 |
-
.preset-image-container {
|
40 |
-
width: 100%;
|
41 |
-
position: relative;
|
42 |
-
overflow: hidden;
|
43 |
-
}
|
44 |
-
.preset-image {
|
45 |
-
cursor: pointer;
|
46 |
-
transition: transform 0.2s;
|
47 |
-
width: 100%;
|
48 |
-
height: auto;
|
49 |
-
max-height: none;
|
50 |
-
object-fit: contain;
|
51 |
-
}
|
52 |
-
.preset-image:hover {
|
53 |
-
transform: scale(1.05);
|
54 |
-
}
|
55 |
-
.dropzone {
|
56 |
-
border: 2px dashed #ccc;
|
57 |
-
border-radius: 4px;
|
58 |
-
padding: 20px;
|
59 |
-
text-align: center;
|
60 |
-
background: #f8f9fa;
|
61 |
-
margin: 20px 0;
|
62 |
-
}
|
63 |
-
.dropzone.dragover {
|
64 |
-
background: #e9ecef;
|
65 |
-
border-color: #0d6efd;
|
66 |
-
}
|
67 |
-
[data-tooltip] {
|
68 |
-
position: relative;
|
69 |
-
cursor: help;
|
70 |
-
}
|
71 |
-
[data-tooltip]:hover:after {
|
72 |
-
content: attr(data-tooltip);
|
73 |
-
position: absolute;
|
74 |
-
bottom: 100%;
|
75 |
-
left: 50%;
|
76 |
-
transform: translateX(-50%);
|
77 |
-
background: #333;
|
78 |
-
color: white;
|
79 |
-
padding: 5px 10px;
|
80 |
-
border-radius: 4px;
|
81 |
-
font-size: 12px;
|
82 |
-
white-space: pre-wrap;
|
83 |
-
width: 200px;
|
84 |
-
z-index: 1000;
|
85 |
-
}
|
86 |
-
.image-caption {
|
87 |
-
font-style: italic;
|
88 |
-
font-size: 0.8rem;
|
89 |
-
color: #666;
|
90 |
-
margin-top: 5px;
|
91 |
-
text-align: center;
|
92 |
-
}
|
93 |
-
.loading-container {
|
94 |
-
text-align: center;
|
95 |
-
padding: 40px;
|
96 |
-
background: #f8f9fa;
|
97 |
-
border-radius: 8px;
|
98 |
-
margin: 20px 0;
|
99 |
-
}
|
100 |
-
.loading-spinner {
|
101 |
-
width: 3rem;
|
102 |
-
height: 3rem;
|
103 |
-
margin-bottom: 1rem;
|
104 |
-
}
|
105 |
-
.loading-text {
|
106 |
-
color: #6c757d;
|
107 |
-
font-size: 1.1rem;
|
108 |
-
}
|
109 |
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
display: block;
|
114 |
-
margin: 0 auto;
|
115 |
-
}
|
116 |
-
.result-image {
|
117 |
-
cursor: pointer;
|
118 |
-
transition: transform 0.2s;
|
119 |
-
max-height: 150px;
|
120 |
-
object-fit: contain;
|
121 |
-
}
|
122 |
-
.result-image:hover {
|
123 |
-
transform: scale(1.05);
|
124 |
-
}
|
125 |
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
position: relative;
|
131 |
-
}
|
132 |
-
.preset-image {
|
133 |
-
position: absolute;
|
134 |
-
top: 0;
|
135 |
-
left: 0;
|
136 |
-
width: 100%;
|
137 |
-
height: 100%;
|
138 |
-
object-fit: cover;
|
139 |
-
}
|
140 |
-
.result-card {
|
141 |
-
flex-direction: column !important;
|
142 |
-
}
|
143 |
-
.result-card .image-container {
|
144 |
-
flex-basis: 100% !important;
|
145 |
-
max-width: 100% !important;
|
146 |
-
margin-bottom: 15px;
|
147 |
-
}
|
148 |
-
.result-card .metrics-container {
|
149 |
-
flex-basis: 100% !important;
|
150 |
-
max-width: 100% !important;
|
151 |
-
padding-left: 0 !important;
|
152 |
-
}
|
153 |
-
.result-card img {
|
154 |
-
max-height: 200px !important;
|
155 |
-
width: 100%;
|
156 |
-
object-fit: cover;
|
157 |
-
}
|
158 |
-
.col-md-4 {
|
159 |
-
margin-bottom: 20px;
|
160 |
-
}
|
161 |
-
#previewContainer {
|
162 |
-
margin-bottom: 20px;
|
163 |
-
text-align: center;
|
164 |
-
}
|
165 |
-
#imagePreview {
|
166 |
-
max-width: 100%;
|
167 |
-
height: auto;
|
168 |
-
margin: 0 auto;
|
169 |
-
}
|
170 |
-
.sticky-top {
|
171 |
-
position: relative !important;
|
172 |
-
}
|
173 |
}
|
174 |
-
|
175 |
-
</head>
|
176 |
-
<body>
|
177 |
-
<div class="container mt-4">
|
178 |
-
<h1>Reward Simulator</h1>
|
179 |
-
|
180 |
-
<!-- Ligne de 2 groupes de 3 images -->
|
181 |
-
<div class="row mt-4">
|
182 |
-
<!-- Groupe 1 -->
|
183 |
-
<div class="col-md-6">
|
184 |
-
<h5>Select an original image</h5>
|
185 |
-
<div class="row">
|
186 |
-
<div class="col-4">
|
187 |
-
<div class="preset-image-container">
|
188 |
-
<img src="/static/1.webp" class="preset-image" onclick="selectPreset(1)" alt="Preset 1">
|
189 |
-
</div>
|
190 |
-
<div class="image-caption">A vibrant red rose in full bloom</div>
|
191 |
-
</div>
|
192 |
-
<div class="col-4">
|
193 |
-
<div class="preset-image-container">
|
194 |
-
<img src="/static/2.webp" class="preset-image" onclick="selectPreset(2)" alt="Preset 2">
|
195 |
-
</div>
|
196 |
-
<div class="image-caption">Fluffy orange cat portrait</div>
|
197 |
-
</div>
|
198 |
-
<div class="col-4">
|
199 |
-
<div class="preset-image-container">
|
200 |
-
<img src="/static/3.webp" class="preset-image" onclick="selectPreset(3)" alt="Preset 3">
|
201 |
-
</div>
|
202 |
-
<div class="image-caption">Underwater scene with fish</div>
|
203 |
-
</div>
|
204 |
-
</div>
|
205 |
-
</div>
|
206 |
-
|
207 |
-
<!-- Groupe 2 -->
|
208 |
-
<div class="col-md-6">
|
209 |
-
<h5>Select an AI-generated image</h5>
|
210 |
-
<div class="row">
|
211 |
-
<div class="col-4">
|
212 |
-
<div class="preset-image-container">
|
213 |
-
<img src="/static/1.webp" class="preset-image" onclick="selectPreset(4)" alt="Preset 4">
|
214 |
-
</div>
|
215 |
-
<div class="image-caption">Mountain sunrise landscape</div>
|
216 |
-
</div>
|
217 |
-
<div class="col-4">
|
218 |
-
<div class="preset-image-container">
|
219 |
-
<img src="/static/2.webp" class="preset-image" onclick="selectPreset(5)" alt="Preset 5">
|
220 |
-
</div>
|
221 |
-
<div class="image-caption">Retro-style robot</div>
|
222 |
-
</div>
|
223 |
-
<div class="col-4">
|
224 |
-
<div class="preset-image-container">
|
225 |
-
<img src="/static/3.webp" class="preset-image" onclick="selectPreset(6)" alt="Preset 6">
|
226 |
-
</div>
|
227 |
-
<div class="image-caption">Futuristic cityscape</div>
|
228 |
-
</div>
|
229 |
-
</div>
|
230 |
-
</div>
|
231 |
-
|
232 |
-
<!-- Ligne des deux zones d’upload -->
|
233 |
-
<div class="row mt-5">
|
234 |
-
<div class="col-md-6 mb-4">
|
235 |
-
<div class="dropzone" id="dropzone1">
|
236 |
-
<p>Or drag and drop an image here</p>
|
237 |
-
<input type="file" id="fileInput1" class="form-control" accept="image/*" hidden>
|
238 |
-
<button class="btn btn-outline-primary" onclick="document.getElementById('fileInput1').click()">
|
239 |
-
Select Original file
|
240 |
-
</button>
|
241 |
-
</div>
|
242 |
-
</div>
|
243 |
-
<div class="col-md-6 mb-4">
|
244 |
-
<div class="dropzone" id="dropzone2">
|
245 |
-
<p>Or drag and drop an image here</p>
|
246 |
-
<input type="file" id="fileInput2" class="form-control" accept="image/*" hidden>
|
247 |
-
<button class="btn btn-outline-primary" onclick="document.getElementById('fileInput2').click()">
|
248 |
-
Select AI generated file
|
249 |
-
</button>
|
250 |
-
</div>
|
251 |
-
</div>
|
252 |
-
</div>
|
253 |
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
type="button"
|
261 |
-
data-bs-toggle="collapse"
|
262 |
-
data-bs-target="#parametersCollapse"
|
263 |
-
aria-expanded="false"
|
264 |
-
aria-controls="parametersCollapse">
|
265 |
-
<i class="bi bi-gear"></i> Advanced Parameters
|
266 |
-
</button>
|
267 |
-
</h5>
|
268 |
-
</div>
|
269 |
-
<div class="collapse" id="parametersCollapse">
|
270 |
-
<div class="card-body">
|
271 |
-
<form id="imageForm">
|
272 |
-
<div id="parametersContainer">
|
273 |
-
<!-- Will be populated by JavaScript -->
|
274 |
-
</div>
|
275 |
-
<button type="submit" class="btn btn-primary">Update</button>
|
276 |
-
</form>
|
277 |
-
</div>
|
278 |
-
</div>
|
279 |
-
</div>
|
280 |
-
</div>
|
281 |
-
|
282 |
-
<!-- Results -->
|
283 |
-
<div class="col-12">
|
284 |
-
<div class="row">
|
285 |
-
<div id="previewContainer" class="col-md-3" style="display: none;">
|
286 |
-
<div class="sticky-top pt-3">
|
287 |
-
<img id="imagePreview" class="img-fluid mb-3 preview-image">
|
288 |
-
</div>
|
289 |
-
</div>
|
290 |
-
<div id="results" class="col-md-9">
|
291 |
-
<!-- Will be populated by JavaScript -->
|
292 |
-
</div>
|
293 |
-
</div>
|
294 |
-
</div>
|
295 |
-
</div>
|
296 |
-
|
297 |
-
<!-- Helper Section -->
|
298 |
-
<div class="row mt-5">
|
299 |
-
<div class="col-12">
|
300 |
-
<div class="card">
|
301 |
-
<div class="card-header">
|
302 |
-
<h5>Learn more</h5>
|
303 |
-
</div>
|
304 |
-
<div class="card-body">
|
305 |
-
|
306 |
-
<h6>How it works</h6>
|
307 |
-
<ol>
|
308 |
-
<li>Select or upload an image that represents AI-generated content</li>
|
309 |
-
<li>The system finds similar images that might have influenced the generation in a database of 10M images (Open Images)</li>
|
310 |
-
<li>Based on your parameters, it calculates potential rewards for:
|
311 |
-
<ul>
|
312 |
-
<li>Original image authors</li>
|
313 |
-
<li>Rights owners (e.g., stock photo companies, galleries)</li>
|
314 |
-
</ul>
|
315 |
-
</li>
|
316 |
-
</ol>
|
317 |
-
|
318 |
-
<h6>Warning</h6>
|
319 |
-
<ol>
|
320 |
-
<li>This demonstrator calculates similarities using the Open Images database alone. However, the image generators do not specify whether they have been trained with datasets from this database. As a result, calculations can be more or less biased depending on the generator used.</li>
|
321 |
-
<li>Furthermore, the infrastructures used are designed for limited use. More generally, this demonstrator is proposed for teaching and academic purposes. Its designers decline all responsibility for the relevance of the results, as well as for the user experience, which are provided without guarantee.</li>
|
322 |
-
</ol>
|
323 |
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
|
|
|
|
|
|
|
|
330 |
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
// </ul>
|
337 |
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
DINOv2 is used to extract image features of all the images in the database (10M images from Open Images).
|
342 |
-
FAISS is used to perform efficient similarity search to find the most similar images to the input image, and output the attribution scores.
|
343 |
-
<ul class="small text-muted">
|
344 |
-
<li><a href="https://github.com/facebookresearch/dinov2" target="_blank">DINOv2</a> - Vision transformer for image feature extraction</li>
|
345 |
-
<li><a href="https://github.com/facebookresearch/faiss" target="_blank">FAISS</a> - Efficient similarity search library, more specifically:
|
346 |
-
<ul>
|
347 |
-
<li>HNSW (Hierarchical Navigable Small World) for approximate nearest neighbor search</li>
|
348 |
-
<li>PQ (Product Quantization) for memory-efficient vector compression</li>
|
349 |
-
</ul>
|
350 |
-
</li>
|
351 |
-
</ul>
|
352 |
-
</p>
|
353 |
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
<i class="bi bi-file-earmark-pdf"></i> Regulatory Summary
|
360 |
-
</a>
|
361 |
-
<a href="/static/RO_Summary.pdf" class="btn btn-outline-primary" download>
|
362 |
-
<i class="bi bi-file-earmark-pdf"></i> Rights Owners Summary
|
363 |
-
</a>
|
364 |
-
</div>
|
365 |
-
</div>
|
366 |
</div>
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
<!-- Footer Section -->
|
371 |
-
<div class="row mt-5">
|
372 |
-
<div class="col-12">
|
373 |
-
<footer>
|
374 |
-
<p class="text-center text-muted">
|
375 |
-
© 2025 Reward Simulator. Apache 2.0.
|
376 |
-
</p>
|
377 |
-
</footer>
|
378 |
-
</div>
|
379 |
-
</div>
|
380 |
-
</div>
|
381 |
-
|
382 |
-
<script src="/static/js/parameters.js"></script>
|
383 |
-
<script>
|
384 |
-
// Add this new function at the beginning of your script section
|
385 |
-
function scrollToResults() {
|
386 |
-
const resultsDiv = document.getElementById('results');
|
387 |
-
if (resultsDiv) {
|
388 |
-
resultsDiv.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
389 |
-
}
|
390 |
-
}
|
391 |
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
container.innerHTML += `
|
397 |
-
<div class="mb-3">
|
398 |
-
<label class="form-label" data-tooltip="${param.tooltip}">${param.label}</label>
|
399 |
-
<input type="number" class="form-control" name="${key}" value="${param.defaultValue}">
|
400 |
-
</div>
|
401 |
-
`;
|
402 |
});
|
403 |
-
}
|
404 |
-
|
405 |
-
// Call this when the document loads
|
406 |
-
document.addEventListener('DOMContentLoaded', () => {
|
407 |
-
initializeParameters();
|
408 |
-
// Check if URL has a hash to prevent unwanted scrolling
|
409 |
-
if (!window.location.hash) {
|
410 |
-
window.scrollTo(0, 0);
|
411 |
-
}
|
412 |
-
});
|
413 |
|
414 |
-
|
415 |
-
|
416 |
-
const resultsDiv = document.getElementById('results');
|
417 |
-
resultsDiv.innerHTML = `
|
418 |
-
<div class="loading-container">
|
419 |
-
<div class="spinner-border loading-spinner text-primary" role="status">
|
420 |
-
<span class="visually-hidden">Loading...</span>
|
421 |
-
</div>
|
422 |
-
<div class="loading-text">Processing image...</div>
|
423 |
-
</div>`;
|
424 |
-
|
425 |
-
// Scroll to results immediately when processing starts
|
426 |
-
setTimeout(scrollToResults, 100);
|
427 |
-
|
428 |
-
try {
|
429 |
-
const formData = new FormData();
|
430 |
-
|
431 |
-
if (imageData instanceof Blob) {
|
432 |
-
formData.append('image', imageData, 'image.jpg');
|
433 |
-
} else if (typeof imageData === 'number') {
|
434 |
-
// For preset images, use the preset endpoint
|
435 |
-
const response = await fetch(`/select_preset/${imageData}`);
|
436 |
-
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
437 |
-
const data = await response.json();
|
438 |
-
updateResults(data);
|
439 |
-
return;
|
440 |
-
}
|
441 |
-
|
442 |
-
if (!useDefaultParams) {
|
443 |
-
// Add form parameters if not using defaults
|
444 |
-
const form = document.getElementById('imageForm');
|
445 |
-
const formElements = new FormData(form);
|
446 |
-
for (let [key, value] of formElements.entries()) {
|
447 |
-
formData.append(key, value);
|
448 |
-
}
|
449 |
-
}
|
450 |
|
451 |
-
|
452 |
-
method: 'POST',
|
453 |
-
body: formData
|
454 |
-
});
|
455 |
-
|
456 |
-
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
457 |
-
const data = await response.json();
|
458 |
-
updateResults(data);
|
459 |
-
|
460 |
-
} catch (error) {
|
461 |
-
console.error('Error:', error);
|
462 |
resultsDiv.innerHTML = `
|
463 |
-
<div class="
|
464 |
-
<
|
465 |
-
|
466 |
</div>`;
|
|
|
|
|
467 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
468 |
}
|
469 |
-
|
470 |
-
|
471 |
-
function updateResults(data) {
|
472 |
-
if (!data || !data.results || !Array.isArray(data.results)) {
|
473 |
-
throw new Error('Invalid response format from server');
|
474 |
-
}
|
475 |
-
|
476 |
-
const selectedImage = document.getElementById('imagePreview');
|
477 |
-
document.getElementById('results').innerHTML = `
|
478 |
-
${data.results.map((result, i) => `
|
479 |
-
<div class="result-card d-flex flex-row">
|
480 |
-
<div class="image-container" style="flex-basis: 25%; max-width: 25%;">
|
481 |
-
<a href="${result.other.flickr_url}" target="_blank">
|
482 |
-
<img src="${result.image_url}" class="img-fluid result-image" alt="Similar image ${i+1}">
|
483 |
-
</a>
|
484 |
-
</div>
|
485 |
-
<div class="metrics-container" style="flex-basis: 75%; max-width: 75%; padding-left: 20px;">
|
486 |
-
<div class="metric-row">
|
487 |
-
<span class="metric-label">Attribution:</span>
|
488 |
-
<span class="metric-value">${result.rewards.attribution}</span>
|
489 |
-
</div>
|
490 |
-
|
491 |
-
<div class="metric-row">
|
492 |
-
<span class="metric-label">Distance (test ligne 458):</span>
|
493 |
-
<span class="metric-value">${result.rewards.raw_similarity}</span>
|
494 |
-
</div>
|
495 |
-
|
496 |
-
<div class="metric-row">
|
497 |
-
<span class="metric-label">Author Monthly Reward:</span>
|
498 |
-
<span class="metric-value">${result.rewards.author_month_reward}</span>
|
499 |
-
</div>
|
500 |
-
<div class="metric-row">
|
501 |
-
<span class="metric-label">Right Owner Monthly Reward:</span>
|
502 |
-
<span class="metric-value">${result.rewards.ro_month_reward}</span>
|
503 |
-
</div>
|
504 |
-
<div class="metric-row">
|
505 |
-
<span class="metric-label">Author:</span>
|
506 |
-
<span class="metric-value">${result.other.realname || result.other.username}</span>
|
507 |
-
</div>
|
508 |
-
</div>
|
509 |
-
</div>
|
510 |
-
`).join('')}
|
511 |
-
`;
|
512 |
-
}
|
513 |
-
|
514 |
-
// Preset image selection handler
|
515 |
-
function selectPreset(presetNumber) {
|
516 |
-
document.getElementById('imagePreview').src = `/static/${presetNumber}.webp`;
|
517 |
-
document.getElementById('previewContainer').style.display = 'block';
|
518 |
-
processImage(presetNumber);
|
519 |
-
}
|
520 |
-
|
521 |
-
// File input handler
|
522 |
-
document.getElementById('fileInput').addEventListener('change', (e) => {
|
523 |
-
const file = e.target.files[0];
|
524 |
-
if (file && file.type.startsWith('image/')) {
|
525 |
-
const reader = new FileReader();
|
526 |
-
reader.onload = (event) => {
|
527 |
-
document.getElementById('imagePreview').src = event.target.result;
|
528 |
-
document.getElementById('previewContainer').style.display = 'block';
|
529 |
-
// Process image immediately with default parameters
|
530 |
-
fetch(event.target.result)
|
531 |
-
.then(res => res.blob())
|
532 |
-
.then(blob => processImage(blob));
|
533 |
-
};
|
534 |
-
reader.readAsDataURL(file);
|
535 |
-
}
|
536 |
-
});
|
537 |
-
|
538 |
-
// Form submit handler for custom parameters
|
539 |
-
document.getElementById('imageForm').onsubmit = async (e) => {
|
540 |
-
e.preventDefault();
|
541 |
-
const imagePreview = document.getElementById('imagePreview');
|
542 |
-
if (imagePreview && imagePreview.src) {
|
543 |
-
const response = await fetch(imagePreview.src);
|
544 |
-
const blob = await response.blob();
|
545 |
-
processImage(blob, false); // Process with custom parameters
|
546 |
-
}
|
547 |
-
};
|
548 |
-
|
549 |
-
const dropzone = document.getElementById('dropzone');
|
550 |
-
dropzone.addEventListener('dragover', (e) => {
|
551 |
-
e.preventDefault();
|
552 |
-
dropzone.classList.add('dragover');
|
553 |
-
});
|
554 |
-
dropzone.addEventListener('dragleave', () => {
|
555 |
-
dropzone.classList.remove('dragover');
|
556 |
-
});
|
557 |
-
dropzone.addEventListener('drop', (e) => {
|
558 |
-
e.preventDefault();
|
559 |
-
dropzone.classList.remove('dragover');
|
560 |
-
const file = e.dataTransfer.files[0];
|
561 |
-
if (file && file.type.startsWith('image/')) {
|
562 |
-
const reader = new FileReader();
|
563 |
-
reader.onload = (event) => {
|
564 |
-
document.getElementById('imagePreview').src = event.target.result;
|
565 |
-
document.getElementById('previewContainer').style.display = 'block';
|
566 |
-
// Process image immediately with default parameters
|
567 |
-
fetch(event.target.result)
|
568 |
-
.then(res => res.blob())
|
569 |
-
.then(blob => processImage(blob));
|
570 |
-
};
|
571 |
-
reader.readAsDataURL(file);
|
572 |
-
}
|
573 |
-
});
|
574 |
-
</script>
|
575 |
-
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
576 |
-
</body>
|
577 |
-
</html>
|
|
|
1 |
+
<script>
|
2 |
+
let file1 = null;
|
3 |
+
let file2 = null;
|
4 |
+
|
5 |
+
function updatePreview(id, file) {
|
6 |
+
const reader = new FileReader();
|
7 |
+
reader.onload = (event) => {
|
8 |
+
const previewId = id === 1 ? 'dropzone1' : 'dropzone2';
|
9 |
+
document.getElementById(previewId).innerHTML = `
|
10 |
+
<img src="${event.target.result}" class="preview-image"/>
|
11 |
+
`;
|
12 |
+
};
|
13 |
+
reader.readAsDataURL(file);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
+
// Vérifie si les deux images sont sélectionnées pour afficher le bouton
|
16 |
+
checkReadyToCompare();
|
17 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
19 |
+
document.getElementById('fileInput1').addEventListener('change', (e) => {
|
20 |
+
file1 = e.target.files[0];
|
21 |
+
if (file1 && file1.type.startsWith('image/')) {
|
22 |
+
updatePreview(1, file1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
}
|
24 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
+
document.getElementById('fileInput2').addEventListener('change', (e) => {
|
27 |
+
file2 = e.target.files[0];
|
28 |
+
if (file2 && file2.type.startsWith('image/')) {
|
29 |
+
updatePreview(2, file2);
|
30 |
+
}
|
31 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
|
33 |
+
function checkReadyToCompare() {
|
34 |
+
const compareContainer = document.getElementById('compareButtonContainer');
|
35 |
+
if (file1 && file2) {
|
36 |
+
compareContainer.innerHTML = `
|
37 |
+
<button class="btn btn-primary" onclick="processImages()">Compare Images</button>
|
38 |
+
`;
|
39 |
+
} else {
|
40 |
+
compareContainer.innerHTML = '';
|
41 |
+
}
|
42 |
+
}
|
43 |
|
44 |
+
async function processImages() {
|
45 |
+
if (!file1 || !file2) {
|
46 |
+
alert('Please select two images first.');
|
47 |
+
return;
|
48 |
+
}
|
|
|
49 |
|
50 |
+
const formData = new FormData();
|
51 |
+
formData.append('image1', file1);
|
52 |
+
formData.append('image2', file2);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
|
54 |
+
const resultsDiv = document.getElementById('results');
|
55 |
+
resultsDiv.innerHTML = `
|
56 |
+
<div class="loading-container">
|
57 |
+
<div class="spinner-border loading-spinner text-primary" role="status">
|
58 |
+
<span class="visually-hidden">Loading...</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
</div>
|
60 |
+
<div class="loading-text">Processing images...</div>
|
61 |
+
</div>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
|
63 |
+
try {
|
64 |
+
const response = await fetch('/process', {
|
65 |
+
method: 'POST',
|
66 |
+
body: formData
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
|
69 |
+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
70 |
+
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
|
72 |
+
if (data.distance !== undefined) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
resultsDiv.innerHTML = `
|
74 |
+
<div class="card p-4 text-center">
|
75 |
+
<h4>Euclidean Distance</h4>
|
76 |
+
<p class="display-6">${data.distance.toFixed(4)}</p>
|
77 |
</div>`;
|
78 |
+
} else {
|
79 |
+
throw new Error('Invalid server response.');
|
80 |
}
|
81 |
+
} catch (error) {
|
82 |
+
console.error('Error:', error);
|
83 |
+
resultsDiv.innerHTML = `
|
84 |
+
<div class="alert alert-danger" role="alert">
|
85 |
+
Error: ${error.message}
|
86 |
+
</div>`;
|
87 |
}
|
88 |
+
}
|
89 |
+
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|