Download idp_model_labels.py from spark-ux/indic-doc-parser: direct link, hf CLI and curl.
- Browser
- Download file 5.72 kB
-
https://huggingface.co/spark-ux/indic-doc-parser/resolve/main/idp_model_labels.py
- Command line
-
hf download hf://spark-ux/indic-doc-parser/idp_model_labels.py
-
curl -L -o idp_model_labels.py https://huggingface.co/spark-ux/indic-doc-parser/resolve/main/idp_model_labels.py
5.72 kB
| # 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 | |