File size: 5,719 Bytes
7b2177e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | # GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/layout/labels.py -- do not edit.
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
# whole install. See indic_doc_parser.py for usage.
"""IndicDocLayout's 37 education-domain layout classes = SUPERSET of the printed + handwritten
taxonomies.
Order is fixed; do not reorder and only APPEND (ids are baked into checkpoints). ids 0-24 are the
original 25; 25-26 are the printed-only additions (Chapter-title, Chapter-end-section); 27-36 are
the indicdlp-v2 magazine/newspaper additions. Class weights are sqrt-inverse block frequency
(capped [0.5, 5]); optional, used only if enabled.
These are the raw *labels*. The map onto the coarse pipeline *types* that select prompts lives in
``ocr.templates.contract`` -- see its module docstring for why the two vocabularies differ.
"""
CLASSES = [
"Question",
"Paragraph",
"Answer",
"List",
"Title",
"Section-title",
"Equation",
"Table",
"Diagram",
"Image",
"MCQ",
"Infobox",
"Sub-section-title",
"Expression",
"Image-caption",
"Placeholder-text",
"Chart",
"Solved-example",
"Footnote",
"Table-caption",
"Sub-sub-section-title",
"Footer",
"Header",
"Code",
"Page-number",
"Chapter-title",
"Chapter-end-section", # 25-26: printed-only additions (superset)
# 27-36: indicdlp-v2 additions (magazine/newspaper furniture kept as their own classes;
# only populated by the indicdlp-printed source -> no cross-dataset partial-annotation conflict).
"Folio",
"Reference",
"Table-of-contents",
"Index",
"Advertisement",
"Author",
"Dateline",
"Contact-info",
"Website-link",
"Flag",
]
LABEL2ID = {c: i for i, c in enumerate(CLASSES)}
ID2LABEL = {i: c for i, c in enumerate(CLASSES)}
NUM_CLASSES = len(CLASSES)
# sqrt-inverse-frequency weights (measured on 155,922 blocks); rare classes upweighted.
CLASS_WEIGHTS = {
"Question": 0.50,
"Paragraph": 0.50,
"Equation": 0.50,
"Answer": 0.50,
"List": 0.50,
"Section-title": 0.50,
"Title": 0.50,
"Image": 0.50,
"Table": 0.52,
"Diagram": 0.53,
"MCQ": 0.74,
"Expression": 1.00,
"Image-caption": 1.11,
"Infobox": 1.43,
"Solved-example": 1.43,
"Placeholder-text": 1.48,
"Sub-section-title": 1.64,
"Chart": 2.11,
"Footnote": 4.63,
"Table-caption": 5.0,
"Sub-sub-section-title": 5.0,
"Footer": 5.0,
"Header": 5.0,
"Code": 5.0,
"Page-number": 5.0,
"Chapter-title": 0.74,
"Chapter-end-section": 4.5, # by frequency in label_frequency.csv
}
# --- Shared label parser (used by BOTH the training blob cache AND the val/test disk path,
# so train/val/test are always consistent). Content blocks + metadata Header/Footer region
# boxes (header reads first, footer last; placeholder stubs dropped; hw dedup vs content). ---
MIN_HF_AREA = 1e-4 # drop placeholder header/footer stubs (e.g. bbox [0,0,5,10])
def _to_cxcywh(bb):
"""[y0,x0,y1,x1] in 0-1000 -> clamped [cx,cy,w,h] normalized, or None if degenerate."""
try:
y0, x0, y1, x1 = [float(c) / 1000 for c in bb]
except (TypeError, ValueError):
return None # malformed bbox (e.g. nested list) -> skip this box
if x1 - x0 <= 1e-3 or y1 - y0 <= 1e-3:
return None
cx, cy, w, h = (x0 + x1) / 2, (y0 + y1) / 2, x1 - x0, y1 - y0
cx, cy = min(max(cx, 1e-4), 1 - 1e-4), min(max(cy, 1e-4), 1 - 1e-4)
w, h = min(w, 2 * min(cx, 1 - cx)), min(h, 2 * min(cy, 1 - cy))
return [cx, cy, w, h]
def _iou(a, b):
iw = max(0, min(a[0] + a[2] / 2, b[0] + b[2] / 2) - max(a[0] - a[2] / 2, b[0] - b[2] / 2))
ih = max(0, min(a[1] + a[3] / 2, b[1] + b[3] / 2) - max(a[1] - a[3] / 2, b[1] - b[3] / 2))
inter = iw * ih
ua = a[2] * a[3] + b[2] * b[3] - inter
return inter / ua if ua > 0 else 0.0
def labels_from_doc(d, drop_hf_strips=False):
"""Parse a page JSON dict -> (boxes[cxcywh], cls[ids], order). THE canonical parser.
drop_hf_strips: skip metadata Header/Footer boxes that are full-width page-edge STRIPS (the
hw-vs/hw-dps placeholders like [0,0,100,1000]) — they're not real text regions and teach the
model to hallucinate top/bottom bars. Real (non-strip) header/footer regions are kept."""
cls, boxes, order = [], [], []
for b in d.get("content", []):
bb, lab = b.get("bbox"), b.get("label")
if not (isinstance(bb, list) and len(bb) == 4) or lab not in LABEL2ID:
continue
box = _to_cxcywh(bb)
if box is None:
continue
cls.append(LABEL2ID[lab])
boxes.append(box)
order.append(b.get("reading_order", len(order) + 1))
meta = d.get("metadata", {}) or {}
ords = order or [0]
for field, lab, ro in (
("header", "Header", min(ords) - 1),
("footer", "Footer", max(ords) + 1),
):
bb = (meta.get(field) or {}).get("bbox")
if not (isinstance(bb, list) and len(bb) == 4):
continue
box = _to_cxcywh(bb)
if box is None or box[2] * box[3] < MIN_HF_AREA:
continue
if drop_hf_strips and box[2] > 0.9: # full-width strip flush to a page edge = placeholder
y0, y1 = box[1] - box[3] / 2, box[1] + box[3] / 2
if (field == "header" and y0 < 0.02) or (field == "footer" and y1 > 0.98):
continue
lid = LABEL2ID[lab]
if any(cls[i] == lid and _iou(boxes[i], box) > 0.5 for i in range(len(boxes))):
continue
cls.append(lid)
boxes.append(box)
order.append(ro)
return boxes, cls, order
|