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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +251 -84
index.html CHANGED
@@ -3,68 +3,104 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Interactive Leaf Segmentation</title>
7
  <style>
8
- :root {
9
- --primary: #4CAF50;
10
- --dark-bg: #121212;
11
- --surface-bg: #1E1E2E;
12
- --text-primary: #E0E0E0;
13
- --text-secondary: #9E9E9E;
14
- --border-color: rgba(255, 255, 255, 0.1);
15
- }
16
- * { margin: 0; padding: 0; box-sizing: border-box; }
17
  body {
18
- font-family: 'Segoe UI', sans-serif;
19
- background: var(--dark-bg);
20
- color: var(--text-primary);
 
21
  padding: 20px;
22
  }
 
23
  .container {
24
  max-width: 1400px;
25
  margin: 0 auto;
26
- background: var(--surface-bg);
27
  border-radius: 16px;
28
- box-shadow: 0 10px 30px rgba(0,0,0,0.3);
29
- border: 1px solid var(--border-color);
 
30
  }
 
31
  h1 {
32
  text-align: center;
33
- color: var(--primary);
34
- padding: 30px;
35
- font-size: 2.2em;
36
  font-weight: 300;
 
37
  }
 
38
  .main-content {
39
  display: grid;
40
  grid-template-columns: 1fr 1fr;
41
  gap: 30px;
42
- padding: 0 30px 30px 30px;
43
  }
 
44
  .column {
45
- background: rgba(0,0,0,0.2);
46
  border-radius: 12px;
47
  padding: 25px;
 
48
  }
 
49
  .column h2 {
50
- color: var(--primary);
51
  margin-bottom: 20px;
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;
@@ -72,86 +108,215 @@
72
  display: flex;
73
  justify-content: center;
74
  align-items: center;
75
- height: 400px;
76
- border: 1px solid var(--border-color);
77
  }
 
78
  canvas {
79
  max-width: 100%;
80
- max-height: 100%;
81
  object-fit: contain;
 
82
  cursor: crosshair;
83
  }
84
- .tools-panel, .output-controls {
85
- background: rgba(0,0,0,0.2);
 
86
  border-radius: 8px;
87
  padding: 20px;
88
  margin-bottom: 20px;
89
  }
90
- .tool-group { margin-bottom: 15px; }
 
 
 
 
91
  .tool-group label {
92
  display: block;
93
  margin-bottom: 8px;
94
  font-weight: 500;
95
- color: var(--text-secondary);
 
 
 
 
 
 
96
  }
97
- .tool-buttons { display: flex; gap: 10px; }
98
  .tool-btn {
99
  background: rgba(76, 175, 80, 0.2);
100
- color: var(--primary);
101
- border: 1px solid var(--primary);
102
  padding: 10px 16px;
103
  border-radius: 6px;
104
  cursor: pointer;
 
105
  transition: all 0.3s ease;
106
  flex: 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  }
108
- .tool-btn:hover { background: rgba(76, 175, 80, 0.3); }
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; }
 
 
 
 
 
 
 
 
128
  }
129
  </style>
130
  </head>
131
  <body>
132
  <div class="container">
133
- <h1>Interactive Leaf Segmentation</h1>
 
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>
 
143
  <div class="tools-panel">
144
  <div class="tool-group">
145
  <label>Annotation Tools</label>
146
  <div class="tool-buttons">
147
- <button class="tool-btn active" data-tool="point">Point (Dot)</button>
148
  <button class="tool-btn" data-tool="line">Line/Drag</button>
149
  </div>
150
  </div>
151
- <button class="tool-btn" id="clear-annotations" style="width:100%; margin-top:10px;">Clear Annotations</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  </div>
153
- <button class="predict-btn" id="predict-btn" disabled>Predict</button>
154
  </div>
 
155
  <div class="column">
156
  <h2>Output</h2>
157
  <div class="output-tabs">
@@ -165,7 +330,8 @@
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>
@@ -175,7 +341,6 @@
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', () => {
@@ -192,7 +357,7 @@
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;
197
  this.uploadedImage = null;
198
  this.predictedMask = null;
@@ -202,6 +367,7 @@
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
 
@@ -213,9 +379,11 @@
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
 
@@ -240,7 +408,7 @@
240
  this.predictBtn.onclick = () => this.predict();
241
  this.downloadBtn.onclick = () => this.downloadMask();
242
  }
243
-
244
  handleFile(file) {
245
  if (!file || !file.type.startsWith('image/')) return;
246
  const reader = new FileReader();
@@ -250,7 +418,6 @@
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;
@@ -260,7 +427,7 @@
260
  };
261
  reader.readAsDataURL(file);
262
  }
263
-
264
  reset() {
265
  this.annotations = [];
266
  this.predictedMask = null;
@@ -268,17 +435,17 @@
268
  this.downloadBtn.disabled = true;
269
  this.outputCtx.clearRect(0,0, this.outputCanvas.width, this.outputCanvas.height);
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) {
283
  const rect = this.inputCanvas.getBoundingClientRect();
284
  return {
@@ -294,7 +461,7 @@
294
  if (this.currentTool === 'point') this.drawOnCanvas(coords.x, coords.y);
295
  else { this.lastX = coords.x; this.lastY = coords.y; }
296
  }
297
-
298
  draw(e) {
299
  if (!this.isDrawing || this.currentTool !== 'line') return;
300
  const coords = this.getCanvasCoordinates(e);
@@ -303,7 +470,7 @@
303
  }
304
 
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();
@@ -320,14 +487,14 @@
320
  this.annotations.push(true);
321
  this.predictBtn.disabled = false;
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);
329
  }
330
-
331
  async predict() {
332
  if (!this.uploadedImage || this.annotations.length === 0) return;
333
  this.predictBtn.disabled = true; this.predictBtn.innerText = "Processing...";
@@ -343,7 +510,6 @@
343
  });
344
  if (!response.ok) throw new Error(`Server error: ${response.status}`);
345
  const result = await response.json();
346
-
347
  const maskImg = new Image();
348
  maskImg.onload = () => {
349
  this.predictedMask = maskImg;
@@ -359,19 +525,21 @@
359
  this.predictBtn.disabled = this.annotations.length === 0;
360
  }
361
  }
362
-
363
  setView(view) {
364
  this.currentView = view;
365
  document.querySelectorAll('.output-tabs .tool-btn').forEach(b => b.classList.remove('active'));
366
  document.getElementById(`view-${view}`).classList.add('active');
367
- this.transparencyControl.style.display = (view === 'filled') ? 'block' : 'none';
368
  this.renderOutput();
369
  }
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;
@@ -381,7 +549,7 @@
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';
@@ -391,7 +559,6 @@
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();
@@ -399,15 +566,15 @@
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() {
412
  if (!this.predictedMask) return;
413
  const link = document.createElement('a');
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Leaf Segmentation with precision</title>
7
  <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
 
 
 
14
  body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #1e1e2e 0%, #2d2d44 100%);
17
+ color: #e0e0e0;
18
+ min-height: 100vh;
19
  padding: 20px;
20
  }
21
+
22
  .container {
23
  max-width: 1400px;
24
  margin: 0 auto;
25
+ background: rgba(45, 45, 68, 0.8);
26
  border-radius: 16px;
27
+ padding: 30px;
28
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
29
+ backdrop-filter: blur(10px);
30
  }
31
+
32
  h1 {
33
  text-align: center;
34
+ color: #4CAF50;
35
+ margin-bottom: 30px;
36
+ font-size: 2.5em;
37
  font-weight: 300;
38
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
39
  }
40
+
41
  .main-content {
42
  display: grid;
43
  grid-template-columns: 1fr 1fr;
44
  gap: 30px;
45
+ min-height: 600px;
46
  }
47
+
48
  .column {
49
+ background: rgba(30, 30, 46, 0.6);
50
  border-radius: 12px;
51
  padding: 25px;
52
+ border: 1px solid rgba(76, 175, 80, 0.2);
53
  }
54
+
55
  .column h2 {
56
+ color: #4CAF50;
57
  margin-bottom: 20px;
58
  font-size: 1.5em;
59
+ font-weight: 400;
60
  }
61
+
62
  .upload-area {
63
+ border: 2px dashed #4CAF50;
64
+ border-radius: 12px;
65
+ padding: 40px;
66
  text-align: center;
67
  margin-bottom: 20px;
68
  transition: all 0.3s ease;
69
  cursor: pointer;
70
+ background: rgba(76, 175, 80, 0.05);
71
  }
72
+
73
+ .upload-area:hover {
74
  background: rgba(76, 175, 80, 0.1);
75
  border-color: #66BB6A;
76
  }
77
+
78
+ .upload-area.dragover {
79
+ background: rgba(76, 175, 80, 0.15);
80
+ border-color: #81C784;
81
+ transform: scale(1.02);
82
+ }
83
+
84
+ .upload-btn {
85
+ background: linear-gradient(135deg, #4CAF50, #45a049);
86
+ color: white;
87
+ border: none;
88
+ padding: 12px 24px;
89
+ border-radius: 8px;
90
+ cursor: pointer;
91
+ font-size: 16px;
92
+ font-weight: 500;
93
+ margin-bottom: 10px;
94
+ transition: all 0.3s ease;
95
+ box-shadow: 0 4px 8px rgba(76, 175, 80, 0.3);
96
+ }
97
+
98
+ .upload-btn:hover {
99
+ background: linear-gradient(135deg, #45a049, #4CAF50);
100
+ transform: translateY(-2px);
101
+ box-shadow: 0 6px 12px rgba(76, 175, 80, 0.4);
102
+ }
103
+
104
  .canvas-container {
105
  background: #000;
106
  border-radius: 8px;
 
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;
121
  }
122
+
123
+ .tools-panel {
124
+ background: rgba(0, 0, 0, 0.3);
125
  border-radius: 8px;
126
  padding: 20px;
127
  margin-bottom: 20px;
128
  }
129
+
130
+ .tool-group {
131
+ margin-bottom: 15px;
132
+ }
133
+
134
  .tool-group label {
135
  display: block;
136
  margin-bottom: 8px;
137
  font-weight: 500;
138
+ color: #B0B0B0;
139
+ }
140
+
141
+ .tool-buttons {
142
+ display: flex;
143
+ gap: 10px;
144
+ flex-wrap: wrap;
145
  }
146
+
147
  .tool-btn {
148
  background: rgba(76, 175, 80, 0.2);
149
+ color: #4CAF50;
150
+ border: 1px solid #4CAF50;
151
  padding: 10px 16px;
152
  border-radius: 6px;
153
  cursor: pointer;
154
+ font-size: 14px;
155
  transition: all 0.3s ease;
156
  flex: 1;
157
+ min-width: 100px;
158
+ }
159
+
160
+ .tool-btn:hover {
161
+ background: rgba(76, 175, 80, 0.3);
162
+ }
163
+
164
+ .tool-btn.active {
165
+ background: #4CAF50;
166
+ color: white;
167
+ box-shadow: 0 0 10px rgba(76, 175, 80, 0.5);
168
+ }
169
+
170
+ .slider-container {
171
+ display: flex;
172
+ align-items: center;
173
+ gap: 10px;
174
+ }
175
+
176
+ .slider {
177
+ width:100%;
178
+ }
179
+
180
+ .action-panel {
181
+ text-align: center;
182
  }
183
+
 
184
  .predict-btn {
185
  background: linear-gradient(135deg, #FF6B35, #F7931E);
186
+ color: white;
187
+ border: none;
188
+ padding: 15px 30px;
189
+ border-radius: 8px;
190
+ cursor: pointer;
191
+ font-size: 18px;
192
+ font-weight: 600;
193
+ margin-bottom: 15px;
194
+ transition: all 0.3s ease;
195
+ box-shadow: 0 4px 8px rgba(255, 107, 53, 0.3);
196
+ width: 100%;
197
+ }
198
+
199
+ .predict-btn:hover:not(:disabled) {
200
+ background: linear-gradient(135deg, #F7931E, #FF6B35);
201
+ transform: translateY(-2px);
202
+ box-shadow: 0 6px 12px rgba(255, 107, 53, 0.4);
203
+ }
204
+
205
+ .predict-btn:disabled {
206
+ background: #666;
207
+ cursor: not-allowed;
208
+ transform: none;
209
+ box-shadow: none;
210
+ }
211
+
212
+ .clear-btn {
213
+ background: transparent;
214
+ color: #ff6b6b;
215
+ border: 1px solid #ff6b6b;
216
+ padding: 8px 16px;
217
+ border-radius: 6px;
218
+ cursor: pointer;
219
+ font-size: 14px;
220
+ transition: all 0.3s ease;
221
+ width: 100%;
222
+ }
223
+
224
+ .clear-btn:hover {
225
+ background: rgba(255, 107, 107, 0.1);
226
  }
227
+
228
+ .output-controls {
229
+ background: rgba(0, 0, 0, 0.3);
230
+ border-radius: 8px;
231
+ padding: 20px;
232
+ }
233
+
234
  .download-btn {
235
+ background: linear-gradient(135deg, #9C27B0, #673AB7);
236
+ color: white;
237
+ border: none;
238
+ padding: 12px 24px;
239
+ border-radius: 8px;
240
+ cursor: pointer;
241
+ font-size: 16px;
242
+ font-weight: 500;
243
+ transition: all 0.3s ease;
244
+ box-shadow: 0 4px 8px rgba(156, 39, 176, 0.3);
245
+ width: 100%;
246
+ margin-top: 15px;
247
+ }
248
+
249
+ .download-btn:hover:not(:disabled) {
250
+ background: linear-gradient(135deg, #673AB7, #9C27B0);
251
+ transform: translateY(-2px);
252
+ box-shadow: 0 6px 12px rgba(156, 39, 176, 0.4);
253
+ }
254
+
255
+ .download-btn:disabled {
256
+ background: #666;
257
+ cursor: not-allowed;
258
+ }
259
+
260
+ .hidden {
261
+ display: none !important;
262
  }
263
+
264
+ .output-tabs {
265
+ display: flex;
266
+ gap: 10px;
267
+ margin-bottom: 20px;
268
+ }
269
+
270
+ @media (max-width: 768px) {
271
+ .main-content {
272
+ grid-template-columns: 1fr;
273
+ gap: 20px;
274
+ }
275
  }
276
  </style>
277
  </head>
278
  <body>
279
  <div class="container">
280
+ <h1>Leaf Segmentation with precision</h1>
281
+
282
  <div class="main-content">
283
  <div class="column">
284
  <h2>Input & Controls</h2>
285
+
286
  <div class="upload-area" id="upload-area">
287
+ <button class="upload-btn">Upload Image</button>
288
+ <p>Or drop an image here</p>
289
  </div>
290
+
291
  <div class="canvas-container">
292
  <canvas id="input-canvas"></canvas>
293
  </div>
294
+
295
  <div class="tools-panel">
296
  <div class="tool-group">
297
  <label>Annotation Tools</label>
298
  <div class="tool-buttons">
299
+ <button class="tool-btn active" data-tool="point">Positive Point (Dot)</button>
300
  <button class="tool-btn" data-tool="line">Line/Drag</button>
301
  </div>
302
  </div>
303
+ <div class="tool-group">
304
+ <label>Brush Size</label>
305
+ <div class="slider-container">
306
+ <input type="range" class="slider" id="brush-size" min="2" max="20" value="5">
307
+ <span id="brush-size-value">5</span>
308
+ </div>
309
+ </div>
310
+ <div class="tool-group">
311
+ <button class="clear-btn" id="clear-annotations">Clear Annotations</button>
312
+ </div>
313
+ </div>
314
+
315
+ <div class="action-panel">
316
+ <button class="predict-btn" id="predict-btn" disabled>Predict</button>
317
  </div>
 
318
  </div>
319
+
320
  <div class="column">
321
  <h2>Output</h2>
322
  <div class="output-tabs">
 
330
  <div class="output-controls">
331
  <div class="tool-group" id="transparency-control">
332
  <label>Mask Transparency</label>
333
+ <input type="range" class="slider" id="transparency" min="0" max="100" value="50">
334
+ <span id="transparency-value">50%</span>
335
  </div>
336
  <button class="download-btn" id="download-btn" disabled>Download Mask</button>
337
  </div>
 
341
 
342
  <script src="https://docs.opencv.org/4.8.0/opencv.js"></script>
343
  <script>
 
344
  cv['onRuntimeInitialized'] = () => {
345
  console.log('OpenCV.js is ready.');
346
  document.addEventListener('DOMContentLoaded', () => {
 
357
  this.maskCanvas = document.createElement('canvas');
358
  this.maskCtx = this.maskCanvas.getContext('2d');
359
  this.currentTool = 'point';
360
+ this.brushSize = 5;
361
  this.isDrawing = false;
362
  this.uploadedImage = null;
363
  this.predictedMask = null;
 
367
  this.downloadBtn = document.getElementById('download-btn');
368
  this.transparencySlider = document.getElementById('transparency');
369
  this.transparencyControl = document.getElementById('transparency-control');
370
+
371
  this.initEventListeners();
372
  }
373
 
 
379
 
380
  const uploadArea = document.getElementById('upload-area');
381
  uploadArea.onclick = () => fileInput.click();
382
+ uploadArea.ondragover = e => { e.preventDefault(); uploadArea.classList.add('dragover'); };
383
+ uploadArea.ondragleave = () => uploadArea.classList.remove('dragover');
384
  uploadArea.ondrop = e => {
385
  e.preventDefault();
386
+ uploadArea.classList.remove('dragover');
387
  this.handleFile(e.dataTransfer.files[0]);
388
  };
389
 
 
408
  this.predictBtn.onclick = () => this.predict();
409
  this.downloadBtn.onclick = () => this.downloadMask();
410
  }
411
+
412
  handleFile(file) {
413
  if (!file || !file.type.startsWith('image/')) return;
414
  const reader = new FileReader();
 
418
  this.uploadedImage = img;
419
  this.reset();
420
  this.drawImageScaled(this.inputCanvas, this.inputCtx, img);
 
421
  this.outputCanvas.width = this.inputCanvas.width;
422
  this.outputCanvas.height = this.inputCanvas.height;
423
  this.maskCanvas.width = this.inputCanvas.width;
 
427
  };
428
  reader.readAsDataURL(file);
429
  }
430
+
431
  reset() {
432
  this.annotations = [];
433
  this.predictedMask = null;
 
435
  this.downloadBtn.disabled = true;
436
  this.outputCtx.clearRect(0,0, this.outputCanvas.width, this.outputCanvas.height);
437
  }
438
+
439
  drawImageScaled(canvas, ctx, img) {
440
+ const container = canvas.parentElement;
441
+ const hRatio = container.clientWidth / img.width;
442
+ const vRatio = container.clientHeight / img.height;
443
  const ratio = Math.min(hRatio, vRatio);
444
  canvas.width = img.width * ratio;
445
  canvas.height = img.height * ratio;
 
446
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
447
  }
448
+
449
  getCanvasCoordinates(e) {
450
  const rect = this.inputCanvas.getBoundingClientRect();
451
  return {
 
461
  if (this.currentTool === 'point') this.drawOnCanvas(coords.x, coords.y);
462
  else { this.lastX = coords.x; this.lastY = coords.y; }
463
  }
464
+
465
  draw(e) {
466
  if (!this.isDrawing || this.currentTool !== 'line') return;
467
  const coords = this.getCanvasCoordinates(e);
 
470
  }
471
 
472
  stopDrawing() { this.isDrawing = false; }
473
+
474
  drawOnCanvas(x1, y1, x2 = null, y2 = null) {
475
  [this.inputCtx, this.maskCtx].forEach((ctx, i) => {
476
  ctx.beginPath();
 
487
  this.annotations.push(true);
488
  this.predictBtn.disabled = false;
489
  }
490
+
491
  clearAnnotations() {
492
  if (!this.uploadedImage) return;
493
  this.reset();
494
  this.drawImageScaled(this.inputCanvas, this.inputCtx, this.uploadedImage);
495
  this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height);
496
  }
497
+
498
  async predict() {
499
  if (!this.uploadedImage || this.annotations.length === 0) return;
500
  this.predictBtn.disabled = true; this.predictBtn.innerText = "Processing...";
 
510
  });
511
  if (!response.ok) throw new Error(`Server error: ${response.status}`);
512
  const result = await response.json();
 
513
  const maskImg = new Image();
514
  maskImg.onload = () => {
515
  this.predictedMask = maskImg;
 
525
  this.predictBtn.disabled = this.annotations.length === 0;
526
  }
527
  }
528
+
529
  setView(view) {
530
  this.currentView = view;
531
  document.querySelectorAll('.output-tabs .tool-btn').forEach(b => b.classList.remove('active'));
532
  document.getElementById(`view-${view}`).classList.add('active');
533
+ this.transparencyControl.style.display = (view === 'filled' || view === 'outline') ? 'block' : 'none';
534
  this.renderOutput();
535
  }
536
+
537
  renderOutput() {
538
  if (!this.predictedMask) return;
539
+
540
+ this.outputCtx.clearRect(0,0, this.outputCanvas.width, this.outputCanvas.height);
541
  this.drawImageScaled(this.outputCanvas, this.outputCtx, this.uploadedImage);
542
+
543
  if (this.currentView === 'binary') {
544
  this.drawImageScaled(this.outputCanvas, this.outputCtx, this.predictedMask);
545
  return;
 
549
  const tempCtx = tempCanvas.getContext('2d');
550
  tempCanvas.width = this.outputCanvas.width;
551
  tempCanvas.height = this.outputCanvas.height;
552
+ tempCtx.drawImage(this.predictedMask, 0, 0, tempCanvas.width, tempCanvas.height);
553
 
554
  if (this.currentView === 'filled') {
555
  tempCtx.globalCompositeOperation = 'source-in';
 
559
  this.outputCtx.globalAlpha = this.transparencySlider.value / 100;
560
  this.outputCtx.drawImage(tempCanvas, 0, 0);
561
  this.outputCtx.globalAlpha = 1.0;
 
562
  } else if (this.currentView === 'outline') {
563
  const src = cv.imread(tempCanvas);
564
  const contours = new cv.MatVector();
 
566
  cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0);
567
  cv.findContours(src, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);
568
 
569
+ let color = new cv.Scalar(255, 255, 255, 255); // White color
 
570
  for (let i = 0; i < contours.size(); ++i) {
571
+ cv.drawContours(src, contours, i, color, 2, cv.LINE_8, hierarchy, 100);
572
  }
573
+ cv.imshow('output-canvas', src); // Draw contours back onto the output canvas
574
  src.delete(); contours.delete(); hierarchy.delete();
575
  }
576
  }
577
+
578
  downloadMask() {
579
  if (!this.predictedMask) return;
580
  const link = document.createElement('a');