kamau1 commited on
Commit
d2693ac
Β·
verified Β·
1 Parent(s): 1ccc4ec

Fix: dashboard CSS loading and layout issues by correcting static paths

Browse files
save_annotated_images.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Simple script to save annotated images from the Marine Species API.
4
+ No external dependencies required - uses only Python standard library.
5
+ """
6
+
7
+ import requests
8
+ import base64
9
+ import json
10
+ import time
11
+ from pathlib import Path
12
+ import sys
13
+
14
+
15
+ def encode_image_to_base64(image_path: str) -> str:
16
+ """Encode an image file to base64 string."""
17
+ try:
18
+ with open(image_path, "rb") as image_file:
19
+ return base64.b64encode(image_file.read()).decode('utf-8')
20
+ except Exception as e:
21
+ print(f"Error encoding image {image_path}: {e}")
22
+ return None
23
+
24
+
25
+ def save_base64_image(base64_string: str, output_path: str) -> bool:
26
+ """Save a base64 encoded image to a file."""
27
+ try:
28
+ # Decode base64 to bytes
29
+ image_bytes = base64.b64decode(base64_string)
30
+
31
+ # Save directly to file
32
+ with open(output_path, 'wb') as f:
33
+ f.write(image_bytes)
34
+
35
+ print(f"βœ… Saved annotated image: {output_path}")
36
+ return True
37
+
38
+ except Exception as e:
39
+ print(f"❌ Failed to save image: {e}")
40
+ return False
41
+
42
+
43
+ def process_image(api_url: str, image_path: str, output_dir: str):
44
+ """Process an image and save the annotated result."""
45
+
46
+ image_name = Path(image_path).stem
47
+ print(f"\n🐟 Processing {Path(image_path).name}")
48
+ print("-" * 50)
49
+
50
+ # Encode input image
51
+ print("πŸ“· Encoding image...")
52
+ image_b64 = encode_image_to_base64(image_path)
53
+ if not image_b64:
54
+ return None
55
+
56
+ # API request
57
+ detection_request = {
58
+ "image": image_b64,
59
+ "confidence_threshold": 0.25,
60
+ "iou_threshold": 0.45,
61
+ "image_size": 640,
62
+ "return_annotated_image": True
63
+ }
64
+
65
+ try:
66
+ print("πŸ” Sending detection request...")
67
+ start_time = time.time()
68
+
69
+ response = requests.post(
70
+ f"{api_url}/api/v1/detect",
71
+ json=detection_request,
72
+ timeout=60
73
+ )
74
+
75
+ request_time = time.time() - start_time
76
+ print(f"⏱️ Request completed in {request_time:.2f}s")
77
+
78
+ if response.status_code == 200:
79
+ result = response.json()
80
+
81
+ detections = result.get('detections', [])
82
+ processing_time = result.get('processing_time', 0)
83
+ annotated_image_b64 = result.get('annotated_image')
84
+
85
+ print(f"βœ… SUCCESS!")
86
+ print(f" Processing Time: {processing_time:.3f}s")
87
+ print(f" Detections Found: {len(detections)}")
88
+
89
+ # Show detections
90
+ if detections:
91
+ print(f" 🎯 Detected Species:")
92
+ for i, detection in enumerate(detections[:5]):
93
+ species = detection.get('class_name', 'Unknown')
94
+ confidence = detection.get('confidence', 0)
95
+ print(f" {i+1}. {species} ({confidence:.1%})")
96
+
97
+ # Save annotated image
98
+ if annotated_image_b64:
99
+ output_path = Path(output_dir) / f"{image_name}_annotated.jpg"
100
+ success = save_base64_image(annotated_image_b64, str(output_path))
101
+
102
+ if success:
103
+ return str(output_path)
104
+ else:
105
+ print(" ❌ No annotated image returned")
106
+
107
+ else:
108
+ print(f"❌ Request failed: {response.status_code}")
109
+
110
+ except Exception as e:
111
+ print(f"❌ Request failed: {e}")
112
+
113
+ return None
114
+
115
+
116
+ def main():
117
+ """Main function."""
118
+ api_url = "https://seamo-ai-fishapi.hf.space"
119
+
120
+ print("🐟 Marine Species API - Save Annotated Images")
121
+ print("=" * 60)
122
+
123
+ # Create output directory
124
+ output_dir = Path("annotated_results")
125
+ output_dir.mkdir(exist_ok=True)
126
+ print(f"πŸ“ Output directory: {output_dir.absolute()}")
127
+
128
+ # Find test images
129
+ image_dir = Path("docs/gradio/images")
130
+ if not image_dir.exists():
131
+ print(f"\n❌ Image directory not found: {image_dir}")
132
+ return
133
+
134
+ # Get image files
135
+ image_files = []
136
+ for ext in ['*.png', '*.jpg', '*.jpeg']:
137
+ image_files.extend(image_dir.glob(ext))
138
+
139
+ if not image_files:
140
+ print(f"\n❌ No images found in {image_dir}")
141
+ return
142
+
143
+ print(f"πŸ“· Found {len(image_files)} test images")
144
+
145
+ # Process each image
146
+ saved_images = []
147
+ for image_path in sorted(image_files):
148
+ result_path = process_image(api_url, str(image_path), str(output_dir))
149
+ if result_path:
150
+ saved_images.append(result_path)
151
+ time.sleep(1)
152
+
153
+ # Summary
154
+ print("\n" + "=" * 60)
155
+ print(f"🎯 Results:")
156
+ print(f" πŸ“· Processed: {len(image_files)} images")
157
+ print(f" πŸ’Ύ Saved: {len(saved_images)} annotated images")
158
+
159
+ if saved_images:
160
+ print(f"\nπŸ–ΌοΈ Annotated images saved:")
161
+ for img_path in saved_images:
162
+ print(f" πŸ“„ {img_path}")
163
+
164
+ print(f"\nπŸ’‘ To view the annotated images:")
165
+ print(f" πŸ–₯️ macOS: open {output_dir}")
166
+ print(f" 🐧 Linux: xdg-open {output_dir}")
167
+ print(f" πŸͺŸ Windows: explorer {output_dir}")
168
+ print(f" πŸ“‚ Or browse to: {output_dir.absolute()}")
169
+
170
+ print(f"\nπŸ” The images show:")
171
+ print(f" β€’ Bounding boxes around detected marine species")
172
+ print(f" β€’ Species names and confidence scores")
173
+ print(f" β€’ Color-coded detection results")
174
+
175
+
176
+ if __name__ == "__main__":
177
+ main()
static/css/dashboard-simple.css ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Simplified Dashboard CSS - More Compatible */
2
+
3
+ .dashboard {
4
+ max-width: 1400px;
5
+ margin: 0 auto;
6
+ padding: 20px;
7
+ }
8
+
9
+ .page-header {
10
+ text-align: center;
11
+ margin-bottom: 40px;
12
+ }
13
+
14
+ .page-header h1 {
15
+ font-size: 2rem;
16
+ margin-bottom: 10px;
17
+ color: #111827;
18
+ }
19
+
20
+ .page-description {
21
+ font-size: 1.125rem;
22
+ color: #6b7280;
23
+ max-width: 600px;
24
+ margin: 0 auto;
25
+ }
26
+
27
+ /* Status Banner */
28
+ .status-banner {
29
+ background: white;
30
+ border: 1px solid #e5e7eb;
31
+ border-radius: 12px;
32
+ padding: 20px;
33
+ margin-bottom: 40px;
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: space-between;
37
+ box-shadow: 0 1px 2px rgba(0,0,0,0.05);
38
+ }
39
+
40
+ .status-indicator {
41
+ display: flex;
42
+ align-items: center;
43
+ gap: 10px;
44
+ }
45
+
46
+ .status-dot {
47
+ width: 12px;
48
+ height: 12px;
49
+ border-radius: 50%;
50
+ background: #9ca3af;
51
+ }
52
+
53
+ .status-dot.healthy {
54
+ background: #059669;
55
+ }
56
+
57
+ .status-text {
58
+ font-weight: 500;
59
+ color: #374151;
60
+ }
61
+
62
+ .model-info {
63
+ font-size: 0.875rem;
64
+ color: #6b7280;
65
+ }
66
+
67
+ /* Dashboard Grid */
68
+ .dashboard-grid {
69
+ display: grid;
70
+ grid-template-columns: 1fr 1fr 1fr;
71
+ gap: 30px;
72
+ margin-bottom: 40px;
73
+ }
74
+
75
+ @media (max-width: 1024px) {
76
+ .dashboard-grid {
77
+ grid-template-columns: 1fr;
78
+ gap: 20px;
79
+ }
80
+ }
81
+
82
+ .panel {
83
+ min-height: 600px;
84
+ }
85
+
86
+ /* Cards */
87
+ .card {
88
+ background: white;
89
+ border: 1px solid #e5e7eb;
90
+ border-radius: 12px;
91
+ box-shadow: 0 1px 2px rgba(0,0,0,0.05);
92
+ overflow: hidden;
93
+ height: 100%;
94
+ }
95
+
96
+ .card-header {
97
+ padding: 20px;
98
+ border-bottom: 1px solid #e5e7eb;
99
+ background: #f9fafb;
100
+ }
101
+
102
+ .card-title {
103
+ font-size: 1.125rem;
104
+ font-weight: 600;
105
+ color: #111827;
106
+ margin: 0;
107
+ }
108
+
109
+ .card-body {
110
+ padding: 20px;
111
+ }
112
+
113
+ /* Upload Area */
114
+ .upload-area {
115
+ border: 2px dashed #d1d5db;
116
+ border-radius: 12px;
117
+ padding: 40px 20px;
118
+ text-align: center;
119
+ cursor: pointer;
120
+ transition: all 0.2s ease;
121
+ margin-bottom: 20px;
122
+ min-height: 200px;
123
+ display: flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+ background: white;
127
+ }
128
+
129
+ .upload-area:hover {
130
+ border-color: #2563eb;
131
+ background-color: #f9fafb;
132
+ }
133
+
134
+ .upload-content {
135
+ display: flex;
136
+ flex-direction: column;
137
+ align-items: center;
138
+ gap: 15px;
139
+ }
140
+
141
+ .upload-icon {
142
+ font-size: 3rem;
143
+ color: #9ca3af;
144
+ }
145
+
146
+ .upload-primary {
147
+ font-size: 1.125rem;
148
+ font-weight: 500;
149
+ color: #374151;
150
+ margin: 0;
151
+ }
152
+
153
+ .upload-secondary {
154
+ font-size: 0.875rem;
155
+ color: #6b7280;
156
+ margin: 0;
157
+ }
158
+
159
+ /* Settings */
160
+ .settings-section {
161
+ margin-bottom: 20px;
162
+ padding-top: 20px;
163
+ border-top: 1px solid #e5e7eb;
164
+ }
165
+
166
+ .settings-section h4 {
167
+ margin-bottom: 15px;
168
+ color: #374151;
169
+ font-size: 1rem;
170
+ font-weight: 600;
171
+ }
172
+
173
+ .setting-item {
174
+ margin-bottom: 15px;
175
+ }
176
+
177
+ .setting-item label {
178
+ display: block;
179
+ font-size: 0.875rem;
180
+ font-weight: 500;
181
+ color: #374151;
182
+ margin-bottom: 5px;
183
+ }
184
+
185
+ .slider-container {
186
+ display: flex;
187
+ align-items: center;
188
+ gap: 15px;
189
+ }
190
+
191
+ .slider-container input[type="range"] {
192
+ flex: 1;
193
+ height: 6px;
194
+ border-radius: 3px;
195
+ background: #e5e7eb;
196
+ outline: none;
197
+ -webkit-appearance: none;
198
+ }
199
+
200
+ .slider-value {
201
+ font-size: 0.875rem;
202
+ font-weight: 500;
203
+ color: #2563eb;
204
+ min-width: 40px;
205
+ text-align: right;
206
+ }
207
+
208
+ /* Buttons */
209
+ .btn {
210
+ display: inline-flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ gap: 8px;
214
+ padding: 12px 24px;
215
+ font-size: 1rem;
216
+ font-weight: 500;
217
+ text-decoration: none;
218
+ border: none;
219
+ border-radius: 8px;
220
+ cursor: pointer;
221
+ transition: all 0.2s ease;
222
+ min-height: 44px;
223
+ }
224
+
225
+ .btn-primary {
226
+ background: #2563eb;
227
+ color: white;
228
+ }
229
+
230
+ .btn-primary:hover {
231
+ background: #1d4ed8;
232
+ }
233
+
234
+ .btn-primary:disabled {
235
+ background: #d1d5db;
236
+ cursor: not-allowed;
237
+ }
238
+
239
+ .btn-full {
240
+ width: 100%;
241
+ }
242
+
243
+ /* Image Containers */
244
+ .image-container {
245
+ border: 1px solid #e5e7eb;
246
+ border-radius: 8px;
247
+ overflow: hidden;
248
+ min-height: 250px;
249
+ display: flex;
250
+ align-items: center;
251
+ justify-content: center;
252
+ background: #f9fafb;
253
+ }
254
+
255
+ .image-container img {
256
+ max-width: 100%;
257
+ max-height: 100%;
258
+ object-fit: contain;
259
+ }
260
+
261
+ .image-placeholder {
262
+ text-align: center;
263
+ color: #9ca3af;
264
+ }
265
+
266
+ .placeholder-icon {
267
+ font-size: 2rem;
268
+ margin-bottom: 10px;
269
+ }
270
+
271
+ /* Results */
272
+ .annotated-section,
273
+ .metadata-section,
274
+ .species-section {
275
+ margin-bottom: 20px;
276
+ }
277
+
278
+ .annotated-section h4,
279
+ .metadata-section h4,
280
+ .species-section h4 {
281
+ margin-bottom: 15px;
282
+ color: #374151;
283
+ font-size: 1rem;
284
+ font-weight: 600;
285
+ }
286
+
287
+ .metadata-grid {
288
+ display: grid;
289
+ gap: 8px;
290
+ }
291
+
292
+ .metadata-item {
293
+ display: flex;
294
+ justify-content: space-between;
295
+ align-items: center;
296
+ padding: 10px;
297
+ background: #f9fafb;
298
+ border-radius: 6px;
299
+ }
300
+
301
+ .metadata-label {
302
+ font-size: 0.875rem;
303
+ color: #6b7280;
304
+ }
305
+
306
+ .metadata-value {
307
+ font-size: 0.875rem;
308
+ font-weight: 500;
309
+ color: #111827;
310
+ }
311
+
312
+ .species-list {
313
+ display: grid;
314
+ gap: 8px;
315
+ max-height: 300px;
316
+ overflow-y: auto;
317
+ }
318
+
319
+ .species-item {
320
+ display: flex;
321
+ justify-content: space-between;
322
+ align-items: center;
323
+ padding: 10px;
324
+ background: white;
325
+ border: 1px solid #e5e7eb;
326
+ border-radius: 6px;
327
+ }
328
+
329
+ .species-name {
330
+ font-size: 0.875rem;
331
+ font-weight: 500;
332
+ color: #111827;
333
+ }
334
+
335
+ .species-confidence {
336
+ font-size: 0.875rem;
337
+ font-weight: 600;
338
+ color: #2563eb;
339
+ }
static/css/dashboard.css CHANGED
@@ -72,37 +72,40 @@
72
  /* Dashboard Grid */
73
  .dashboard-grid {
74
  display: grid;
75
- grid-template-columns: 1fr 1fr 1fr;
76
- gap: var(--spacing-xl);
77
- margin-bottom: var(--spacing-2xl);
 
78
  }
79
 
80
  .panel {
81
  min-height: 600px;
 
82
  }
83
 
84
  /* Upload Area */
85
  .upload-area {
86
- border: 2px dashed var(--gray-300);
87
- border-radius: var(--radius-lg);
88
- padding: var(--spacing-2xl);
89
  text-align: center;
90
  cursor: pointer;
91
  transition: all 0.2s ease;
92
- margin-bottom: var(--spacing-lg);
93
  min-height: 200px;
94
  display: flex;
95
  align-items: center;
96
  justify-content: center;
 
97
  }
98
 
99
  .upload-area:hover {
100
- border-color: var(--primary-blue);
101
- background-color: var(--gray-50);
102
  }
103
 
104
  .upload-area.dragover {
105
- border-color: var(--primary-blue);
106
  background-color: #eff6ff;
107
  transform: scale(1.02);
108
  }
 
72
  /* Dashboard Grid */
73
  .dashboard-grid {
74
  display: grid;
75
+ grid-template-columns: repeat(3, 1fr);
76
+ gap: 2rem;
77
+ margin-bottom: 3rem;
78
+ width: 100%;
79
  }
80
 
81
  .panel {
82
  min-height: 600px;
83
+ width: 100%;
84
  }
85
 
86
  /* Upload Area */
87
  .upload-area {
88
+ border: 2px dashed #d1d5db;
89
+ border-radius: 12px;
90
+ padding: 3rem;
91
  text-align: center;
92
  cursor: pointer;
93
  transition: all 0.2s ease;
94
+ margin-bottom: 1.5rem;
95
  min-height: 200px;
96
  display: flex;
97
  align-items: center;
98
  justify-content: center;
99
+ background: white;
100
  }
101
 
102
  .upload-area:hover {
103
+ border-color: #2563eb;
104
+ background-color: #f9fafb;
105
  }
106
 
107
  .upload-area.dragover {
108
+ border-color: #2563eb;
109
  background-color: #eff6ff;
110
  transform: scale(1.02);
111
  }
static/css/main.css CHANGED
@@ -165,20 +165,21 @@ h3 {
165
  /* Cards */
166
  .card {
167
  background: white;
168
- border: 1px solid var(--gray-200);
169
- border-radius: var(--radius-lg);
170
- box-shadow: var(--shadow-sm);
171
  overflow: hidden;
 
172
  }
173
 
174
  .card-header {
175
- padding: var(--spacing-lg);
176
- border-bottom: 1px solid var(--gray-200);
177
- background: var(--gray-50);
178
  }
179
 
180
  .card-body {
181
- padding: var(--spacing-lg);
182
  }
183
 
184
  .card-title {
 
165
  /* Cards */
166
  .card {
167
  background: white;
168
+ border: 1px solid #e5e7eb;
169
+ border-radius: 12px;
170
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
171
  overflow: hidden;
172
+ width: 100%;
173
  }
174
 
175
  .card-header {
176
+ padding: 1.5rem;
177
+ border-bottom: 1px solid #e5e7eb;
178
+ background: #f9fafb;
179
  }
180
 
181
  .card-body {
182
+ padding: 1.5rem;
183
  }
184
 
185
  .card-title {
templates/base.html CHANGED
@@ -10,9 +10,30 @@
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  <!-- Custom CSS -->
14
- <link rel="stylesheet" href="{{ url_for('static', path='/css/main.css') }}">
15
-
16
  {% block extra_css %}{% endblock %}
17
  </head>
18
  <body>
@@ -53,7 +74,7 @@
53
  </footer>
54
 
55
  <!-- JavaScript -->
56
- <script src="{{ url_for('static', path='/js/main.js') }}"></script>
57
  {% block extra_js %}{% endblock %}
58
  </body>
59
  </html>
 
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
12
 
13
+ <!-- Critical CSS (inline for immediate loading) -->
14
+ <style>
15
+ * { margin: 0; padding: 0; box-sizing: border-box; }
16
+ body {
17
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
18
+ background: #f9fafb;
19
+ color: #111827;
20
+ line-height: 1.5;
21
+ }
22
+ .container { max-width: 1200px; margin: 0 auto; padding: 0 1rem; }
23
+ .dashboard-grid {
24
+ display: grid;
25
+ grid-template-columns: 1fr 1fr 1fr;
26
+ gap: 2rem;
27
+ margin: 2rem 0;
28
+ }
29
+ @media (max-width: 1024px) {
30
+ .dashboard-grid { grid-template-columns: 1fr; }
31
+ }
32
+ </style>
33
+
34
  <!-- Custom CSS -->
35
+ <link rel="stylesheet" href="/static/css/main.css">
36
+
37
  {% block extra_css %}{% endblock %}
38
  </head>
39
  <body>
 
74
  </footer>
75
 
76
  <!-- JavaScript -->
77
+ <script src="/static/js/main.js"></script>
78
  {% block extra_js %}{% endblock %}
79
  </body>
80
  </html>
templates/dashboard.html CHANGED
@@ -3,7 +3,8 @@
3
  {% block title %}Dashboard - Marine Species Identification API{% endblock %}
4
 
5
  {% block extra_css %}
6
- <link rel="stylesheet" href="{{ url_for('static', path='/css/dashboard.css') }}">
 
7
  {% endblock %}
8
 
9
  {% block content %}
@@ -145,5 +146,5 @@
145
  {% endblock %}
146
 
147
  {% block extra_js %}
148
- <script src="{{ url_for('static', path='/js/dashboard.js') }}"></script>
149
  {% endblock %}
 
3
  {% block title %}Dashboard - Marine Species Identification API{% endblock %}
4
 
5
  {% block extra_css %}
6
+ <link rel="stylesheet" href="/static/css/dashboard.css">
7
+ <link rel="stylesheet" href="/static/css/dashboard-simple.css">
8
  {% endblock %}
9
 
10
  {% block content %}
 
146
  {% endblock %}
147
 
148
  {% block extra_js %}
149
+ <script src="/static/js/dashboard.js"></script>
150
  {% endblock %}
view_annotated_images.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Script to test the Marine Species API and save annotated images for viewing.
4
+ """
5
+
6
+ import requests
7
+ import base64
8
+ import json
9
+ import time
10
+ from pathlib import Path
11
+ import sys
12
+ from PIL import Image
13
+ import io
14
+
15
+
16
+ def encode_image_to_base64(image_path: str) -> str:
17
+ """Encode an image file to base64 string."""
18
+ try:
19
+ with open(image_path, "rb") as image_file:
20
+ return base64.b64encode(image_file.read()).decode('utf-8')
21
+ except Exception as e:
22
+ print(f"Error encoding image {image_path}: {e}")
23
+ return None
24
+
25
+
26
+ def save_base64_image(base64_string: str, output_path: str) -> bool:
27
+ """Save a base64 encoded image to a file."""
28
+ try:
29
+ # Decode base64 to bytes
30
+ image_bytes = base64.b64decode(base64_string)
31
+
32
+ # Open with PIL and save
33
+ image = Image.open(io.BytesIO(image_bytes))
34
+ image.save(output_path)
35
+
36
+ print(f"βœ… Saved annotated image: {output_path}")
37
+ return True
38
+
39
+ except Exception as e:
40
+ print(f"❌ Failed to save image: {e}")
41
+ return False
42
+
43
+
44
+ def test_and_save_annotated_image(api_url: str, image_path: str, output_dir: str):
45
+ """Test API with an image and save the annotated result."""
46
+
47
+ image_name = Path(image_path).stem
48
+ print(f"\n🐟 Processing {Path(image_path).name}")
49
+ print("-" * 50)
50
+
51
+ # Encode input image
52
+ print("πŸ“· Encoding image...")
53
+ image_b64 = encode_image_to_base64(image_path)
54
+ if not image_b64:
55
+ return False
56
+
57
+ # Prepare API request
58
+ detection_request = {
59
+ "image": image_b64,
60
+ "confidence_threshold": 0.25,
61
+ "iou_threshold": 0.45,
62
+ "image_size": 640,
63
+ "return_annotated_image": True
64
+ }
65
+
66
+ try:
67
+ print("πŸ” Sending detection request...")
68
+ start_time = time.time()
69
+
70
+ response = requests.post(
71
+ f"{api_url}/api/v1/detect",
72
+ json=detection_request,
73
+ timeout=60
74
+ )
75
+
76
+ request_time = time.time() - start_time
77
+ print(f"⏱️ Request completed in {request_time:.2f}s")
78
+
79
+ if response.status_code == 200:
80
+ result = response.json()
81
+
82
+ detections = result.get('detections', [])
83
+ processing_time = result.get('processing_time', 0)
84
+ annotated_image_b64 = result.get('annotated_image')
85
+
86
+ print(f"βœ… SUCCESS!")
87
+ print(f" Processing Time: {processing_time:.3f}s")
88
+ print(f" Detections Found: {len(detections)}")
89
+
90
+ # Show top detections
91
+ if detections:
92
+ print(f" 🎯 Top Detections:")
93
+ for i, detection in enumerate(detections[:3]):
94
+ species = detection.get('class_name', 'Unknown')
95
+ confidence = detection.get('confidence', 0)
96
+ print(f" {i+1}. {species} (confidence: {confidence:.3f})")
97
+
98
+ # Save annotated image
99
+ if annotated_image_b64:
100
+ output_path = Path(output_dir) / f"{image_name}_annotated.jpg"
101
+ success = save_base64_image(annotated_image_b64, str(output_path))
102
+
103
+ if success:
104
+ print(f" πŸ–ΌοΈ View annotated image: {output_path}")
105
+ return str(output_path)
106
+ else:
107
+ print(" ❌ No annotated image returned")
108
+
109
+ else:
110
+ print(f"❌ Request failed with status {response.status_code}")
111
+ print(f" Response: {response.text}")
112
+
113
+ except Exception as e:
114
+ print(f"❌ Request failed: {e}")
115
+
116
+ return None
117
+
118
+
119
+ def main():
120
+ """Main function."""
121
+ # API URL
122
+ api_url = sys.argv[1] if len(sys.argv) > 1 else "https://seamo-ai-fishapi.hf.space"
123
+
124
+ print("🐟 Marine Species API - Annotated Image Viewer")
125
+ print("=" * 60)
126
+ print(f"API URL: {api_url}")
127
+
128
+ # Create output directory
129
+ output_dir = Path("annotated_results")
130
+ output_dir.mkdir(exist_ok=True)
131
+ print(f"Output directory: {output_dir.absolute()}")
132
+
133
+ # Find test images
134
+ image_dir = Path("docs/gradio/images")
135
+ if not image_dir.exists():
136
+ print(f"\n❌ Image directory not found: {image_dir}")
137
+ return
138
+
139
+ # Get image files
140
+ image_files = []
141
+ for ext in ['*.png', '*.jpg', '*.jpeg']:
142
+ image_files.extend(image_dir.glob(ext))
143
+
144
+ if not image_files:
145
+ print(f"\n❌ No image files found in {image_dir}")
146
+ return
147
+
148
+ print(f"\nπŸ“ Found {len(image_files)} test images")
149
+
150
+ # Process each image
151
+ saved_images = []
152
+ for image_path in sorted(image_files):
153
+ result_path = test_and_save_annotated_image(api_url, str(image_path), str(output_dir))
154
+ if result_path:
155
+ saved_images.append(result_path)
156
+ time.sleep(1) # Small delay between requests
157
+
158
+ # Summary
159
+ print("\n" + "=" * 60)
160
+ print(f"🎯 Summary:")
161
+ print(f" Processed: {len(image_files)} images")
162
+ print(f" Saved: {len(saved_images)} annotated images")
163
+
164
+ if saved_images:
165
+ print(f"\nπŸ–ΌοΈ Annotated images saved to:")
166
+ for img_path in saved_images:
167
+ print(f" - {img_path}")
168
+
169
+ print(f"\nπŸ’‘ To view the images:")
170
+ print(f" - Open the files in any image viewer")
171
+ print(f" - Or run: open {output_dir}") # macOS
172
+ print(f" - Or run: xdg-open {output_dir}") # Linux
173
+ print(f" - Or navigate to: {output_dir.absolute()}")
174
+
175
+
176
+ if __name__ == "__main__":
177
+ main()