Coverage for src / puzzletree / reconstruct / pipeline.py: 100.00%
65 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-12 20:35 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-12 20:35 +0000
1from __future__ import annotations
3from dataclasses import dataclass
4from pathlib import Path
5from typing import Callable, List, cast
7import numpy as np
8from PIL import Image
10from puzzletree.reconstruct.core import AdjList, Coord, build_weight_matrices, msgt, reconstruct_layout
11from puzzletree.reconstruct.io import load_tiles_from_dir
12from puzzletree.reconstruct.render import render_reconstruction, save_tree_build_animation
14ProgressCallback = Callable[[str], None]
17@dataclass
18class ReconstructionRun:
19 adjs: AdjList
20 placements: dict[int, Coord]
21 output: Image.Image
24@dataclass
25class ReconstructionRunWithHistory(ReconstructionRun):
26 history: List[AdjList]
29@dataclass
30class ReconstructOptions:
31 input_dir: Path
32 output: Path = Path("reconstructed.png")
33 r: float = 12.0
34 minset: float = 0.1
35 animation: Path | None = None
36 animation_seed: int = 0
37 animation_size: int = 1024
38 animation_max_angle: float = 35.0
39 animation_duration_ms: int = 1000
40 animation_frames_dir: Path | None = None
43def _notify_progress(progress_callback: ProgressCallback | None, stage: str) -> None:
44 if progress_callback is not None:
45 progress_callback(stage)
48def run_reconstruction(
49 tiles: List[np.ndarray],
50 r: float,
51 minset: float,
52 progress_callback: ProgressCallback | None = None,
53) -> ReconstructionRun:
54 h, w = tiles[0].shape[:2]
55 _notify_progress(progress_callback, "Computing edge weights")
56 lr, ud = build_weight_matrices(tiles, r=r)
57 _notify_progress(progress_callback, "Assembling reconstruction tree")
58 adjs = cast("AdjList", msgt(lr, ud, minset=minset, lr_side_size=w, ud_side_size=h))
59 _notify_progress(progress_callback, "Rendering reconstructed image")
60 placements = reconstruct_layout(adjs)
61 output = render_reconstruction(tiles, placements)
62 return ReconstructionRun(adjs=adjs, placements=placements, output=output)
65def run_reconstruction_with_history(
66 tiles: List[np.ndarray],
67 r: float,
68 minset: float,
69 progress_callback: ProgressCallback | None = None,
70) -> ReconstructionRunWithHistory:
71 h, w = tiles[0].shape[:2]
72 _notify_progress(progress_callback, "Computing edge weights")
73 lr, ud = build_weight_matrices(tiles, r=r)
74 _notify_progress(progress_callback, "Assembling reconstruction tree")
75 adjs, history = cast(
76 "tuple[AdjList, List[AdjList]]",
77 msgt(lr, ud, minset=minset, lr_side_size=w, ud_side_size=h, record_history=True),
78 )
79 _notify_progress(progress_callback, "Rendering reconstructed image")
80 placements = reconstruct_layout(adjs)
81 output = render_reconstruction(tiles, placements)
82 return ReconstructionRunWithHistory(adjs=adjs, placements=placements, output=output, history=history)
85def run_from_options(
86 options: ReconstructOptions,
87 progress_callback: ProgressCallback | None = None,
88) -> ReconstructionRun | ReconstructionRunWithHistory:
89 _notify_progress(progress_callback, "Loading tiles")
90 tiles = load_tiles_from_dir(options.input_dir)
92 if options.animation is None:
93 result = run_reconstruction(tiles, r=options.r, minset=options.minset, progress_callback=progress_callback)
94 else:
95 result = run_reconstruction_with_history(
96 tiles,
97 r=options.r,
98 minset=options.minset,
99 progress_callback=progress_callback,
100 )
102 _notify_progress(progress_callback, "Saving reconstructed image")
103 result.output.save(options.output)
105 if options.animation is not None and isinstance(result, ReconstructionRunWithHistory):
106 _notify_progress(progress_callback, "Rendering animation")
107 save_tree_build_animation(
108 tiles=tiles,
109 history=result.history,
110 output_path=options.animation,
111 seed=options.animation_seed,
112 frame_size=options.animation_size,
113 max_angle=options.animation_max_angle,
114 duration_ms=options.animation_duration_ms,
115 frames_dir=options.animation_frames_dir,
116 )
118 return result