from __future__ import annotations import argparse import json from pathlib import Path from PIL import Image def coco_to_label_studio( image_paths: list[Path], coco_predictions_path: Path, category_name: str = "knot", ) -> list[dict]: """Convert COCO predictions to Label Studio import format with pre-annotations.""" # Load COCO predictions with coco_predictions_path.open("r", encoding="utf-8") as f: coco_data = json.load(f) # Build image_id -> annotations mapping annotations_by_image = {} for ann in coco_data.get("annotations", []): img_id = ann["image_id"] if img_id not in annotations_by_image: annotations_by_image[img_id] = [] annotations_by_image[img_id].append(ann) # Build image_id -> image info mapping image_info = {img["id"]: img for img in coco_data.get("images", [])} # Convert to Label Studio format ls_tasks = [] for img_id, img_data in image_info.items(): file_name = img_data["file_name"] width = img_data["width"] height = img_data["height"] # Find the actual file path matching_paths = [p for p in image_paths if p.name == file_name] if not matching_paths: continue img_path = matching_paths[0] # Build predictions (pre-annotations) predictions = [] for ann in annotations_by_image.get(img_id, []): # COCO bbox: [x_min, y_min, width, height] x, y, w, h = ann["bbox"] # Label Studio uses percentages x_percent = (x / width) * 100 y_percent = (y / height) * 100 w_percent = (w / width) * 100 h_percent = (h / height) * 100 predictions.append({ "value": { "x": x_percent, "y": y_percent, "width": w_percent, "height": h_percent, "rectanglelabels": [category_name] }, "from_name": "label", "to_name": "image", "type": "rectanglelabels", "score": ann.get("score", 1.0) }) # Create Label Studio task with predictions task = { "data": { "image": f"/data/local-files/?d={img_path.absolute()}" }, "predictions": [{ "result": predictions, "model_version": "rfdetr-auto-label" }] } ls_tasks.append(task) return ls_tasks def main() -> int: parser = argparse.ArgumentParser( description="Convert COCO predictions to Label Studio format for review." ) parser.add_argument("--coco-json", type=Path, required=True, help="COCO predictions JSON from auto_label_images.py") parser.add_argument("--images-dir", type=Path, required=True, help="Directory with the images") parser.add_argument("--output-json", type=Path, required=True, help="Output Label Studio tasks JSON") parser.add_argument("--category-name", default="knot", help="Label name in Label Studio") args = parser.parse_args() if not args.coco_json.exists(): raise SystemExit(f"COCO JSON not found: {args.coco_json}") if not args.images_dir.exists(): raise SystemExit(f"Images dir not found: {args.images_dir}") # Collect all images image_paths = list(args.images_dir.glob("*.jpg")) + list(args.images_dir.glob("*.png")) if not image_paths: raise SystemExit(f"No images found in {args.images_dir}") # Convert ls_tasks = coco_to_label_studio(image_paths, args.coco_json, args.category_name) # Write Label Studio import format with args.output_json.open("w", encoding="utf-8") as f: json.dump(ls_tasks, f, indent=2) print(f"✓ Converted {len(ls_tasks)} tasks with predictions") print(f"✓ Output: {args.output_json}") print(f"\nImport into Label Studio:") print(f" 1. Open your project") print(f" 2. Settings → Storage → Add Local Storage") print(f" Absolute path: {args.images_dir.absolute()}") print(f" 3. Import → Upload Files → select {args.output_json.name}") print(f" 4. Start reviewing predictions!") return 0 if __name__ == "__main__": raise SystemExit(main())