Subh775 commited on
Commit
9238bbf
·
verified ·
1 Parent(s): 9abdf76

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +140 -41
index.html CHANGED
@@ -68,6 +68,7 @@
68
  transition: all 0.3s ease;
69
  cursor: pointer;
70
  background: rgba(76, 175, 80, 0.05);
 
71
  }
72
 
73
  .upload-area:hover {
@@ -81,6 +82,16 @@
81
  transform: scale(1.02);
82
  }
83
 
 
 
 
 
 
 
 
 
 
 
84
  .upload-btn {
85
  background: linear-gradient(135deg, #4CAF50, #45a049);
86
  color: white;
@@ -108,13 +119,13 @@
108
  display: flex;
109
  justify-content: center;
110
  align-items: center;
111
- height: 400px; /* Fixed height for consistent alignment */
112
  border: 1px solid rgba(255,255,255,0.1);
113
  }
114
 
115
  canvas {
116
  max-width: 100%;
117
- max-height: 100%; /* Scales image down within container */
118
  object-fit: contain;
119
  border-radius: 4px;
120
  cursor: crosshair;
@@ -266,6 +277,16 @@
266
  gap: 10px;
267
  margin-bottom: 20px;
268
  }
 
 
 
 
 
 
 
 
 
 
269
 
270
  @media (max-width: 768px) {
271
  .main-content {
@@ -284,9 +305,9 @@
284
  <h2>Input & Controls</h2>
285
 
286
  <div class="upload-area" id="upload-area">
287
- <button class="upload-btn" id="upload-btn">Upload Image</button>
 
288
  <p>Or drop an image here</p>
289
- <input type="file" id="file-input" accept="image/*" style="display: none;">
290
  </div>
291
 
292
  <div class="canvas-container">
@@ -316,6 +337,10 @@
316
  <div class="action-panel">
317
  <button class="predict-btn" id="predict-btn" disabled>Predict</button>
318
  </div>
 
 
 
 
319
  </div>
320
 
321
  <div class="column">
@@ -344,21 +369,42 @@
344
 
345
  <script src="https://docs.opencv.org/4.8.0/opencv.js"></script>
346
  <script>
 
 
 
 
 
 
 
 
 
347
  cv['onRuntimeInitialized'] = () => {
348
  console.log('OpenCV.js is ready.');
349
- document.addEventListener('DOMContentLoaded', () => {
 
 
 
 
 
 
 
350
  new SegmentationApp();
351
- });
352
  };
353
 
354
  class SegmentationApp {
355
  constructor() {
 
 
 
356
  this.inputCanvas = document.getElementById('input-canvas');
357
  this.inputCtx = this.inputCanvas.getContext('2d');
358
  this.outputCanvas = document.getElementById('output-canvas');
359
  this.outputCtx = this.outputCanvas.getContext('2d');
360
  this.maskCanvas = document.createElement('canvas');
361
  this.maskCtx = this.maskCanvas.getContext('2d');
 
 
362
  this.currentTool = 'point';
363
  this.brushSize = 5;
364
  this.isDrawing = false;
@@ -366,94 +412,133 @@
366
  this.predictedMask = null;
367
  this.annotations = [];
368
  this.currentView = 'filled';
 
 
369
  this.predictBtn = document.getElementById('predict-btn');
370
  this.downloadBtn = document.getElementById('download-btn');
371
  this.transparencySlider = document.getElementById('transparency');
372
  this.transparencyControl = document.getElementById('transparency-control');
373
 
 
374
  this.initEventListeners();
 
375
  }
376
 
377
  initEventListeners() {
378
- // File input setup
379
  const fileInput = document.getElementById('file-input');
380
  const uploadBtn = document.getElementById('upload-btn');
381
  const uploadArea = document.getElementById('upload-area');
382
 
383
- // Upload button click
384
- uploadBtn.onclick = () => fileInput.click();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
 
386
- // File input change
387
- fileInput.onchange = e => {
388
- if (e.target.files && e.target.files[0]) {
 
 
389
  this.handleFile(e.target.files[0]);
 
 
390
  }
391
- };
392
 
393
  // Drag and drop
394
- uploadArea.ondragover = e => {
395
- e.preventDefault();
396
- uploadArea.classList.add('dragover');
397
- };
398
- uploadArea.ondragleave = () => uploadArea.classList.remove('dragover');
399
- uploadArea.ondrop = e => {
 
 
 
 
400
  e.preventDefault();
401
  uploadArea.classList.remove('dragover');
402
- if (e.dataTransfer.files && e.dataTransfer.files[0]) {
 
403
  this.handleFile(e.dataTransfer.files[0]);
404
  }
405
- };
406
 
407
  // Canvas drawing events
408
- this.inputCanvas.addEventListener('mousedown', e => this.startDrawing(e));
409
- this.inputCanvas.addEventListener('mousemove', e => this.draw(e));
410
- ['mouseup', 'mouseout'].forEach(evt => this.inputCanvas.addEventListener(evt, () => this.stopDrawing()));
 
411
 
412
  // Tool selection
413
  document.querySelectorAll('.tool-btn[data-tool]').forEach(btn => {
414
- btn.onclick = (e) => {
415
  document.querySelectorAll('.tool-btn[data-tool]').forEach(b => b.classList.remove('active'));
416
  e.target.classList.add('active');
417
  this.currentTool = e.target.dataset.tool;
418
- };
 
419
  });
420
 
421
  // View tabs
422
- document.getElementById('view-filled').onclick = () => this.setView('filled');
423
- document.getElementById('view-outline').onclick = () => this.setView('outline');
424
- document.getElementById('view-binary').onclick = () => this.setView('binary');
425
 
426
  // Brush size slider
427
  const brushSizeSlider = document.getElementById('brush-size');
428
  const brushSizeValue = document.getElementById('brush-size-value');
429
- brushSizeSlider.oninput = (e) => {
430
  this.brushSize = parseInt(e.target.value);
431
  brushSizeValue.textContent = this.brushSize;
432
- };
433
 
434
  // Transparency slider
435
  const transparencyValue = document.getElementById('transparency-value');
436
- this.transparencySlider.oninput = (e) => {
437
  transparencyValue.textContent = e.target.value + '%';
438
  this.renderOutput();
439
- };
440
 
441
  // Action buttons
442
- document.getElementById('clear-annotations').onclick = () => this.clearAnnotations();
443
- this.predictBtn.onclick = () => this.predict();
444
- this.downloadBtn.onclick = () => this.downloadMask();
 
 
445
  }
446
 
447
  handleFile(file) {
 
 
448
  if (!file || !file.type.startsWith('image/')) {
449
  alert('Please select a valid image file.');
 
450
  return;
451
  }
452
 
453
  const reader = new FileReader();
454
- reader.onload = e => {
 
455
  const img = new Image();
456
  img.onload = () => {
 
457
  this.uploadedImage = img;
458
  this.reset();
459
  this.drawImageScaled(this.inputCanvas, this.inputCtx, img);
@@ -461,15 +546,17 @@
461
  this.outputCanvas.height = this.inputCanvas.height;
462
  this.maskCanvas.width = this.inputCanvas.width;
463
  this.maskCanvas.height = this.inputCanvas.height;
464
- console.log('Image loaded successfully');
465
  };
466
  img.onerror = () => {
467
  alert('Failed to load image. Please try another file.');
 
468
  };
469
  img.src = e.target.result;
470
  };
471
  reader.onerror = () => {
472
  alert('Failed to read file. Please try again.');
 
473
  };
474
  reader.readAsDataURL(file);
475
  }
@@ -480,6 +567,7 @@
480
  this.predictBtn.disabled = true;
481
  this.downloadBtn.disabled = true;
482
  this.outputCtx.clearRect(0, 0, this.outputCanvas.width, this.outputCanvas.height);
 
483
  }
484
 
485
  drawImageScaled(canvas, ctx, img) {
@@ -490,6 +578,7 @@
490
  canvas.width = img.width * ratio;
491
  canvas.height = img.height * ratio;
492
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
 
493
  }
494
 
495
  getCanvasCoordinates(e) {
@@ -541,6 +630,7 @@
541
  });
542
  this.annotations.push(true);
543
  this.predictBtn.disabled = false;
 
544
  }
545
 
546
  clearAnnotations() {
@@ -548,6 +638,7 @@
548
  this.reset();
549
  this.drawImageScaled(this.inputCanvas, this.inputCtx, this.uploadedImage);
550
  this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height);
 
551
  }
552
 
553
  async predict() {
@@ -555,6 +646,7 @@
555
 
556
  this.predictBtn.disabled = true;
557
  this.predictBtn.innerText = "Processing...";
 
558
 
559
  try {
560
  const payload = {
@@ -562,6 +654,7 @@
562
  scribble_mask: this.maskCanvas.toDataURL('image/png')
563
  };
564
 
 
565
  const response = await fetch('/predict', {
566
  method: 'POST',
567
  headers: { 'Content-Type': 'application/json' },
@@ -573,12 +666,14 @@
573
  }
574
 
575
  const result = await response.json();
 
 
576
  const maskImg = new Image();
577
  maskImg.onload = () => {
578
  this.predictedMask = maskImg;
579
  this.renderOutput();
580
  this.downloadBtn.disabled = false;
581
- console.log('Prediction completed successfully');
582
  };
583
  maskImg.onerror = () => {
584
  throw new Error('Failed to load predicted mask image');
@@ -587,6 +682,7 @@
587
 
588
  } catch (error) {
589
  console.error('Prediction failed:', error);
 
590
  alert(`Prediction failed: ${error.message}`);
591
  } finally {
592
  this.predictBtn.innerText = "Predict";
@@ -600,6 +696,7 @@
600
  document.getElementById(`view-${view}`).classList.add('active');
601
  this.transparencyControl.style.display = (view === 'filled' || view === 'outline') ? 'block' : 'none';
602
  this.renderOutput();
 
603
  }
604
 
605
  renderOutput() {
@@ -635,16 +732,17 @@
635
  cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0);
636
  cv.findContours(src, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);
637
 
638
- let color = new cv.Scalar(255, 255, 255, 255); // White color
639
  for (let i = 0; i < contours.size(); ++i) {
640
  cv.drawContours(src, contours, i, color, 2, cv.LINE_8, hierarchy, 100);
641
  }
642
- cv.imshow('output-canvas', src); // Draw contours back onto the output canvas
643
  src.delete();
644
  contours.delete();
645
  hierarchy.delete();
646
  } catch (error) {
647
  console.error('Error rendering outline view:', error);
 
648
  }
649
  }
650
  }
@@ -655,6 +753,7 @@
655
  link.download = 'binary_mask.png';
656
  link.href = this.predictedMask.src;
657
  link.click();
 
658
  }
659
  }
660
  </script>
 
68
  transition: all 0.3s ease;
69
  cursor: pointer;
70
  background: rgba(76, 175, 80, 0.05);
71
+ position: relative;
72
  }
73
 
74
  .upload-area:hover {
 
82
  transform: scale(1.02);
83
  }
84
 
85
+ .file-input {
86
+ position: absolute;
87
+ opacity: 0;
88
+ width: 100%;
89
+ height: 100%;
90
+ top: 0;
91
+ left: 0;
92
+ cursor: pointer;
93
+ }
94
+
95
  .upload-btn {
96
  background: linear-gradient(135deg, #4CAF50, #45a049);
97
  color: white;
 
119
  display: flex;
120
  justify-content: center;
121
  align-items: center;
122
+ height: 400px;
123
  border: 1px solid rgba(255,255,255,0.1);
124
  }
125
 
126
  canvas {
127
  max-width: 100%;
128
+ max-height: 100%;
129
  object-fit: contain;
130
  border-radius: 4px;
131
  cursor: crosshair;
 
277
  gap: 10px;
278
  margin-bottom: 20px;
279
  }
280
+
281
+ .debug-info {
282
+ background: rgba(255, 0, 0, 0.1);
283
+ border: 1px solid #ff6b6b;
284
+ border-radius: 4px;
285
+ padding: 10px;
286
+ margin-top: 10px;
287
+ font-size: 12px;
288
+ color: #ff9999;
289
+ }
290
 
291
  @media (max-width: 768px) {
292
  .main-content {
 
305
  <h2>Input & Controls</h2>
306
 
307
  <div class="upload-area" id="upload-area">
308
+ <input type="file" class="file-input" id="file-input" accept="image/*">
309
+ <button class="upload-btn" id="upload-btn" type="button">Upload Image</button>
310
  <p>Or drop an image here</p>
 
311
  </div>
312
 
313
  <div class="canvas-container">
 
337
  <div class="action-panel">
338
  <button class="predict-btn" id="predict-btn" disabled>Predict</button>
339
  </div>
340
+
341
+ <div class="debug-info" id="debug-info" style="display: none;">
342
+ Debug info will appear here
343
+ </div>
344
  </div>
345
 
346
  <div class="column">
 
369
 
370
  <script src="https://docs.opencv.org/4.8.0/opencv.js"></script>
371
  <script>
372
+ // Debug function
373
+ function debugLog(message) {
374
+ console.log('DEBUG:', message);
375
+ const debugDiv = document.getElementById('debug-info');
376
+ debugDiv.style.display = 'block';
377
+ debugDiv.innerHTML += '<br>' + message;
378
+ }
379
+
380
+ // Initialize when OpenCV is ready
381
  cv['onRuntimeInitialized'] = () => {
382
  console.log('OpenCV.js is ready.');
383
+ // Wait for DOM to be ready
384
+ if (document.readyState === 'loading') {
385
+ document.addEventListener('DOMContentLoaded', () => {
386
+ debugLog('DOM loaded, initializing app...');
387
+ new SegmentationApp();
388
+ });
389
+ } else {
390
+ debugLog('DOM already loaded, initializing app...');
391
  new SegmentationApp();
392
+ }
393
  };
394
 
395
  class SegmentationApp {
396
  constructor() {
397
+ debugLog('SegmentationApp constructor called');
398
+
399
+ // Initialize canvas elements
400
  this.inputCanvas = document.getElementById('input-canvas');
401
  this.inputCtx = this.inputCanvas.getContext('2d');
402
  this.outputCanvas = document.getElementById('output-canvas');
403
  this.outputCtx = this.outputCanvas.getContext('2d');
404
  this.maskCanvas = document.createElement('canvas');
405
  this.maskCtx = this.maskCanvas.getContext('2d');
406
+
407
+ // Initialize state
408
  this.currentTool = 'point';
409
  this.brushSize = 5;
410
  this.isDrawing = false;
 
412
  this.predictedMask = null;
413
  this.annotations = [];
414
  this.currentView = 'filled';
415
+
416
+ // Get UI elements
417
  this.predictBtn = document.getElementById('predict-btn');
418
  this.downloadBtn = document.getElementById('download-btn');
419
  this.transparencySlider = document.getElementById('transparency');
420
  this.transparencyControl = document.getElementById('transparency-control');
421
 
422
+ debugLog('Elements initialized, setting up event listeners...');
423
  this.initEventListeners();
424
+ debugLog('SegmentationApp initialized successfully');
425
  }
426
 
427
  initEventListeners() {
428
+ // File upload elements
429
  const fileInput = document.getElementById('file-input');
430
  const uploadBtn = document.getElementById('upload-btn');
431
  const uploadArea = document.getElementById('upload-area');
432
 
433
+ debugLog('Setting up file upload listeners...');
434
+
435
+ // Multiple ways to trigger file selection - more reliable
436
+ uploadBtn.addEventListener('click', (e) => {
437
+ e.preventDefault();
438
+ e.stopPropagation();
439
+ debugLog('Upload button clicked');
440
+ fileInput.value = ''; // Reset to ensure change event fires
441
+ fileInput.click();
442
+ });
443
+
444
+ uploadArea.addEventListener('click', (e) => {
445
+ if (e.target !== uploadBtn) {
446
+ e.preventDefault();
447
+ debugLog('Upload area clicked');
448
+ fileInput.value = '';
449
+ fileInput.click();
450
+ }
451
+ });
452
 
453
+ // File input change event
454
+ fileInput.addEventListener('change', (e) => {
455
+ debugLog('File input changed');
456
+ if (e.target.files && e.target.files.length > 0) {
457
+ debugLog(`File selected: ${e.target.files[0].name}`);
458
  this.handleFile(e.target.files[0]);
459
+ } else {
460
+ debugLog('No file selected');
461
  }
462
+ });
463
 
464
  // Drag and drop
465
+ uploadArea.addEventListener('dragover', (e) => {
466
+ e.preventDefault();
467
+ uploadArea.classList.add('dragover');
468
+ });
469
+
470
+ uploadArea.addEventListener('dragleave', (e) => {
471
+ uploadArea.classList.remove('dragover');
472
+ });
473
+
474
+ uploadArea.addEventListener('drop', (e) => {
475
  e.preventDefault();
476
  uploadArea.classList.remove('dragover');
477
+ debugLog('File dropped');
478
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
479
  this.handleFile(e.dataTransfer.files[0]);
480
  }
481
+ });
482
 
483
  // Canvas drawing events
484
+ this.inputCanvas.addEventListener('mousedown', (e) => this.startDrawing(e));
485
+ this.inputCanvas.addEventListener('mousemove', (e) => this.draw(e));
486
+ this.inputCanvas.addEventListener('mouseup', () => this.stopDrawing());
487
+ this.inputCanvas.addEventListener('mouseout', () => this.stopDrawing());
488
 
489
  // Tool selection
490
  document.querySelectorAll('.tool-btn[data-tool]').forEach(btn => {
491
+ btn.addEventListener('click', (e) => {
492
  document.querySelectorAll('.tool-btn[data-tool]').forEach(b => b.classList.remove('active'));
493
  e.target.classList.add('active');
494
  this.currentTool = e.target.dataset.tool;
495
+ debugLog(`Tool changed to: ${this.currentTool}`);
496
+ });
497
  });
498
 
499
  // View tabs
500
+ document.getElementById('view-filled').addEventListener('click', () => this.setView('filled'));
501
+ document.getElementById('view-outline').addEventListener('click', () => this.setView('outline'));
502
+ document.getElementById('view-binary').addEventListener('click', () => this.setView('binary'));
503
 
504
  // Brush size slider
505
  const brushSizeSlider = document.getElementById('brush-size');
506
  const brushSizeValue = document.getElementById('brush-size-value');
507
+ brushSizeSlider.addEventListener('input', (e) => {
508
  this.brushSize = parseInt(e.target.value);
509
  brushSizeValue.textContent = this.brushSize;
510
+ });
511
 
512
  // Transparency slider
513
  const transparencyValue = document.getElementById('transparency-value');
514
+ this.transparencySlider.addEventListener('input', (e) => {
515
  transparencyValue.textContent = e.target.value + '%';
516
  this.renderOutput();
517
+ });
518
 
519
  // Action buttons
520
+ document.getElementById('clear-annotations').addEventListener('click', () => this.clearAnnotations());
521
+ this.predictBtn.addEventListener('click', () => this.predict());
522
+ this.downloadBtn.addEventListener('click', () => this.downloadMask());
523
+
524
+ debugLog('All event listeners set up');
525
  }
526
 
527
  handleFile(file) {
528
+ debugLog(`Handling file: ${file.name}, type: ${file.type}, size: ${file.size}`);
529
+
530
  if (!file || !file.type.startsWith('image/')) {
531
  alert('Please select a valid image file.');
532
+ debugLog('Invalid file type');
533
  return;
534
  }
535
 
536
  const reader = new FileReader();
537
+ reader.onload = (e) => {
538
+ debugLog('FileReader loaded successfully');
539
  const img = new Image();
540
  img.onload = () => {
541
+ debugLog(`Image loaded: ${img.width}x${img.height}`);
542
  this.uploadedImage = img;
543
  this.reset();
544
  this.drawImageScaled(this.inputCanvas, this.inputCtx, img);
 
546
  this.outputCanvas.height = this.inputCanvas.height;
547
  this.maskCanvas.width = this.inputCanvas.width;
548
  this.maskCanvas.height = this.inputCanvas.height;
549
+ debugLog('Image setup completed');
550
  };
551
  img.onerror = () => {
552
  alert('Failed to load image. Please try another file.');
553
+ debugLog('Image load error');
554
  };
555
  img.src = e.target.result;
556
  };
557
  reader.onerror = () => {
558
  alert('Failed to read file. Please try again.');
559
+ debugLog('FileReader error');
560
  };
561
  reader.readAsDataURL(file);
562
  }
 
567
  this.predictBtn.disabled = true;
568
  this.downloadBtn.disabled = true;
569
  this.outputCtx.clearRect(0, 0, this.outputCanvas.width, this.outputCanvas.height);
570
+ debugLog('App state reset');
571
  }
572
 
573
  drawImageScaled(canvas, ctx, img) {
 
578
  canvas.width = img.width * ratio;
579
  canvas.height = img.height * ratio;
580
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
581
+ debugLog(`Canvas scaled to: ${canvas.width}x${canvas.height}`);
582
  }
583
 
584
  getCanvasCoordinates(e) {
 
630
  });
631
  this.annotations.push(true);
632
  this.predictBtn.disabled = false;
633
+ debugLog(`Annotation added. Total: ${this.annotations.length}`);
634
  }
635
 
636
  clearAnnotations() {
 
638
  this.reset();
639
  this.drawImageScaled(this.inputCanvas, this.inputCtx, this.uploadedImage);
640
  this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height);
641
+ debugLog('Annotations cleared');
642
  }
643
 
644
  async predict() {
 
646
 
647
  this.predictBtn.disabled = true;
648
  this.predictBtn.innerText = "Processing...";
649
+ debugLog('Starting prediction...');
650
 
651
  try {
652
  const payload = {
 
654
  scribble_mask: this.maskCanvas.toDataURL('image/png')
655
  };
656
 
657
+ debugLog('Sending request to /predict endpoint...');
658
  const response = await fetch('/predict', {
659
  method: 'POST',
660
  headers: { 'Content-Type': 'application/json' },
 
666
  }
667
 
668
  const result = await response.json();
669
+ debugLog('Prediction response received');
670
+
671
  const maskImg = new Image();
672
  maskImg.onload = () => {
673
  this.predictedMask = maskImg;
674
  this.renderOutput();
675
  this.downloadBtn.disabled = false;
676
+ debugLog('Prediction completed successfully');
677
  };
678
  maskImg.onerror = () => {
679
  throw new Error('Failed to load predicted mask image');
 
682
 
683
  } catch (error) {
684
  console.error('Prediction failed:', error);
685
+ debugLog(`Prediction error: ${error.message}`);
686
  alert(`Prediction failed: ${error.message}`);
687
  } finally {
688
  this.predictBtn.innerText = "Predict";
 
696
  document.getElementById(`view-${view}`).classList.add('active');
697
  this.transparencyControl.style.display = (view === 'filled' || view === 'outline') ? 'block' : 'none';
698
  this.renderOutput();
699
+ debugLog(`View changed to: ${view}`);
700
  }
701
 
702
  renderOutput() {
 
732
  cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0);
733
  cv.findContours(src, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);
734
 
735
+ let color = new cv.Scalar(255, 255, 255, 255);
736
  for (let i = 0; i < contours.size(); ++i) {
737
  cv.drawContours(src, contours, i, color, 2, cv.LINE_8, hierarchy, 100);
738
  }
739
+ cv.imshow('output-canvas', src);
740
  src.delete();
741
  contours.delete();
742
  hierarchy.delete();
743
  } catch (error) {
744
  console.error('Error rendering outline view:', error);
745
+ debugLog(`Outline rendering error: ${error.message}`);
746
  }
747
  }
748
  }
 
753
  link.download = 'binary_mask.png';
754
  link.href = this.predictedMask.src;
755
  link.click();
756
+ debugLog('Mask download initiated');
757
  }
758
  }
759
  </script>