"""Typer CLI to annotate an image with bounding boxes from a JSON file. Run with uv, e.g.:: uv run python annotate_cli.py assets/example_0_in.jpg assets/example_0.json The JSON must be a list of object dicts, each with a pascal_voc ``boundingBox`` (``{x0, y0, x1, y1}``); see ``assets/example_0.json``. """ from pathlib import Path from typing import Optional import typer from loguru import logger import pilbox app = typer.Typer(add_completion=False, help=__doc__) @app.command() def main( image: Path = typer.Argument( ..., exists=True, dir_okay=False, readable=True, help="Source image path." ), annotations: Path = typer.Argument( ..., exists=True, dir_okay=False, readable=True, help="JSON annotations path." ), output: Optional[Path] = typer.Option( None, "--output", "-o", help="Output path (default: _out.jpg)." ), label_key: str = typer.Option( "object_id", "--label-key", help="Object key drawn as each box's label." ), color_key: str = typer.Option( "object_id", "--color-key", help="Object key used to color-group boxes." ), mask_key: str = typer.Option( "b64_mask", "--mask-key", help="Object key holding a base64 PNG mask (empty disables masks).", ), mask_alpha: float = typer.Option( 0.5, "--mask-alpha", help="Mask overlay opacity in [0, 1]." ), width: int = typer.Option(3, "--width", help="Box outline width in pixels."), font_size: int = typer.Option(20, "--font-size", help="Label font size."), ): """Draw bounding boxes from ANNOTATIONS onto IMAGE and save the result.""" out_path = output or image.with_name(f"{image.stem}_out.jpg") logger.info(f"annotating {image} with {annotations} -> {out_path}") pilbox.annotate_file( str(image), str(annotations), str(out_path), label_key=label_key, color_key=color_key, mask_key=mask_key, mask_alpha=mask_alpha, width=width, font_size=font_size, ) logger.info(f"saved {out_path}") if __name__ == "__main__": app()