ivere27's picture
tiny-receipt-reader-digit-slots-2m โ€” question-free receipt digit reader (2.46M params, epoch 100)
c9fa078
Raw
History Blame Contribute Delete
3.4 kB
#!/usr/bin/env python3
"""Standalone runtime for the exported reader.
python inference.py --model-dir . --image receipt.jpg \
--question "๊ฐ€๊ฒŒ ์ „ํ™”๋ฒˆํ˜ธ์˜ ๋’ค์—์„œ 2๋ฒˆ์งธ ์ˆซ์ž๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?"
The graph runs once and produces a record. Questions are answered from that
record by `question_router`, so several questions about the same image cost one
forward pass -- pass `--question` more than once to see it.
Requires onnxruntime, numpy, and Pillow. No tokenizer, no vocabulary.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import numpy as np
from question_router import (
address_op_from_question, answer_from_record, phone_op_from_question,
route_family_from_question,
)
def load_config(model_dir: Path) -> dict:
return json.loads((model_dir / "config.json").read_text(encoding="utf-8"))
def preprocess(image_path: Path, cfg: dict) -> np.ndarray:
from PIL import Image
spec = cfg["input"]
img = Image.open(image_path).convert("L").resize(
(int(spec["width"]), int(spec["height"])), Image.Resampling.BILINEAR
)
arr = np.asarray(img, dtype=np.float32) / 255.0
return ((arr - 0.5) / 0.5)[None, None, :, :]
def decode(logits: np.ndarray, cfg: dict) -> dict:
blank = int(cfg["blank_class"])
phone_slots = int(cfg["phone_slots"])
ids = logits[0].argmax(-1)
def run(values) -> str:
out = []
for v in values:
if int(v) == blank:
break
out.append(str(int(v)))
return "".join(out)
return {"phone": run(ids[:phone_slots]), "street": run(ids[phone_slots:])}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--model-dir", default=".")
ap.add_argument("--image", required=True)
ap.add_argument("--question", action="append", default=[])
ap.add_argument("--precision", choices=["fp32", "int8"], default="fp32")
ap.add_argument("--threads", type=int, default=1)
args = ap.parse_args()
import onnxruntime as ort
model_dir = Path(args.model_dir)
cfg = load_config(model_dir)
name = "model.onnx" if args.precision == "fp32" else "model_int8.onnx"
path = model_dir / name
if not path.exists():
raise SystemExit(f"missing {path}")
opts = ort.SessionOptions()
opts.intra_op_num_threads = args.threads
opts.inter_op_num_threads = args.threads
sess = ort.InferenceSession(str(path), opts, providers=["CPUExecutionProvider"])
logits = sess.run(None, {"image": preprocess(Path(args.image), cfg)})[0]
record = decode(logits, cfg)
answers = []
for q in args.question:
family = route_family_from_question(q)
if family == "phone":
op = phone_op_from_question(q)
elif family == "address":
op = address_op_from_question(q)
else:
op = "unsupported"
answers.append({
"question": q,
"family": family,
"op": op,
"answer": answer_from_record(q, record["phone"], record["street"]),
})
print(json.dumps({
"image": args.image,
"precision": args.precision,
"record": record,
"forward_passes": 1,
"answers": answers,
}, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())