Svngoku commited on
Commit
4f002c0
·
verified ·
1 Parent(s): eb223d5

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +407 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Deepsite Image Annotator
3
- emoji: 📊
4
- colorFrom: red
5
- colorTo: red
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: deepsite-image-annotator
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,407 @@
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>Image Annotation Tool for AI Dataset</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ .dropzone {
11
+ border: 2px dashed #9ca3af;
12
+ transition: all 0.3s;
13
+ }
14
+ .dropzone.active {
15
+ border-color: #3b82f6;
16
+ background-color: rgba(59, 130, 246, 0.05);
17
+ }
18
+ .preview-image {
19
+ max-height: 60vh;
20
+ object-fit: contain;
21
+ }
22
+ .saved-item:hover .saved-item-actions {
23
+ opacity: 1;
24
+ }
25
+ .saved-item-thumbnail {
26
+ height: 80px;
27
+ width: 80px;
28
+ object-fit: cover;
29
+ }
30
+ </style>
31
+ </head>
32
+ <body class="bg-gray-50 min-h-screen">
33
+ <div class="container mx-auto px-4 py-8">
34
+ <!-- Header -->
35
+ <header class="mb-8 text-center">
36
+ <h1 class="text-3xl font-bold text-indigo-700 mb-2">Image Annotation Tool</h1>
37
+ <p class="text-gray-600">
38
+ Create a dataset for AI image generation by annotating images with descriptive prompts
39
+ </p>
40
+ </header>
41
+
42
+ <!-- Main Content -->
43
+ <div class="grid lg:grid-cols-2 gap-8">
44
+ <!-- Annotation Section -->
45
+ <div class="bg-white rounded-xl shadow-md overflow-hidden p-6">
46
+ <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
47
+ <i class="fas fa-image-upload mr-2 text-indigo-500"></i>
48
+ Upload & Annotate
49
+ </h2>
50
+
51
+ <!-- Image Dropzone -->
52
+ <div
53
+ id="dropzone"
54
+ class="dropzone rounded-lg p-8 text-center mb-6 cursor-pointer hover:bg-gray-50"
55
+ >
56
+ <input type="file" id="fileInput" accept="image/*" class="hidden">
57
+ <div id="dropzoneContent" class="space-y-2">
58
+ <div class="text-indigo-500 text-4xl mb-2">
59
+ <i class="fas fa-cloud-arrow-up"></i>
60
+ </div>
61
+ <p class="text-gray-700 font-medium">Drag & drop an image here</p>
62
+ <p class="text-gray-500 text-sm">or click to browse files (JPEG, PNG, WEBP)</p>
63
+ </div>
64
+ <div id="imagePreview" class="hidden">
65
+ <img id="previewImage" src="#" alt="Preview" class="preview-image mx-auto mb-4">
66
+ <button
67
+ id="changeImageBtn"
68
+ class="text-sm text-indigo-600 hover:text-indigo-800 font-medium"
69
+ >
70
+ <i class="fas fa-sync-alt mr-1"></i> Change Image
71
+ </button>
72
+ </div>
73
+ </div>
74
+
75
+ <!-- Prompt Input -->
76
+ <div class="mb-6">
77
+ <label for="prompt" class="block text-sm font-medium text-gray-700 mb-2">
78
+ <i class="fas fa-comment-dots mr-1 text-indigo-500"></i>
79
+ Descriptive Prompt
80
+ </label>
81
+ <div class="relative">
82
+ <textarea
83
+ id="prompt"
84
+ rows="4"
85
+ class="block w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 resize-none"
86
+ placeholder="Write a detailed description of the image that could be used to generate a similar image with AI..."
87
+ ></textarea>
88
+ <div class="absolute bottom-2 right-2 text-xs text-gray-500">
89
+ <span id="charCount">0</span>/500
90
+ </div>
91
+ </div>
92
+ </div>
93
+
94
+ <!-- Metadata -->
95
+ <div class="grid grid-cols-2 gap-4 mb-6">
96
+ <div>
97
+ <label for="tags" class="block text-sm font-medium text-gray-700 mb-2">
98
+ <i class="fas fa-tags mr-1 text-indigo-500"></i>
99
+ Tags
100
+ </label>
101
+ <input
102
+ type="text"
103
+ id="tags"
104
+ class="block w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
105
+ placeholder="tag1, tag2, tag3"
106
+ >
107
+ </div>
108
+ <div>
109
+ <label for="category" class="block text-sm font-medium text-gray-700 mb-2">
110
+ <i class="fas fa-folder-open mr-1 text-indigo-500"></i>
111
+ Category
112
+ </label>
113
+ <select
114
+ id="category"
115
+ class="block w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
116
+ >
117
+ <option value="">Select a category</option>
118
+ <option value="nature">Nature</option>
119
+ <option value="portrait">Portrait</option>
120
+ <option value="architecture">Architecture</option>
121
+ <option value="art">Art</option>
122
+ <option value="product">Product</option>
123
+ <option value="other">Other</option>
124
+ </select>
125
+ </div>
126
+ </div>
127
+
128
+ <!-- Save Button -->
129
+ <button
130
+ id="saveBtn"
131
+ class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-3 px-4 rounded-lg transition duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
132
+ disabled
133
+ >
134
+ <i class="fas fa-save mr-2"></i> Save Annotation
135
+ </button>
136
+ </div>
137
+
138
+ <!-- Saved Annotations Section -->
139
+ <div class="bg-white rounded-xl shadow-md overflow-hidden p-6">
140
+ <div class="flex justify-between items-center mb-4">
141
+ <h2 class="text-xl font-semibold text-gray-800 flex items-center">
142
+ <i class="fas fa-database mr-2 text-indigo-500"></i>
143
+ Your Dataset
144
+ </h2>
145
+ <button
146
+ id="exportBtn"
147
+ class="text-sm bg-emerald-100 hover:bg-emerald-200 text-emerald-800 font-medium py-2 px-3 rounded-lg transition duration-200"
148
+ >
149
+ <i class="fas fa-file-export mr-1"></i> Export JSON
150
+ </button>
151
+ </div>
152
+
153
+ <div id="savedItems" class="space-y-3 max-h-[60vh] overflow-y-auto pr-2">
154
+ <div class="text-center py-10 text-gray-400">
155
+ <i class="fas fa-box-open text-3xl mb-2"></i>
156
+ <p>Your saved annotations will appear here</p>
157
+ </div>
158
+ </div>
159
+
160
+ <div class="mt-4 text-sm text-gray-500 flex justify-between items-center">
161
+ <div>
162
+ <span id="itemCount">0</span> annotations
163
+ </div>
164
+ <div>
165
+ <span id="dataSize">0 KB</span>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </div>
170
+ </div>
171
+
172
+ <!-- Success Toast -->
173
+ <div id="toast" class="fixed bottom-4 right-4 bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded-lg shadow-lg hidden max-w-xs transition-all duration-300 transform translate-x-32">
174
+ <div class="flex items-start">
175
+ <div class="flex-shrink-0 pt-0.5">
176
+ <i class="fas fa-check-circle text-green-500 text-xl"></i>
177
+ </div>
178
+ <div class="ml-3">
179
+ <p class="font-medium">Annotation saved!</p>
180
+ <p id="toastMessage" class="text-sm">Your image and prompt have been added to the dataset.</p>
181
+ </div>
182
+ <button id="closeToast" class="ml-4 text-green-700 hover:text-green-900 focus:outline-none">
183
+ <i class="fas fa-times"></i>
184
+ </button>
185
+ </div>
186
+ </div>
187
+
188
+ <script>
189
+ // DOM Elements
190
+ const fileInput = document.getElementById('fileInput');
191
+ const dropzone = document.getElementById('dropzone');
192
+ const dropzoneContent = document.getElementById('dropzoneContent');
193
+ const imagePreview = document.getElementById('imagePreview');
194
+ const previewImage = document.getElementById('previewImage');
195
+ const changeImageBtn = document.getElementById('changeImageBtn');
196
+ const prompt = document.getElementById('prompt');
197
+ const tags = document.getElementById('tags');
198
+ const category = document.getElementById('category');
199
+ const charCount = document.getElementById('charCount');
200
+ const saveBtn = document.getElementById('saveBtn');
201
+ const exportBtn = document.getElementById('exportBtn');
202
+ const savedItems = document.getElementById('savedItems');
203
+ const itemCount = document.getElementById('itemCount');
204
+ const dataSize = document.getElementById('dataSize');
205
+ const toast = document.getElementById('toast');
206
+ const toastMessage = document.getElementById('toastMessage');
207
+ const closeToast = document.getElementById('closeToast');
208
+
209
+ // Dataset array to store all annotations
210
+ let dataset = [];
211
+
212
+ // Event Listeners
213
+ dropzone.addEventListener('click', () => fileInput.click());
214
+ dropzone.addEventListener('dragover', (e) => {
215
+ e.preventDefault();
216
+ dropzone.classList.add('active');
217
+ });
218
+ dropzone.addEventListener('dragleave', () => {
219
+ dropzone.classList.remove('active');
220
+ });
221
+ dropzone.addEventListener('drop', handleDrop);
222
+ fileInput.addEventListener('change', handleFileSelect);
223
+ changeImageBtn.addEventListener('click', () => fileInput.click());
224
+ prompt.addEventListener('input', updateSaveButtonState);
225
+ prompt.addEventListener('input', updateCharCount);
226
+ saveBtn.addEventListener('click', saveAnnotation);
227
+ exportBtn.addEventListener('click', exportDataset);
228
+ closeToast.addEventListener('click', () => toast.classList.add('hidden'));
229
+
230
+ // Functions
231
+ function handleDrop(e) {
232
+ e.preventDefault();
233
+ dropzone.classList.remove('active');
234
+
235
+ if (e.dataTransfer.files.length) {
236
+ fileInput.files = e.dataTransfer.files;
237
+ handleFileSelect();
238
+ }
239
+ }
240
+
241
+ function handleFileSelect() {
242
+ const file = fileInput.files[0];
243
+ if (!file || !file.type.match('image.*')) return;
244
+
245
+ const reader = new FileReader();
246
+ reader.onload = function(e) {
247
+ previewImage.src = e.target.result;
248
+ dropzoneContent.classList.add('hidden');
249
+ imagePreview.classList.remove('hidden');
250
+ updateSaveButtonState();
251
+ };
252
+ reader.readAsDataURL(file);
253
+ }
254
+
255
+ function updateCharCount() {
256
+ const count = prompt.value.length;
257
+ charCount.textContent = count;
258
+
259
+ if (count > 500) {
260
+ charCount.classList.add('text-red-500');
261
+ } else {
262
+ charCount.classList.remove('text-red-500');
263
+ }
264
+ }
265
+
266
+ function updateSaveButtonState() {
267
+ saveBtn.disabled = !(previewImage.src !== '#' && prompt.value.trim().length > 0);
268
+ }
269
+
270
+ function saveAnnotation() {
271
+ if (previewImage.src === '#' || !prompt.value.trim()) return;
272
+
273
+ const reader = new FileReader();
274
+ reader.onload = function(e) {
275
+ const base64Image = e.target.result;
276
+
277
+ const annotation = {
278
+ id: Date.now().toString(),
279
+ image: base64Image,
280
+ prompt: prompt.value.trim(),
281
+ tags: tags.value.split(',').map(tag => tag.trim()).filter(tag => tag),
282
+ category: category.value,
283
+ createdAt: new Date().toISOString()
284
+ };
285
+
286
+ dataset.unshift(annotation);
287
+ updateDatasetDisplay();
288
+
289
+ // Reset form
290
+ fileInput.value = '';
291
+ previewImage.src = '#';
292
+ prompt.value = '';
293
+ tags.value = '';
294
+ category.value = '';
295
+ dropzoneContent.classList.remove('hidden');
296
+ imagePreview.classList.add('hidden');
297
+ updateCharCount();
298
+ updateSaveButtonState();
299
+
300
+ // Show success toast
301
+ toastMessage.textContent = `"${annotation.prompt.substring(0, 20)}${annotation.prompt.length > 20 ? '...' : ''}" added to dataset.`;
302
+ toast.classList.remove('hidden');
303
+
304
+ // Auto hide toast
305
+ setTimeout(() => toast.classList.add('hidden'), 3000);
306
+ };
307
+ reader.readAsDataURL(fileInput.files[0]);
308
+ }
309
+
310
+ function updateDatasetDisplay() {
311
+ if (dataset.length === 0) {
312
+ savedItems.innerHTML = `
313
+ <div class="text-center py-10 text-gray-400">
314
+ <i class="fas fa-box-open text-3xl mb-2"></i>
315
+ <p>Your saved annotations will appear here</p>
316
+ </div>
317
+ `;
318
+ itemCount.textContent = '0';
319
+ dataSize.textContent = '0 KB';
320
+ return;
321
+ }
322
+
323
+ // Calculate data size
324
+ const jsonString = JSON.stringify(dataset);
325
+ const sizeInKB = Math.round(new Blob([jsonString]).size / 1024);
326
+ dataSize.textContent = `${sizeInKB} KB`;
327
+ itemCount.textContent = dataset.length;
328
+
329
+ savedItems.innerHTML = dataset.map(item => `
330
+ <div class="saved-item group bg-gray-50 rounded-lg p-3 hover:bg-gray-100 transition duration-200 relative">
331
+ <div class="flex gap-3">
332
+ <div class="flex-shrink-0">
333
+ <img src="${item.image}" alt="Thumbnail" class="saved-item-thumbnail rounded">
334
+ </div>
335
+ <div class="min-w-0">
336
+ <p class="text-sm font-medium text-gray-800 truncate">${item.prompt.substring(0, 80)}${item.prompt.length > 80 ? '...' : ''}</p>
337
+ <div class="flex items-center text-xs text-gray-500 mt-1">
338
+ ${item.category ? `<span class="mr-2"><i class="fas fa-folder-open mr-1"></i>${item.category}</span>` : ''}
339
+ ${item.tags.length ? `<span><i class="fas fa-tags mr-1"></i>${item.tags.slice(0, 2).join(', ')}${item.tags.length > 2 ? '...' : ''}</span>` : ''}
340
+ </div>
341
+ </div>
342
+ </div>
343
+ <div class="saved-item-actions opacity-0 group-hover:opacity-100 absolute right-3 top-3 transition duration-200">
344
+ <button class="delete-btn text-red-500 hover:text-red-700 p-1" data-id="${item.id}">
345
+ <i class="fas fa-trash"></i>
346
+ </button>
347
+ </div>
348
+ </div>
349
+ `).join('');
350
+
351
+ // Add event listeners to delete buttons
352
+ document.querySelectorAll('.delete-btn').forEach(btn => {
353
+ btn.addEventListener('click', function() {
354
+ const id = this.getAttribute('data-id');
355
+ dataset = dataset.filter(item => item.id !== id);
356
+ updateDatasetDisplay();
357
+
358
+ // Show deletion toast
359
+ toastMessage.textContent = 'Annotation removed from dataset.';
360
+ toast.classList.remove('hidden');
361
+ toast.classList.remove('bg-green-100', 'border-green-500', 'text-green-700');
362
+ toast.classList.add('bg-red-100', 'border-red-500', 'text-red-700');
363
+
364
+ // Auto hide toast
365
+ setTimeout(() => {
366
+ toast.classList.add('hidden');
367
+ toast.classList.remove('bg-red-100', 'border-red-500', 'text-red-700');
368
+ toast.classList.add('bg-green-100', 'border-green-500', 'text-green-700');
369
+ }, 3000);
370
+ });
371
+ });
372
+ }
373
+
374
+ function exportDataset() {
375
+ if (dataset.length === 0) {
376
+ toastMessage.textContent = 'No annotations to export. Please add some first.';
377
+ toast.classList.remove('hidden');
378
+ toast.classList.remove('bg-green-100', 'border-green-500', 'text-green-700');
379
+ toast.classList.add('bg-yellow-100', 'border-yellow-500', 'text-yellow-700');
380
+
381
+ setTimeout(() => {
382
+ toast.classList.add('hidden');
383
+ toast.classList.remove('bg-yellow-100', 'border-yellow-500', 'text-yellow-700');
384
+ toast.classList.add('bg-green-100', 'border-green-500', 'text-green-700');
385
+ }, 3000);
386
+ return;
387
+ }
388
+
389
+ const jsonString = JSON.stringify(dataset, null, 2);
390
+ const blob = new Blob([jsonString], { type: 'application/json' });
391
+ const url = URL.createObjectURL(blob);
392
+
393
+ const a = document.createElement('a');
394
+ a.href = url;
395
+ a.download = `image-dataset-${new Date().toISOString().slice(0, 10)}.json`;
396
+ document.body.appendChild(a);
397
+ a.click();
398
+ document.body.removeChild(a);
399
+
400
+ toastMessage.textContent = `Dataset exported with ${dataset.length} annotations.`;
401
+ toast.classList.remove('hidden');
402
+
403
+ setTimeout(() => toast.classList.add('hidden'), 3000);
404
+ }
405
+ </script>
406
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
407
+ </html>