"""Path-first unified CLI for PP-OCRv6.""" from __future__ import annotations import argparse import sys from pathlib import Path from .commands import run_input, run_verify from .logging_utils import default_log_path, setup_run_logger from .runtime import PipelineProvider, RuntimeConfig PROJECT_ROOT = Path(__file__).resolve().parent.parent LEGACY_COMMANDS = {"image", "pdf", "batch"} def _add_bool_option(parser: argparse.ArgumentParser, name: str, default: bool, help_text: str) -> None: parser.add_argument( f"--{name}", action=argparse.BooleanOptionalAction, default=default, help=help_text, ) def _add_device_options(parser: argparse.ArgumentParser, default: str | None) -> None: parser.add_argument("--device", choices=("cpu", "gpu"), default=default or "cpu", help="运行设备") parser.add_argument("--device-id", type=int, default=0, help="GPU 编号") parser.add_argument("--threads", type=int, default=None, help="CPU 线程数") parser.add_argument("--log-file", type=Path, default=None, help="日志文件路径") parser.add_argument("--verbose", action="store_true", help="输出详细日志") def _add_model_options(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--model-size", "--model", dest="model_size", choices=("tiny", "small", "medium"), default="medium", help="PP-OCRv6 检测与识别模型规格", ) parser.add_argument("--lang", default="ch", help="结果元数据中的语言标识,默认中文/中英混合") parser.add_argument("--text-rec-score-thresh", type=float, default=0.0, help="识别置信度阈值") parser.add_argument("--text-det-limit-side-len", type=int, default=None, help="文本检测边长限制") parser.add_argument("--text-det-limit-type", choices=("min", "max"), default=None, help="检测边长限制方式") parser.add_argument("--text-det-thresh", type=float, default=None, help="文本检测像素阈值") parser.add_argument("--text-det-box-thresh", type=float, default=None, help="文本框阈值") parser.add_argument("--text-det-unclip-ratio", type=float, default=None, help="文本框扩张比例") parser.add_argument("--text-recognition-batch-size", type=int, default=6, help="文本识别批大小") parser.add_argument("--return-word-box", action="store_true", help="返回单词级坐标") _add_bool_option(parser, "doc-orientation-classify", True, "启用文档方向分类") _add_bool_option(parser, "doc-unwarping", False, "启用文档去畸变") _add_bool_option(parser, "textline-orientation", True, "启用文本行方向分类") def build_input_parser(device_override: str | None = None) -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="ocr.py", description="使用 PP-OCRv6 自动处理图片、PDF 或目录", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument("input", type=Path, help="图片、PDF 或目录") parser.add_argument("--recursive", action="store_true", help="目录模式递归扫描子目录") parser.add_argument("--output", type=Path, default=PROJECT_ROOT / "outputs", help="输出根目录") parser.add_argument("--fail-fast", action="store_true", help="单文件失败后立即停止目录任务") parser.add_argument("--warmup", type=int, default=0, help="首张图片预热轮数") parser.add_argument("--rounds", type=int, default=1, help="每张图片推理轮数") parser.add_argument("--benchmark-json", type=Path, default=None, help="额外复制单图 Benchmark JSON") parser.add_argument("--no-result", action="store_true", help="不在日志中逐行记录识别文字") parser.add_argument("--save-visualization", action="store_true", help="保存 OCR 可视化图片") parser.add_argument("--save-raw-result", action="store_true", help="保存 PaddleOCR 原始 JSON") parser.add_argument("--pdf-mode", "--mode", dest="pdf_mode", choices=("hybrid", "text", "ocr"), default="hybrid", help="PDF 处理模式") parser.add_argument("--pages", help="PDF 页码范围,例如 1-5,8,10-") parser.add_argument("--dpi", type=int, default=144, help="PDF OCR 页面渲染 DPI") parser.add_argument("--password", help="PDF 密码") parser.add_argument("--resume", action="store_true", help="PDF 断点续传") parser.add_argument("--overwrite", action="store_true", help="覆盖已有 PDF 输出") parser.add_argument("--keep-rendered", action="store_true", help="保留 OCR 页面 PNG") parser.add_argument("--text-min-chars", type=int, default=50, help="有效文本最小字符数") parser.add_argument("--text-min-printable-ratio", type=float, default=0.85, help="可打印字符比例阈值") parser.add_argument("--text-min-content-ratio", type=float, default=0.60, help="字母/数字/CJK 比例阈值") parser.add_argument("--text-max-replacement-ratio", type=float, default=0.02, help="替换字符最大比例") parser.add_argument("--text-min-density", type=float, default=25.0, help="文本密度阈值") _add_model_options(parser) _add_device_options(parser, device_override) return parser def build_verify_parser(device_override: str | None = None) -> argparse.ArgumentParser: parser = argparse.ArgumentParser(prog="ocr.py verify", description="验证 CPU/GPU Paddle 环境") _add_model_options(parser) _add_device_options(parser, device_override) return parser def normalize_argv(argv: list[str]) -> tuple[str, list[str]]: if argv and argv[0] == "verify": return "verify", argv[1:] if argv and argv[0] in LEGACY_COMMANDS: return "input", argv[1:] return "input", argv def main(device: str | None = None, argv: list[str] | None = None) -> int: raw_argv = list(sys.argv[1:] if argv is None else argv) command, normalized = normalize_argv(raw_argv) parser = build_verify_parser(device) if command == "verify" else build_input_parser(device) args = parser.parse_args(normalized) if device is not None: args.device = device stem = "verify" if command == "verify" else (args.input.stem or args.input.name or "input") category = "verify" if command == "verify" else "input" log_file = args.log_file or default_log_path(PROJECT_ROOT, category, stem, device=args.device) logger = setup_run_logger(f"ppocrv6.{category}.{args.device}", log_file, verbose=args.verbose) provider = PipelineProvider( RuntimeConfig( device=args.device, threads=args.threads, device_id=args.device_id, lang=args.lang, model_size=args.model_size, use_doc_orientation_classify=args.doc_orientation_classify, use_doc_unwarping=args.doc_unwarping, use_textline_orientation=args.textline_orientation, text_recognition_batch_size=args.text_recognition_batch_size, ), logger, ) try: if command == "verify": return run_verify(args, provider, logger, PROJECT_ROOT) if args.warmup < 0 or args.rounds < 1: raise ValueError("--warmup 必须 >= 0,--rounds 必须 >= 1") if args.text_recognition_batch_size < 1: raise ValueError("--text-recognition-batch-size 必须 >= 1") return run_input(args, provider, logger, PROJECT_ROOT) except Exception as exc: logger.exception("COMMAND_FAILED command=%s error=%s", command, exc) return 1