Files
Fu DaiandClaude Opus 4.8 1a585693be Suya OCR API — vLLM-backed, OpenAI-compatible OCR service
FastAPI service wrapping the Surya-OCR-2 model (datalab-to) served through vLLM:
legacy /v1/api/ai/* endpoints, an OpenAI-compatible /v1/chat/completions endpoint,
a coalescing request batcher, a local OCR CLI, Docker packaging, multilingual
example outputs, and quantization/concurrency benchmarks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:20:02 +04:00

67 lines
2.3 KiB
Python

"""Greedy bbox IoU matching between reference and candidate page boxes."""
from __future__ import annotations
import json
from pathlib import Path
from typing import Dict, List, Sequence
Box = Sequence[float]
def iou(a: Box, b: Box) -> float:
ax0, ay0, ax1, ay1 = a
bx0, by0, bx1, by1 = b
ix0, iy0 = max(ax0, bx0), max(ay0, by0)
ix1, iy1 = min(ax1, bx1), min(ay1, by1)
iw, ih = max(0.0, ix1 - ix0), max(0.0, iy1 - iy0)
inter = iw * ih
if inter <= 0:
return 0.0
area_a = max(0.0, ax1 - ax0) * max(0.0, ay1 - ay0)
area_b = max(0.0, bx1 - bx0) * max(0.0, by1 - by0)
union = area_a + area_b - inter
return inter / union if union > 0 else 0.0
def match_boxes(ref: List[Box], cand: List[Box], iou_threshold: float = 0.5) -> Dict[str, float]:
used = [False] * len(cand)
matched_ious: List[float] = []
for r in ref:
best_j, best_iou = -1, 0.0
for j, c in enumerate(cand):
if used[j]:
continue
cur = iou(r, c)
if cur > best_iou:
best_iou, best_j = cur, j
if best_j >= 0 and best_iou >= iou_threshold:
used[best_j] = True
matched_ious.append(best_iou)
matched = len(matched_ious)
return {
"matched": matched,
"missed": len(ref) - matched,
"extra": len(cand) - matched,
"mean_matched_iou": sum(matched_ious) / matched if matched else 0.0,
}
def bbox_iou_over_dirs(ref_dir: Path, cand_dir: Path, iou_threshold: float = 0.5) -> Dict:
per_file: Dict[str, Dict[str, float]] = {}
for ref_path in sorted(ref_dir.glob("*.json")):
ref_boxes = json.loads(ref_path.read_text(encoding="utf-8")).get("boxes", [])
cand_path = cand_dir / ref_path.name
cand_boxes = (
json.loads(cand_path.read_text(encoding="utf-8")).get("boxes", [])
if cand_path.exists()
else []
)
per_file[ref_path.name] = match_boxes(ref_boxes, cand_boxes, iou_threshold)
n = len(per_file) or 1
return {
"mean_bbox_iou": sum(v["mean_matched_iou"] for v in per_file.values()) / n,
"mean_missed_lines": sum(v["missed"] for v in per_file.values()) / n,
"mean_extra_lines": sum(v["extra"] for v in per_file.values()) / n,
"per_file": per_file,
}