#!/usr/bin/env python3 """ Validate 100 frames with ball annotations from a COCO dataset. Generates HTML with toggleable bounding boxes. """ import json import base64 from pathlib import Path from typing import List, Dict from PIL import Image import io def load_coco_annotations(annotation_path: str) -> Dict: """Load COCO format annotation file.""" with open(annotation_path, 'r') as f: return json.load(f) def get_images_with_balls(coco_data: Dict) -> List[Dict]: """Get images that have ball annotations.""" categories = {cat['id']: cat['name'] for cat in coco_data['categories']} # Find ball category ID ball_category_id = None for cat_id, cat_name in categories.items(): if cat_name.lower() == 'ball': ball_category_id = cat_id break if ball_category_id is None: raise ValueError("Ball category not found in annotations") # Group annotations by image image_annotations = {} for ann in coco_data['annotations']: if ann['category_id'] == ball_category_id: img_id = ann['image_id'] if img_id not in image_annotations: image_annotations[img_id] = [] image_annotations[img_id].append(ann['bbox']) # Get images with balls images = {img['id']: img for img in coco_data['images']} images_with_balls = [] for img_id in sorted(image_annotations.keys()): if img_id in images: images_with_balls.append({ 'image': images[img_id], 'bboxes': image_annotations[img_id] }) return images_with_balls def image_to_base64(image_path: Path) -> str: """Convert image to base64 string.""" try: with open(image_path, 'rb') as f: img_data = f.read() img = Image.open(io.BytesIO(img_data)) # Resize if too large (max 1920px width) max_width = 1920 if img.width > max_width: ratio = max_width / img.width new_height = int(img.height * ratio) img = img.resize((max_width, new_height), Image.Resampling.LANCZOS) # Convert to base64 buffer = io.BytesIO() img.save(buffer, format='PNG') img_str = base64.b64encode(buffer.getvalue()).decode() return f"data:image/png;base64,{img_str}" except Exception as e: print(f"Error loading image {image_path}: {e}") return "" def generate_html(images_data: List[Dict], annotation_file: str, output_path: Path): """Generate HTML with toggleable bounding boxes.""" annotation_name = Path(annotation_file).name html_content = f""" Ball Validation - {annotation_name}

⚽ Ball Validation - 100 Samples

Dataset: {annotation_name}

Total frames with balls: {len(images_data)}

Showing {len(images_data)} frames with ball annotations
""" for idx, img_data in enumerate(images_data): image_info = img_data['image'] bboxes = img_data['bboxes'] # Get image path annotation_dir = Path(annotation_file).parent image_path = annotation_dir / image_info['file_name'] # Try alternative paths if image not found if not image_path.exists(): # Try images subdirectory images_dir = annotation_dir / 'images' if images_dir.exists(): image_path = images_dir / image_info['file_name'] if not image_path.exists(): print(f"Warning: Image not found: {image_path}") continue # Convert image to base64 img_base64 = image_to_base64(image_path) if not img_base64: continue # Calculate bbox positions (relative to image) img_width = image_info['width'] img_height = image_info['height'] bbox_html = "" for bbox in bboxes: # COCO format: [x, y, width, height] x, y, w, h = bbox x_percent = (x / img_width) * 100 y_percent = (y / img_height) * 100 w_percent = (w / img_width) * 100 h_percent = (h / img_height) * 100 bbox_html += f"""
ball
""" html_content += f"""
Frame {idx + 1}
{bbox_html}
Frame {idx + 1}: {image_info['file_name']} | {len(bboxes)} ball(s) | Size: {img_width}x{img_height}
""" html_content += """
""" with open(output_path, 'w') as f: f.write(html_content) print(f"āœ… Generated HTML: {output_path}") def main(): """Main function to validate 100 frames.""" annotation_file = "/workspace/soccer_coach_cv/models/ball_detection/dataset/valid/_annotations.coco.json" annotation_path = Path(annotation_file) if not annotation_path.exists(): print(f"Error: Annotation file not found: {annotation_file}") return print(f"šŸ“‹ Loading annotations from: {annotation_file}") coco_data = load_coco_annotations(annotation_file) print("šŸ” Finding images with ball annotations...") images_with_balls = get_images_with_balls(coco_data) print(f"šŸ“Š Found {len(images_with_balls)} images with ball annotations") # Select first 100 samples samples = images_with_balls[:100] print(f"āœ… Selected {len(samples)} samples for validation") # Generate output filename annotation_name = annotation_path.stem if annotation_name.startswith('_'): annotation_name = annotation_name[1:] output_html = annotation_path.parent / f"9_validate_100_frames_{annotation_name}.html" print(f"šŸŽØ Generating HTML visualization...") generate_html(samples, annotation_file, output_html) print(f"\nāœ… Done! Open {output_html} in your browser to view the validation.") if __name__ == "__main__": main()