--- license: apache-2.0 pipeline_tag: object-detection library_name: onnxruntime language: [en] tags: - object-detection - document-ai - form-field-detection - onnx - open-weights metrics: - accuracy --- # form-field-v1-nano โ€” open-weight **A 0.90M-parameter form-field detector that punches far above its weight.** Locates `Text`, `Choice` (checkbox/radio), and `Signature` widgets on document pages โ€” on **empty, filled, and handwritten** forms โ€” at **3.7 MB** and **~90 pages/sec on a laptop CPU**. Apache-2.0, downloadable, runs via ONNX Runtime with no custom code. Part of the `form-field-v1` family (`-nano` open ยท `-small` / `-medium` commercial, higher accuracy). - ๐ŸŽฏ **Try it:** [form-field-v1-demo](https://huggingface.co/spaces/nutrientdocs/form-field-v1-demo) - ๐Ÿ† **Leaderboard:** [form-field-v1-leaderboard](https://huggingface.co/spaces/nutrientdocs/form-field-v1-leaderboard) - ๐Ÿ“Š **Benchmark:** [form-field-v1-benchmark](https://huggingface.co/datasets/nutrientdocs/form-field-v1-benchmark) ## Why it's notable At **0.90M params** it detects `Text`, `Choice`, and `Signature` across **empty, filled, and handwritten** forms โ€” at 3.7 MB and ~90 pages/sec on a laptop CPU. | Model | Params | Empty mAP50-95 | Filled | Handwritten | |---|---|---|---|---| | **form-field-v1-nano** | **0.90M** | **0.353** | **0.560** | **0.526** | | FFDNet-S (baseline) | 6M | 0.339 | 0.312 | 0.262 | | FFDNet-L (baseline) | 25M | 0.373 | 0.285 | 0.285 | COCO mAP50-95, one shared pycocotools scorer across all models. On empty, per-class it reaches Text 0.469 / Choice 0.407 / Signature 0.182. ## What's in this repo | File | | |---|---| | `model.onnx` | ONNX graph (fp32), input `1ร—3ร—640ร—640`, decode baked in | | `model_fp16.onnx` | fp16 ONNX (1.9 MB), near-lossless | | `best_ckpt.pth` | training checkpoint | | `exp.py` | model/training config (YOLOX exp) | Classes: `0 = Text`, `1 = ChoiceButton`, `2 = Signature`. ## Usage (torch) The checkpoint is a YOLOX-Nano model (`best_ckpt.pth` + `exp.py`); run it with the [`yolox`](https://github.com/Megvii-BaseDetection/YOLOX) package. ```python import torch, numpy as np from PIL import Image from yolox.exp import get_exp from yolox.utils import postprocess exp = get_exp("exp.py"); model = exp.get_model().eval() model.load_state_dict(torch.load("best_ckpt.pth", map_location="cpu")["model"]) S = 640 img = Image.open("page.png").convert("RGB") r = min(S / img.width, S / img.height) # letterbox to SxS, pad 114, raw 0-255 (no normalize) rs = img.resize((int(img.width * r), int(img.height * r))) canvas = Image.new("RGB", (S, S), (114, 114, 114)); canvas.paste(rs, (0, 0)) x = torch.from_numpy(np.asarray(canvas, np.float32).transpose(2, 0, 1)[None]) with torch.no_grad(): dets = postprocess(model(x), num_classes=3, conf_thre=0.3, nms_thre=0.6)[0] # x1,y1,x2,y2,obj,cls_score,cls # divide boxes by r to map back to page pixels; class 0=Text, 1=ChoiceButton, 2=Signature ``` ## Usage (ONNX Runtime) ```python import numpy as np, onnxruntime as ort from PIL import Image sess = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"]) S = 640 img = Image.open("page.png").convert("RGB") r = min(S / img.width, S / img.height) rs = img.resize((int(img.width * r), int(img.height * r))) canvas = Image.new("RGB", (S, S), (114, 114, 114)); canvas.paste(rs, (0, 0)) x = np.asarray(canvas, np.float32).transpose(2, 0, 1)[None] out = sess.run(None, {sess.get_inputs()[0].name: x})[0] # [1, 8400, 8] โ€” already DECODED (boxes in 640px) # rows: [cx, cy, w, h, obj, cls_Text, cls_Choice, cls_Signature]. score = out[0, :, 4:5] * out[0, :, 5:8] # obj * class prob; take argmax class, NMS per class, # convert cx,cy,w,h -> x1,y1,x2,y2, then divide by r to map back to page pixels. ``` **Verified:** running this ONNX end-to-end on the full benchmark reproduces the torch model โ€” **fp32 mAP50-95 = 0.4788, fp16 (`model_fp16.onnx`) = 0.4777** (torch = 0.479). The ONNX is exported with decode baked in (`tools/export_onnx.py --decode_in_inference`), so no external grid-decode is needed. **Footprint:** `model_fp16.onnx` (1.9 MB) halves the fp32 file with near-lossless accuracy (0.4777 vs 0.4788). fp16 is a size/GPU optimization โ€” no CPU-speed change. For an even smaller bundle, INT8 shrinks further but its *speed* only materializes on an int8-optimized mobile backend (CoreML / NNAPI / TFLite-XNNPACK), not desktop ORT-CPU. ## License & data Apache-2.0. Trained on synthetic form renders (empty + filled + handwritten); no PII. Reproduction details in the project's `docs/YOLOX.md`. ## About the author This project is maintained and funded by [Nutrient](https://nutrient.io/) - The deterministic document infrastructure enterprises run their highest-stakes workflows on: replayable output, clear exceptions, and full audit trails on the messy, regulated documents where AI alone breaks.