from __future__ import annotations import argparse from pathlib import Path import cv2 import clean_characters import vectorize_characters def process_image( image_path: Path, image_output_dir: Path, svg_output_dir: Path, min_component_area: int | None, max_hole_area: int | None, max_isolated_speck_area: int | None, clean_smooth: bool, vector_smooth_iterations: int | None, epsilon_ratio: float | None, ) -> None: original = clean_characters.read_image(image_path) gray = cv2.cvtColor(original, cv2.COLOR_BGR2GRAY) binary = clean_characters.binarize_foreground(gray) ( adaptive_min_area, adaptive_max_hole_area, adaptive_max_isolated_speck_area, ) = clean_characters.adaptive_clean_params(binary) cleaned = clean_characters.clean_mask( binary, min_component_area=( adaptive_min_area if min_component_area is None else min_component_area ), max_hole_area=( adaptive_max_hole_area if max_hole_area is None else max_hole_area ), max_isolated_speck_area=( adaptive_max_isolated_speck_area if max_isolated_speck_area is None else max_isolated_speck_area ), smooth=clean_smooth, ) auto_smooth_iterations, auto_epsilon_ratio = vectorize_characters.vector_params( cleaned ) smoothed = vectorize_characters.smooth_mask( cleaned, iterations=( auto_smooth_iterations if vector_smooth_iterations is None else vector_smooth_iterations ), ) ratio = auto_epsilon_ratio if epsilon_ratio is None else epsilon_ratio stem = image_path.stem clean_characters.write_image( image_output_dir / f"{stem}_cleaned_smoothed.png", clean_characters.mask_to_display(smoothed), ) svg = vectorize_characters.mask_to_svg( smoothed, title=stem, epsilon_ratio=ratio, ) (svg_output_dir / f"{stem}.svg").write_text(svg, encoding="utf-8") def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description="Clean, smooth, and vectorize complex seal-script characters." ) parser.add_argument( "--input-dir", type=Path, default=Path("PreProcessing_Common/original_characters"), ) parser.add_argument( "--image-output-dir", type=Path, default=Path("PreProcessing_Common/processed_characters"), help="Output folder for clean+smoothed PNG files.", ) parser.add_argument( "--svg-output-dir", type=Path, default=Path("PreProcessing_Common/vector_characters"), help="Output folder for SVG vector files.", ) parser.add_argument("--min-component-area", type=int, default=None) parser.add_argument("--max-hole-area", type=int, default=None) parser.add_argument("--max-isolated-speck-area", type=int, default=None) parser.add_argument( "--no-clean-smooth", action="store_true", help="Skip the light 3x3 smoothing inside the clean stage.", ) parser.add_argument( "--vector-smooth-iterations", type=int, default=None, help="Override automatic vector smoothing. Dense characters default to less smoothing.", ) parser.add_argument( "--epsilon-ratio", type=float, default=None, help="Override contour simplification ratio. Smaller values keep more detail.", ) return parser def main() -> None: args = build_parser().parse_args() args.image_output_dir.mkdir(parents=True, exist_ok=True) args.svg_output_dir.mkdir(parents=True, exist_ok=True) image_paths = clean_characters.iter_images(args.input_dir) if not image_paths: raise SystemExit(f"No images found in {args.input_dir}") for image_path in image_paths: process_image( image_path=image_path, image_output_dir=args.image_output_dir, svg_output_dir=args.svg_output_dir, min_component_area=args.min_component_area, max_hole_area=args.max_hole_area, max_isolated_speck_area=args.max_isolated_speck_area, clean_smooth=not args.no_clean_smooth, vector_smooth_iterations=args.vector_smooth_iterations, epsilon_ratio=args.epsilon_ratio, ) print(f"processed: {image_path.name}") if __name__ == "__main__": main()