Coverage for src / puzzletree / cli / commands / tile / __init__.py: 100.00%

32 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-12 20:35 +0000

1from __future__ import annotations 

2 

3import logging 

4from pathlib import Path 

5 

6from typer import Context, Exit, Option, Typer 

7 

8from puzzletree.cli.messages import error_panel, info_panel 

9from puzzletree.reconstruct.io import save_tiles_from_image 

10from puzzletree.utils.logging import get_logger_console 

11from puzzletree.utils.progress_bar import StageProgressBar 

12 

13app = Typer(add_completion=True, no_args_is_help=True) 

14 

15DEFAULT_TILE_ROWS = 4 

16DEFAULT_TILE_COLS = 5 

17 

18 

19def _default_output_dir(input_image: Path) -> Path: 

20 return Path.cwd() / f"puzzletree-{input_image.stem}-tiles" 

21 

22 

23@app.callback(invoke_without_command=True, no_args_is_help=True) 

24def tile( 

25 ctx: Context, 

26 input_image: Path = Option(..., "--input-image", "-i", help="Source image to split into tiles."), 

27 output_dir: Path | None = Option( 

28 None, 

29 "--output-dir", 

30 "-o", 

31 help="Directory to write tile PNGs. Defaults to ./puzzletree-<image-stem>-tiles.", 

32 ), 

33 rows: int = Option(DEFAULT_TILE_ROWS, "--rows", help="Number of tile rows."), 

34 cols: int = Option(DEFAULT_TILE_COLS, "--cols", help="Number of tile columns."), 

35 prefix: str = Option("tile", "--prefix", help="Filename prefix for generated tiles."), 

36 overwrite: bool = Option( 

37 False, 

38 "--overwrite", 

39 help="Replace existing generated PNG tiles with the same prefix in the output directory.", 

40 ), 

41) -> None: 

42 """Split an image into a regular grid of tiles.""" 

43 verbose = bool(ctx.obj.get("verbose", False)) if isinstance(ctx.obj, dict) else False 

44 log_level = logging.DEBUG if verbose else logging.INFO 

45 logger, console = get_logger_console("puzzletree.tile", log_level=log_level) 

46 resolved_output_dir = output_dir if output_dir is not None else _default_output_dir(input_image) 

47 

48 logger.debug( 

49 "Tile command options: input_image=%s output_dir=%s rows=%s cols=%s prefix=%s overwrite=%s", 

50 input_image, 

51 resolved_output_dir, 

52 rows, 

53 cols, 

54 prefix, 

55 overwrite, 

56 ) 

57 

58 with StageProgressBar(console=console, use_progress_bar=bool(getattr(console, "is_terminal", True))) as progress: 

59 progress.start(total_steps=2, description="Validating tiling request") 

60 try: 

61 progress.advance("Writing tile images") 

62 result = save_tiles_from_image( 

63 input_image=input_image, 

64 output_dir=resolved_output_dir, 

65 rows=rows, 

66 cols=cols, 

67 prefix=prefix, 

68 overwrite=overwrite, 

69 ) 

70 progress.advance("Finalizing tile summary") 

71 except Exception as exc: 

72 logger.exception("Tile generation failed") 

73 console.print(error_panel(str(exc), console=console)) 

74 raise Exit(1) from exc 

75 

76 summary = "\n".join( 

77 [ 

78 f"Tiles written: {len(result.paths)}", 

79 f"Grid: {result.rows}x{result.cols}", 

80 f"Tile size: {result.tile_width}x{result.tile_height}", 

81 f"Saved: {result.output_dir}", 

82 ], 

83 ) 

84 console.print(info_panel(summary, console=console))