Eroux's picture
v4 leak-free tam2col PP-DocLayout-L: inference model + weights + card + infer.py
da894bb verified
Raw
History Blame Contribute Delete
3.21 kB
#!/usr/bin/env python3
"""Run the TiBLA PP-DocLayout-L Tibetan book layout detector on one or more page
images, writing YOLO-format labels.
The model is a 4-class PP-DocLayout-L (header, text-area, footnote, footer)
fine-tuned on the leak-free v4 `tam2col` split of TiBLAD. It is served from the
exported PaddlePaddle inference model in `inference/`. The recommended global
operating confidence is ~0.68 (the best-mean-F1 point on the v4 test).
Usage:
python infer.py --model-dir inference --source page.jpg
python infer.py --model-dir inference --source pages/ --out preds --conf 0.68
Requires: paddlepaddle==3.0.0, paddlex.
"""
from __future__ import annotations
import argparse
from pathlib import Path
IMG_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".tif", ".tiff"}
# native class order from inference.yml
NAMES = {"header": 0, "text-area": 1, "footnote": 2, "footer": 3}
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--model-dir", default="inference",
help="path to the exported inference model dir")
ap.add_argument("--source", required=True, help="image file or folder")
ap.add_argument("--out", default=None,
help="optional folder to write YOLO-format .txt labels")
ap.add_argument("--conf", type=float, default=0.68,
help="global operating confidence (default 0.68)")
args = ap.parse_args()
from PIL import Image
from paddlex import create_model
model = create_model(model_name="PP-DocLayout-L", model_dir=args.model_dir)
src = Path(args.source)
imgs = sorted(p for p in src.iterdir() if p.suffix.lower() in IMG_EXTS) \
if src.is_dir() else [src]
out_dir = Path(args.out) if args.out else None
if out_dir:
out_dir.mkdir(parents=True, exist_ok=True)
n_img = n_kept = 0
for ip in imgs:
n_img += 1
with Image.open(ip) as im:
W, H = im.size
lines = []
for res in model.predict(str(ip), threshold=args.conf):
for box in res["boxes"]:
label = box["label"]
cls = NAMES.get(label)
if cls is None:
continue
x1, y1, x2, y2 = box["coordinate"]
cx, cy = ((x1 + x2) / 2) / W, ((y1 + y2) / 2) / H
w, h = (x2 - x1) / W, (y2 - y1) / H
lines.append((cls, float(box["score"]), cx, cy, w, h))
n_kept += len(lines)
print(f"{ip.stem}: {len(lines)} boxes")
for cls, score, cx, cy, w, h in lines:
name = next(k for k, v in NAMES.items() if v == cls)
print(f" {name:10} conf={score:.3f} cx={cx:.3f} cy={cy:.3f} "
f"w={w:.3f} h={h:.3f}")
if out_dir:
(out_dir / f"{ip.stem}.txt").write_text(
"".join(f"{c} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n"
for c, _, cx, cy, w, h in lines))
print(f"\n{n_img} images, {n_kept} boxes kept (conf {args.conf})")
return 0
if __name__ == "__main__":
raise SystemExit(main())