Spaces:
Running on Zero
Running on Zero
Commit ·
da75fa3
0
Parent(s):
form-field-v1
Browse files- .gitattributes +45 -0
- README.md +30 -0
- app.py +198 -0
- examples/acme-credit-card-application.png +3 -0
- examples/great-west-life-notice-of-claim.jpg +3 -0
- examples/handwritten-nursing-licensure.jpg +3 -0
- examples/handwritten-police-records.jpg +3 -0
- examples/us-direct-deposit-sign-up-form-1199a.jpg +3 -0
- requirements.txt +6 -0
.gitattributes
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
examples/city-of-mesquite-front-carport-application.jpg filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
examples/global-electronics-employee-information-form.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
examples/great-west-life-notice-of-claim.jpg filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
examples/us-direct-deposit-sign-up-form-1199a.jpg filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
examples/acme-credit-card-application.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
examples/filled-service-tiers.jpg filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
examples/filled-substitution-request.jpg filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
examples/handwritten-diploma-order.jpg filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
examples/handwritten-nursing-licensure.jpg filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
examples/handwritten-police-records.jpg filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: form-field-v1 detector demo
|
| 3 |
+
emoji: 🖼️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 6.20.0
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: other
|
| 11 |
+
short_description: Detect Text/Choice/Signature fields on a form page
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
# form-field-v1 · detector demo
|
| 15 |
+
|
| 16 |
+
Upload a form page and detect its **Text**, **Choice** (checkbox/radio) and **Signature** fields with the
|
| 17 |
+
`form-field-v1` detectors — choose **nano** (open), **small**, or **medium** (best). Runs the verified ONNX via
|
| 18 |
+
ONNX Runtime on ZeroGPU.
|
| 19 |
+
|
| 20 |
+
- 🏆 [Leaderboard](https://huggingface.co/spaces/nutrientdocs/form-field-v1-leaderboard)
|
| 21 |
+
- 📊 [Benchmark](https://huggingface.co/datasets/nutrientdocs/form-field-v1-benchmark)
|
| 22 |
+
|
| 23 |
+
The **nano** model is open (Apache-2.0). The **small** and **medium** models are commercial; their weights are
|
| 24 |
+
served from a private repository and are not included in this Space.
|
| 25 |
+
|
| 26 |
+
## About the author
|
| 27 |
+
|
| 28 |
+
<a href="https://nutrient.io/"><img src="https://avatars2.githubusercontent.com/u/1527679?v=3&s=200" height="80" /></a>
|
| 29 |
+
|
| 30 |
+
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.
|
app.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""form-field-v1 detector demo — nano/small/medium run as verified ONNX via ONNX Runtime. Field STATE
|
| 2 |
+
(filled/empty, checked/unchecked, signed/blank) comes from the medium 6-class head when available, and an
|
| 3 |
+
ink-presence check that rescues filled fields the head misses (and supplies state for the 3-class models).
|
| 4 |
+
ZeroGPU (CUDA) with CPU fallback."""
|
| 5 |
+
import os, json
|
| 6 |
+
import numpy as np
|
| 7 |
+
import cv2
|
| 8 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 9 |
+
from huggingface_hub import hf_hub_download
|
| 10 |
+
import onnxruntime as ort
|
| 11 |
+
import gradio as gr
|
| 12 |
+
try:
|
| 13 |
+
import spaces
|
| 14 |
+
except Exception: # local dev without the spaces shim
|
| 15 |
+
class _S:
|
| 16 |
+
def GPU(self, *a, **k):
|
| 17 |
+
def deco(f): return f
|
| 18 |
+
return deco
|
| 19 |
+
spaces = _S()
|
| 20 |
+
|
| 21 |
+
TOKEN = os.environ.get("HF_TOKEN")
|
| 22 |
+
CLASSES = ["Text", "ChoiceButton", "Signature"]
|
| 23 |
+
COLORS = {"Text": (29, 95, 168), "ChoiceButton": (22, 121, 79), "Signature": (176, 84, 20)}
|
| 24 |
+
# medium is a 6-class coarse3_state head: raw index -> (coarse type, state). nano/small are 3-class (no state).
|
| 25 |
+
RF_STATE = {0: ("ChoiceButton", "checked"), 1: ("ChoiceButton", "unchecked"),
|
| 26 |
+
2: ("Signature", "blank"), 3: ("Signature", "signed"),
|
| 27 |
+
4: ("Text", "empty"), 5: ("Text", "filled")}
|
| 28 |
+
FILLED_OF = {"Text": "filled", "ChoiceButton": "checked", "Signature": "signed"}
|
| 29 |
+
EMPTY_OF = {"Text": "empty", "ChoiceButton": "unchecked", "Signature": "blank"}
|
| 30 |
+
POS = {"filled", "checked", "signed"}
|
| 31 |
+
MODELS = {
|
| 32 |
+
"nano · open · 0.9M": {"repo": "nutrientdocs/form-field-v1-nano", "kind": "yolox", "size": 640},
|
| 33 |
+
"small · commercial · 8.9M": {"repo": "nutrientdocs/form-field-v1-small-private", "kind": "yolox", "size": 896},
|
| 34 |
+
"medium · commercial · 34M": {"repo": "nutrientdocs/form-field-v1-medium-private", "kind": "rfdetr", "size": 1216},
|
| 35 |
+
}
|
| 36 |
+
_SESS = {}
|
| 37 |
+
_MEAN = np.array([0.485, 0.456, 0.406], np.float32); _STD = np.array([0.229, 0.224, 0.225], np.float32)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def has_ink(pil_crop):
|
| 41 |
+
"""Training-free ink presence: local-adaptive contrast (any polarity -> white/colored/dark/gradient fills) +
|
| 42 |
+
morphological removal of the box frame / underline (so a mark touching the border survives) + component filter."""
|
| 43 |
+
a = np.asarray(pil_crop.convert("RGB"), np.uint8); H, W = a.shape[:2]
|
| 44 |
+
if H < 6 or W < 6: return False
|
| 45 |
+
gray = cv2.cvtColor(a, cv2.COLOR_RGB2GRAY).astype(np.int16)
|
| 46 |
+
blk = max(9, (min(H, W)) | 1)
|
| 47 |
+
mean = cv2.blur(gray.astype(np.float32), (blk, blk))
|
| 48 |
+
mask = (np.abs(gray - mean) > 25).astype(np.uint8)
|
| 49 |
+
if mask.sum() == 0: return False
|
| 50 |
+
hk = cv2.getStructuringElement(cv2.MORPH_RECT, (max(8, int(0.55 * W)), 1))
|
| 51 |
+
vk = cv2.getStructuringElement(cv2.MORPH_RECT, (1, max(8, int(0.55 * H))))
|
| 52 |
+
lines = cv2.morphologyEx(mask, cv2.MORPH_OPEN, hk) | cv2.morphologyEx(mask, cv2.MORPH_OPEN, vk)
|
| 53 |
+
mask = cv2.bitwise_and(mask, cv2.bitwise_not(lines))
|
| 54 |
+
n, _lab, stats, _c = cv2.connectedComponentsWithStats(mask, connectivity=8)
|
| 55 |
+
content = 0; kept = 0; floor = max(6, int(0.0008 * H * W))
|
| 56 |
+
for i in range(1, n):
|
| 57 |
+
if stats[i, 4] >= floor: content += stats[i, 4]; kept += 1
|
| 58 |
+
return (content / (H * W)) >= 0.004 and kept >= 1
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _cb_marked(pil_crop, inset=0.22, thr=0.06):
|
| 62 |
+
"""Checkbox/radio state: measure ink strictly INSIDE the box frame (inset past the border) vs the paper. An
|
| 63 |
+
empty box's interior is blank; a checked box's mark lives in the interior. Excluding the frame by construction
|
| 64 |
+
means an empty box can't be mistaken for checked (frame ink never counts)."""
|
| 65 |
+
a = np.asarray(pil_crop.convert("RGB"), np.uint8); H, W = a.shape[:2]
|
| 66 |
+
if H < 6 or W < 6: return False
|
| 67 |
+
g = cv2.cvtColor(a, cv2.COLOR_RGB2GRAY)
|
| 68 |
+
bg = np.percentile(g, 90) # paper / fill color
|
| 69 |
+
mx = max(1, int(W * inset)); my = max(1, int(H * inset))
|
| 70 |
+
core = g[my:H - my, mx:W - mx]
|
| 71 |
+
if core.size < 4: return False
|
| 72 |
+
return float((core.astype(np.int16) < bg - 50).mean()) >= thr
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def _resolve_state(det, pil):
|
| 76 |
+
"""Hybrid: trust the head's positive (filled/checked/signed); otherwise let the ink check rescue strays and
|
| 77 |
+
supply state for the 3-class models. One-directional — the heuristic never downgrades a positive.
|
| 78 |
+
Checkboxes/radios use the interior-fill method; text/signature use the frame-removal component method."""
|
| 79 |
+
t, hs = det["type"], det["state"]
|
| 80 |
+
if hs in POS:
|
| 81 |
+
return hs
|
| 82 |
+
x, y, w, h = det["box"]
|
| 83 |
+
crop = pil.crop((int(x), int(y), int(x + w), int(y + h)))
|
| 84 |
+
ink = _cb_marked(crop) if t == "ChoiceButton" else has_ink(crop)
|
| 85 |
+
return FILLED_OF[t] if ink else EMPTY_OF[t]
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def _session(name):
|
| 89 |
+
if name not in _SESS:
|
| 90 |
+
cfg = MODELS[name]
|
| 91 |
+
path = hf_hub_download(cfg["repo"], "model.onnx", token=TOKEN)
|
| 92 |
+
provs = [("CUDAExecutionProvider", {"use_tf32": 0}), "CPUExecutionProvider"]
|
| 93 |
+
_SESS[name] = (ort.InferenceSession(path, providers=provs), cfg)
|
| 94 |
+
return _SESS[name]
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def _nms(boxes, scores, iou=0.6):
|
| 98 |
+
if not boxes: return []
|
| 99 |
+
b = np.array(boxes); x1, y1, x2, y2 = b[:, 0], b[:, 1], b[:, 0] + b[:, 2], b[:, 1] + b[:, 3]
|
| 100 |
+
a = (x2 - x1) * (y2 - y1); order = np.array(scores).argsort()[::-1]; keep = []
|
| 101 |
+
while order.size:
|
| 102 |
+
i = order[0]; keep.append(i)
|
| 103 |
+
xx1 = np.maximum(x1[i], x1[order[1:]]); yy1 = np.maximum(y1[i], y1[order[1:]])
|
| 104 |
+
xx2 = np.minimum(x2[i], x2[order[1:]]); yy2 = np.minimum(y2[i], y2[order[1:]])
|
| 105 |
+
w = np.maximum(0, xx2 - xx1); h = np.maximum(0, yy2 - yy1); inter = w * h
|
| 106 |
+
ov = inter / (a[i] + a[order[1:]] - inter + 1e-9); order = order[1:][ov <= iou]
|
| 107 |
+
return keep
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def _infer(name, pil, thr):
|
| 111 |
+
sess, cfg = _session(name); W, H = pil.size; S = cfg["size"]
|
| 112 |
+
dets = []
|
| 113 |
+
iname = sess.get_inputs()[0].name
|
| 114 |
+
if cfg["kind"] == "rfdetr":
|
| 115 |
+
x = np.asarray(pil.resize((S, S), Image.BILINEAR), np.float32) / 255.0
|
| 116 |
+
x = ((x - _MEAN) / _STD).transpose(2, 0, 1)[None]
|
| 117 |
+
logits, boxes = sess.run(None, {iname: x}); logits, boxes = logits[0], boxes[0]
|
| 118 |
+
prob = 1 / (1 + np.exp(-logits)); lab = prob.argmax(1); sc = prob.max(1)
|
| 119 |
+
for q in range(len(lab)):
|
| 120 |
+
if sc[q] < thr: continue
|
| 121 |
+
typ, state = RF_STATE[int(lab[q])] # 6-class head: keep the state (filled/empty, checked/…)
|
| 122 |
+
cx, cy, bw, bh = boxes[q]
|
| 123 |
+
dets.append({"type": typ, "state": state, "score": float(sc[q]),
|
| 124 |
+
"box": [(cx-bw/2)*W, (cy-bh/2)*H, bw*W, bh*H]})
|
| 125 |
+
else: # yolox: letterbox, decoded output [N,8] (3-class, no state head)
|
| 126 |
+
r = min(S/W, S/H); nw, nh = int(W*r), int(H*r)
|
| 127 |
+
canvas = np.full((S, S, 3), 114, np.float32)
|
| 128 |
+
canvas[:nh, :nw] = np.asarray(pil.resize((nw, nh), Image.BILINEAR), np.float32)
|
| 129 |
+
out = sess.run(None, {iname: canvas.transpose(2, 0, 1)[None]})[0][0]
|
| 130 |
+
obj = out[:, 4]; cls = out[:, 5:8]; clab = cls.argmax(1); score = obj * cls.max(1)
|
| 131 |
+
for c in (0, 1, 2):
|
| 132 |
+
idx = [i for i in range(len(clab)) if clab[i] == c and score[i] >= thr]
|
| 133 |
+
kb = [[float((out[i, 0]-out[i, 2]/2)/r), float((out[i, 1]-out[i, 3]/2)/r),
|
| 134 |
+
float(out[i, 2]/r), float(out[i, 3]/r)] for i in idx]
|
| 135 |
+
ks = [float(score[i]) for i in idx]
|
| 136 |
+
for j in _nms(kb, ks):
|
| 137 |
+
dets.append({"type": CLASSES[c], "state": None, "score": ks[j], "box": kb[j]})
|
| 138 |
+
return dets
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
@spaces.GPU(duration=60)
|
| 142 |
+
def detect(image, model_name, threshold):
|
| 143 |
+
if image is None:
|
| 144 |
+
return None, "Upload a form page to detect its fields."
|
| 145 |
+
pil = image.convert("RGB")
|
| 146 |
+
try:
|
| 147 |
+
dets = _infer(model_name, pil, float(threshold))
|
| 148 |
+
except Exception as e:
|
| 149 |
+
return None, f"⚠️ Model unavailable — this Space needs access to the model weights. ({str(e)[:120]})"
|
| 150 |
+
out = pil.copy(); d = ImageDraw.Draw(out)
|
| 151 |
+
try: font = ImageFont.load_default()
|
| 152 |
+
except Exception: font = None
|
| 153 |
+
rows = []
|
| 154 |
+
for det in sorted(dets, key=lambda t: -t["score"]):
|
| 155 |
+
cls, sc = det["type"], det["score"]; x, y, w, h = det["box"]
|
| 156 |
+
state = _resolve_state(det, pil) # hybrid: 6-class head + ink-presence stray-catch
|
| 157 |
+
col = COLORS[cls]; d.rectangle([x, y, x+w, y+h], outline=col, width=3)
|
| 158 |
+
d.text((x+2, max(0, y-11)), f"{cls}-{state} {sc:.2f}", fill=col, font=font)
|
| 159 |
+
rows.append({"box": [round(x), round(y), round(w), round(h)], "type": cls,
|
| 160 |
+
"state": state, "score": round(sc, 3)})
|
| 161 |
+
return out, json.dumps(rows, indent=2)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
INTRO = """# form-field-v1 · detector demo
|
| 165 |
+
Detect **Text**, **Choice** (checkbox/radio) and **Signature** fields on empty, filled, and handwritten form pages —
|
| 166 |
+
and read each field's **state** (filled/empty, checked/unchecked, signed/blank). State comes from the model on
|
| 167 |
+
`medium`, plus an ink check that catches filled fields the model misses. Just upload a page.
|
| 168 |
+
|
| 169 |
+
- 🏆 [Leaderboard](https://huggingface.co/spaces/nutrientdocs/form-field-v1-leaderboard) ·
|
| 170 |
+
📊 [Benchmark](https://huggingface.co/datasets/nutrientdocs/form-field-v1-benchmark) ·
|
| 171 |
+
Models: [nano](https://huggingface.co/nutrientdocs/form-field-v1-nano) (open) ·
|
| 172 |
+
[small](https://huggingface.co/nutrientdocs/form-field-v1-small) ·
|
| 173 |
+
[medium](https://huggingface.co/nutrientdocs/form-field-v1-medium) (commercial)
|
| 174 |
+
"""
|
| 175 |
+
ABOUT = """## About the author
|
| 176 |
+
<a href="https://nutrient.io/"><img src="https://avatars2.githubusercontent.com/u/1527679?v=3&s=200" height="80" /></a>
|
| 177 |
+
|
| 178 |
+
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.
|
| 179 |
+
"""
|
| 180 |
+
|
| 181 |
+
with gr.Blocks(title="form-field-v1 detector") as demo:
|
| 182 |
+
gr.Markdown(INTRO)
|
| 183 |
+
with gr.Row():
|
| 184 |
+
with gr.Column():
|
| 185 |
+
inp = gr.Image(type="pil", label="Form page")
|
| 186 |
+
model = gr.Dropdown(list(MODELS), value="medium · commercial · 34M", label="Model")
|
| 187 |
+
thr = gr.Slider(0.05, 0.9, value=0.35, step=0.05, label="Confidence threshold")
|
| 188 |
+
btn = gr.Button("Detect fields", variant="primary")
|
| 189 |
+
with gr.Column():
|
| 190 |
+
outimg = gr.Image(type="pil", label="Detections")
|
| 191 |
+
outjson = gr.Code(language="json", label="Fields", lines=8, max_lines=24)
|
| 192 |
+
ex = [[os.path.join("examples", x)] for x in sorted(os.listdir("examples"))] if os.path.isdir("examples") else []
|
| 193 |
+
if ex: gr.Examples(ex, inputs=[inp], run_on_click=False)
|
| 194 |
+
btn.click(detect, [inp, model, thr], [outimg, outjson])
|
| 195 |
+
gr.Markdown(ABOUT)
|
| 196 |
+
|
| 197 |
+
if __name__ == "__main__":
|
| 198 |
+
demo.queue().launch(theme=gr.themes.Soft())
|
examples/acme-credit-card-application.png
ADDED
|
Git LFS Details
|
examples/great-west-life-notice-of-claim.jpg
ADDED
|
Git LFS Details
|
examples/handwritten-nursing-licensure.jpg
ADDED
|
Git LFS Details
|
examples/handwritten-police-records.jpg
ADDED
|
Git LFS Details
|
examples/us-direct-deposit-sign-up-form-1199a.jpg
ADDED
|
Git LFS Details
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
onnxruntime-gpu==1.20.2
|
| 2 |
+
spaces
|
| 3 |
+
pillow
|
| 4 |
+
numpy
|
| 5 |
+
opencv-python-headless
|
| 6 |
+
huggingface_hub
|