Spaces:
Running
Running
Download server/layout.py from FineEnvs/nayana-ocr-env: direct link, hf CLI and curl.
- Browser
- Download file 3.6 kB
-
https://huggingface.co/spaces/FineEnvs/nayana-ocr-env/resolve/main/server/layout.py
- Command line
-
hf download hf://spaces/FineEnvs/nayana-ocr-env/server/layout.py
-
curl -L -o layout.py https://huggingface.co/spaces/FineEnvs/nayana-ocr-env/resolve/main/server/layout.py
3.6 kB
| """Class-aware maximum-cardinality matching; mean F1 over IoU .50:.05:.95.""" | |
| import json | |
| import math | |
| from ..data.schema import LAYOUT_LABELS | |
| POLICY = "layout-mean-f1-iou-50-95-v1" | |
| def parse_regions(text, width=None, height=None): | |
| if len(text) > 65536: | |
| raise ValueError("Layout answer exceeds 65536 characters") | |
| def unique_object(pairs): | |
| if len(dict(pairs)) != len(pairs): | |
| raise ValueError("Duplicate JSON keys") | |
| return dict(pairs) | |
| value = json.loads(text, object_pairs_hook=unique_object) | |
| if not isinstance(value, list) or len(value) > 512: | |
| raise ValueError("Expected at most 512 regions") | |
| for region in value: | |
| if not isinstance(region, dict) or set(region) != {"label", "bbox"}: | |
| raise ValueError("Each region needs exactly label and bbox") | |
| if region["label"] not in LAYOUT_LABELS: | |
| raise ValueError("Unknown layout label") | |
| box = region["bbox"] | |
| if ( | |
| not isinstance(box, list) | |
| or len(box) != 4 | |
| or not all(type(v) in (int, float) and math.isfinite(v) for v in box) | |
| ): | |
| raise ValueError("Expected four finite numeric coordinates") | |
| x0, y0, x1, y1 = box | |
| if not (0 <= x0 < x1 and 0 <= y0 < y1): | |
| raise ValueError("Invalid box bounds") | |
| if width is not None and (x1 > width or y1 > height): | |
| raise ValueError("Box outside image") | |
| return value | |
| def iou(a, b): | |
| intersection = max(0, min(a[2], b[2]) - max(a[0], b[0])) * max( | |
| 0, min(a[3], b[3]) - max(a[1], b[1]) | |
| ) | |
| union = (a[2] - a[0]) * (a[3] - a[1]) + (b[2] - b[0]) * (b[3] - b[1]) - intersection | |
| return intersection / union | |
| def matched_count(overlaps, threshold): | |
| """Augmenting paths find the maximum number of one-to-one qualifying pairs.""" | |
| matches = {} | |
| def visit(predicted, seen): | |
| for target, overlap in enumerate(overlaps[predicted]): | |
| if overlap < threshold or target in seen: | |
| continue | |
| seen.add(target) | |
| if target not in matches or visit(matches[target], seen): | |
| matches[target] = predicted | |
| return True | |
| return False | |
| return sum(visit(p, set()) for p in range(len(overlaps))) | |
| def score_layout(prediction, reference, width=None, height=None): | |
| targets = parse_regions(reference, width, height) | |
| if not targets: | |
| raise ValueError("Layout reference is empty") | |
| try: | |
| predicted = parse_regions(prediction, width, height) | |
| except (ValueError, TypeError, RecursionError): | |
| return 0.0, {"valid_format": False, "mean_f1": 0.0, "exact_match": False} | |
| overlaps = [ | |
| [ | |
| iou(p["bbox"], t["bbox"]) if p["label"] == t["label"] else 0.0 | |
| for t in targets | |
| ] | |
| for p in predicted | |
| ] | |
| matches = [matched_count(overlaps, (50 + 5 * i) / 100) for i in range(10)] | |
| denominator = len(predicted) + len(targets) | |
| reward = sum(2 * count / denominator for count in matches) / 10 | |
| return reward, { | |
| "valid_format": True, | |
| "exact_match": sorted((r["label"], *r["bbox"]) for r in predicted) | |
| == sorted((r["label"], *r["bbox"]) for r in targets), | |
| "mean_f1": reward, | |
| "precision_at_50": matches[0] / len(predicted) if predicted else 0.0, | |
| "recall_at_50": matches[0] / len(targets), | |
| "f1_at_50": 2 * matches[0] / denominator, | |
| "f1_at_75": 2 * matches[5] / denominator, | |
| "predicted_regions": len(predicted), | |
| "reference_regions": len(targets), | |
| } | |