decosa-readconf / usage.py
sipratt's picture
decosa-readconf: weights, card, eval summary (Apache-2.0)
8ac5c44 verified
Raw History Blame Contribute Delete
2.52 kB
# SPDX-License-Identifier: Apache-2.0
# Copyright 2026 Decosa
"""Score a page read by a document reader: P(each table cell or text region is misread) and the likely error.
pip install torch safetensors numpy pillow (transformers only for a vision variant; this one has none)
python usage.py page.png page.json # prints the 20 units most likely misread
The page dict is one page of a decosa.docreader.v1 document: {"elements": [{id, kind, bbox: [x, y, w, h], text?,
table?: {rows, cols, cells: [{r, c, rs, cs, text, tok?}]}, source, conf_bp?, min_bp?, ...}]}, with bboxes in the pixels of
the page image you pass. Cell token confidences (cell["tok"]) are optional but help a lot (see the eval).
"""
import json
import sys
import numpy as np
import torch
from PIL import Image
from safetensors.torch import load_file
import features as F
from model import TYPES, ReadConf, encode_text
D = "."
cfg = json.load(open(f"{D}/readconf_config.json"))
vcfg = None
if cfg.get("vision"):
from transformers import Siglip2VisionConfig
vcfg = Siglip2VisionConfig.from_dict(cfg["vision"]["config"])
m = ReadConf(cfg["n_feat"], vision_config=vcfg, vision_layers=(cfg.get("vision") or {}).get("layers", 0))
m.load_state_dict(load_file(f"{D}/model.safetensors"))
m.eval()
@torch.no_grad()
def score(image: Image.Image, page: dict) -> list[dict]:
us = F.page_units(image.convert("RGB"), page)
if not us:
return []
x = torch.tensor(np.array([u["x"] for u in us], np.float32))
ch = encode_text([u["text"] for u in us])
img = None
if m.vision_layers:
w = cfg["crop"][1]
a = np.full((len(us), F.CROP_H, w), 255, np.uint8)
for i, u in enumerate(us):
c = F.crop(image.convert("RGB"), u, max_w=w)
a[i, :, : c.width] = np.asarray(c, np.uint8)
img = torch.tensor(a)
lw, lt = m(x, ch, img)
p, tp = m.p_wrong(lw), lt.softmax(-1)
out = []
for i, u in enumerate(us):
# No thresholds ship with this model: rank by p_wrong and review from the top (see the model card).
out.append({"uid": u["uid"], "text": u["text"], "p_wrong": round(float(p[i]), 4), "type": TYPES[int(tp[i].argmax())]})
return out
if __name__ == "__main__":
im = Image.open(sys.argv[1])
page = json.load(open(sys.argv[2]))
if "pages" in page: # a whole decosa.docreader.v1 document: score its first page
page = page["pages"][0]
for r in sorted(score(im, page), key=lambda r: -r["p_wrong"])[:20]:
print(r)