mi23 commited on
Commit
bad8b3a
·
verified ·
1 Parent(s): c7c0b01

website for mushroom images classification. I have backend which accept /upload, and return id of task. Then there is endpoint /status/<task_id> which shows progress, and results can be fetched at /results/<task_id>.

Browse files
Files changed (2) hide show
  1. README.md +8 -5
  2. index.html +391 -18
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Fungus Among Us Classifier
3
- emoji:
4
- colorFrom: red
5
- colorTo: purple
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: Fungus Among Us Classifier 🍄
3
+ colorFrom: gray
4
+ colorTo: green
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://deepsite.hf.co).
index.html CHANGED
@@ -1,19 +1,392 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
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>Fungus Among Us Classifier</title>
7
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://unpkg.com/feather-icons"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
11
+ <style>
12
+ .dropzone {
13
+ border: 2px dashed #9CA3AF;
14
+ transition: all 0.3s ease;
15
+ }
16
+ .dropzone-active {
17
+ border-color: #10B981;
18
+ background-color: rgba(16, 185, 129, 0.05);
19
+ }
20
+ .dropzone-reject {
21
+ border-color: #EF4444;
22
+ background-color: rgba(239, 68, 68, 0.05);
23
+ }
24
+ .progress-bar {
25
+ transition: width 0.3s ease;
26
+ }
27
+ .result-card {
28
+ transition: all 0.3s ease;
29
+ }
30
+ .result-card:hover {
31
+ transform: translateY(-2px);
32
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
33
+ }
34
+ </style>
35
+ </head>
36
+ <body class="bg-gray-50 min-h-screen">
37
+ <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
38
+ <!-- Header -->
39
+ <header class="text-center mb-12">
40
+ <div class="flex items-center justify-center mb-4">
41
+ <i data-feather="moon" class="w-10 h-10 text-emerald-600 mr-3"></i>
42
+ <h1 class="text-4xl font-bold text-gray-800">Fungus Among Us</h1>
43
+ </div>
44
+ <p class="text-lg text-gray-600 max-w-2xl mx-auto">
45
+ Upload mushroom images to identify species with our AI classifier.
46
+ Supports common edible and poisonous varieties.
47
+ </p>
48
+ </header>
49
+
50
+ <!-- Main Content -->
51
+ <main>
52
+ <!-- Upload Section -->
53
+ <section class="mb-12">
54
+ <div id="dropzone" class="dropzone rounded-xl p-8 md:p-12 text-center cursor-pointer bg-white shadow-sm">
55
+ <div class="max-w-md mx-auto">
56
+ <i data-feather="upload-cloud" class="w-12 h-12 text-emerald-500 mx-auto mb-4"></i>
57
+ <h2 class="text-xl font-semibold text-gray-800 mb-2">Upload Mushroom Image</h2>
58
+ <p class="text-gray-500 mb-6">Drag & drop or click to browse (JPG, PNG up to 5MB)</p>
59
+ <input type="file" id="fileInput" class="hidden" accept="image/jpeg,image/png" />
60
+ <button id="uploadBtn" class="px-6 py-2 bg-emerald-500 text-white rounded-lg hover:bg-emerald-600 transition">
61
+ Select File
62
+ </button>
63
+ <p class="text-xs text-gray-400 mt-4">We don't store your images after processing</p>
64
+ </div>
65
+ </div>
66
+ </section>
67
+
68
+ <!-- Status Section -->
69
+ <section id="statusSection" class="hidden mb-12">
70
+ <div class="bg-white rounded-xl p-6 shadow-sm">
71
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">Processing</h2>
72
+ <div class="flex items-center mb-4">
73
+ <div class="flex-1 bg-gray-200 rounded-full h-2.5">
74
+ <div id="progressBar" class="progress-bar bg-emerald-500 h-2.5 rounded-full" style="width: 0%"></div>
75
+ </div>
76
+ <span id="progressText" class="ml-4 text-sm font-medium text-gray-700">0%</span>
77
+ </div>
78
+ <div class="flex items-center">
79
+ <div class="animate-spin rounded-full h-5 w-5 border-b-2 border-emerald-500 mr-3"></div>
80
+ <p id="statusMessage" class="text-gray-600">Uploading image...</p>
81
+ </div>
82
+ </div>
83
+ </section>
84
+
85
+ <!-- Results Section -->
86
+ <section id="resultsSection" class="hidden">
87
+ <h2 class="text-2xl font-semibold text-gray-800 mb-6">Classification Results</h2>
88
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
89
+ <!-- Image Preview -->
90
+ <div class="result-card bg-white rounded-xl p-4 shadow-sm">
91
+ <h3 class="text-lg font-medium text-gray-800 mb-3">Your Image</h3>
92
+ <div class="rounded-lg overflow-hidden bg-gray-100">
93
+ <img id="previewImage" src="" alt="Uploaded mushroom" class="w-full h-64 object-contain" />
94
+ </div>
95
+ </div>
96
+
97
+ <!-- Classification Results -->
98
+ <div class="result-card bg-white rounded-xl p-4 shadow-sm">
99
+ <h3 class="text-lg font-medium text-gray-800 mb-3">Prediction</h3>
100
+ <div id="resultsContainer" class="space-y-4">
101
+ <!-- Results will be populated here -->
102
+ </div>
103
+ </div>
104
+ </div>
105
+
106
+ <div class="mt-6 text-center">
107
+ <button id="newUploadBtn" class="px-6 py-2 border border-emerald-500 text-emerald-500 rounded-lg hover:bg-emerald-50 transition">
108
+ <i data-feather="plus" class="w-4 h-4 inline mr-2"></i> Upload Another
109
+ </button>
110
+ </div>
111
+ </section>
112
+
113
+ <!-- Error Section -->
114
+ <section id="errorSection" class="hidden">
115
+ <div class="bg-red-50 border-l-4 border-red-500 p-4 rounded-lg">
116
+ <div class="flex">
117
+ <div class="flex-shrink-0">
118
+ <i data-feather="alert-triangle" class="h-5 w-5 text-red-500"></i>
119
+ </div>
120
+ <div class="ml-3">
121
+ <h3 class="text-sm font-medium text-red-800" id="errorTitle">Error</h3>
122
+ <div class="mt-2 text-sm text-red-700" id="errorMessage">
123
+ <!-- Error message will be inserted here -->
124
+ </div>
125
+ <div class="mt-4">
126
+ <button id="tryAgainBtn" class="px-4 py-1 text-sm font-medium text-red-800 hover:text-red-700">
127
+ Try Again
128
+ </button>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ </section>
134
+ </main>
135
+
136
+ <!-- Footer -->
137
+ <footer class="mt-16 text-center text-gray-500 text-sm">
138
+ <p>© 2023 Fungus Among Us Classifier. Not responsible for foraging decisions.</p>
139
+ <p class="mt-1">Always consult an expert before consuming wild mushrooms.</p>
140
+ </footer>
141
+ </div>
142
+
143
+ <script>
144
+ // Initialize feather icons
145
+ feather.replace();
146
+
147
+ // DOM Elements
148
+ const dropzone = document.getElementById('dropzone');
149
+ const fileInput = document.getElementById('fileInput');
150
+ const uploadBtn = document.getElementById('uploadBtn');
151
+ const statusSection = document.getElementById('statusSection');
152
+ const resultsSection = document.getElementById('resultsSection');
153
+ const errorSection = document.getElementById('errorSection');
154
+ const progressBar = document.getElementById('progressBar');
155
+ const progressText = document.getElementById('progressText');
156
+ const statusMessage = document.getElementById('statusMessage');
157
+ const previewImage = document.getElementById('previewImage');
158
+ const resultsContainer = document.getElementById('resultsContainer');
159
+ const newUploadBtn = document.getElementById('newUploadBtn');
160
+ const tryAgainBtn = document.getElementById('tryAgainBtn');
161
+ const errorMessage = document.getElementById('errorMessage');
162
+ const errorTitle = document.getElementById('errorTitle');
163
+
164
+ // State
165
+ let currentTaskId = null;
166
+ let statusCheckInterval = null;
167
+
168
+ // Event Listeners
169
+ uploadBtn.addEventListener('click', () => fileInput.click());
170
+ fileInput.addEventListener('change', handleFileSelect);
171
+ newUploadBtn.addEventListener('click', resetUpload);
172
+ tryAgainBtn.addEventListener('click', resetUpload);
173
+
174
+ // Drag and Drop Events
175
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
176
+ dropzone.addEventListener(eventName, preventDefaults, false);
177
+ });
178
+
179
+ function preventDefaults(e) {
180
+ e.preventDefault();
181
+ e.stopPropagation();
182
+ }
183
+
184
+ ['dragenter', 'dragover'].forEach(eventName => {
185
+ dropzone.addEventListener(eventName, highlight, false);
186
+ });
187
+
188
+ ['dragleave', 'drop'].forEach(eventName => {
189
+ dropzone.addEventListener(eventName, unhighlight, false);
190
+ });
191
+
192
+ function highlight() {
193
+ dropzone.classList.add('dropzone-active');
194
+ }
195
+
196
+ function unhighlight() {
197
+ dropzone.classList.remove('dropzone-active');
198
+ }
199
+
200
+ dropzone.addEventListener('drop', handleDrop, false);
201
+
202
+ function handleDrop(e) {
203
+ const dt = e.dataTransfer;
204
+ const files = dt.files;
205
+ if (files.length) {
206
+ fileInput.files = files;
207
+ handleFileSelect({ target: fileInput });
208
+ }
209
+ }
210
+
211
+ // File Handling
212
+ function handleFileSelect(e) {
213
+ const file = e.target.files[0];
214
+ if (!file) return;
215
+
216
+ // Validate file type
217
+ if (!file.type.match('image.*')) {
218
+ showError('Invalid File Type', 'Please upload an image file (JPEG or PNG)');
219
+ return;
220
+ }
221
+
222
+ // Validate file size (5MB max)
223
+ if (file.size > 5 * 1024 * 1024) {
224
+ showError('File Too Large', 'Maximum file size is 5MB');
225
+ return;
226
+ }
227
+
228
+ // Show preview
229
+ const reader = new FileReader();
230
+ reader.onload = function(e) {
231
+ previewImage.src = e.target.result;
232
+ };
233
+ reader.readAsDataURL(file);
234
+
235
+ // Start upload process
236
+ startUpload(file);
237
+ }
238
+
239
+ // Upload Process
240
+ async function startUpload(file) {
241
+ try {
242
+ // Show status section
243
+ statusSection.classList.remove('hidden');
244
+ dropzone.classList.add('hidden');
245
+ updateStatus('Uploading image...', 0);
246
+
247
+ // Simulate upload progress (in a real app, this would be actual upload progress)
248
+ for (let i = 0; i <= 100; i += 10) {
249
+ await new Promise(resolve => setTimeout(resolve, 300));
250
+ updateStatus('Uploading image...', i);
251
+ }
252
+
253
+ // In a real app, you would send the file to your backend here
254
+ // const formData = new FormData();
255
+ // formData.append('file', file);
256
+ // const response = await fetch('/upload', {
257
+ // method: 'POST',
258
+ // body: formData
259
+ // });
260
+ // const data = await response.json();
261
+ // currentTaskId = data.task_id;
262
+
263
+ // For demo purposes, we'll simulate a task ID
264
+ currentTaskId = 'demo_' + Date.now();
265
+
266
+ // Start checking status
267
+ checkStatus();
268
+ } catch (error) {
269
+ showError('Upload Failed', error.message);
270
+ }
271
+ }
272
+
273
+ // Status Checking
274
+ async function checkStatus() {
275
+ updateStatus('Analyzing mushroom...', 30);
276
+
277
+ // In a real app, you would poll your backend here
278
+ // Example:
279
+ // const response = await fetch(`/status/${currentTaskId}`);
280
+ // const data = await response.json();
281
+
282
+ // For demo, we'll simulate processing
283
+ statusCheckInterval = setInterval(async () => {
284
+ try {
285
+ // Simulate progress
286
+ const currentProgress = parseInt(progressBar.style.width) || 30;
287
+ const newProgress = Math.min(currentProgress + 10, 90);
288
+ updateStatus('Analyzing mushroom...', newProgress);
289
+
290
+ // Simulate completion after reaching 90%
291
+ if (newProgress >= 90) {
292
+ clearInterval(statusCheckInterval);
293
+ await new Promise(resolve => setTimeout(resolve, 1500));
294
+ updateStatus('Finalizing results...', 100);
295
+ await new Promise(resolve => setTimeout(resolve, 500));
296
+ showResults();
297
+ }
298
+ } catch (error) {
299
+ clearInterval(statusCheckInterval);
300
+ showError('Processing Error', error.message);
301
+ }
302
+ }, 1500);
303
+ }
304
+
305
+ // Update Status UI
306
+ function updateStatus(message, percent) {
307
+ statusMessage.textContent = message;
308
+ progressBar.style.width = `${percent}%`;
309
+ progressText.textContent = `${percent}%`;
310
+ }
311
+
312
+ // Show Results
313
+ async function showResults() {
314
+ try {
315
+ // In a real app, you would fetch results from your backend
316
+ // Example:
317
+ // const response = await fetch(`/results/${currentTaskId}`);
318
+ // const results = await response.json();
319
+
320
+ // For demo, we'll use mock data
321
+ const mockResults = [
322
+ { species: "Agaricus bisporus", confidence: 0.92, edible: true, description: "Common button mushroom, widely cultivated and edible." },
323
+ { species: "Amanita phalloides", confidence: 0.07, edible: false, description: "Death cap, extremely poisonous." },
324
+ { species: "Boletus edulis", confidence: 0.01, edible: true, description: "Porcini mushroom, prized edible species." }
325
+ ];
326
+
327
+ // Populate results
328
+ resultsContainer.innerHTML = '';
329
+ mockResults.forEach(result => {
330
+ const resultItem = document.createElement('div');
331
+ resultItem.className = 'p-3 rounded-lg border';
332
+ resultItem.style.borderColor = result.edible ? '#D1FAE5' : '#FEE2E2';
333
+ resultItem.style.backgroundColor = result.edible ? '#ECFDF5' : '#FEF2F2';
334
+
335
+ const confidencePercentage = Math.round(result.confidence * 100);
336
+
337
+ resultItem.innerHTML = `
338
+ <div class="flex justify-between items-start mb-1">
339
+ <h4 class="font-medium ${result.edible ? 'text-emerald-800' : 'text-red-800'}">
340
+ ${result.species}
341
+ </h4>
342
+ <span class="text-xs px-2 py-1 rounded-full ${result.edible ? 'bg-emerald-100 text-emerald-800' : 'bg-red-100 text-red-800'}">
343
+ ${confidencePercentage}% confidence
344
+ </span>
345
+ </div>
346
+ <div class="w-full bg-gray-200 rounded-full h-1.5 mb-2">
347
+ <div class="h-1.5 rounded-full ${result.edible ? 'bg-emerald-500' : 'bg-red-500'}" style="width: ${confidencePercentage}%"></div>
348
+ </div>
349
+ <p class="text-sm ${result.edible ? 'text-emerald-700' : 'text-red-700'}">
350
+ <i data-feather="${result.edible ? 'check-circle' : 'alert-circle'}" class="w-4 h-4 inline mr-1"></i>
351
+ ${result.edible ? 'Edible' : 'Poisonous'} - ${result.description}
352
+ </p>
353
+ `;
354
+ resultsContainer.appendChild(resultItem);
355
+ });
356
+
357
+ // Show results section
358
+ statusSection.classList.add('hidden');
359
+ resultsSection.classList.remove('hidden');
360
+
361
+ // Update feather icons in new elements
362
+ feather.replace();
363
+ } catch (error) {
364
+ showError('Results Error', error.message);
365
+ }
366
+ }
367
+
368
+ // Error Handling
369
+ function showError(title, message) {
370
+ clearInterval(statusCheckInterval);
371
+ errorTitle.textContent = title;
372
+ errorMessage.textContent = message;
373
+ statusSection.classList.add('hidden');
374
+ resultsSection.classList.add('hidden');
375
+ errorSection.classList.remove('hidden');
376
+ }
377
+
378
+ // Reset Upload
379
+ function resetUpload() {
380
+ fileInput.value = '';
381
+ currentTaskId = null;
382
+ clearInterval(statusCheckInterval);
383
+ statusSection.classList.add('hidden');
384
+ resultsSection.classList.add('hidden');
385
+ errorSection.classList.add('hidden');
386
+ dropzone.classList.remove('hidden');
387
+ progressBar.style.width = '0%';
388
+ progressText.textContent = '0%';
389
+ }
390
+ </script>
391
+ </body>
392
  </html>