Fahd-B commited on
Commit
6830417
ยท
verified ยท
1 Parent(s): 539eabc

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +270 -56
index.js CHANGED
@@ -1,76 +1,290 @@
1
- import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.7.6';
 
 
 
2
 
3
  // Reference the elements that we will need
4
- const status = document.getElementById('status');
5
- const fileUpload = document.getElementById('upload');
6
- const imageContainer = document.getElementById('container');
7
- const example = document.getElementById('example');
 
 
 
 
 
 
 
 
8
 
9
- const EXAMPLE_URL = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/city-streets.jpg';
 
 
 
 
 
 
10
 
11
- // Create a new object detection pipeline
12
- status.textContent = 'Loading model...';
13
- const detector = await pipeline('object-detection', 'Xenova/detr-resnet-50');
14
- status.textContent = 'Ready';
15
 
16
- example.addEventListener('click', (e) => {
17
- e.preventDefault();
18
- detect(EXAMPLE_URL);
 
 
 
 
 
19
  });
20
 
21
- fileUpload.addEventListener('change', function (e) {
22
- const file = e.target.files[0];
23
- if (!file) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  return;
25
  }
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  const reader = new FileReader();
 
 
28
 
29
- // Set up a callback when the file is loaded
30
- reader.onload = e2 => detect(e2.target.result);
 
 
 
 
 
31
 
 
 
32
  reader.readAsDataURL(file);
33
- });
 
 
 
 
 
 
34
 
 
 
 
 
 
 
 
35
 
36
- // Detect objects in the image
37
- async function detect(img) {
38
- imageContainer.innerHTML = '';
39
- imageContainer.style.backgroundImage = `url(${img})`;
40
 
41
- status.textContent = 'Analysing...';
42
- const output = await detector(img, {
43
- threshold: 0.5,
44
- percentage: true,
45
- });
46
- status.textContent = '';
47
- output.forEach(renderBox);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
 
50
- // Render a bounding box and label on the image
51
- function renderBox({ box, label }) {
52
- const { xmax, xmin, ymax, ymin } = box;
53
-
54
- // Generate a random color for the box
55
- const color = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, 0);
56
-
57
- // Draw the box
58
- const boxElement = document.createElement('div');
59
- boxElement.className = 'bounding-box';
60
- Object.assign(boxElement.style, {
61
- borderColor: color,
62
- left: 100 * xmin + '%',
63
- top: 100 * ymin + '%',
64
- width: 100 * (xmax - xmin) + '%',
65
- height: 100 * (ymax - ymin) + '%',
66
- })
67
-
68
- // Draw label
69
- const labelElement = document.createElement('span');
70
- labelElement.textContent = label;
71
- labelElement.className = 'bounding-box-label';
72
- labelElement.style.backgroundColor = color;
73
-
74
- boxElement.appendChild(labelElement);
75
- imageContainer.appendChild(boxElement);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  }
 
 
 
 
 
 
 
 
 
 
 
1
+ import { AutoProcessor, CLIPVisionModelWithProjection, RawImage, env } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.6.0';
2
+
3
+ // Since we will download the model from the Hugging Face Hub, we can skip the local model check
4
+ env.allowLocalModels = false;
5
 
6
  // Reference the elements that we will need
7
+ const statusText = document.getElementById('status-text');
8
+ const fileUpload = document.getElementById('file-upload');
9
+ const dropZone = document.getElementById('drop-zone');
10
+ const imagePreview1 = document.getElementById('image-preview-1');
11
+ const imagePreview2 = document.getElementById('image-preview-2');
12
+ const meterContainer = document.getElementById('meter-container');
13
+ const spinner = document.querySelector('.spinner');
14
+ const showGraphBtn = document.getElementById('show-graph-btn');
15
+ const graphModal = document.getElementById('graph-modal');
16
+ const closeModalBtn = document.querySelector('.close-button');
17
+ const resetZoomBtn = document.getElementById('reset-zoom-btn');
18
+ const graphContainerModal = document.getElementById('graph-container-modal');
19
 
20
+ // Load processor and vision model for more direct embedding control
21
+ statusText.textContent = 'Loading model...';
22
+ spinner.style.display = 'block';
23
+ const processor = await AutoProcessor.from_pretrained('Xenova/clip-vit-base-patch16');
24
+ const vision_model = await CLIPVisionModelWithProjection.from_pretrained('Xenova/clip-vit-base-patch16');
25
+ statusText.textContent = 'Ready';
26
+ spinner.style.display = 'none';
27
 
28
+ let imageSrc1 = null;
29
+ let imageSrc2 = null;
30
+ let lastEmbeds = null;
 
31
 
32
+ // Initial setup of upload placeholders
33
+ clearUploads();
34
+
35
+
36
+ // Prevent default drag behaviors
37
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
38
+ dropZone.addEventListener(eventName, preventDefaults, false);
39
+ document.body.addEventListener(eventName, preventDefaults, false);
40
  });
41
 
42
+ // Highlight drop zone when item is dragged over it
43
+ ['dragenter', 'dragover'].forEach(eventName => {
44
+ dropZone.addEventListener(eventName, () => dropZone.classList.add('highlight'), false);
45
+ });
46
+
47
+ ['dragleave', 'drop'].forEach(eventName => {
48
+ dropZone.addEventListener(eventName, () => dropZone.classList.remove('highlight'), false);
49
+ });
50
+
51
+ // Handle dropped files
52
+ dropZone.addEventListener('drop', handleDrop, false);
53
+
54
+ // Handle clear button click
55
+ const clearBtn = document.getElementById('clear-btn');
56
+ clearBtn.addEventListener('click', clearUploads);
57
+
58
+ // Handle file selection via click
59
+ fileUpload.addEventListener('change', handleFileSelect);
60
+
61
+ // Modal event listeners
62
+ showGraphBtn.addEventListener('click', () => {
63
+ if (lastEmbeds) {
64
+ graphModal.style.display = 'block';
65
+ renderEmbeddingGraph(lastEmbeds.embeds1, lastEmbeds.embeds2);
66
+ }
67
+ });
68
+
69
+ closeModalBtn.addEventListener('click', () => {
70
+ graphModal.style.display = 'none';
71
+ });
72
+
73
+ window.addEventListener('click', (event) => {
74
+ if (event.target == graphModal) {
75
+ graphModal.style.display = 'none';
76
+ }
77
+ });
78
+
79
+ function preventDefaults(e) {
80
+ e.preventDefault();
81
+ e.stopPropagation();
82
+ }
83
+
84
+ function handleDrop(e) {
85
+ const dt = e.dataTransfer;
86
+ const files = dt.files;
87
+ handleFiles(files);
88
+ }
89
+
90
+ function handleFileSelect(e) {
91
+ handleFiles(e.target.files);
92
+ }
93
+
94
+ function handleFiles(files) {
95
+ const filesArray = Array.from(files);
96
+ if (filesArray.length === 0) {
97
  return;
98
  }
99
 
100
+ // If no image is uploaded yet, fill the first slot. Otherwise, fill the second.
101
+ if (!imageSrc1) {
102
+ handleIndividualFile(filesArray[0], '1');
103
+ if (filesArray.length > 1) {
104
+ handleIndividualFile(filesArray[1], '2');
105
+ }
106
+ } else {
107
+ handleIndividualFile(filesArray[0], '2');
108
+ }
109
+ }
110
+
111
+ function handleIndividualFile(file, target) {
112
+ if (!file) {
113
+ return;
114
+ }
115
+
116
  const reader = new FileReader();
117
+ reader.onload = function (e2) {
118
+ const imageSrc = e2.target.result;
119
 
120
+ if (target === '1') {
121
+ imageSrc1 = imageSrc;
122
+ imagePreview1.innerHTML = `<img src="${imageSrc1}" alt="uploaded image 1">`;
123
+ } else if (target === '2') {
124
+ imageSrc2 = imageSrc;
125
+ imagePreview2.innerHTML = `<img src="${imageSrc2}" alt="uploaded image 2">`;
126
+ }
127
 
128
+ checkAndCompare();
129
+ };
130
  reader.readAsDataURL(file);
131
+ }
132
+
133
+ function checkAndCompare() {
134
+ if (imageSrc1 && imageSrc2) {
135
+ compareImages(imageSrc1, imageSrc2);
136
+ }
137
+ }
138
 
139
+ function clearUploads() {
140
+ const placeholder = `<div class="placeholder">
141
+ <i class="fas fa-image"></i>
142
+ <p>Image preview</p>
143
+ </div>`;
144
+ imagePreview1.innerHTML = placeholder;
145
+ imagePreview2.innerHTML = placeholder;
146
 
147
+ imageSrc1 = null;
148
+ imageSrc2 = null;
 
 
149
 
150
+ lastEmbeds = null;
151
+ showGraphBtn.style.display = 'none';
152
+ // Reset file input
153
+ fileUpload.value = '';
154
+
155
+ meterContainer.innerHTML = '';
156
+ }
157
+
158
+ // Function to calculate cosine similarity between two vectors
159
+ function cosineSimilarity(vecA, vecB) {
160
+ let dotProduct = 0.0;
161
+ let normA = 0.0;
162
+ let normB = 0.0;
163
+
164
+ for (let i = 0; i < vecA.length; i++) {
165
+ dotProduct += vecA[i] * vecB[i];
166
+ normA += vecA[i] * vecA[i];
167
+ normB += vecB[i] * vecB[i];
168
+ }
169
+
170
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
171
+ }
172
+
173
+ // Compare the two images using direct embedding calculation
174
+ async function compareImages(img1, img2) {
175
+ statusText.textContent = 'Extracting embeddings...';
176
+ spinner.style.display = 'block';
177
+
178
+ try {
179
+ // Load images using RawImage
180
+ const image1 = await RawImage.read(img1);
181
+ const image2 = await RawImage.read(img2);
182
+
183
+ // Process images and compute embeddings
184
+ const image_inputs1 = await processor(image1);
185
+ const image_inputs2 = await processor(image2);
186
+
187
+ const { image_embeds: embeds1 } = await vision_model(image_inputs1);
188
+ const { image_embeds: embeds2 } = await vision_model(image_inputs2);
189
+
190
+ // Calculate cosine similarity
191
+ const similarity = cosineSimilarity(embeds1.data, embeds2.data);
192
+ lastEmbeds = { embeds1: embeds1.data, embeds2: embeds2.data };
193
+
194
+ statusText.textContent = 'Ready';
195
+ spinner.style.display = 'none';
196
+ renderResults(similarity);
197
+ } catch (error) {
198
+ statusText.textContent = '';
199
+ spinner.style.display = 'none';
200
+ meterContainer.innerHTML = `<div class="error"><p>Failed to compare images: ${error.message}</p></div>`;
201
+ console.error('Comparison error:', error);
202
+ }
203
+ }
204
+
205
+ // Render the comparison results
206
+ function renderResults(similarity) {
207
+ meterContainer.innerHTML = '';
208
+
209
+ // Show the button
210
+ showGraphBtn.style.display = 'block';
211
+
212
+ // Create similarity meter
213
+ const meterElement = document.createElement('div'); // This will be a wrapper
214
+ meterElement.className = 'similarity-meter';
215
+
216
+ const score = Math.round(similarity * 100);
217
+ const meterValue = Math.max(0, Math.min(100, score));
218
+
219
+ meterElement.innerHTML = `
220
+ <div class="meter-label">Similarity Score: ${meterValue}%</div>
221
+ <div class="meter-container">
222
+ <div class="meter-bar" style="width: ${meterValue}%"></div>
223
+ </div>
224
+ <div class="meter-description">${getSimilarityDescription(similarity)}</div>
225
+ `;
226
+
227
+ meterContainer.appendChild(meterElement);
228
  }
229
 
230
+ function renderEmbeddingGraph(embeds1, embeds2) {
231
+ graphContainerModal.innerHTML = `
232
+ <h3 class="graph-title">Embedding Visualization</h3>
233
+ <canvas id="embedding-chart"></canvas>
234
+ `;
235
+
236
+ const ctx = document.getElementById('embedding-chart').getContext('2d');
237
+ new Chart(ctx, {
238
+ type: 'line',
239
+ data: {
240
+ labels: Array.from({ length: embeds1.length }, (_, i) => i), // Dimension index
241
+ datasets: [{
242
+ label: 'Image 1 Embedding',
243
+ data: embeds1,
244
+ borderColor: 'rgba(0, 123, 255, 0.8)',
245
+ backgroundColor: 'rgba(0, 123, 255, 0.1)',
246
+ borderWidth: 1,
247
+ pointRadius: 0,
248
+ }, {
249
+ label: 'Image 2 Embedding',
250
+ data: embeds2,
251
+ borderColor: 'rgba(40, 167, 69, 0.8)',
252
+ backgroundColor: 'rgba(40, 167, 69, 0.1)',
253
+ borderWidth: 1,
254
+ pointRadius: 0,
255
+ }]
256
+ },
257
+ options: {
258
+ responsive: true,
259
+ plugins: {
260
+ legend: { position: 'top' },
261
+ zoom: {
262
+ pan: {
263
+ enabled: true,
264
+ mode: 'xy',
265
+ modifierKey: null, // Allow panning without holding a key
266
+ },
267
+ zoom: {
268
+ wheel: {
269
+ enabled: true,
270
+ },
271
+ pinch: {
272
+ enabled: true
273
+ },
274
+ mode: 'xy',
275
+ }
276
+ }
277
+ }
278
+ }
279
+ });
280
  }
281
+
282
+ function getSimilarityDescription(similarity) {
283
+ if (similarity > 0.9) {
284
+ return "๐Ÿ”ฅ Extremely similar - These images are nearly identical!";
285
+ } else if (similarity > 0.7) {
286
+ return "๐Ÿ‘ Very similar - These images share strong visual characteristics.";
287
+ } else {
288
+ return "๐Ÿšซ Not similar - These images appear to be very different.";
289
+ }
290
+ }