#!/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"""