Subh775 commited on
Commit
b3c8c6c
·
verified ·
1 Parent(s): 487d73b

Create static/index.html

Browse files
Files changed (1) hide show
  1. static/index.html +330 -0
static/index.html ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Tulsi Leaf Segmentation — RF-DETR</title>
7
+ <style>
8
+ :root {
9
+ --primary: #E73562; /* pink */
10
+ --secondary: #EA782D; /* red-ish */
11
+ --dark-bg: #121212;
12
+ --surface-bg: #1E1E2E;
13
+ --text-primary: #E0E0E0;
14
+ --text-secondary: #BDB2C6;
15
+ --border-color: rgba(255, 255, 255, 0.06);
16
+ }
17
+ * { margin: 0; padding: 0; box-sizing: border-box; }
18
+ body {
19
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
20
+ background: var(--dark-bg);
21
+ color: var(--text-primary);
22
+ padding: 20px;
23
+ min-height: 100vh;
24
+ }
25
+ .container {
26
+ max-width: 1200px;
27
+ margin: 0 auto;
28
+ background: var(--surface-bg);
29
+ border-radius: 14px;
30
+ box-shadow: 0 10px 30px rgba(0,0,0,0.35);
31
+ border: 1px solid var(--border-color);
32
+ overflow: hidden;
33
+ }
34
+ header {
35
+ text-align: center;
36
+ padding: 30px;
37
+ background: rgba(0,0,0,0.18);
38
+ border-bottom: 1px solid var(--border-color);
39
+ }
40
+ h1 {
41
+ color: var(--primary);
42
+ font-size: 2.2em;
43
+ font-weight: 600;
44
+ margin-bottom: 8px;
45
+ }
46
+ .header-note {
47
+ font-size: 0.92rem;
48
+ background-color: rgba(255, 59, 48, 0.06);
49
+ color: var(--secondary);
50
+ padding: 8px 12px;
51
+ border-radius: 6px;
52
+ display: inline-block;
53
+ border: 1px solid rgba(255,59,48,0.08);
54
+ }
55
+
56
+ .main-content {
57
+ display: grid;
58
+ grid-template-columns: 1fr 1fr;
59
+ gap: 26px;
60
+ padding: 30px;
61
+ }
62
+ .column {
63
+ background: rgba(0,0,0,0.15);
64
+ border-radius: 12px;
65
+ padding: 22px;
66
+ display: flex;
67
+ flex-direction: column;
68
+ }
69
+ .column h2 {
70
+ color: var(--primary);
71
+ margin-bottom: 16px;
72
+ font-size: 1.4em;
73
+ font-weight: 500;
74
+ text-align: center;
75
+ }
76
+
77
+ .upload-area {
78
+ border: 2px dashed var(--primary);
79
+ border-radius: 8px;
80
+ padding: 28px;
81
+ text-align: center;
82
+ margin-bottom: 18px;
83
+ transition: all 0.2s ease;
84
+ cursor: pointer;
85
+ user-select: none;
86
+ }
87
+ .upload-area.dragover {
88
+ background: rgba(255, 77, 166, 0.06);
89
+ transform: translateY(-2px);
90
+ }
91
+ .upload-area p { color: var(--text-secondary); font-size: 1.05em; }
92
+ .canvas-container {
93
+ background: #000;
94
+ border-radius: 8px;
95
+ margin-bottom: 18px;
96
+ display: flex;
97
+ justify-content: center;
98
+ align-items: center;
99
+ min-height: 420px;
100
+ border: 1px solid var(--border-color);
101
+ position: relative;
102
+ overflow: hidden;
103
+ }
104
+ canvas, img {
105
+ max-width: 100%;
106
+ max-height: 520px;
107
+ object-fit: contain;
108
+ border-radius: 4px;
109
+ }
110
+
111
+ .controls-panel {
112
+ background: rgba(0,0,0,0.18);
113
+ border-radius: 8px;
114
+ padding: 14px;
115
+ margin-top: 6px;
116
+ }
117
+ .control-group { margin-bottom: 12px; }
118
+ label { display:block; margin-bottom:6px; color:var(--text-secondary); font-size:0.95em; }
119
+ .confidence-display { display:flex; justify-content:space-between; align-items:center; margin-bottom:8px; }
120
+ .confidence-value { color: var(--primary); font-weight:700; font-size:1.05em; }
121
+
122
+ input[type="range"] { width:100%; height:8px; border-radius:4px; background: rgba(255,255,255,0.06); outline:none; }
123
+ input[type="range"]::-webkit-slider-thumb { -webkit-appearance:none; width:18px; height:18px; border-radius:50%; background:var(--primary); cursor:pointer; }
124
+ input[type="range"]::-moz-range-thumb { width:18px; height:18px; border-radius:50%; background:var(--primary); cursor:pointer; }
125
+
126
+ .action-btn {
127
+ background: linear-gradient(135deg, var(--secondary), #FF7A5A);
128
+ color: white;
129
+ border: none;
130
+ padding: 12px 20px;
131
+ font-size: 1.02em;
132
+ width: 100%;
133
+ border-radius: 8px;
134
+ cursor: pointer;
135
+ transition: all 0.22s ease;
136
+ margin-top: 6px;
137
+ font-weight:600;
138
+ }
139
+ .action-btn:disabled { background: linear-gradient(135deg,#444,#666); cursor:not-allowed; opacity:0.6; }
140
+
141
+ .info-panel { background: rgba(0,0,0,0.14); border-radius:8px; padding:14px; margin-top:10px; }
142
+ .detection-info { color: var(--text-secondary); font-size:0.95em; }
143
+ .detection-count { color: var(--primary); font-weight:700; font-size:1.1em; margin-top:8px; }
144
+
145
+ .download-btn {
146
+ background: linear-gradient(135deg,#D63384,#8E2B57);
147
+ color: white;
148
+ border: none;
149
+ padding: 10px 16px;
150
+ border-radius: 8px;
151
+ cursor: pointer;
152
+ width: 100%;
153
+ font-weight:600;
154
+ }
155
+ @media (max-width: 900px) {
156
+ .main-content { grid-template-columns: 1fr; }
157
+ h1 { font-size: 1.8em; }
158
+ }
159
+ .visually-hidden { position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }
160
+ </style>
161
+ </head>
162
+ <body>
163
+ <div class="container">
164
+ <header>
165
+ <h1>Tulsi Leaf Segmentation — RF-DETR</h1>
166
+ <p class="header-note">Upload an image, set confidence, press <strong>Detect</strong>. CPU-only inference.</p>
167
+ </header>
168
+
169
+ <div class="main-content">
170
+ <!-- Left column: upload & controls -->
171
+ <div class="column">
172
+ <h2>Input & Controls</h2>
173
+
174
+ <div id="upload-area" class="upload-area">
175
+ <p>Click or drop an image here (jpg, png)</p>
176
+ </div>
177
+ <input id="file-input" class="visually-hidden" type="file" accept="image/*" />
178
+
179
+ <div class="canvas-container">
180
+ <canvas id="input-canvas"></canvas>
181
+ </div>
182
+
183
+ <div class="controls-panel">
184
+ <div class="control-group">
185
+ <div class="confidence-display">
186
+ <label>Confidence Threshold</label>
187
+ <span class="confidence-value" id="conf-display">0.25</span>
188
+ </div>
189
+ <input id="conf-slider" type="range" min="0" max="100" value="25" step="1">
190
+ </div>
191
+
192
+ <button id="predict-btn" class="action-btn" disabled>Detect Leaves</button>
193
+ </div>
194
+ </div>
195
+
196
+ <!-- Right column: output & info -->
197
+ <div class="column">
198
+ <h2>Detection Results</h2>
199
+
200
+ <div class="canvas-container">
201
+ <img id="annotated-img" alt="Annotated result" src="" />
202
+ </div>
203
+
204
+ <div class="info-panel">
205
+ <div class="detection-info">
206
+ <p><strong>Status:</strong> <span id="status-text">Ready</span></p>
207
+ <p class="detection-count">Leaves Detected: <span id="leaf-count">0</span></p>
208
+ <p class="detection-info">Highest confidence: <strong id="best-conf">0.00</strong></p>
209
+ <button id="download-btn" class="download-btn" disabled>Download Annotated Image</button>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ </div>
215
+
216
+ <script>
217
+ (function(){
218
+ const uploadArea = document.getElementById('upload-area');
219
+ const fileInput = document.getElementById('file-input');
220
+ const inputCanvas = document.getElementById('input-canvas');
221
+ const inputCtx = inputCanvas.getContext('2d');
222
+ const predictBtn = document.getElementById('predict-btn');
223
+ const confSlider = document.getElementById('conf-slider');
224
+ const confDisplay = document.getElementById('conf-display');
225
+ const annotatedImg = document.getElementById('annotated-img');
226
+ const statusText = document.getElementById('status-text');
227
+ const leafCountEl = document.getElementById('leaf-count');
228
+ const bestConfEl = document.getElementById('best-conf');
229
+ const downloadBtn = document.getElementById('download-btn');
230
+
231
+ let currentImage = null;
232
+ let lastAnnotatedDataUrl = null;
233
+ let boxes = [];
234
+
235
+ function setStatus(text) { statusText.textContent = text; }
236
+
237
+ uploadArea.addEventListener('click', ()=> fileInput.click());
238
+ uploadArea.addEventListener('dragover', (e)=>{ e.preventDefault(); uploadArea.classList.add('dragover'); });
239
+ uploadArea.addEventListener('dragleave', ()=> uploadArea.classList.remove('dragover'));
240
+ uploadArea.addEventListener('drop', (e)=>{
241
+ e.preventDefault(); uploadArea.classList.remove('dragover');
242
+ const f = e.dataTransfer.files && e.dataTransfer.files[0];
243
+ if (f) handleFile(f);
244
+ });
245
+
246
+ fileInput.addEventListener('change', (e)=> {
247
+ const f = e.target.files && e.target.files[0];
248
+ if (f) handleFile(f);
249
+ });
250
+
251
+ function handleFile(file) {
252
+ const reader = new FileReader();
253
+ reader.onload = (ev) => {
254
+ const img = new Image();
255
+ img.onload = () => {
256
+ currentImage = img;
257
+ drawToCanvas(img);
258
+ predictBtn.disabled = false;
259
+ annotatedImg.src = "";
260
+ downloadBtn.disabled = true;
261
+ setStatus("Image ready");
262
+ };
263
+ img.src = ev.target.result;
264
+ };
265
+ reader.readAsDataURL(file);
266
+ }
267
+
268
+ function drawToCanvas(img) {
269
+ const maxW = 600, maxH = 520;
270
+ let w = img.width, h = img.height;
271
+ const ratio = Math.min(maxW/w, maxH/h, 1);
272
+ w = Math.round(w*ratio); h = Math.round(h*ratio);
273
+ inputCanvas.width = w; inputCanvas.height = h;
274
+ inputCtx.clearRect(0,0,w,h);
275
+ inputCtx.drawImage(img, 0,0,w,h);
276
+ }
277
+
278
+ confSlider.addEventListener('input', ()=> {
279
+ const val = confSlider.value / 100;
280
+ confDisplay.textContent = val.toFixed(2);
281
+ });
282
+
283
+ predictBtn.addEventListener('click', async ()=>{
284
+ if (!currentImage) return;
285
+ predictBtn.disabled = true;
286
+ setStatus("Processing...");
287
+ const dataUrl = inputCanvas.toDataURL('image/jpeg', 0.9);
288
+ const conf = confSlider.value / 100;
289
+ try {
290
+ const res = await fetch('/predict', {
291
+ method: 'POST',
292
+ headers: { 'Content-Type': 'application/json' },
293
+ body: JSON.stringify({ image: dataUrl, conf })
294
+ });
295
+ if (!res.ok) throw new Error('Server error: ' + res.status);
296
+ const j = await res.json();
297
+ // j.annotated: data URL, j.confidences: [..], j.count
298
+ lastAnnotatedDataUrl = j.annotated;
299
+ boxes = j.confidences || [];
300
+ annotatedImg.src = lastAnnotatedDataUrl;
301
+ leafCountEl.textContent = j.count || 0;
302
+ const best = j.confidences && j.confidences.length ? Math.max(...j.confidences) : 0;
303
+ bestConfEl.textContent = (best).toFixed(2);
304
+ downloadBtn.disabled = false;
305
+ setStatus("Done");
306
+ } catch (err) {
307
+ console.error(err);
308
+ setStatus("Error: " + (err.message || err));
309
+ alert('Prediction failed. See console for details.');
310
+ } finally {
311
+ predictBtn.disabled = false;
312
+ }
313
+ });
314
+
315
+ downloadBtn.addEventListener('click', ()=>{
316
+ if (!lastAnnotatedDataUrl) return;
317
+ const a = document.createElement('a');
318
+ a.href = lastAnnotatedDataUrl;
319
+ a.download = `tulsi_segment_${Date.now()}.png`;
320
+ document.body.appendChild(a);
321
+ a.click();
322
+ a.remove();
323
+ });
324
+
325
+ // Initialize
326
+ setStatus('Ready');
327
+ })();
328
+ </script>
329
+ </body>
330
+ </html>