Subh775 commited on
Commit
f0a3b89
·
verified ·
1 Parent(s): d222532

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +77 -108
index.html CHANGED
@@ -52,6 +52,19 @@
52
  font-size: 1.5em;
53
  font-weight: 500;
54
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  .canvas-container {
56
  background: #000;
57
  border-radius: 8px;
@@ -96,21 +109,19 @@
96
  .tool-btn.active { background: var(--primary); color: white; }
97
  .predict-btn {
98
  background: linear-gradient(135deg, #FF6B35, #F7931E);
99
- color: white;
100
- padding: 15px 30px;
101
- font-size: 18px;
102
- width: 100%;
103
  }
 
104
  .predict-btn:disabled { background: #666; cursor: not-allowed; }
105
- .output-tabs {
106
- display: flex;
107
- gap: 10px;
108
- margin-bottom: 20px;
109
- }
110
  .download-btn {
111
  background: linear-gradient(135deg, #9C27B0, #673AB7);
112
- color: white;
 
113
  }
 
114
  .hidden { display: none !important; }
115
  @media (max-width: 900px) {
116
  .main-content { grid-template-columns: 1fr; }
@@ -123,6 +134,9 @@
123
  <div class="main-content">
124
  <div class="column">
125
  <h2>Input & Controls</h2>
 
 
 
126
  <div class="canvas-container">
127
  <canvas id="input-canvas"></canvas>
128
  </div>
@@ -151,7 +165,7 @@
151
  <div class="output-controls">
152
  <div class="tool-group" id="transparency-control">
153
  <label>Mask Transparency</label>
154
- <input type="range" id="transparency" min="0" max="100" value="40">
155
  </div>
156
  <button class="download-btn" id="download-btn" disabled>Download Mask</button>
157
  </div>
@@ -159,18 +173,24 @@
159
  </div>
160
  </div>
161
 
 
162
  <script>
 
 
 
 
 
 
 
 
163
  class SegmentationApp {
164
  constructor() {
165
- // Canvases
166
  this.inputCanvas = document.getElementById('input-canvas');
167
  this.inputCtx = this.inputCanvas.getContext('2d');
168
  this.outputCanvas = document.getElementById('output-canvas');
169
  this.outputCtx = this.outputCanvas.getContext('2d');
170
  this.maskCanvas = document.createElement('canvas');
171
  this.maskCtx = this.maskCanvas.getContext('2d');
172
-
173
- // State
174
  this.currentTool = 'point';
175
  this.brushSize = 8;
176
  this.isDrawing = false;
@@ -178,37 +198,31 @@
178
  this.predictedMask = null;
179
  this.annotations = [];
180
  this.currentView = 'filled';
181
-
182
- // DOM Elements
183
  this.predictBtn = document.getElementById('predict-btn');
184
  this.downloadBtn = document.getElementById('download-btn');
185
  this.transparencySlider = document.getElementById('transparency');
186
  this.transparencyControl = document.getElementById('transparency-control');
187
-
188
  this.initEventListeners();
189
  }
190
 
191
  initEventListeners() {
192
- // Use a single file input for both click and drop
193
  const fileInput = document.createElement('input');
194
  fileInput.type = 'file';
195
  fileInput.accept = 'image/*';
196
  fileInput.onchange = e => this.handleFile(e.target.files[0]);
197
 
198
- this.inputCanvas.parentElement.onclick = () => fileInput.click();
199
- this.inputCanvas.parentElement.ondragover = e => e.preventDefault();
200
- this.inputCanvas.parentElement.ondrop = e => {
 
201
  e.preventDefault();
202
  this.handleFile(e.dataTransfer.files[0]);
203
  };
204
 
205
- // Drawing events
206
  this.inputCanvas.addEventListener('mousedown', e => this.startDrawing(e));
207
  this.inputCanvas.addEventListener('mousemove', e => this.draw(e));
208
- this.inputCanvas.addEventListener('mouseup', () => this.stopDrawing());
209
- this.inputCanvas.addEventListener('mouseout', () => this.stopDrawing());
210
 
211
- // Tool selection
212
  document.querySelectorAll('.tool-btn[data-tool]').forEach(btn => {
213
  btn.onclick = (e) => {
214
  document.querySelectorAll('.tool-btn[data-tool]').forEach(b => b.classList.remove('active'));
@@ -217,12 +231,10 @@
217
  };
218
  });
219
 
220
- // View selection
221
  document.getElementById('view-filled').onclick = () => this.setView('filled');
222
  document.getElementById('view-outline').onclick = () => this.setView('outline');
223
  document.getElementById('view-binary').onclick = () => this.setView('binary');
224
 
225
- // Controls
226
  this.transparencySlider.oninput = () => this.renderOutput();
227
  document.getElementById('clear-annotations').onclick = () => this.clearAnnotations();
228
  this.predictBtn.onclick = () => this.predict();
@@ -238,6 +250,11 @@
238
  this.uploadedImage = img;
239
  this.reset();
240
  this.drawImageScaled(this.inputCanvas, this.inputCtx, img);
 
 
 
 
 
241
  };
242
  img.src = e.target.result;
243
  };
@@ -253,14 +270,13 @@
253
  }
254
 
255
  drawImageScaled(canvas, ctx, img) {
256
- const hRatio = canvas.width / img.width;
257
- const vRatio = canvas.height / img.height;
258
  const ratio = Math.min(hRatio, vRatio);
259
- const centerShift_x = (canvas.width - img.width * ratio) / 2;
260
- const centerShift_y = (canvas.height - img.height * ratio) / 2;
261
  ctx.clearRect(0, 0, canvas.width, canvas.height);
262
- ctx.drawImage(img, 0, 0, img.width, img.height,
263
- centerShift_x, centerShift_y, img.width * ratio, img.height * ratio);
264
  }
265
 
266
  getCanvasCoordinates(e) {
@@ -289,20 +305,16 @@
289
  stopDrawing() { this.isDrawing = false; }
290
 
291
  drawOnCanvas(x1, y1, x2 = null, y2 = null) {
292
- // This function draws on both the visible and hidden scribble canvases
293
  [this.inputCtx, this.maskCtx].forEach((ctx, i) => {
294
  ctx.beginPath();
295
- ctx.lineWidth = this.brushSize;
296
  ctx.strokeStyle = (i === 0) ? '#4CAF50' : 'white';
297
  ctx.fillStyle = (i === 0) ? '#4CAF50' : 'white';
298
  ctx.lineCap = 'round';
299
- if (x2 && y2) { // Line
300
- ctx.moveTo(x1, y1);
301
- ctx.lineTo(x2, y2);
302
- ctx.stroke();
303
- } else { // Point
304
- ctx.arc(x1, y1, this.brushSize, 0, Math.PI * 2);
305
- ctx.fill();
306
  }
307
  });
308
  this.annotations.push(true);
@@ -310,6 +322,7 @@
310
  }
311
 
312
  clearAnnotations() {
 
313
  this.reset();
314
  this.drawImageScaled(this.inputCanvas, this.inputCtx, this.uploadedImage);
315
  this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height);
@@ -317,9 +330,7 @@
317
 
318
  async predict() {
319
  if (!this.uploadedImage || this.annotations.length === 0) return;
320
- this.predictBtn.disabled = true;
321
- this.predictBtn.innerText = "Processing...";
322
-
323
  try {
324
  const payload = {
325
  image: this.inputCanvas.toDataURL('image/jpeg'),
@@ -340,7 +351,6 @@
340
  this.downloadBtn.disabled = false;
341
  };
342
  maskImg.src = result.predicted_mask;
343
-
344
  } catch (error) {
345
  console.error('Prediction failed:', error);
346
  alert('Prediction failed. Check the console for details.');
@@ -360,69 +370,42 @@
360
 
361
  renderOutput() {
362
  if (!this.predictedMask) return;
363
-
364
- this.outputCtx.clearRect(0,0, this.outputCanvas.width, this.outputCanvas.height);
365
  this.drawImageScaled(this.outputCanvas, this.outputCtx, this.uploadedImage);
366
 
367
- // Create a temporary canvas to draw the raw mask
368
- const tempCanvas = document.createElement('canvas');
369
- tempCanvas.width = this.uploadedImage.width;
370
- tempCanvas.height = this.uploadedImage.height;
371
- const tempCtx = tempCanvas.getContext('2d');
372
- tempCtx.drawImage(this.predictedMask, 0, 0);
373
- const maskData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
374
-
375
  if (this.currentView === 'binary') {
376
  this.drawImageScaled(this.outputCanvas, this.outputCtx, this.predictedMask);
377
  return;
378
  }
379
-
 
 
 
 
 
 
380
  if (this.currentView === 'filled') {
381
- const alpha = this.transparencySlider.value / 100;
382
- this.outputCtx.globalAlpha = alpha;
383
- const colorOverlay = this.createColorOverlay(maskData, [255, 0, 255]); // Magenta
384
- this.drawImageScaled(this.outputCanvas, this.outputCtx, colorOverlay);
 
 
385
  this.outputCtx.globalAlpha = 1.0;
 
386
  } else if (this.currentView === 'outline') {
387
- const mask_uint8 = new cv.Mat();
388
- const src = cv.matFromImageData(maskData);
389
- cv.cvtColor(src, mask_uint8, cv.COLOR_RGBA2GRAY, 0);
390
- let contours = new cv.MatVector();
391
- let hierarchy = new cv.Mat();
392
- cv.findContours(mask_uint8, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);
393
 
394
- const scaleRatio = this.outputCanvas.width / this.uploadedImage.width;
395
  this.outputCtx.strokeStyle = 'white';
396
  this.outputCtx.lineWidth = 2;
397
  for (let i = 0; i < contours.size(); ++i) {
398
- const contour = contours.get(i);
399
- this.outputCtx.beginPath();
400
- const d = contour.data32S;
401
- this.outputCtx.moveTo(d[0] * scaleRatio, d[1] * scaleRatio);
402
- for (let j = 2; j < d.length; j += 2) {
403
- this.outputCtx.lineTo(d[j] * scaleRatio, d[j + 1] * scaleRatio);
404
- }
405
- this.outputCtx.closePath();
406
- this.outputCtx.stroke();
407
- }
408
- src.delete(); mask_uint8.delete(); contours.delete(); hierarchy.delete();
409
- }
410
- }
411
-
412
- createColorOverlay(imageData, color) {
413
- const data = imageData.data;
414
- for (let i = 0; i < data.length; i += 4) {
415
- if (data[i] > 128) { // If the pixel is white in the mask
416
- data[i] = color[0];
417
- data[i + 1] = color[1];
418
- data[i + 2] = color[2];
419
  }
 
420
  }
421
- const canvas = document.createElement('canvas');
422
- canvas.width = imageData.width;
423
- canvas.height = imageData.height;
424
- canvas.getContext('2d').putImageData(imageData, 0, 0);
425
- return canvas;
426
  }
427
 
428
  downloadMask() {
@@ -433,20 +416,6 @@
433
  link.click();
434
  }
435
  }
436
-
437
- // OpenCV.js loader
438
- const script = document.createElement('script');
439
- script.src = 'https://docs.opencv.org/4.8.0/opencv.js';
440
- script.async = true;
441
- script.onload = () => {
442
- cv['onRuntimeInitialized'] = () => {
443
- console.log('OpenCV.js is ready.');
444
- document.addEventListener('DOMContentLoaded', () => {
445
- new SegmentationApp();
446
- });
447
- };
448
- };
449
- document.head.appendChild(script);
450
  </script>
451
  </body>
452
  </html>
 
52
  font-size: 1.5em;
53
  font-weight: 500;
54
  }
55
+ .upload-area {
56
+ border: 2px dashed var(--primary);
57
+ border-radius: 8px;
58
+ padding: 30px;
59
+ text-align: center;
60
+ margin-bottom: 20px;
61
+ transition: all 0.3s ease;
62
+ cursor: pointer;
63
+ }
64
+ .upload-area:hover, .upload-area.dragover {
65
+ background: rgba(76, 175, 80, 0.1);
66
+ border-color: #66BB6A;
67
+ }
68
  .canvas-container {
69
  background: #000;
70
  border-radius: 8px;
 
109
  .tool-btn.active { background: var(--primary); color: white; }
110
  .predict-btn {
111
  background: linear-gradient(135deg, #FF6B35, #F7931E);
112
+ color: white; border: none;
113
+ padding: 15px 30px; font-size: 18px; width: 100%;
114
+ border-radius: 8px; cursor: pointer; transition: all 0.3s ease;
 
115
  }
116
+ .predict-btn:hover:not(:disabled) { transform: translateY(-2px); }
117
  .predict-btn:disabled { background: #666; cursor: not-allowed; }
118
+ .output-tabs { display: flex; gap: 10px; margin-bottom: 20px; }
 
 
 
 
119
  .download-btn {
120
  background: linear-gradient(135deg, #9C27B0, #673AB7);
121
+ color: white; border: none; padding: 12px 24px;
122
+ border-radius: 8px; cursor: pointer; width: 100%;
123
  }
124
+ .download-btn:disabled { background: #666; cursor: not-allowed; }
125
  .hidden { display: none !important; }
126
  @media (max-width: 900px) {
127
  .main-content { grid-template-columns: 1fr; }
 
134
  <div class="main-content">
135
  <div class="column">
136
  <h2>Input & Controls</h2>
137
+ <div class="upload-area" id="upload-area">
138
+ <p>Click or Drop Image Here</p>
139
+ </div>
140
  <div class="canvas-container">
141
  <canvas id="input-canvas"></canvas>
142
  </div>
 
165
  <div class="output-controls">
166
  <div class="tool-group" id="transparency-control">
167
  <label>Mask Transparency</label>
168
+ <input type="range" id="transparency" min="0" max="100" value="40" style="width:100%;">
169
  </div>
170
  <button class="download-btn" id="download-btn" disabled>Download Mask</button>
171
  </div>
 
173
  </div>
174
  </div>
175
 
176
+ <script src="https://docs.opencv.org/4.8.0/opencv.js"></script>
177
  <script>
178
+ // Wait for OpenCV to be ready
179
+ cv['onRuntimeInitialized'] = () => {
180
+ console.log('OpenCV.js is ready.');
181
+ document.addEventListener('DOMContentLoaded', () => {
182
+ new SegmentationApp();
183
+ });
184
+ };
185
+
186
  class SegmentationApp {
187
  constructor() {
 
188
  this.inputCanvas = document.getElementById('input-canvas');
189
  this.inputCtx = this.inputCanvas.getContext('2d');
190
  this.outputCanvas = document.getElementById('output-canvas');
191
  this.outputCtx = this.outputCanvas.getContext('2d');
192
  this.maskCanvas = document.createElement('canvas');
193
  this.maskCtx = this.maskCanvas.getContext('2d');
 
 
194
  this.currentTool = 'point';
195
  this.brushSize = 8;
196
  this.isDrawing = false;
 
198
  this.predictedMask = null;
199
  this.annotations = [];
200
  this.currentView = 'filled';
 
 
201
  this.predictBtn = document.getElementById('predict-btn');
202
  this.downloadBtn = document.getElementById('download-btn');
203
  this.transparencySlider = document.getElementById('transparency');
204
  this.transparencyControl = document.getElementById('transparency-control');
 
205
  this.initEventListeners();
206
  }
207
 
208
  initEventListeners() {
 
209
  const fileInput = document.createElement('input');
210
  fileInput.type = 'file';
211
  fileInput.accept = 'image/*';
212
  fileInput.onchange = e => this.handleFile(e.target.files[0]);
213
 
214
+ const uploadArea = document.getElementById('upload-area');
215
+ uploadArea.onclick = () => fileInput.click();
216
+ uploadArea.ondragover = e => e.preventDefault();
217
+ uploadArea.ondrop = e => {
218
  e.preventDefault();
219
  this.handleFile(e.dataTransfer.files[0]);
220
  };
221
 
 
222
  this.inputCanvas.addEventListener('mousedown', e => this.startDrawing(e));
223
  this.inputCanvas.addEventListener('mousemove', e => this.draw(e));
224
+ ['mouseup', 'mouseout'].forEach(evt => this.inputCanvas.addEventListener(evt, () => this.stopDrawing()));
 
225
 
 
226
  document.querySelectorAll('.tool-btn[data-tool]').forEach(btn => {
227
  btn.onclick = (e) => {
228
  document.querySelectorAll('.tool-btn[data-tool]').forEach(b => b.classList.remove('active'));
 
231
  };
232
  });
233
 
 
234
  document.getElementById('view-filled').onclick = () => this.setView('filled');
235
  document.getElementById('view-outline').onclick = () => this.setView('outline');
236
  document.getElementById('view-binary').onclick = () => this.setView('binary');
237
 
 
238
  this.transparencySlider.oninput = () => this.renderOutput();
239
  document.getElementById('clear-annotations').onclick = () => this.clearAnnotations();
240
  this.predictBtn.onclick = () => this.predict();
 
250
  this.uploadedImage = img;
251
  this.reset();
252
  this.drawImageScaled(this.inputCanvas, this.inputCtx, img);
253
+ // Also set the size for other canvases
254
+ this.outputCanvas.width = this.inputCanvas.width;
255
+ this.outputCanvas.height = this.inputCanvas.height;
256
+ this.maskCanvas.width = this.inputCanvas.width;
257
+ this.maskCanvas.height = this.inputCanvas.height;
258
  };
259
  img.src = e.target.result;
260
  };
 
270
  }
271
 
272
  drawImageScaled(canvas, ctx, img) {
273
+ const hRatio = canvas.parentElement.clientWidth / img.width;
274
+ const vRatio = canvas.parentElement.clientHeight / img.height;
275
  const ratio = Math.min(hRatio, vRatio);
276
+ canvas.width = img.width * ratio;
277
+ canvas.height = img.height * ratio;
278
  ctx.clearRect(0, 0, canvas.width, canvas.height);
279
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
 
280
  }
281
 
282
  getCanvasCoordinates(e) {
 
305
  stopDrawing() { this.isDrawing = false; }
306
 
307
  drawOnCanvas(x1, y1, x2 = null, y2 = null) {
 
308
  [this.inputCtx, this.maskCtx].forEach((ctx, i) => {
309
  ctx.beginPath();
310
+ ctx.lineWidth = this.brushSize * 2;
311
  ctx.strokeStyle = (i === 0) ? '#4CAF50' : 'white';
312
  ctx.fillStyle = (i === 0) ? '#4CAF50' : 'white';
313
  ctx.lineCap = 'round';
314
+ if (x2 && y2) {
315
+ ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();
316
+ } else {
317
+ ctx.arc(x1, y1, this.brushSize, 0, Math.PI * 2); ctx.fill();
 
 
 
318
  }
319
  });
320
  this.annotations.push(true);
 
322
  }
323
 
324
  clearAnnotations() {
325
+ if (!this.uploadedImage) return;
326
  this.reset();
327
  this.drawImageScaled(this.inputCanvas, this.inputCtx, this.uploadedImage);
328
  this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height);
 
330
 
331
  async predict() {
332
  if (!this.uploadedImage || this.annotations.length === 0) return;
333
+ this.predictBtn.disabled = true; this.predictBtn.innerText = "Processing...";
 
 
334
  try {
335
  const payload = {
336
  image: this.inputCanvas.toDataURL('image/jpeg'),
 
351
  this.downloadBtn.disabled = false;
352
  };
353
  maskImg.src = result.predicted_mask;
 
354
  } catch (error) {
355
  console.error('Prediction failed:', error);
356
  alert('Prediction failed. Check the console for details.');
 
370
 
371
  renderOutput() {
372
  if (!this.predictedMask) return;
 
 
373
  this.drawImageScaled(this.outputCanvas, this.outputCtx, this.uploadedImage);
374
 
 
 
 
 
 
 
 
 
375
  if (this.currentView === 'binary') {
376
  this.drawImageScaled(this.outputCanvas, this.outputCtx, this.predictedMask);
377
  return;
378
  }
379
+
380
+ const tempCanvas = document.createElement('canvas');
381
+ const tempCtx = tempCanvas.getContext('2d');
382
+ tempCanvas.width = this.outputCanvas.width;
383
+ tempCanvas.height = this.outputCanvas.height;
384
+ this.drawImageScaled(tempCanvas, tempCtx, this.predictedMask);
385
+
386
  if (this.currentView === 'filled') {
387
+ tempCtx.globalCompositeOperation = 'source-in';
388
+ tempCtx.fillStyle = '#FF00FF'; // Magenta
389
+ tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
390
+
391
+ this.outputCtx.globalAlpha = this.transparencySlider.value / 100;
392
+ this.outputCtx.drawImage(tempCanvas, 0, 0);
393
  this.outputCtx.globalAlpha = 1.0;
394
+
395
  } else if (this.currentView === 'outline') {
396
+ const src = cv.imread(tempCanvas);
397
+ const contours = new cv.MatVector();
398
+ const hierarchy = new cv.Mat();
399
+ cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0);
400
+ cv.findContours(src, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);
 
401
 
 
402
  this.outputCtx.strokeStyle = 'white';
403
  this.outputCtx.lineWidth = 2;
404
  for (let i = 0; i < contours.size(); ++i) {
405
+ cv.drawContours(this.outputCtx, contours, i, [255, 255, 255, 255], 2, cv.LINE_8, hierarchy, 100);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
  }
407
+ src.delete(); contours.delete(); hierarchy.delete();
408
  }
 
 
 
 
 
409
  }
410
 
411
  downloadMask() {
 
416
  link.click();
417
  }
418
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  </script>
420
  </body>
421
  </html>