eeeeeeeeeeeeee3 commited on
Commit
73dae45
·
verified ·
1 Parent(s): 0b76d1e

Upload 9_validate_100_frames_Validation_images_OFFICIAL_train.py with huggingface_hub

Browse files
9_validate_100_frames_Validation_images_OFFICIAL_train.py ADDED
@@ -0,0 +1,398 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Validate 100 frames with ball annotations from a COCO dataset.
4
+ Generates HTML with toggleable bounding boxes.
5
+ """
6
+ import json
7
+ import base64
8
+ from pathlib import Path
9
+ from typing import List, Dict
10
+ from PIL import Image
11
+ import io
12
+
13
+
14
+ def load_coco_annotations(annotation_path: str) -> Dict:
15
+ """Load COCO format annotation file."""
16
+ with open(annotation_path, 'r') as f:
17
+ return json.load(f)
18
+
19
+
20
+ def get_images_with_balls(coco_data: Dict) -> List[Dict]:
21
+ """Get images that have ball annotations."""
22
+ categories = {cat['id']: cat['name'] for cat in coco_data['categories']}
23
+
24
+ # Find ball category ID
25
+ ball_category_id = None
26
+ for cat_id, cat_name in categories.items():
27
+ if cat_name.lower() == 'ball':
28
+ ball_category_id = cat_id
29
+ break
30
+
31
+ if ball_category_id is None:
32
+ raise ValueError("Ball category not found in annotations")
33
+
34
+ # Group annotations by image
35
+ image_annotations = {}
36
+ for ann in coco_data['annotations']:
37
+ if ann['category_id'] == ball_category_id:
38
+ img_id = ann['image_id']
39
+ if img_id not in image_annotations:
40
+ image_annotations[img_id] = []
41
+ image_annotations[img_id].append(ann['bbox'])
42
+
43
+ # Get images with balls
44
+ images = {img['id']: img for img in coco_data['images']}
45
+ images_with_balls = []
46
+
47
+ for img_id in sorted(image_annotations.keys()):
48
+ if img_id in images:
49
+ images_with_balls.append({
50
+ 'image': images[img_id],
51
+ 'bboxes': image_annotations[img_id]
52
+ })
53
+
54
+ return images_with_balls
55
+
56
+
57
+ def image_to_base64(image_path: Path) -> str:
58
+ """Convert image to base64 string."""
59
+ try:
60
+ with open(image_path, 'rb') as f:
61
+ img_data = f.read()
62
+ img = Image.open(io.BytesIO(img_data))
63
+ # Resize if too large (max 1920px width)
64
+ max_width = 1920
65
+ if img.width > max_width:
66
+ ratio = max_width / img.width
67
+ new_height = int(img.height * ratio)
68
+ img = img.resize((max_width, new_height), Image.Resampling.LANCZOS)
69
+
70
+ # Convert to base64
71
+ buffer = io.BytesIO()
72
+ img.save(buffer, format='PNG')
73
+ img_str = base64.b64encode(buffer.getvalue()).decode()
74
+ return f"data:image/png;base64,{img_str}"
75
+ except Exception as e:
76
+ print(f"Error loading image {image_path}: {e}")
77
+ return ""
78
+
79
+
80
+ def generate_html(images_data: List[Dict], annotation_file: str, output_path: Path):
81
+ """Generate HTML with toggleable bounding boxes."""
82
+
83
+ annotation_name = Path(annotation_file).name
84
+
85
+ html_content = f"""<!DOCTYPE html>
86
+ <html lang="en">
87
+ <head>
88
+ <meta charset="UTF-8">
89
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
90
+ <title>Ball Validation - {annotation_name}</title>
91
+ <style>
92
+ * {{
93
+ margin: 0;
94
+ padding: 0;
95
+ box-sizing: border-box;
96
+ }}
97
+
98
+ body {{
99
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
100
+ background: #1a1a1a;
101
+ color: #e0e0e0;
102
+ padding: 20px;
103
+ }}
104
+
105
+ .header {{
106
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
107
+ padding: 20px;
108
+ border-radius: 10px;
109
+ margin-bottom: 20px;
110
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
111
+ }}
112
+
113
+ .header h1 {{
114
+ color: white;
115
+ margin-bottom: 10px;
116
+ }}
117
+
118
+ .header p {{
119
+ color: rgba(255, 255, 255, 0.9);
120
+ font-size: 14px;
121
+ }}
122
+
123
+ .controls {{
124
+ background: #2a2a2a;
125
+ padding: 15px;
126
+ border-radius: 8px;
127
+ margin-bottom: 20px;
128
+ display: flex;
129
+ align-items: center;
130
+ gap: 15px;
131
+ flex-wrap: wrap;
132
+ }}
133
+
134
+ .toggle-btn {{
135
+ background: #4CAF50;
136
+ color: white;
137
+ border: none;
138
+ padding: 12px 24px;
139
+ border-radius: 6px;
140
+ cursor: pointer;
141
+ font-size: 16px;
142
+ font-weight: bold;
143
+ transition: all 0.3s;
144
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
145
+ }}
146
+
147
+ .toggle-btn:hover {{
148
+ background: #45a049;
149
+ transform: translateY(-2px);
150
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
151
+ }}
152
+
153
+ .toggle-btn.off {{
154
+ background: #ff9800;
155
+ }}
156
+
157
+ .toggle-btn.off:hover {{
158
+ background: #f57c00;
159
+ }}
160
+
161
+ .stats {{
162
+ color: #b0b0b0;
163
+ font-size: 14px;
164
+ }}
165
+
166
+ .grid {{
167
+ display: grid;
168
+ grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
169
+ gap: 20px;
170
+ }}
171
+
172
+ .frame-container {{
173
+ background: #2a2a2a;
174
+ border-radius: 8px;
175
+ padding: 15px;
176
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
177
+ transition: transform 0.2s;
178
+ }}
179
+
180
+ .frame-container:hover {{
181
+ transform: translateY(-4px);
182
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
183
+ }}
184
+
185
+ .image-wrapper {{
186
+ position: relative;
187
+ width: 100%;
188
+ margin-bottom: 10px;
189
+ border-radius: 6px;
190
+ overflow: hidden;
191
+ background: #1a1a1a;
192
+ }}
193
+
194
+ .image-wrapper img {{
195
+ width: 100%;
196
+ height: auto;
197
+ display: block;
198
+ }}
199
+
200
+ .bbox-overlay {{
201
+ position: absolute;
202
+ top: 0;
203
+ left: 0;
204
+ width: 100%;
205
+ height: 100%;
206
+ pointer-events: none;
207
+ }}
208
+
209
+ .bbox-overlay.hidden {{
210
+ display: none;
211
+ }}
212
+
213
+ .bbox {{
214
+ position: absolute;
215
+ border: 3px solid #FFC107;
216
+ background: rgba(255, 193, 7, 0.3);
217
+ box-sizing: border-box;
218
+ }}
219
+
220
+ .bbox-label {{
221
+ position: absolute;
222
+ top: -20px;
223
+ left: 0;
224
+ background: rgba(0, 0, 0, 0.8);
225
+ color: #FFC107;
226
+ padding: 2px 6px;
227
+ font-size: 12px;
228
+ font-weight: bold;
229
+ border-radius: 3px;
230
+ white-space: nowrap;
231
+ }}
232
+
233
+ .frame-info {{
234
+ color: #b0b0b0;
235
+ font-size: 13px;
236
+ margin-top: 8px;
237
+ }}
238
+
239
+ .frame-info strong {{
240
+ color: #FFC107;
241
+ }}
242
+ </style>
243
+ </head>
244
+ <body>
245
+ <div class="header">
246
+ <h1>⚽ Ball Validation - 100 Samples</h1>
247
+ <p>Dataset: {annotation_name}</p>
248
+ <p>Total frames with balls: {len(images_data)}</p>
249
+ </div>
250
+
251
+ <div class="controls">
252
+ <button class="toggle-btn" id="toggleBtn" onclick="toggleBoxes()">Hide Boxes</button>
253
+ <div class="stats">
254
+ Showing {len(images_data)} frames with ball annotations
255
+ </div>
256
+ </div>
257
+
258
+ <div class="grid">
259
+ """
260
+
261
+ for idx, img_data in enumerate(images_data):
262
+ image_info = img_data['image']
263
+ bboxes = img_data['bboxes']
264
+
265
+ # Get image path
266
+ annotation_dir = Path(annotation_file).parent
267
+ image_path = annotation_dir / image_info['file_name']
268
+
269
+ # Try alternative paths if image not found
270
+ if not image_path.exists():
271
+ # Try images subdirectory
272
+ images_dir = annotation_dir / 'images'
273
+ if images_dir.exists():
274
+ image_path = images_dir / image_info['file_name']
275
+
276
+ if not image_path.exists():
277
+ print(f"Warning: Image not found: {image_path}")
278
+ continue
279
+
280
+ # Convert image to base64
281
+ img_base64 = image_to_base64(image_path)
282
+ if not img_base64:
283
+ continue
284
+
285
+ # Calculate bbox positions (relative to image)
286
+ img_width = image_info['width']
287
+ img_height = image_info['height']
288
+
289
+ bbox_html = ""
290
+ for bbox in bboxes:
291
+ # COCO format: [x, y, width, height]
292
+ x, y, w, h = bbox
293
+ x_percent = (x / img_width) * 100
294
+ y_percent = (y / img_height) * 100
295
+ w_percent = (w / img_width) * 100
296
+ h_percent = (h / img_height) * 100
297
+
298
+ bbox_html += f"""
299
+ <div class="bbox" style="left: {x_percent}%; top: {y_percent}%; width: {w_percent}%; height: {h_percent}%;">
300
+ <div class="bbox-label">ball</div>
301
+ </div>"""
302
+
303
+ html_content += f"""
304
+ <div class="frame-container">
305
+ <div class="image-wrapper">
306
+ <img src="{img_base64}" alt="Frame {idx + 1}">
307
+ <div class="bbox-overlay" id="overlay-{idx}">
308
+ {bbox_html}
309
+ </div>
310
+ </div>
311
+ <div class="frame-info">
312
+ <strong>Frame {idx + 1}:</strong> {image_info['file_name']} |
313
+ <strong>{len(bboxes)}</strong> ball(s) |
314
+ Size: {img_width}x{img_height}
315
+ </div>
316
+ </div>
317
+ """
318
+
319
+ html_content += """
320
+ </div>
321
+
322
+ <script>
323
+ let boxesVisible = true;
324
+
325
+ function toggleBoxes() {
326
+ boxesVisible = !boxesVisible;
327
+ const overlays = document.querySelectorAll('.bbox-overlay');
328
+ const btn = document.getElementById('toggleBtn');
329
+
330
+ overlays.forEach(overlay => {
331
+ if (boxesVisible) {
332
+ overlay.classList.remove('hidden');
333
+ } else {
334
+ overlay.classList.add('hidden');
335
+ }
336
+ });
337
+
338
+ if (boxesVisible) {
339
+ btn.textContent = 'Hide Boxes';
340
+ btn.classList.remove('off');
341
+ } else {
342
+ btn.textContent = 'Show Boxes';
343
+ btn.classList.add('off');
344
+ }
345
+ }
346
+
347
+ // Keyboard shortcut: 'H' to toggle
348
+ document.addEventListener('keydown', function(event) {
349
+ if (event.key === 'h' || event.key === 'H') {
350
+ toggleBoxes();
351
+ }
352
+ });
353
+ </script>
354
+ </body>
355
+ </html>
356
+ """
357
+
358
+ with open(output_path, 'w') as f:
359
+ f.write(html_content)
360
+
361
+ print(f"✅ Generated HTML: {output_path}")
362
+
363
+
364
+ def main():
365
+ """Main function to validate 100 frames."""
366
+ annotation_file = "/workspace/soccer_coach_cv/data/raw/real_data/Validation images OFFICIAL/train/_annotations.coco.json"
367
+ annotation_path = Path(annotation_file)
368
+
369
+ if not annotation_path.exists():
370
+ print(f"Error: Annotation file not found: {annotation_file}")
371
+ return
372
+
373
+ print(f"📋 Loading annotations from: {annotation_file}")
374
+ coco_data = load_coco_annotations(annotation_file)
375
+
376
+ print("🔍 Finding images with ball annotations...")
377
+ images_with_balls = get_images_with_balls(coco_data)
378
+
379
+ print(f"📊 Found {len(images_with_balls)} images with ball annotations")
380
+
381
+ # Select first 100 samples
382
+ samples = images_with_balls[:100]
383
+ print(f"✅ Selected {len(samples)} samples for validation")
384
+
385
+ # Generate output filename
386
+ annotation_name = annotation_path.stem
387
+ if annotation_name.startswith('_'):
388
+ annotation_name = annotation_name[1:]
389
+ output_html = annotation_path.parent / f"9_validate_100_frames_{annotation_name}.html"
390
+
391
+ print(f"🎨 Generating HTML visualization...")
392
+ generate_html(samples, annotation_file, output_html)
393
+
394
+ print(f"\n✅ Done! Open {output_html} in your browser to view the validation.")
395
+
396
+
397
+ if __name__ == "__main__":
398
+ main()