Subh775 commited on
Commit
4678876
·
verified ·
1 Parent(s): f5a1a7f

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +83 -46
index.html CHANGED
@@ -63,6 +63,7 @@
63
  margin-bottom: 20px;
64
  transition: all 0.3s ease;
65
  cursor: pointer;
 
66
  }
67
  .upload-area:hover, .upload-area.dragover {
68
  background: rgba(76, 175, 80, 0.1);
@@ -78,6 +79,7 @@
78
  height: 400px;
79
  border: 1px solid var(--border-color);
80
  flex-shrink: 0;
 
81
  }
82
  canvas {
83
  max-width: 100%;
@@ -127,6 +129,14 @@
127
  }
128
  .download-btn:disabled { background: #666; cursor: not-allowed; }
129
  .hidden { display: none !important; }
 
 
 
 
 
 
 
 
130
  @media (max-width: 900px) {
131
  .main-content { grid-template-columns: 1fr; }
132
  }
@@ -138,10 +148,11 @@
138
  <div class="main-content">
139
  <div class="column">
140
  <h2>Input & Controls</h2>
141
- <div class="upload-area" id="upload-area">
142
  <p>Click or Drop Image Here</p>
143
  </div>
144
- <input type="file" id="file-input" class="hidden" accept="image/*">
 
145
 
146
  <div class="canvas-container">
147
  <canvas id="input-canvas"></canvas>
@@ -181,12 +192,18 @@
181
 
182
  <script src="https://docs.opencv.org/4.8.0/opencv.js"></script>
183
  <script>
184
- cv['onRuntimeInitialized'] = () => {
185
- console.log('OpenCV.js is ready.');
186
- document.addEventListener('DOMContentLoaded', () => {
187
- new SegmentationApp();
188
- });
189
- };
 
 
 
 
 
 
190
 
191
  class SegmentationApp {
192
  constructor() {
@@ -214,17 +231,21 @@
214
  const fileInput = document.getElementById('file-input');
215
  const uploadArea = document.getElementById('upload-area');
216
 
217
- uploadArea.onclick = () => fileInput.click();
218
- fileInput.onchange = e => this.handleFile(e.target.files[0]);
219
-
220
- uploadArea.ondragover = e => { e.preventDefault(); uploadArea.classList.add('dragover'); };
221
- uploadArea.ondragleave = () => uploadArea.classList.remove('dragover');
222
- uploadArea.ondrop = e => {
223
- e.preventDefault();
224
- uploadArea.classList.remove('dragover');
225
- this.handleFile(e.dataTransfer.files[0]);
226
- };
227
-
 
 
 
 
228
  this.inputCanvas.addEventListener('mousedown', e => this.startDrawing(e));
229
  this.inputCanvas.addEventListener('mousemove', e => this.draw(e));
230
  ['mouseup', 'mouseout'].forEach(evt => this.inputCanvas.addEventListener(evt, () => this.stopDrawing()));
@@ -236,7 +257,7 @@
236
  this.currentTool = e.target.dataset.tool;
237
  };
238
  });
239
-
240
  document.getElementById('view-filled').onclick = () => this.setView('filled');
241
  document.getElementById('view-outline').onclick = () => this.setView('outline');
242
  document.getElementById('view-binary').onclick = () => this.setView('binary');
@@ -245,6 +266,10 @@
245
  document.getElementById('clear-annotations').onclick = () => this.clearAnnotations();
246
  this.predictBtn.onclick = () => this.predict();
247
  this.downloadBtn.onclick = () => this.downloadMask();
 
 
 
 
248
  }
249
 
250
  handleFile(file) {
@@ -265,7 +290,7 @@
265
  };
266
  reader.readAsDataURL(file);
267
  }
268
-
269
  reset() {
270
  this.annotations = [];
271
  this.predictedMask = null;
@@ -273,25 +298,31 @@
273
  this.downloadBtn.disabled = true;
274
  this.outputCtx.clearRect(0,0, this.outputCanvas.width, this.outputCanvas.height);
275
  }
276
-
277
  drawImageScaled(canvas, ctx, img) {
278
  const container = canvas.parentElement;
279
  const hRatio = container.clientWidth / img.width;
280
  const vRatio = container.clientHeight / img.height;
281
  const ratio = Math.min(hRatio, vRatio);
282
- canvas.width = img.width * ratio;
283
- canvas.height = img.height * ratio;
 
 
 
284
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
285
  }
286
-
287
  getCanvasCoordinates(e) {
288
  const rect = this.inputCanvas.getBoundingClientRect();
 
 
 
289
  return {
290
- x: (e.clientX - rect.left) * (this.inputCanvas.width / rect.width),
291
- y: (e.clientY - rect.top) * (this.inputCanvas.height / rect.height)
292
  };
293
  }
294
-
295
  startDrawing(e) {
296
  if (!this.uploadedImage) return;
297
  this.isDrawing = true;
@@ -299,16 +330,16 @@
299
  if (this.currentTool === 'point') this.drawOnCanvas(coords.x, coords.y);
300
  else { this.lastX = coords.x; this.lastY = coords.y; }
301
  }
302
-
303
  draw(e) {
304
  if (!this.isDrawing || this.currentTool !== 'line') return;
305
  const coords = this.getCanvasCoordinates(e);
306
  this.drawOnCanvas(this.lastX, this.lastY, coords.x, coords.y);
307
  [this.lastX, this.lastY] = [coords.x, coords.y];
308
  }
309
-
310
  stopDrawing() { this.isDrawing = false; }
311
-
312
  drawOnCanvas(x1, y1, x2 = null, y2 = null) {
313
  [this.inputCtx, this.maskCtx].forEach((ctx, i) => {
314
  ctx.beginPath();
@@ -316,7 +347,7 @@
316
  ctx.strokeStyle = (i === 0) ? '#4CAF50' : 'white';
317
  ctx.fillStyle = (i === 0) ? '#4CAF50' : 'white';
318
  ctx.lineCap = 'round';
319
- if (x2 && y2) {
320
  ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();
321
  } else {
322
  ctx.arc(x1, y1, this.brushSize, 0, Math.PI * 2); ctx.fill();
@@ -325,14 +356,14 @@
325
  this.annotations.push(true);
326
  this.predictBtn.disabled = false;
327
  }
328
-
329
  clearAnnotations() {
330
  if (!this.uploadedImage) return;
331
  this.reset();
332
  this.drawImageScaled(this.inputCanvas, this.inputCtx, this.uploadedImage);
333
  this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height);
334
  }
335
-
336
  async predict() {
337
  if (!this.uploadedImage || this.annotations.length === 0) return;
338
  this.predictBtn.disabled = true; this.predictBtn.innerText = "Processing...";
@@ -363,21 +394,22 @@
363
  this.predictBtn.disabled = this.annotations.length === 0;
364
  }
365
  }
366
-
367
  setView(view) {
368
  this.currentView = view;
369
  document.querySelectorAll('.output-tabs .tool-btn').forEach(b => b.classList.remove('active'));
370
- document.getElementById(`view-${view}`).classList.add('active');
 
371
  this.transparencyControl.style.display = (view === 'filled') ? 'block' : 'none';
372
  this.renderOutput();
373
  }
374
-
375
  renderOutput() {
376
  if (!this.predictedMask) {
377
  this.outputCtx.clearRect(0, 0, this.outputCanvas.width, this.outputCanvas.height);
378
  return;
379
  }
380
-
381
  this.outputCtx.clearRect(0,0, this.outputCanvas.width, this.outputCanvas.height);
382
  this.drawImageScaled(this.outputCanvas, this.outputCtx, this.uploadedImage);
383
 
@@ -385,18 +417,18 @@
385
  this.drawImageScaled(this.outputCanvas, this.outputCtx, this.predictedMask);
386
  return;
387
  }
388
-
389
  const tempCanvas = document.createElement('canvas');
390
  const tempCtx = tempCanvas.getContext('2d');
391
  tempCanvas.width = this.outputCanvas.width;
392
  tempCanvas.height = this.outputCanvas.height;
393
  tempCtx.drawImage(this.predictedMask, 0, 0, tempCanvas.width, tempCanvas.height);
394
-
395
  if (this.currentView === 'filled') {
396
  tempCtx.globalCompositeOperation = 'source-in';
397
  tempCtx.fillStyle = '#FF00FF'; // Magenta
398
  tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
399
-
400
  this.outputCtx.globalAlpha = this.transparencySlider.value / 100;
401
  this.outputCtx.drawImage(tempCanvas, 0, 0);
402
  this.outputCtx.globalAlpha = 1.0;
@@ -410,19 +442,24 @@
410
  const contours = new cv.MatVector();
411
  const hierarchy = new cv.Mat();
412
  cv.findContours(src, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);
413
-
414
  const color = new cv.Scalar(255, 255, 255, 255); // White color
415
  for (let i = 0; i < contours.size(); ++i) {
416
  cv.drawContours(contourImg, contours, i, color, 2, cv.LINE_8, hierarchy, 0);
417
  }
418
-
419
  // Draw the contours over the original image
420
- this.outputCtx.drawImage(contourImg, 0, 0, this.outputCanvas.width, this.outputCanvas.height);
 
 
 
 
 
421
 
422
  src.delete(); contours.delete(); hierarchy.delete(); contourImg.delete();
423
  }
424
  }
425
-
426
  downloadMask() {
427
  if (!this.predictedMask) return;
428
  const link = document.createElement('a');
@@ -433,4 +470,4 @@
433
  }
434
  </script>
435
  </body>
436
- </html>
 
63
  margin-bottom: 20px;
64
  transition: all 0.3s ease;
65
  cursor: pointer;
66
+ user-select: none;
67
  }
68
  .upload-area:hover, .upload-area.dragover {
69
  background: rgba(76, 175, 80, 0.1);
 
79
  height: 400px;
80
  border: 1px solid var(--border-color);
81
  flex-shrink: 0;
82
+ position: relative;
83
  }
84
  canvas {
85
  max-width: 100%;
 
129
  }
130
  .download-btn:disabled { background: #666; cursor: not-allowed; }
131
  .hidden { display: none !important; }
132
+ /* visually-hidden allows programmatic click while keeping it out of sight */
133
+ .visually-hidden {
134
+ position: absolute !important;
135
+ left: -9999px !important;
136
+ width: 1px !important;
137
+ height: 1px !important;
138
+ overflow: hidden !important;
139
+ }
140
  @media (max-width: 900px) {
141
  .main-content { grid-template-columns: 1fr; }
142
  }
 
148
  <div class="main-content">
149
  <div class="column">
150
  <h2>Input & Controls</h2>
151
+ <div class="upload-area" id="upload-area" role="button" tabindex="0">
152
  <p>Click or Drop Image Here</p>
153
  </div>
154
+ <!-- keep file input in DOM but visually hidden (not display:none) so programmatic click works reliably -->
155
+ <input type="file" id="file-input" class="visually-hidden" accept="image/*">
156
 
157
  <div class="canvas-container">
158
  <canvas id="input-canvas"></canvas>
 
192
 
193
  <script src="https://docs.opencv.org/4.8.0/opencv.js"></script>
194
  <script>
195
+ // Robust init: wait for both DOMContentLoaded and cv runtime
196
+ let cvReady = false;
197
+ let domReady = false;
198
+ function tryInit() {
199
+ if (cvReady && domReady) {
200
+ // instantiate app
201
+ window.segApp = new SegmentationApp();
202
+ }
203
+ }
204
+
205
+ document.addEventListener('DOMContentLoaded', () => { domReady = true; tryInit(); });
206
+ cv['onRuntimeInitialized'] = () => { console.log('OpenCV.js is ready.'); cvReady = true; tryInit(); };
207
 
208
  class SegmentationApp {
209
  constructor() {
 
231
  const fileInput = document.getElementById('file-input');
232
  const uploadArea = document.getElementById('upload-area');
233
 
234
+ // clicking upload area triggers the hidden file input
235
+ uploadArea.addEventListener('click', () => fileInput.click());
236
+ // also support keyboard activation for accessibility
237
+ uploadArea.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); fileInput.click(); } });
238
+
239
+ fileInput.addEventListener('change', e => this.handleFile(e.target.files[0]));
240
+
241
+ uploadArea.addEventListener('dragover', e => { e.preventDefault(); uploadArea.classList.add('dragover'); });
242
+ uploadArea.addEventListener('dragleave', () => uploadArea.classList.remove('dragover'));
243
+ uploadArea.addEventListener('drop', e => {
244
+ e.preventDefault(); uploadArea.classList.remove('dragover');
245
+ const f = e.dataTransfer.files && e.dataTransfer.files[0];
246
+ if (f) this.handleFile(f);
247
+ });
248
+
249
  this.inputCanvas.addEventListener('mousedown', e => this.startDrawing(e));
250
  this.inputCanvas.addEventListener('mousemove', e => this.draw(e));
251
  ['mouseup', 'mouseout'].forEach(evt => this.inputCanvas.addEventListener(evt, () => this.stopDrawing()));
 
257
  this.currentTool = e.target.dataset.tool;
258
  };
259
  });
260
+
261
  document.getElementById('view-filled').onclick = () => this.setView('filled');
262
  document.getElementById('view-outline').onclick = () => this.setView('outline');
263
  document.getElementById('view-binary').onclick = () => this.setView('binary');
 
266
  document.getElementById('clear-annotations').onclick = () => this.clearAnnotations();
267
  this.predictBtn.onclick = () => this.predict();
268
  this.downloadBtn.onclick = () => this.downloadMask();
269
+
270
+ // prevent accidental image drag from navigating away
271
+ window.addEventListener('dragover', (e) => e.preventDefault());
272
+ window.addEventListener('drop', (e) => e.preventDefault());
273
  }
274
 
275
  handleFile(file) {
 
290
  };
291
  reader.readAsDataURL(file);
292
  }
293
+
294
  reset() {
295
  this.annotations = [];
296
  this.predictedMask = null;
 
298
  this.downloadBtn.disabled = true;
299
  this.outputCtx.clearRect(0,0, this.outputCanvas.width, this.outputCanvas.height);
300
  }
301
+
302
  drawImageScaled(canvas, ctx, img) {
303
  const container = canvas.parentElement;
304
  const hRatio = container.clientWidth / img.width;
305
  const vRatio = container.clientHeight / img.height;
306
  const ratio = Math.min(hRatio, vRatio);
307
+ // Guard against zero sizes
308
+ const finalRatio = (ratio && isFinite(ratio)) ? ratio : 1;
309
+ canvas.width = Math.round(img.width * finalRatio);
310
+ canvas.height = Math.round(img.height * finalRatio);
311
+ ctx.clearRect(0,0,canvas.width, canvas.height);
312
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
313
  }
314
+
315
  getCanvasCoordinates(e) {
316
  const rect = this.inputCanvas.getBoundingClientRect();
317
+ // Support touch events
318
+ const clientX = e.touches ? e.touches[0].clientX : e.clientX;
319
+ const clientY = e.touches ? e.touches[0].clientY : e.clientY;
320
  return {
321
+ x: (clientX - rect.left) * (this.inputCanvas.width / rect.width),
322
+ y: (clientY - rect.top) * (this.inputCanvas.height / rect.height)
323
  };
324
  }
325
+
326
  startDrawing(e) {
327
  if (!this.uploadedImage) return;
328
  this.isDrawing = true;
 
330
  if (this.currentTool === 'point') this.drawOnCanvas(coords.x, coords.y);
331
  else { this.lastX = coords.x; this.lastY = coords.y; }
332
  }
333
+
334
  draw(e) {
335
  if (!this.isDrawing || this.currentTool !== 'line') return;
336
  const coords = this.getCanvasCoordinates(e);
337
  this.drawOnCanvas(this.lastX, this.lastY, coords.x, coords.y);
338
  [this.lastX, this.lastY] = [coords.x, coords.y];
339
  }
340
+
341
  stopDrawing() { this.isDrawing = false; }
342
+
343
  drawOnCanvas(x1, y1, x2 = null, y2 = null) {
344
  [this.inputCtx, this.maskCtx].forEach((ctx, i) => {
345
  ctx.beginPath();
 
347
  ctx.strokeStyle = (i === 0) ? '#4CAF50' : 'white';
348
  ctx.fillStyle = (i === 0) ? '#4CAF50' : 'white';
349
  ctx.lineCap = 'round';
350
+ if (x2 !== null && y2 !== null) {
351
  ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();
352
  } else {
353
  ctx.arc(x1, y1, this.brushSize, 0, Math.PI * 2); ctx.fill();
 
356
  this.annotations.push(true);
357
  this.predictBtn.disabled = false;
358
  }
359
+
360
  clearAnnotations() {
361
  if (!this.uploadedImage) return;
362
  this.reset();
363
  this.drawImageScaled(this.inputCanvas, this.inputCtx, this.uploadedImage);
364
  this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height);
365
  }
366
+
367
  async predict() {
368
  if (!this.uploadedImage || this.annotations.length === 0) return;
369
  this.predictBtn.disabled = true; this.predictBtn.innerText = "Processing...";
 
394
  this.predictBtn.disabled = this.annotations.length === 0;
395
  }
396
  }
397
+
398
  setView(view) {
399
  this.currentView = view;
400
  document.querySelectorAll('.output-tabs .tool-btn').forEach(b => b.classList.remove('active'));
401
+ const el = document.getElementById(`view-${view}`);
402
+ if (el) el.classList.add('active');
403
  this.transparencyControl.style.display = (view === 'filled') ? 'block' : 'none';
404
  this.renderOutput();
405
  }
406
+
407
  renderOutput() {
408
  if (!this.predictedMask) {
409
  this.outputCtx.clearRect(0, 0, this.outputCanvas.width, this.outputCanvas.height);
410
  return;
411
  }
412
+
413
  this.outputCtx.clearRect(0,0, this.outputCanvas.width, this.outputCanvas.height);
414
  this.drawImageScaled(this.outputCanvas, this.outputCtx, this.uploadedImage);
415
 
 
417
  this.drawImageScaled(this.outputCanvas, this.outputCtx, this.predictedMask);
418
  return;
419
  }
420
+
421
  const tempCanvas = document.createElement('canvas');
422
  const tempCtx = tempCanvas.getContext('2d');
423
  tempCanvas.width = this.outputCanvas.width;
424
  tempCanvas.height = this.outputCanvas.height;
425
  tempCtx.drawImage(this.predictedMask, 0, 0, tempCanvas.width, tempCanvas.height);
426
+
427
  if (this.currentView === 'filled') {
428
  tempCtx.globalCompositeOperation = 'source-in';
429
  tempCtx.fillStyle = '#FF00FF'; // Magenta
430
  tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
431
+
432
  this.outputCtx.globalAlpha = this.transparencySlider.value / 100;
433
  this.outputCtx.drawImage(tempCanvas, 0, 0);
434
  this.outputCtx.globalAlpha = 1.0;
 
442
  const contours = new cv.MatVector();
443
  const hierarchy = new cv.Mat();
444
  cv.findContours(src, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);
445
+
446
  const color = new cv.Scalar(255, 255, 255, 255); // White color
447
  for (let i = 0; i < contours.size(); ++i) {
448
  cv.drawContours(contourImg, contours, i, color, 2, cv.LINE_8, hierarchy, 0);
449
  }
450
+
451
  // Draw the contours over the original image
452
+ // draw contourImg into a temporary HTML canvas first
453
+ const contourCanvas = document.createElement('canvas');
454
+ contourCanvas.width = contourImg.cols;
455
+ contourCanvas.height = contourImg.rows;
456
+ cv.imshow(contourCanvas, contourImg);
457
+ this.outputCtx.drawImage(contourCanvas, 0, 0, this.outputCanvas.width, this.outputCanvas.height);
458
 
459
  src.delete(); contours.delete(); hierarchy.delete(); contourImg.delete();
460
  }
461
  }
462
+
463
  downloadMask() {
464
  if (!this.predictedMask) return;
465
  const link = document.createElement('a');
 
470
  }
471
  </script>
472
  </body>
473
+ </html>