Copy from bodhan-ai/indic-doc-parser
Browse files- .gitattributes +23 -0
- ARCHITECTURE.md +95 -0
- README.md +359 -0
- TROUBLESHOOTING.md +41 -0
- assets/cand-hindi-maths-g10-6pr6eq-7dcd5d95-p20.png +3 -0
- assets/diagram.png +3 -0
- assets/gallery-1-english-math-ramanujan.png +3 -0
- assets/gallery-2-telugu-novel.png +3 -0
- idp_blocks.py +139 -0
- idp_contract.py +128 -0
- idp_crops.py +65 -0
- idp_layout.py +124 -0
- idp_model_infer.py +93 -0
- idp_model_labels.py +159 -0
- idp_model_order_loss.py +47 -0
- idp_model_ppdoc.py +129 -0
- idp_offline.py +177 -0
- idp_recognizer.py +146 -0
- idp_reconstruct.py +112 -0
- idp_types.py +246 -0
- indic_doc_parser.py +147 -0
- install.sh +68 -0
- requirements.txt +19 -0
- schemas/layout_output.schema.json +148 -0
- schemas/parse_output.schema.json +148 -0
- weights/layout/config.json +292 -0
- weights/layout/model.safetensors +3 -0
- weights/ocr/chat_template.jinja +154 -0
- weights/ocr/config.json +149 -0
- weights/ocr/generation_config.json +6 -0
- weights/ocr/model-00001-of-00001.safetensors +3 -0
- weights/ocr/model.safetensors.index.json +480 -0
- weights/ocr/processor_config.json +60 -0
- weights/ocr/tokenizer.json +3 -0
- weights/ocr/tokenizer_config.json +40 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,26 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
weights/ocr/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
assets/layout-bengali.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
assets/layout-english-thumb.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
assets/layout-english.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
assets/layout-hindi-thumb.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
assets/layout-hindi.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
assets/layout-santali-thumb.png filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
assets/layout-santali.png filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
assets/layout-telugu.png filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
assets/layout-bengali-thumb.png filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
assets/layout-urdu-thumb.png filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
assets/layout-urdu.png filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
assets/layout-hw-english-thumb.png filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
assets/layout-hw-english.png filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
assets/layout-hw-hindi-thumb.png filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
assets/layout-hw-hindi.png filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
assets/example-ramanujan-thumb.png filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
assets/example-ramanujan.png filter=lfs diff=lfs merge=lfs -text
|
| 54 |
+
assets/layout-telugu-thumb.png filter=lfs diff=lfs merge=lfs -text
|
| 55 |
+
assets/diagram.png filter=lfs diff=lfs merge=lfs -text
|
| 56 |
+
assets/cand-hindi-maths-g10-6pr6eq-7dcd5d95-p20.png filter=lfs diff=lfs merge=lfs -text
|
| 57 |
+
assets/gallery-1-english-math-ramanujan.png filter=lfs diff=lfs merge=lfs -text
|
| 58 |
+
assets/gallery-2-telugu-novel.png filter=lfs diff=lfs merge=lfs -text
|
ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architecture
|
| 2 |
+
|
| 3 |
+
Two models with a JSON file between them.
|
| 4 |
+
|
| 5 |
+
```text
|
| 6 |
+
page image ──> IndicDocLayout ──> layout JSON ──> IndicBlockOCR ──> page JSON + markdown
|
| 7 |
+
```
|
| 8 |
+
|
| 9 |
+
`IndicDocLayout` finds the blocks and orders them. `IndicBlockOCR` transcribes one block crop
|
| 10 |
+
per request. Everything between the two models (cropping, prompt selection, reassembly) is this
|
| 11 |
+
package.
|
| 12 |
+
|
| 13 |
+
## The call path
|
| 14 |
+
|
| 15 |
+
One page, top to bottom, with the file that owns each step.
|
| 16 |
+
|
| 17 |
+
```text
|
| 18 |
+
IndicDocParser.parse(image) idp_offline.py
|
| 19 |
+
|
| 20 |
+
IndicDocLayout.detect(image) idp_offline.py
|
| 21 |
+
_open image -> PIL RGB idp_offline.py
|
| 22 |
+
IndicDocLayoutBackend.detect idp_layout.py
|
| 23 |
+
infer -> [y0,x0,y1,x1] at 0-1000 idp_model_infer.py
|
| 24 |
+
convert + clamp_to_page -> pixel [x0,y0,x1,y1] idp_layout.py, blocks.py
|
| 25 |
+
clean_layout drop duplicate boxes idp_blocks.py
|
| 26 |
+
_densify order -> gap-free 0..n-1 idp_layout.py
|
| 27 |
+
= PageResult, every block has text None idp_types.py
|
| 28 |
+
|
| 29 |
+
IndicBlockOCR.run(image, layout) idp_offline.py
|
| 30 |
+
_as_page dict -> PageResult, validated
|
| 31 |
+
is_transcribed drop OCR_SKIP_LABELS idp_contract.py
|
| 32 |
+
resolve_nested_equations drop nested equations idp_blocks.py
|
| 33 |
+
build_requests -> [CropRequest] idp_recognizer.py
|
| 34 |
+
crop_for + area_clamp block -> image idp_crops.py
|
| 35 |
+
prompt_for(block.type) type -> prompt idp_contract.py
|
| 36 |
+
backend.transcribe [CropRequest] -> [str] a recognizer backend
|
| 37 |
+
match by order texts -> blocks idp_offline.py
|
| 38 |
+
reconstruct blocks -> markdown idp_reconstruct.py
|
| 39 |
+
= PageResult, every block has text
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
Blocks that were skipped are not deleted. They come back with `text: ""`.
|
| 43 |
+
|
| 44 |
+
## Files by role
|
| 45 |
+
|
| 46 |
+
**Contract and data.** The vocabulary everything else shares.
|
| 47 |
+
|
| 48 |
+
| file | holds |
|
| 49 |
+
| --- | --- |
|
| 50 |
+
| `idp_contract.py` | prompts, the label to type map, which labels are skipped |
|
| 51 |
+
| `idp_types.py` | `Block`, `PageResult`, and every tunable config |
|
| 52 |
+
|
| 53 |
+
**The layout model.** Torch lives here and nowhere else.
|
| 54 |
+
|
| 55 |
+
| file | holds |
|
| 56 |
+
| --- | --- |
|
| 57 |
+
| `idp_model_infer.py` | preprocessing and decode for one page |
|
| 58 |
+
| `idp_model_labels.py` | the 37 classes, pure Python |
|
| 59 |
+
| `idp_model_ppdoc.py` | the PP-DocLayoutV3 subclass |
|
| 60 |
+
| `idp_model_order_loss.py` | reading-order decode, training loss |
|
| 61 |
+
|
| 62 |
+
**Geometry and crops.** No model, no network.
|
| 63 |
+
|
| 64 |
+
| file | holds |
|
| 65 |
+
| --- | --- |
|
| 66 |
+
| `idp_blocks.py` | box math, `clean_layout`, nested-equation dedup |
|
| 67 |
+
| `idp_crops.py` | `crop_for` and `area_clamp` |
|
| 68 |
+
|
| 69 |
+
**Backends.** Two Protocols, five implementations, one per deployment.
|
| 70 |
+
|
| 71 |
+
| class | file | used by |
|
| 72 |
+
| --- | --- | --- |
|
| 73 |
+
| `IndicDocLayoutBackend` | `idp_layout.py` | the real detector |
|
| 74 |
+
| `JsonLayoutBackend` | `idp_layout.py` | replaying a layout file, no torch |
|
| 75 |
+
| `HfRecognizer` | `idp_recognizer.py` | plain transformers, the Hub package |
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
**Orchestration.**
|
| 79 |
+
|
| 80 |
+
| file | holds |
|
| 81 |
+
| --- | --- |
|
| 82 |
+
| `idp_offline.py` | `IndicDocLayout`, `IndicBlockOCR`, `IndicDocParser` |
|
| 83 |
+
| `idp_recognizer.py` | `CropRequest`, `build_requests`, the recognizer Protocol |
|
| 84 |
+
| `idp_reconstruct.py` | blocks to markdown, math and hyphen repair |
|
| 85 |
+
|
| 86 |
+
## Invariants
|
| 87 |
+
|
| 88 |
+
1. **Two box conventions.** The model emits `[y0, x0, y1, x1]` normalised to 0-1000. The
|
| 89 |
+
pipeline uses pixel `[x0, y0, x1, y1]`.
|
| 90 |
+
2. **`order` must be gap-free and 0-based.** Transcriptions are matched back to blocks by
|
| 91 |
+
position, so a gap or a duplicate moves text onto the wrong block.
|
| 92 |
+
3. **The crop clamp is on area, not on a side.** Pinning a side explodes elongated crops. See
|
| 93 |
+
`CropConfig` in `idp_types.py`.
|
| 94 |
+
4. **`type` selects the prompt.** A wrong `type` changes what the model was asked to do, not
|
| 95 |
+
just how a block is labelled. Unrecognised labels are rejected for this reason.
|
README.md
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
language:
|
| 3 |
+
- en
|
| 4 |
+
- as
|
| 5 |
+
- bn
|
| 6 |
+
- brx
|
| 7 |
+
- doi
|
| 8 |
+
- gu
|
| 9 |
+
- hi
|
| 10 |
+
- kn
|
| 11 |
+
- ks
|
| 12 |
+
- kok
|
| 13 |
+
- mai
|
| 14 |
+
- ml
|
| 15 |
+
- mni
|
| 16 |
+
- mr
|
| 17 |
+
- ne
|
| 18 |
+
- or
|
| 19 |
+
- pa
|
| 20 |
+
- sa
|
| 21 |
+
- sat
|
| 22 |
+
- sd
|
| 23 |
+
- ta
|
| 24 |
+
- te
|
| 25 |
+
- ur
|
| 26 |
+
pipeline_tag: image-to-text
|
| 27 |
+
tags:
|
| 28 |
+
- ocr
|
| 29 |
+
- document-parsing
|
| 30 |
+
- layout-analysis
|
| 31 |
+
- reading-order
|
| 32 |
+
- indic
|
| 33 |
+
- vision-language-model
|
| 34 |
+
- qwen
|
| 35 |
+
- rt-detr
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
<div align="center">
|
| 40 |
+
|
| 41 |
+
[](#the-two-models)
|
| 42 |
+
[](#the-two-models)
|
| 43 |
+
[](#the-two-models)
|
| 44 |
+
[](#supported-languages)
|
| 45 |
+
[](#license)
|
| 46 |
+
|
| 47 |
+
</div>
|
| 48 |
+
|
| 49 |
+
**Document parsing for English and 22 Indian languages, printed and handwritten.** A page image
|
| 50 |
+
in; reading-ordered Markdown out, with math as LaTeX and tables as HTML or Markdown, plus
|
| 51 |
+
per-block JSON.
|
| 52 |
+
|
| 53 |
+
<div align="center">
|
| 54 |
+
|
| 55 |
+
<img src="assets/diagram.png" alt="IndicDocParser: page image to layout detection with reading order, then block-level OCR, then Markdown" width="100%">
|
| 56 |
+
|
| 57 |
+
</div>
|
| 58 |
+
|
| 59 |
+
IndicDocParser reads a document page and returns its text in reading order. It is a modular,
|
| 60 |
+
two-stage parser: **IndicDocLayout** detects the blocks on the page and orders them, and
|
| 61 |
+
**IndicBlockOCR** transcribes the textual blocks. The two stages communicate through a structured
|
| 62 |
+
JSON file, so either stage can be used independently or replaced with another implementation.
|
| 63 |
+
|
| 64 |
+
[`ARCHITECTURE.md`](ARCHITECTURE.md) traces one page through the whole call path, names what
|
| 65 |
+
each module does, and lists the invariants that break the output silently when violated.
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
<h2 id="examples" style="color:#F97316;">Examples</h2>
|
| 70 |
+
|
| 71 |
+
Detected blocks with their reading order on the left, the transcription on the right.
|
| 72 |
+
|
| 73 |
+
<div align="center">
|
| 74 |
+
<img src="assets/gallery-1-english-math-ramanujan.png" alt="A page from Ramanujan's notebooks: text and display equations detected in reading order, with the transcription rendering the mathematics as LaTeX" width="100%">
|
| 75 |
+
<p><b>Example #1. English page with dense mathematics.</b></p>
|
| 76 |
+
</div>
|
| 77 |
+
|
| 78 |
+
<div align="center">
|
| 79 |
+
<img src="assets/gallery-2-telugu-novel.png" alt="A printed Telugu novel page: paragraph blocks and a page number detected and numbered in reading order, with the Telugu transcription beside it" width="100%">
|
| 80 |
+
<p><b>Example #2. Printed Telugu page.</b></p>
|
| 81 |
+
</div>
|
| 82 |
+
|
| 83 |
+
<div align="center">
|
| 84 |
+
<img src="assets/cand-hindi-maths-g10-6pr6eq-7dcd5d95-p20.png" alt="A handwritten Hindi maths exercise on ruled paper: alternating Equation and Paragraph blocks detected in reading order, with the transcription rendering the algebra as LaTeX" width="100%">
|
| 85 |
+
<p><b>Example #3. Handwritten Hindi maths.</b></p>
|
| 86 |
+
</div>
|
| 87 |
+
|
| 88 |
+
---
|
| 89 |
+
|
| 90 |
+
<h2 id="model-summary" style="color:#F97316;">Model Summary</h2>
|
| 91 |
+
|
| 92 |
+
| | IndicDocLayout | IndicBlockOCR |
|
| 93 |
+
| --- | --- | --- |
|
| 94 |
+
| **Role** | Layout detection + reading order | Block-level text recognition |
|
| 95 |
+
| **Architecture** | PP-DocLayoutV3 / RT-DETR | Qwen3.5-0.8B |
|
| 96 |
+
| **Parameters** | 33 M | 0.8 B |
|
| 97 |
+
| **Precision** | fp32 | bf16 |
|
| 98 |
+
| **In this repo** | `weights/layout` (133 MB) | `weights/ocr` (1.7 GB) |
|
| 99 |
+
| **Output** | Layout JSON | Markdown + block JSON |
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
IndicBlockOCR uses the **Sarvam-30B tokenizer**, with a vocabulary designed to cover Indian
|
| 103 |
+
scripts. IndicDocLayout is a fine-tune of PP-DocLayoutV3/RT-DETR, trained with a 37-class
|
| 104 |
+
taxonomy designed for education-domain documents.
|
| 105 |
+
|
| 106 |
+
IndicDocLayout predicts a labelled bounding box for each detected layout element.
|
| 107 |
+
The 37 supported labels are:
|
| 108 |
+
|
| 109 |
+
> Advertisement, Answer, Author, Chapter-end-section, Chapter-title, Chart, Code, Contact-info, Dateline, Diagram, Equation, Expression, Flag, Folio, Footer, Footnote, Header, Image, Image-caption, Index, Infobox, List, MCQ, Page-number, Paragraph, Placeholder-text, Question, Reference, Section-title, Solved-example, Sub-section-title, Sub-sub-section-title, Table, Table-caption, Table-of-contents, Title, Website-link
|
| 110 |
+
|
| 111 |
+
---
|
| 112 |
+
|
| 113 |
+
<h2 id="supported-languages" style="color:#F97316;">Supported languages</h2>
|
| 114 |
+
|
| 115 |
+
**Printed** page recognition is supported across English and the 22 constitutionally recognised Indian languages: Assamese, Bengali, Bodo, Dogri,
|
| 116 |
+
Gujarati, Hindi, Kannada, Kashmiri, Konkani, Maithili, Malayalam, Manipuri, Marathi, Nepali,
|
| 117 |
+
Odia, Punjabi, Sanskrit, Santali, Sindhi, Tamil, Telugu, Urdu.
|
| 118 |
+
|
| 119 |
+
**Handwriting** recognition currently supports English and 12 Indian languages: Hindi, Bengali,
|
| 120 |
+
Telugu, Marathi, Tamil, Gujarati, Kannada, Malayalam, Odia, Punjabi, Assamese, and Urdu.
|
| 121 |
+
|
| 122 |
+
Handwriting quality is still a work in progress, particularly across different writing styles. We are working on improving recognition and extending support to additional languages.
|
| 123 |
+
|
| 124 |
+
---
|
| 125 |
+
|
| 126 |
+
<h2 id="usage" style="color:#F97316;">Usage</h2>
|
| 127 |
+
|
| 128 |
+
<h3 style="color:#F97316;">Installation</h3>
|
| 129 |
+
|
| 130 |
+
The repo ships an installer that reads your driver and picks matching CUDA wheels. If you work in a
|
| 131 |
+
virtual environment, please activate it first, as the installer installs into whichever
|
| 132 |
+
Python is active.
|
| 133 |
+
|
| 134 |
+
```bash
|
| 135 |
+
IDP=$(python -c "from huggingface_hub import snapshot_download as d; print(d('bodhan-ai/indic-doc-parser'))")
|
| 136 |
+
cd "$IDP" && ./install.sh
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
It will use `uv` if that is available, and `pip` otherwise. Where running a shell script is not
|
| 140 |
+
convenient, [TROUBLESHOOTING.md](TROUBLESHOOTING.md) lists the two commands it runs.
|
| 141 |
+
|
| 142 |
+
<h3 style="color:#F97316;">Basic inference</h3>
|
| 143 |
+
|
| 144 |
+
```python
|
| 145 |
+
import sys
|
| 146 |
+
from huggingface_hub import snapshot_download
|
| 147 |
+
|
| 148 |
+
repo = snapshot_download("bodhan-ai/indic-doc-parser")
|
| 149 |
+
sys.path.insert(0, repo) # the code ships in the repo
|
| 150 |
+
from indic_doc_parser import IndicDocParser
|
| 151 |
+
|
| 152 |
+
parser = IndicDocParser.from_pretrained(repo)
|
| 153 |
+
|
| 154 |
+
page = parser.parse("page.png")
|
| 155 |
+
print(page["markdown"]) # reading-ordered Markdown
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
`page` also carries the per-block detail, which you can save as follows:
|
| 159 |
+
|
| 160 |
+
```python
|
| 161 |
+
import json
|
| 162 |
+
|
| 163 |
+
with open("page.json", "w", encoding="utf-8") as f:
|
| 164 |
+
json.dump(page, f, ensure_ascii=False, indent=2)
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
<h3 style="color:#F97316;">Running one stage at a time</h3>
|
| 168 |
+
|
| 169 |
+
To run the two stages separately:
|
| 170 |
+
|
| 171 |
+
```python
|
| 172 |
+
from indic_doc_parser import IndicDocLayout, IndicBlockOCR
|
| 173 |
+
|
| 174 |
+
layout = IndicDocLayout(f"{repo}/weights/layout").detect("page.png")
|
| 175 |
+
page = IndicBlockOCR(f"{repo}/weights/ocr").run("page.png", layout)
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
`run()` takes a layout object, a dict, or the path to a layout JSON file.
|
| 179 |
+
|
| 180 |
+
---
|
| 181 |
+
|
| 182 |
+
<h2 id="output" style="color:#F97316;">Output</h2>
|
| 183 |
+
|
| 184 |
+
`parser.parse("page.png")` returns the page metadata and its blocks in reading order:
|
| 185 |
+
|
| 186 |
+
```json
|
| 187 |
+
{
|
| 188 |
+
"image": "sample1.png",
|
| 189 |
+
"width": 800,
|
| 190 |
+
"height": 1273,
|
| 191 |
+
"blocks": [
|
| 192 |
+
{"order": 0, "label": "Header", "type": "PageHeader",
|
| 193 |
+
"bbox_xyxy": [345.6, 51.7, 437.1, 114.7], "conf": 0.6, "text": ""},
|
| 194 |
+
{"order": 1, "label": "Page-number", "type": "PageNumber",
|
| 195 |
+
"bbox_xyxy": [367.8, 78.9, 413.2, 107.4], "conf": 0.747, "text": "229"},
|
| 196 |
+
{"order": 2, "label": "Paragraph", "type": "Text",
|
| 197 |
+
"bbox_xyxy": [77.9, 121.3, 711.8, 199.4], "conf": 0.863,
|
| 198 |
+
"text": "Thus we see that, if we can prove that twice the L.H.S. of (30) ..."}
|
| 199 |
+
]
|
| 200 |
+
}
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
| field | meaning |
|
| 204 |
+
| --- | --- |
|
| 205 |
+
| `order` | reading-order rank, 0-based and gap-free |
|
| 206 |
+
| `label` | the raw IndicDocLayout class (37-class taxonomy) |
|
| 207 |
+
| `type` | coarse pipeline category: `Text`, `Table`, `Equation`, `Title`, ... |
|
| 208 |
+
| `bbox_xyxy` | pixel box `[x0, y0, x1, y1]` |
|
| 209 |
+
| `conf` | detection confidence |
|
| 210 |
+
| `text` | transcription; `""` for blocks not sent to the recognizer |
|
| 211 |
+
|
| 212 |
+
**Note:** Figures, charts, advertisements, running headers, and footers are not sent through the recognizer
|
| 213 |
+
by default. They remain in the JSON with `text: ""`, so you can see what was detected and where.
|
| 214 |
+
Page numbers and other margin text such as folios are transcribed.
|
| 215 |
+
|
| 216 |
+
<h3 style="color:#F97316;">Schemas</h3>
|
| 217 |
+
|
| 218 |
+
Machine-readable JSON Schema for each envelope, in [`schemas/`](schemas):
|
| 219 |
+
|
| 220 |
+
| file | describes |
|
| 221 |
+
| --- | --- |
|
| 222 |
+
| `layout_output.schema.json` | The layout file: what **IndicDocLayout** writes and **IndicBlockOCR** reads. Blocks and reading order, before any text is read, so there is **no** `text` key at all. |
|
| 223 |
+
| `parse_output.schema.json` | The parsed page shown above. Every block now has `text`; `""` means the block was detected but deliberately not sent to the recognizer. |
|
| 224 |
+
|
| 225 |
+
A layout from your own detector must use a `label` from the 37-class taxonomy, or declare `type`
|
| 226 |
+
explicitly. An unrecognised label is rejected rather than silently read as prose.
|
| 227 |
+
|
| 228 |
+
<h3 style="color:#F97316;">Table format</h3>
|
| 229 |
+
|
| 230 |
+
Tables come back as HTML by default. Choose the format when you construct the parser:
|
| 231 |
+
|
| 232 |
+
```python
|
| 233 |
+
parser = IndicDocParser.from_pretrained(repo) # HTML (default)
|
| 234 |
+
parser = IndicDocParser.from_pretrained(repo, table_format="markdown") # Markdown
|
| 235 |
+
```
|
| 236 |
+
|
| 237 |
+
---
|
| 238 |
+
|
| 239 |
+
<h2 id="performance" style="color:#F97316;">Performance</h2>
|
| 240 |
+
|
| 241 |
+
<h3 style="color:#F97316;">OmniDocBench 1.6 (english subset)</h3>
|
| 242 |
+
|
| 243 |
+
| OmniDocBench 1.6 (english subset) | Overall↑ | TextEdit↓ | FormulaCDM↑ | TableTEDS↑ | TableTEDS-S↑ | Read OrderEdit↓ |
|
| 244 |
+
| --- | :---: | :---: | :---: | :---: | :---: | :---: |
|
| 245 |
+
| PaddleOCRVL-1.6 | 96.36 | 0.03 | 98.55 | 93.37 | 96.33 | 0.09 |
|
| 246 |
+
| Chandra OCR 2 | 93.11 | 0.04 | 96.93 | 86.07 | 90.34 | 0.09 |
|
| 247 |
+
| **IndicOCR (ours)** | **92.76** | **0.04** | **97.53** | **85.10** | **90.58** | **0.11** |
|
| 248 |
+
| GPT-5.6-sol | 92.46 | 0.04 | 95.42 | 85.87 | 90.98 | 0.10 |
|
| 249 |
+
| Gemini 3.1 Pro | 91.15 | 0.06 | 95.53 | 83.46 | 88.77 | 0.13 |
|
| 250 |
+
| Surya OCR 2 (model) | 91.13 | 0.04 | 95.67 | 81.61 | 86.37 | 0.10 |
|
| 251 |
+
| Sarvam Vision | 90.08 | 0.04 | 97.62 | 76.82 | 82.01 | 0.10 |
|
| 252 |
+
| Gemma 31B | 86.71 | 0.09 | 89.48 | 79.79 | 85.19 | 0.19 |
|
| 253 |
+
| Nemotron Parse 2 | 79.12 | 0.159 | 78.94 | 74.32 | 81.09 | 0.29 |
|
| 254 |
+
|
| 255 |
+
<h3 style="color:#F97316;">olmOCR-Bench (<a href="https://huggingface.co/datasets/sarvamai/olmOCR-Bench-English" style="color:#F97316;">english subset</a>)</h3>
|
| 256 |
+
|
| 257 |
+
| OlmoOCRBench ([english subset](https://huggingface.co/datasets/sarvamai/olmOCR-Bench-English)) | Overall↑ | arxiv_math↑ | baseline↑ | headers_footers↑ | long_tiny_text↑ | multi_column↑ | old_scans↑ | old_scans_math↑ | table_tests↑ |
|
| 258 |
+
| --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|
| 259 |
+
| Chandra OCR 2 | 85.9 | 86.7 | 99.8 | 91.5 | 93.7 | 84.7 | 51 | 88.2 | 92.2 |
|
| 260 |
+
| Sarvam Vision | 84.3 | 86.5 | 99.6 | 96.3 | 91 | 82.2 | 49.8 | 81 | 88.3 |
|
| 261 |
+
| Gemini 3.1 Pro | 82.6 | 90.5 | 99 | 82.9 | 88.5 | 81.6 | 47 | 84.3 | 87.3 |
|
| 262 |
+
| **IndicOCR (ours)** | **82.2** | **83.2** | **99.4** | **92.9** | **89.8** | **76** | **48.3** | **77.7** | **90** |
|
| 263 |
+
| Surya OCR 2 (model) | 81.4 | 82.5 | 99.8 | 92.9 | 79.9 | 85.1 | 42.8 | 84.3 | 84.2 |
|
| 264 |
+
| Gemma 31B | 80.4 | 79 | 99.4 | 92.9 | 89.8 | 80.5 | 45.8 | 73.8 | 82.2 |
|
| 265 |
+
| PaddleOCRVL-1.6 | 78.7 | 85.1 | 98.4 | 96.2 | 75.3 | 83.9 | 39 | 68.3 | 83 |
|
| 266 |
+
| GPT | 78 | 79.3 | 93.9 | 95.4 | 87.8 | 77.4 | 43.7 | 64.6 | 82.2 |
|
| 267 |
+
| Nemotron Parse 2 | 68.2 | 64 | 96.7 | 90 | 79.6 | 72.8 | 31.9 | 28.6 | 81.8 |
|
| 268 |
+
|
| 269 |
+
<h3 style="color:#F97316;">IndicOCR-PR: printed accuracy by language (higher is better)</h3>
|
| 270 |
+
|
| 271 |
+
Word-level accuracy, reported as 100 x (1 - WER).
|
| 272 |
+
|
| 273 |
+
| Language | Sarvam Vision | **IndicOCR (ours)** | Gemini 3.1 Pro | SuryaOCR | Gemma 31B | Chandra OCR 2 |
|
| 274 |
+
| --- | :---: | :---: | :---: | :---: | :---: | :---: |
|
| 275 |
+
| **Overall** | 86.6 | **86.2** | 80.4 | 67.9 | 66.3 | 64.2 |
|
| 276 |
+
| Assamese | 89.5 | 90.2 | 90.7 | 86.4 | 70.6 | 73.5 |
|
| 277 |
+
| Bodo | 91.0 | 86.5 | 91.0 | 55.6 | 68.1 | 46.6 |
|
| 278 |
+
| Bengali | 91.6 | 91.4 | 92.5 | 81.1 | 83.9 | 79.2 |
|
| 279 |
+
| Dogri | 85.8 | 81.7 | 83.7 | 60.5 | 64.4 | 55.8 |
|
| 280 |
+
| English | 96.6 | 97.0 | 97.7 | 93.8 | 97.2 | 91.3 |
|
| 281 |
+
| Gujarati | 91.6 | 91.7 | 92.8 | 79.6 | 81.6 | 73.0 |
|
| 282 |
+
| Hindi | 95.7 | 96.0 | 96.3 | 90.3 | 93.7 | 89.3 |
|
| 283 |
+
| Konkani | 93.6 | 93.7 | 93.5 | 90.5 | 76.9 | 85.5 |
|
| 284 |
+
| Kannada | 88.8 | 88.0 | 89.8 | 75.7 | 68.3 | 69.6 |
|
| 285 |
+
| Kashmiri | 43.3 | 52.2 | 38.1 | 23.4 | 19.9 | 17.6 |
|
| 286 |
+
| Malayalam | 90.6 | 89.9 | 90.6 | 76.5 | 72.0 | 68.3 |
|
| 287 |
+
| Manipuri | 81.9 | 83.8 | 0.8 | 0.1 | 0.1 | 0.0 |
|
| 288 |
+
| Marathi | 93.9 | 93.5 | 94.5 | 84.3 | 89.1 | 83.1 |
|
| 289 |
+
| Maithili | 86.7 | 83.0 | 86.7 | 67.6 | 76.3 | 66.1 |
|
| 290 |
+
| Nepali | 92.5 | 91.5 | 93.7 | 87.6 | 87.2 | 82.1 |
|
| 291 |
+
| Odia | 77.5 | 75.7 | 84.8 | 64.5 | 38.7 | 62.6 |
|
| 292 |
+
| Punjabi | 92.2 | 93.2 | 93.5 | 86.3 | 75.1 | 84.1 |
|
| 293 |
+
| Sanskrit | 82.0 | 76.2 | 83.7 | 57.8 | 60.8 | 55.8 |
|
| 294 |
+
| Sindhi | 89.2 | 87.1 | 86.3 | 80.5 | 74.5 | 71.4 |
|
| 295 |
+
| Santhali | 71.9 | 74.7 | 0.2 | 0.1 | 0.2 | 0.0 |
|
| 296 |
+
| Tamil | 94.2 | 91.3 | 94.4 | 79.9 | 83.3 | 79.0 |
|
| 297 |
+
| Telugu | 84.3 | 82.3 | 85.5 | 63.1 | 66.6 | 59.6 |
|
| 298 |
+
| Urdu | 87.1 | 85.9 | 88.0 | 76.4 | 76.6 | 74.4 |
|
| 299 |
+
|
| 300 |
+
<h3 style="color:#F97316;">IndicOCR-HW: handwriting accuracy by language (higher is better)</h3>
|
| 301 |
+
|
| 302 |
+
Word-level accuracy, reported as 100 x (1 - WER).
|
| 303 |
+
|
| 304 |
+
| Language | Gemini 3.1 Pro | **IndicOCR (ours)** | Sarvam Vision | Gemma 31B | Chandra OCR 2 | SuryaOCR |
|
| 305 |
+
| --- | :---: | :---: | :---: | :---: | :---: | :---: |
|
| 306 |
+
| **Overall** | 72.0 | **66.7** | 55.4 | 33.9 | 24.7 | 23.0 |
|
| 307 |
+
| Assamese | 71.6 | 66.1 | 47.8 | 24.1 | 8.9 | 17.8 |
|
| 308 |
+
| Bengali | 74.8 | 71.3 | 58.3 | 35.1 | 6.6 | 10.0 |
|
| 309 |
+
| English | 84.4 | 80.7 | 77.7 | 78.5 | 78.2 | 72.7 |
|
| 310 |
+
| Gujarati | 60.0 | 55.9 | 39.2 | 23.7 | 11.8 | 11.5 |
|
| 311 |
+
| Hindi | 83.1 | 77.6 | 72.3 | 70.7 | 54.6 | 42.7 |
|
| 312 |
+
| Kannada | 73.8 | 69.6 | 57.7 | 17.2 | 11.5 | 13.2 |
|
| 313 |
+
| Malayalam | 63.9 | 60.5 | 45.6 | 16.0 | 15.7 | 11.8 |
|
| 314 |
+
| Marathi | 79.0 | 70.2 | 61.8 | 56.5 | 35.4 | 28.8 |
|
| 315 |
+
| Odia | 66.7 | 68.2 | 40.6 | 15.5 | 19.4 | 19.9 |
|
| 316 |
+
| Punjabi | 70.1 | 69.4 | 54.8 | 11.7 | 11.5 | 15.7 |
|
| 317 |
+
| Tamil | 80.5 | 76.8 | 60.5 | 33.4 | 18.8 | 16.8 |
|
| 318 |
+
| Telugu | 72.0 | 53.5 | 59.1 | 32.0 | 20.8 | 14.6 |
|
| 319 |
+
| Urdu | 54.4 | 46.4 | 44.4 | 25.6 | 27.6 | 22.6 |
|
| 320 |
+
|
| 321 |
+
---
|
| 322 |
+
|
| 323 |
+
<h2 id="limitations" style="color:#F97316;">Limitations</h2>
|
| 324 |
+
|
| 325 |
+
Reading order remains a challenge for **complex, multi-column layouts**. Handwriting recognition
|
| 326 |
+
is also still being improved, particularly across different writing styles and writing
|
| 327 |
+
characteristics.
|
| 328 |
+
|
| 329 |
+
We are also extending handwriting support to additional Indic languages.
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
---
|
| 333 |
+
|
| 334 |
+
<h2 id="hardware" style="color:#F97316;">Hardware</h2>
|
| 335 |
+
|
| 336 |
+
Latency and throughput numbers to follow.
|
| 337 |
+
|
| 338 |
+
---
|
| 339 |
+
|
| 340 |
+
<h2 id="license" style="color:#F97316;">License</h2>
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
Released under [Bodhan Open License 1.0]().
|
| 344 |
+
|
| 345 |
+
The release incorporates components distributed under Apache 2.0, including PP-DocLayoutV3,
|
| 346 |
+
Qwen3.5, and the Sarvam-30B tokenizer. See the repository license and the corresponding upstream
|
| 347 |
+
licenses for the applicable terms and attribution requirements.
|
| 348 |
+
---
|
| 349 |
+
|
| 350 |
+
<h2 id="citation" style="color:#F97316;">Citation</h2>
|
| 351 |
+
|
| 352 |
+
```bibtex
|
| 353 |
+
@misc{indicdocparser2026,
|
| 354 |
+
title = {IndicDocParser: Multilingual Document Parsing for English and 22 Indian Languages},
|
| 355 |
+
author = {Bodhan.AI},
|
| 356 |
+
year = {2026},
|
| 357 |
+
url = {https://huggingface.co/bodhan-ai/indic-doc-parser}
|
| 358 |
+
}
|
| 359 |
+
```
|
TROUBLESHOOTING.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# IndicDocParser: troubleshooting
|
| 2 |
+
|
| 3 |
+
Install and runtime problems, and what to do about them. The model card is
|
| 4 |
+
[README.md](README.md).
|
| 5 |
+
|
| 6 |
+
Nearly every install problem is the torch/torchvision/CUDA triangle.
|
| 7 |
+
|
| 8 |
+
**Installing without the script**, on Colab, Windows, or anywhere a shell script is awkward. These
|
| 9 |
+
are the two commands `install.sh` runs:
|
| 10 |
+
|
| 11 |
+
```bash
|
| 12 |
+
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu124 # your CUDA line
|
| 13 |
+
pip install -r requirements.txt
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
Keep `torch` and `torchvision` in that one command. `torchvision` pins an exact `torch`, so
|
| 17 |
+
installing them separately lets pip pick a mismatched pair. A plain `pip install torch` also takes
|
| 18 |
+
the newest wheel, currently CUDA 13, which on an older driver initialises to CPU with nothing but a
|
| 19 |
+
warning. Use the index for *your* CUDA: `cu121`, `cu124`, `cu128`, `cu130`, or `cpu`.
|
| 20 |
+
|
| 21 |
+
**`ImportError` after `ImportError`, or complaints that torchvision is too old.** `torchvision`
|
| 22 |
+
pins an exact `torch`, so if the two were installed separately pip may have paired them wrongly.
|
| 23 |
+
Reinstall both in one command, from one index:
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
pip install --force-reinstall torch torchvision --index-url https://download.pytorch.org/whl/cu124
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
**`CUDA initialization: The NVIDIA driver on your system is too old`, then everything runs on CPU.**
|
| 30 |
+
Plain `pip install torch` fetched a wheel built for a newer CUDA than your driver. Check with
|
| 31 |
+
`nvidia-smi`, then reinstall from the matching index. Verify with:
|
| 32 |
+
|
| 33 |
+
```python
|
| 34 |
+
import torch; print(torch.__version__, torch.cuda.is_available())
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
**`Qwen3VLVideoProcessor requires the Torchvision library`.** The recognizer needs `torchvision`;
|
| 38 |
+
the layout stage does not. Install it *together with* torch, from the same index.
|
| 39 |
+
|
| 40 |
+
**`RTDetrHungarianMatcher requires the scipy library`.** You are on an old revision of this repo.
|
| 41 |
+
Update to the current revision, which no longer builds the matcher during inference.
|
assets/cand-hindi-maths-g10-6pr6eq-7dcd5d95-p20.png
ADDED
|
Git LFS Details
|
assets/diagram.png
ADDED
|
Git LFS Details
|
assets/gallery-1-english-math-ramanujan.png
ADDED
|
Git LFS Details
|
assets/gallery-2-telugu-novel.png
ADDED
|
Git LFS Details
|
idp_blocks.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/engine/blocks.py -- do not edit.
|
| 2 |
+
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
|
| 3 |
+
# whole install. See indic_doc_parser.py for usage.
|
| 4 |
+
|
| 5 |
+
"""Layout cleanup: geometry, duplicate suppression, nested-equation resolution.
|
| 6 |
+
|
| 7 |
+
Pure geometry on Blocks -- no PIL, no torch, no page image -- so the rules that decide what gets
|
| 8 |
+
transcribed stay testable with none of the GPU stack installed.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
from idp_types import Block, DedupConfig
|
| 14 |
+
from idp_contract import HEAD_FOOT, MARGINALIA, is_transcribed
|
| 15 |
+
|
| 16 |
+
TEXTLIKE = frozenset({"Text", "Title", "SectionHeader", "Caption", "Footnote"})
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def area(bbox) -> float:
|
| 20 |
+
return max(0.0, bbox[2] - bbox[0]) * max(0.0, bbox[3] - bbox[1])
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def contained_frac(small, big) -> float:
|
| 24 |
+
"""Fraction of ``small``'s area lying inside ``big``."""
|
| 25 |
+
ix0, iy0 = max(small[0], big[0]), max(small[1], big[1])
|
| 26 |
+
ix1, iy1 = min(small[2], big[2]), min(small[3], big[3])
|
| 27 |
+
inter = max(0.0, ix1 - ix0) * max(0.0, iy1 - iy0)
|
| 28 |
+
a = area(small)
|
| 29 |
+
return inter / a if a > 0 else 0.0
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def clamp_to_page(bbox, width: int, height: int) -> list[float]:
|
| 33 |
+
return [
|
| 34 |
+
max(0.0, bbox[0]),
|
| 35 |
+
max(0.0, bbox[1]),
|
| 36 |
+
min(float(width), bbox[2]),
|
| 37 |
+
min(float(height), bbox[3]),
|
| 38 |
+
]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def clean_layout(blocks: list[Block], cfg: DedupConfig | None = None) -> list[Block]:
|
| 42 |
+
"""Drop duplicate and spurious boxes. Three rules; survivors keep input order.
|
| 43 |
+
|
| 44 |
+
1. Nested duplicates -- a box ``cfg.contain`` inside a larger box of the same group goes.
|
| 45 |
+
2. One header, one footer -- only the largest of each survives.
|
| 46 |
+
3. Empty frames -- a header/footer wrapping nothing is dropped.
|
| 47 |
+
"""
|
| 48 |
+
cfg = cfg or DedupConfig()
|
| 49 |
+
n = len(blocks)
|
| 50 |
+
box = lambda i: blocks[i].bbox_xyxy # noqa: E731
|
| 51 |
+
drop: set[int] = set()
|
| 52 |
+
|
| 53 |
+
# Header/Footer are in neither group: rules 2 and 3 govern them entirely.
|
| 54 |
+
groups = (
|
| 55 |
+
lambda label: label not in MARGINALIA,
|
| 56 |
+
lambda label: label in MARGINALIA and label not in HEAD_FOOT,
|
| 57 |
+
)
|
| 58 |
+
for in_group in groups:
|
| 59 |
+
idxs = sorted(
|
| 60 |
+
(i for i in range(n) if in_group(blocks[i].label)),
|
| 61 |
+
key=lambda i: area(box(i)),
|
| 62 |
+
reverse=True,
|
| 63 |
+
)
|
| 64 |
+
kept: list[int] = []
|
| 65 |
+
for i in idxs:
|
| 66 |
+
has_text = is_transcribed(blocks[i].label)
|
| 67 |
+
# A block is never absorbed into a container that will not itself be transcribed:
|
| 68 |
+
# otherwise a caption inside a Diagram is dropped for a container that is then
|
| 69 |
+
# skipped, losing the text. One-directional -- a non-transcribed box may go anywhere.
|
| 70 |
+
if any(
|
| 71 |
+
contained_frac(box(i), box(j)) >= cfg.contain
|
| 72 |
+
and (is_transcribed(blocks[j].label) or not has_text)
|
| 73 |
+
for j in kept
|
| 74 |
+
):
|
| 75 |
+
drop.add(i)
|
| 76 |
+
else:
|
| 77 |
+
kept.append(i)
|
| 78 |
+
|
| 79 |
+
for label in HEAD_FOOT:
|
| 80 |
+
group = [i for i in range(n) if blocks[i].label == label and i not in drop]
|
| 81 |
+
if not group:
|
| 82 |
+
continue
|
| 83 |
+
biggest = max(group, key=lambda i: area(box(i)))
|
| 84 |
+
drop.update(i for i in group if i != biggest)
|
| 85 |
+
# Measured over range(n), NOT the survivors: a header whose only occupant was already
|
| 86 |
+
# removed as a nested duplicate is still occupied, and deleting it loses a real header.
|
| 87 |
+
wraps = any(
|
| 88 |
+
blocks[k].label != label and contained_frac(box(k), box(biggest)) >= cfg.wrap
|
| 89 |
+
for k in range(n)
|
| 90 |
+
)
|
| 91 |
+
if not wraps:
|
| 92 |
+
drop.add(biggest)
|
| 93 |
+
|
| 94 |
+
return [blocks[i] for i in range(n) if i not in drop]
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def _nested_in(inner, outer, thresh: float) -> bool:
|
| 98 |
+
# The strictness check stops two boxes over the same region each claiming to contain the
|
| 99 |
+
# other, which would drop both.
|
| 100 |
+
inner_area = area(inner)
|
| 101 |
+
if inner_area <= 0:
|
| 102 |
+
return False
|
| 103 |
+
return contained_frac(inner, outer) >= thresh and inner_area < 0.95 * area(outer)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def _absorbs_equation(container: Block, mode: str) -> bool:
|
| 107 |
+
if container.type == "Equation":
|
| 108 |
+
# A display array is one outer box plus a box per row; collapsing keeps it one block.
|
| 109 |
+
return mode != "text_only"
|
| 110 |
+
if container.type in TEXTLIKE:
|
| 111 |
+
# The text prompt already renders inline math as $...$, so a separate equation box
|
| 112 |
+
# would emit it a second time as display math.
|
| 113 |
+
return mode != "eq_only"
|
| 114 |
+
return False
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def resolve_nested_equations(blocks: list[Block], cfg: DedupConfig | None = None) -> list[Block]:
|
| 118 |
+
"""Drop equation boxes nested in a container that already covers them.
|
| 119 |
+
|
| 120 |
+
Runs before cropping -- it needs only boxes and types, so resolving first avoids building
|
| 121 |
+
crops that are immediately discarded.
|
| 122 |
+
"""
|
| 123 |
+
cfg = cfg or DedupConfig()
|
| 124 |
+
if not cfg.nest:
|
| 125 |
+
return list(blocks)
|
| 126 |
+
|
| 127 |
+
drop: set[int] = set()
|
| 128 |
+
for i, inner in enumerate(blocks):
|
| 129 |
+
if inner.type != "Equation":
|
| 130 |
+
continue
|
| 131 |
+
for j, container in enumerate(blocks):
|
| 132 |
+
if (
|
| 133 |
+
i != j
|
| 134 |
+
and _nested_in(inner.bbox_xyxy, container.bbox_xyxy, cfg.nested)
|
| 135 |
+
and _absorbs_equation(container, cfg.mode)
|
| 136 |
+
):
|
| 137 |
+
drop.add(i)
|
| 138 |
+
break
|
| 139 |
+
return [b for k, b in enumerate(blocks) if k not in drop]
|
idp_contract.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/templates/contract.py -- do not edit.
|
| 2 |
+
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
|
| 3 |
+
# whole install. See indic_doc_parser.py for usage.
|
| 4 |
+
|
| 5 |
+
"""IndicDocParser's contract: prompts, block taxonomy, output schema.
|
| 6 |
+
|
| 7 |
+
Two vocabularies meet here and mixing them fails silently. **Labels** are what IndicDocLayout
|
| 8 |
+
emits (``ocr.layout.labels.CLASSES``); MARGINALIA and HEAD_FOOT match them case-SENSITIVELY.
|
| 9 |
+
**Types** are the coarse categories a label maps to, and select the prompt. OCR_SKIP_LABELS is
|
| 10 |
+
the exception: matched case-insensitively.
|
| 11 |
+
|
| 12 |
+
stdlib-only -- importable with no GPU stack and no PIL.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
from enum import StrEnum
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class TableFormat(StrEnum):
|
| 21 |
+
"""HTML is the default: colspan/rowspan and in-cell breaks have no GFM spelling, so a
|
| 22 |
+
merged-cell table rendered as markdown silently loses its structure."""
|
| 23 |
+
|
| 24 |
+
HTML = "html"
|
| 25 |
+
MARKDOWN = "markdown"
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
TEXT_PROMPT = (
|
| 29 |
+
"Transcribe the text in this image. Write any mathematical expressions in LaTeX, "
|
| 30 |
+
"using $...$ for inline math and $$...$$ for display equations."
|
| 31 |
+
)
|
| 32 |
+
EQUATION_PROMPT = "Output only the LaTeX for this equation image."
|
| 33 |
+
TABLE_PROMPTS = {
|
| 34 |
+
TableFormat.HTML: (
|
| 35 |
+
"Convert this table image to HTML. Preserve the structure exactly, using colspan and "
|
| 36 |
+
"rowspan for merged cells and <br/> for line breaks within a cell. "
|
| 37 |
+
"Output only the HTML table."
|
| 38 |
+
),
|
| 39 |
+
TableFormat.MARKDOWN: (
|
| 40 |
+
"Convert this table image to a GitHub-flavored markdown table. Output only the table."
|
| 41 |
+
),
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def prompt_for(block_type: str, table_format: TableFormat = TableFormat.HTML) -> str:
|
| 46 |
+
if block_type == "Table":
|
| 47 |
+
return TABLE_PROMPTS[TableFormat(table_format)]
|
| 48 |
+
if block_type == "Equation":
|
| 49 |
+
return EQUATION_PROMPT
|
| 50 |
+
return TEXT_PROMPT
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# Labels absent from this map fall through to "Text" by design -- Question, Paragraph, Answer,
|
| 54 |
+
# List, MCQ, Code, Reference and the rest all carry prose.
|
| 55 |
+
LABEL_TO_TYPE = {
|
| 56 |
+
"table": "Table",
|
| 57 |
+
"table-caption": "Caption",
|
| 58 |
+
"equation": "Equation",
|
| 59 |
+
"expression": "Equation",
|
| 60 |
+
"diagram": "Figure",
|
| 61 |
+
"chart": "Figure",
|
| 62 |
+
"image": "Picture",
|
| 63 |
+
"image-caption": "Caption",
|
| 64 |
+
"title": "Title",
|
| 65 |
+
"chapter-title": "Title",
|
| 66 |
+
"section-title": "SectionHeader",
|
| 67 |
+
"sub-section-title": "SectionHeader",
|
| 68 |
+
"sub-sub-section-title": "SectionHeader",
|
| 69 |
+
"header": "PageHeader",
|
| 70 |
+
"footer": "PageFooter",
|
| 71 |
+
"page-number": "PageNumber",
|
| 72 |
+
"folio": "PageNumber",
|
| 73 |
+
"footnote": "Footnote",
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def map_label(label) -> str:
|
| 78 |
+
"""Label -> pipeline type. Unknown labels are ``Text``."""
|
| 79 |
+
return LABEL_TO_TYPE.get(str(label).strip().lower(), "Text")
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
# Exactly the types map_label can produce, less DROP_TYPES. test_contract asserts this, so a
|
| 83 |
+
# dead or undocumented type cannot creep in.
|
| 84 |
+
KEPT_BLOCK_TYPES = (
|
| 85 |
+
"Text",
|
| 86 |
+
"Title",
|
| 87 |
+
"SectionHeader",
|
| 88 |
+
"Table",
|
| 89 |
+
"Equation",
|
| 90 |
+
"Caption",
|
| 91 |
+
"Footnote",
|
| 92 |
+
"PageHeader",
|
| 93 |
+
"PageFooter",
|
| 94 |
+
"PageNumber",
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
# Never cropped, never reconstructed: pictorial regions have no text and invite hallucination.
|
| 98 |
+
# Deliberately narrow -- page numbers and margin text DO reach the recognizer.
|
| 99 |
+
DROP_TYPES = frozenset({"Figure", "Picture"})
|
| 100 |
+
|
| 101 |
+
# Cleaned as their own group so a page-spanning paragraph cannot swallow a page number.
|
| 102 |
+
MARGINALIA = frozenset({"Header", "Footer", "Page-number", "Folio"})
|
| 103 |
+
HEAD_FOOT = ("Header", "Footer")
|
| 104 |
+
|
| 105 |
+
# Never sent to the recognizer, but NOT deleted: these keep their place in the output with
|
| 106 |
+
# text "", so a consumer can still see what was detected and where.
|
| 107 |
+
OCR_SKIP_LABELS = frozenset({"header", "footer", "diagram", "image", "chart", "advertisement"})
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def is_transcribed(label) -> bool:
|
| 111 |
+
return str(label).strip().lower() not in OCR_SKIP_LABELS
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
OUTPUT_BLOCK_SCHEMA = {
|
| 115 |
+
"order": "int -- reading-order rank (0 = first)",
|
| 116 |
+
"label": "str -- raw IndicDocLayout label",
|
| 117 |
+
"type": "str -- pipeline type (see KEPT_BLOCK_TYPES)",
|
| 118 |
+
"bbox_xyxy": "[float x4] -- pixel [x0, y0, x1, y1], clamped to the page",
|
| 119 |
+
"conf": "float -- detection confidence",
|
| 120 |
+
"text": "str -- transcription; '' when not sent to the recognizer",
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
OUTPUT_PAGE_SCHEMA = {
|
| 124 |
+
"image": "str -- source image filename",
|
| 125 |
+
"width": "int -- page width in pixels",
|
| 126 |
+
"height": "int -- page height in pixels",
|
| 127 |
+
"blocks": "[BLOCK] -- in reading order",
|
| 128 |
+
}
|
idp_crops.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/engine/crops.py -- do not edit.
|
| 2 |
+
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
|
| 3 |
+
# whole install. See indic_doc_parser.py for usage.
|
| 4 |
+
|
| 5 |
+
"""Turning boxes into the images the recognizer sees.
|
| 6 |
+
|
| 7 |
+
Split from ``engine.blocks`` because this is the only part that needs PIL -- keeping the
|
| 8 |
+
geometry PIL-free is what lets the cleanup rules be tested without an image library.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import math
|
| 14 |
+
from typing import TYPE_CHECKING
|
| 15 |
+
|
| 16 |
+
from idp_types import Block, CropConfig
|
| 17 |
+
from idp_contract import DROP_TYPES, is_transcribed
|
| 18 |
+
|
| 19 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 20 |
+
from PIL.Image import Image
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def area_clamp(image: Image, cfg: CropConfig | None = None) -> Image:
|
| 24 |
+
"""Scale a crop so its area lands in ``[cfg.min_px, cfg.max_px]``, preserving aspect.
|
| 25 |
+
|
| 26 |
+
Area rather than a side: pinning a side exploded elongated crops (a 122:1 rule line became
|
| 27 |
+
~32k image tokens and wedged the engine).
|
| 28 |
+
"""
|
| 29 |
+
from PIL import Image as PILImage
|
| 30 |
+
|
| 31 |
+
cfg = cfg or CropConfig()
|
| 32 |
+
width, height = image.size
|
| 33 |
+
pixels = width * height
|
| 34 |
+
if pixels <= 0:
|
| 35 |
+
return image
|
| 36 |
+
|
| 37 |
+
if pixels < cfg.min_px:
|
| 38 |
+
scale = math.sqrt(cfg.min_px / pixels)
|
| 39 |
+
elif pixels > cfg.max_px:
|
| 40 |
+
scale = math.sqrt(cfg.max_px / pixels)
|
| 41 |
+
else:
|
| 42 |
+
return image
|
| 43 |
+
|
| 44 |
+
return image.resize(
|
| 45 |
+
(max(1, round(width * scale)), max(1, round(height * scale))), PILImage.LANCZOS
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def crop_for(block: Block, page: Image, cfg: CropConfig | None = None) -> Image | None:
|
| 50 |
+
"""The image for one block, or None if it should not be transcribed."""
|
| 51 |
+
if block.type in DROP_TYPES or not is_transcribed(block.label):
|
| 52 |
+
return None
|
| 53 |
+
|
| 54 |
+
cfg = cfg or CropConfig()
|
| 55 |
+
width, height = page.size
|
| 56 |
+
x0, y0, x1, y1 = (round(v) for v in block.bbox_xyxy)
|
| 57 |
+
if cfg.pad_px: # recover glyph edges a tight box clips
|
| 58 |
+
x0, y0 = x0 - cfg.pad_px, y0 - cfg.pad_px
|
| 59 |
+
x1, y1 = x1 + cfg.pad_px, y1 + cfg.pad_px
|
| 60 |
+
x0, y0 = max(0, x0), max(0, y0)
|
| 61 |
+
x1, y1 = min(width, x1), min(height, y1)
|
| 62 |
+
|
| 63 |
+
if x1 <= x0 or y1 <= y0: # rounding can collapse a thin rule to zero width
|
| 64 |
+
return None
|
| 65 |
+
return page.crop((x0, y0, x1, y1)).convert("RGB")
|
idp_layout.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/engine/layout.py -- do not edit.
|
| 2 |
+
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
|
| 3 |
+
# whole install. See indic_doc_parser.py for usage.
|
| 4 |
+
|
| 5 |
+
"""Layout backends: page image -> cleaned, reading-ordered blocks.
|
| 6 |
+
|
| 7 |
+
The two stages hand off a plain JSON layout, so stage 2 does not care where the layout came
|
| 8 |
+
from. :class:`LayoutBackend` makes that an interface rather than a claim.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import json
|
| 14 |
+
from typing import TYPE_CHECKING, Protocol, runtime_checkable
|
| 15 |
+
|
| 16 |
+
from idp_blocks import clamp_to_page, clean_layout
|
| 17 |
+
from idp_types import Block, DedupConfig, LayoutConfig, PageResult
|
| 18 |
+
from idp_contract import map_label
|
| 19 |
+
|
| 20 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 21 |
+
from PIL.Image import Image
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@runtime_checkable
|
| 25 |
+
class LayoutBackend(Protocol):
|
| 26 |
+
"""``detect`` must return blocks already cleaned and densely ordered: ``order`` a gap-free
|
| 27 |
+
0-based rank. Stage 2 matches transcriptions back by ``order``, so gaps mis-assign text."""
|
| 28 |
+
|
| 29 |
+
def detect(self, image: Image) -> list[Block]: ...
|
| 30 |
+
|
| 31 |
+
def close(self) -> None: ...
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _densify(blocks: list[Block]) -> list[Block]:
|
| 35 |
+
"""Sort by the detector's reading order and renumber to a gap-free 0-based rank."""
|
| 36 |
+
ordered = sorted(blocks, key=lambda b: b.order)
|
| 37 |
+
for rank, block in enumerate(ordered):
|
| 38 |
+
block.order = rank
|
| 39 |
+
return ordered
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class IndicDocLayoutBackend:
|
| 43 |
+
"""Our finetuned PP-DocLayoutV3 with an integrated reading-order head. Torch only --
|
| 44 |
+
constructing this does not load vLLM, which is what lets stage 1 run alone."""
|
| 45 |
+
|
| 46 |
+
def __init__(
|
| 47 |
+
self,
|
| 48 |
+
ckpt: str | None = None,
|
| 49 |
+
config: LayoutConfig | None = None,
|
| 50 |
+
dedup: DedupConfig | None = None,
|
| 51 |
+
) -> None:
|
| 52 |
+
from idp_model_infer import get_model
|
| 53 |
+
|
| 54 |
+
self.config = config or LayoutConfig()
|
| 55 |
+
self.dedup = dedup or DedupConfig()
|
| 56 |
+
if ckpt is None:
|
| 57 |
+
raise ValueError(
|
| 58 |
+
"no layout weights given -- "
|
| 59 |
+
"IndicDocParser.from_pretrained(snapshot_download(REPO))"
|
| 60 |
+
)
|
| 61 |
+
self.ckpt = ckpt
|
| 62 |
+
self.model = get_model(self.ckpt, device=self.config.device)
|
| 63 |
+
|
| 64 |
+
def detect(self, image: Image) -> list[Block]:
|
| 65 |
+
from idp_model_infer import infer
|
| 66 |
+
|
| 67 |
+
width, height = image.size
|
| 68 |
+
detections = infer(
|
| 69 |
+
self.model,
|
| 70 |
+
image,
|
| 71 |
+
conf=self.config.conf,
|
| 72 |
+
img_size=self.config.img_size,
|
| 73 |
+
device=self.config.device,
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
# The model emits [y0, x0, y1, x1] normalised to 0-1000; the pipeline works in pixel
|
| 77 |
+
# [x0, y0, x1, y1]. Axis swap and rescale happen here, once.
|
| 78 |
+
blocks = []
|
| 79 |
+
for det in detections:
|
| 80 |
+
y0, x0, y1, x1 = det["bbox"]
|
| 81 |
+
bbox = [x0 / 1000 * width, y0 / 1000 * height, x1 / 1000 * width, y1 / 1000 * height]
|
| 82 |
+
label = str(det["label"])
|
| 83 |
+
blocks.append(
|
| 84 |
+
Block(
|
| 85 |
+
order=det["reading_order"],
|
| 86 |
+
label=label,
|
| 87 |
+
type=map_label(label),
|
| 88 |
+
bbox_xyxy=[round(v, 1) for v in clamp_to_page(bbox, width, height)],
|
| 89 |
+
conf=round(float(det.get("score", 1.0)), 3),
|
| 90 |
+
)
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
return _densify(clean_layout(blocks, self.dedup))
|
| 94 |
+
|
| 95 |
+
def close(self) -> None:
|
| 96 |
+
self.model = None
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
class JsonLayoutBackend:
|
| 100 |
+
"""Replay a layout produced elsewhere -- by stage 1, by hand, or by another detector.
|
| 101 |
+
|
| 102 |
+
Assumed already clean, so no cleanup runs; blocks are only renumbered, which makes a
|
| 103 |
+
hand-edited file usable without fixing ranks. Needs no torch.
|
| 104 |
+
"""
|
| 105 |
+
|
| 106 |
+
def __init__(self, layout: str | dict | PageResult, *, strict: bool = True) -> None:
|
| 107 |
+
if isinstance(layout, PageResult):
|
| 108 |
+
self.page = layout
|
| 109 |
+
else:
|
| 110 |
+
if isinstance(layout, str):
|
| 111 |
+
with open(layout, encoding="utf-8") as fh:
|
| 112 |
+
layout = json.load(fh)
|
| 113 |
+
# Validates by default: this is the door a foreign layout comes through, and an
|
| 114 |
+
# unrecognised label would otherwise become Text without a word. `strict=False`
|
| 115 |
+
# replays a file written before validation existed.
|
| 116 |
+
self.page = PageResult.from_record(layout, strict=strict)
|
| 117 |
+
|
| 118 |
+
def detect(self, image: Image) -> list[Block]:
|
| 119 |
+
"""Copies, so renumbering cannot write back into the stored layout. ``image`` is
|
| 120 |
+
accepted for interface parity and not read."""
|
| 121 |
+
return _densify([b.copy() for b in self.page.blocks])
|
| 122 |
+
|
| 123 |
+
def close(self) -> None:
|
| 124 |
+
return None
|
idp_model_infer.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/layout/infer.py -- do not edit.
|
| 2 |
+
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
|
| 3 |
+
# whole install. See indic_doc_parser.py for usage.
|
| 4 |
+
|
| 5 |
+
"""Single-image inference for IndicDocLayout -> boxes, labels and reading order.
|
| 6 |
+
|
| 7 |
+
Emits ``[{bbox: [y0, x0, y1, x1] normalised to 0-1000, label, reading_order, score}]``. That is
|
| 8 |
+
the layout viewer's schema, kept verbatim so predictions render with the same box and
|
| 9 |
+
reading-order visualisation as the dataset viewer; ``engine.layout`` converts it to pixel
|
| 10 |
+
``[x0, y0, x1, y1]``.
|
| 11 |
+
|
| 12 |
+
The decode mirrors the training-time evaluation exactly: sigmoid-max detections, and reading
|
| 13 |
+
order from a voting sort over the pairwise ``order_logits`` restricted to the kept boxes.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
import numpy as np
|
| 17 |
+
import torch
|
| 18 |
+
|
| 19 |
+
from idp_model_labels import ID2LABEL
|
| 20 |
+
from idp_model_order_loss import decode_order, pairwise_scores
|
| 21 |
+
|
| 22 |
+
_CACHE = {}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def cxcywh_to_xyxy(b):
|
| 26 |
+
"""Centre-form boxes -> corner form, preserving the input tensor's dtype and device."""
|
| 27 |
+
c = b.clone()
|
| 28 |
+
c[..., 0], c[..., 1] = b[..., 0] - b[..., 2] / 2, b[..., 1] - b[..., 3] / 2
|
| 29 |
+
c[..., 2], c[..., 3] = b[..., 0] + b[..., 2] / 2, b[..., 1] + b[..., 3] / 2
|
| 30 |
+
return c
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def get_model(ckpt, device="cuda"):
|
| 34 |
+
"""Load (and cache) an IndicDocLayout checkpoint.
|
| 35 |
+
|
| 36 |
+
The checkpoint is saved as ``PPDocLayoutV3Trainable`` -- our subclass -- so that class has to
|
| 37 |
+
be importable here even though nothing is trained at inference time.
|
| 38 |
+
"""
|
| 39 |
+
key = (ckpt, device)
|
| 40 |
+
if key not in _CACHE:
|
| 41 |
+
from idp_model_ppdoc import PPDocLayoutV3Trainable
|
| 42 |
+
|
| 43 |
+
_CACHE[key] = PPDocLayoutV3Trainable.from_pretrained(ckpt).to(device).eval()
|
| 44 |
+
return _CACHE[key]
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@torch.no_grad()
|
| 48 |
+
def infer(model, pil_img, conf=0.5, img_size=1024, device="cuda"):
|
| 49 |
+
"""Detect blocks on one page image."""
|
| 50 |
+
im = pil_img.convert("RGB").resize((img_size, img_size))
|
| 51 |
+
# np.array (not asarray): a PIL buffer is read-only, and torch warns on every page about
|
| 52 |
+
# wrapping a non-writable array.
|
| 53 |
+
x = torch.from_numpy(np.array(im)).permute(2, 0, 1).float().div(255.0) # [3,S,S] in [0,1]
|
| 54 |
+
out = model(pixel_values=x[None].to(device))
|
| 55 |
+
scores, labels = out.logits.sigmoid().max(-1) # [1,N]
|
| 56 |
+
boxes = cxcywh_to_xyxy(out.pred_boxes)[0] # [N,4] normalised x0,y0,x1,y1
|
| 57 |
+
|
| 58 |
+
if getattr(out, "order_logits", None) is not None:
|
| 59 |
+
order_scores = out.order_logits[0]
|
| 60 |
+
else:
|
| 61 |
+
nq = model.config.num_queries
|
| 62 |
+
order_scores = pairwise_scores(out.last_hidden_state[:, -nq:], model.ro_q, model.ro_k)[0]
|
| 63 |
+
|
| 64 |
+
keep = (scores[0] > conf).nonzero().squeeze(-1)
|
| 65 |
+
if keep.numel() == 0:
|
| 66 |
+
return []
|
| 67 |
+
kept_boxes, kept_scores, kept_labels = boxes[keep], scores[0][keep], labels[0][keep]
|
| 68 |
+
|
| 69 |
+
# Reading order is decoded over the KEPT sub-block only: ranking against suppressed queries
|
| 70 |
+
# would leave gaps in the sequence.
|
| 71 |
+
sub = order_scores[keep][:, keep].cpu().float()
|
| 72 |
+
sequence = decode_order(sub).tolist() # positions, first -> last
|
| 73 |
+
rank = [0] * len(sequence)
|
| 74 |
+
for r, position in enumerate(sequence):
|
| 75 |
+
rank[position] = r + 1
|
| 76 |
+
|
| 77 |
+
content = []
|
| 78 |
+
for j in range(keep.numel()):
|
| 79 |
+
x0, y0, x1, y1 = kept_boxes[j].tolist()
|
| 80 |
+
content.append(
|
| 81 |
+
{
|
| 82 |
+
"bbox": [ # viewer schema: [y0, x0, y1, x1], 0-1000
|
| 83 |
+
round(y0 * 1000, 1),
|
| 84 |
+
round(x0 * 1000, 1),
|
| 85 |
+
round(y1 * 1000, 1),
|
| 86 |
+
round(x1 * 1000, 1),
|
| 87 |
+
],
|
| 88 |
+
"label": ID2LABEL[int(kept_labels[j])],
|
| 89 |
+
"reading_order": rank[j],
|
| 90 |
+
"score": round(float(kept_scores[j]), 3),
|
| 91 |
+
}
|
| 92 |
+
)
|
| 93 |
+
return content
|
idp_model_labels.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/layout/labels.py -- do not edit.
|
| 2 |
+
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
|
| 3 |
+
# whole install. See indic_doc_parser.py for usage.
|
| 4 |
+
|
| 5 |
+
"""IndicDocLayout's 37 education-domain layout classes = SUPERSET of the printed + handwritten
|
| 6 |
+
taxonomies.
|
| 7 |
+
Order is fixed; do not reorder and only APPEND (ids are baked into checkpoints). ids 0-24 are the
|
| 8 |
+
original 25; 25-26 are the printed-only additions (Chapter-title, Chapter-end-section); 27-36 are
|
| 9 |
+
the indicdlp-v2 magazine/newspaper additions. Class weights are sqrt-inverse block frequency
|
| 10 |
+
(capped [0.5, 5]); optional, used only if enabled.
|
| 11 |
+
|
| 12 |
+
These are the raw *labels*. The map onto the coarse pipeline *types* that select prompts lives in
|
| 13 |
+
``ocr.templates.contract`` -- see its module docstring for why the two vocabularies differ.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
CLASSES = [
|
| 17 |
+
"Question",
|
| 18 |
+
"Paragraph",
|
| 19 |
+
"Answer",
|
| 20 |
+
"List",
|
| 21 |
+
"Title",
|
| 22 |
+
"Section-title",
|
| 23 |
+
"Equation",
|
| 24 |
+
"Table",
|
| 25 |
+
"Diagram",
|
| 26 |
+
"Image",
|
| 27 |
+
"MCQ",
|
| 28 |
+
"Infobox",
|
| 29 |
+
"Sub-section-title",
|
| 30 |
+
"Expression",
|
| 31 |
+
"Image-caption",
|
| 32 |
+
"Placeholder-text",
|
| 33 |
+
"Chart",
|
| 34 |
+
"Solved-example",
|
| 35 |
+
"Footnote",
|
| 36 |
+
"Table-caption",
|
| 37 |
+
"Sub-sub-section-title",
|
| 38 |
+
"Footer",
|
| 39 |
+
"Header",
|
| 40 |
+
"Code",
|
| 41 |
+
"Page-number",
|
| 42 |
+
"Chapter-title",
|
| 43 |
+
"Chapter-end-section", # 25-26: printed-only additions (superset)
|
| 44 |
+
# 27-36: indicdlp-v2 additions (magazine/newspaper furniture kept as their own classes;
|
| 45 |
+
# only populated by the indicdlp-printed source -> no cross-dataset partial-annotation conflict).
|
| 46 |
+
"Folio",
|
| 47 |
+
"Reference",
|
| 48 |
+
"Table-of-contents",
|
| 49 |
+
"Index",
|
| 50 |
+
"Advertisement",
|
| 51 |
+
"Author",
|
| 52 |
+
"Dateline",
|
| 53 |
+
"Contact-info",
|
| 54 |
+
"Website-link",
|
| 55 |
+
"Flag",
|
| 56 |
+
]
|
| 57 |
+
|
| 58 |
+
LABEL2ID = {c: i for i, c in enumerate(CLASSES)}
|
| 59 |
+
ID2LABEL = {i: c for i, c in enumerate(CLASSES)}
|
| 60 |
+
NUM_CLASSES = len(CLASSES)
|
| 61 |
+
|
| 62 |
+
# sqrt-inverse-frequency weights (measured on 155,922 blocks); rare classes upweighted.
|
| 63 |
+
CLASS_WEIGHTS = {
|
| 64 |
+
"Question": 0.50,
|
| 65 |
+
"Paragraph": 0.50,
|
| 66 |
+
"Equation": 0.50,
|
| 67 |
+
"Answer": 0.50,
|
| 68 |
+
"List": 0.50,
|
| 69 |
+
"Section-title": 0.50,
|
| 70 |
+
"Title": 0.50,
|
| 71 |
+
"Image": 0.50,
|
| 72 |
+
"Table": 0.52,
|
| 73 |
+
"Diagram": 0.53,
|
| 74 |
+
"MCQ": 0.74,
|
| 75 |
+
"Expression": 1.00,
|
| 76 |
+
"Image-caption": 1.11,
|
| 77 |
+
"Infobox": 1.43,
|
| 78 |
+
"Solved-example": 1.43,
|
| 79 |
+
"Placeholder-text": 1.48,
|
| 80 |
+
"Sub-section-title": 1.64,
|
| 81 |
+
"Chart": 2.11,
|
| 82 |
+
"Footnote": 4.63,
|
| 83 |
+
"Table-caption": 5.0,
|
| 84 |
+
"Sub-sub-section-title": 5.0,
|
| 85 |
+
"Footer": 5.0,
|
| 86 |
+
"Header": 5.0,
|
| 87 |
+
"Code": 5.0,
|
| 88 |
+
"Page-number": 5.0,
|
| 89 |
+
"Chapter-title": 0.74,
|
| 90 |
+
"Chapter-end-section": 4.5, # by frequency in label_frequency.csv
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
# --- Shared label parser (used by BOTH the training blob cache AND the val/test disk path,
|
| 94 |
+
# so train/val/test are always consistent). Content blocks + metadata Header/Footer region
|
| 95 |
+
# boxes (header reads first, footer last; placeholder stubs dropped; hw dedup vs content). ---
|
| 96 |
+
MIN_HF_AREA = 1e-4 # drop placeholder header/footer stubs (e.g. bbox [0,0,5,10])
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def _to_cxcywh(bb):
|
| 100 |
+
"""[y0,x0,y1,x1] in 0-1000 -> clamped [cx,cy,w,h] normalized, or None if degenerate."""
|
| 101 |
+
try:
|
| 102 |
+
y0, x0, y1, x1 = [float(c) / 1000 for c in bb]
|
| 103 |
+
except (TypeError, ValueError):
|
| 104 |
+
return None # malformed bbox (e.g. nested list) -> skip this box
|
| 105 |
+
if x1 - x0 <= 1e-3 or y1 - y0 <= 1e-3:
|
| 106 |
+
return None
|
| 107 |
+
cx, cy, w, h = (x0 + x1) / 2, (y0 + y1) / 2, x1 - x0, y1 - y0
|
| 108 |
+
cx, cy = min(max(cx, 1e-4), 1 - 1e-4), min(max(cy, 1e-4), 1 - 1e-4)
|
| 109 |
+
w, h = min(w, 2 * min(cx, 1 - cx)), min(h, 2 * min(cy, 1 - cy))
|
| 110 |
+
return [cx, cy, w, h]
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def _iou(a, b):
|
| 114 |
+
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))
|
| 115 |
+
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))
|
| 116 |
+
inter = iw * ih
|
| 117 |
+
ua = a[2] * a[3] + b[2] * b[3] - inter
|
| 118 |
+
return inter / ua if ua > 0 else 0.0
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def labels_from_doc(d, drop_hf_strips=False):
|
| 122 |
+
"""Parse a page JSON dict -> (boxes[cxcywh], cls[ids], order). THE canonical parser.
|
| 123 |
+
drop_hf_strips: skip metadata Header/Footer boxes that are full-width page-edge STRIPS (the
|
| 124 |
+
hw-vs/hw-dps placeholders like [0,0,100,1000]) — they're not real text regions and teach the
|
| 125 |
+
model to hallucinate top/bottom bars. Real (non-strip) header/footer regions are kept."""
|
| 126 |
+
cls, boxes, order = [], [], []
|
| 127 |
+
for b in d.get("content", []):
|
| 128 |
+
bb, lab = b.get("bbox"), b.get("label")
|
| 129 |
+
if not (isinstance(bb, list) and len(bb) == 4) or lab not in LABEL2ID:
|
| 130 |
+
continue
|
| 131 |
+
box = _to_cxcywh(bb)
|
| 132 |
+
if box is None:
|
| 133 |
+
continue
|
| 134 |
+
cls.append(LABEL2ID[lab])
|
| 135 |
+
boxes.append(box)
|
| 136 |
+
order.append(b.get("reading_order", len(order) + 1))
|
| 137 |
+
meta = d.get("metadata", {}) or {}
|
| 138 |
+
ords = order or [0]
|
| 139 |
+
for field, lab, ro in (
|
| 140 |
+
("header", "Header", min(ords) - 1),
|
| 141 |
+
("footer", "Footer", max(ords) + 1),
|
| 142 |
+
):
|
| 143 |
+
bb = (meta.get(field) or {}).get("bbox")
|
| 144 |
+
if not (isinstance(bb, list) and len(bb) == 4):
|
| 145 |
+
continue
|
| 146 |
+
box = _to_cxcywh(bb)
|
| 147 |
+
if box is None or box[2] * box[3] < MIN_HF_AREA:
|
| 148 |
+
continue
|
| 149 |
+
if drop_hf_strips and box[2] > 0.9: # full-width strip flush to a page edge = placeholder
|
| 150 |
+
y0, y1 = box[1] - box[3] / 2, box[1] + box[3] / 2
|
| 151 |
+
if (field == "header" and y0 < 0.02) or (field == "footer" and y1 > 0.98):
|
| 152 |
+
continue
|
| 153 |
+
lid = LABEL2ID[lab]
|
| 154 |
+
if any(cls[i] == lid and _iou(boxes[i], box) > 0.5 for i in range(len(boxes))):
|
| 155 |
+
continue
|
| 156 |
+
cls.append(lid)
|
| 157 |
+
boxes.append(box)
|
| 158 |
+
order.append(ro)
|
| 159 |
+
return boxes, cls, order
|
idp_model_order_loss.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/layout/order_loss.py -- do not edit.
|
| 2 |
+
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
|
| 3 |
+
# whole install. See indic_doc_parser.py for usage.
|
| 4 |
+
|
| 5 |
+
"""Locality-weighted Generalized Cross-Entropy on the antisymmetric pairwise
|
| 6 |
+
precedence scores. Validated in learnings (recovers arbitrary order, tau=1.0).
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import math
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def decode_order(S):
|
| 15 |
+
"""Reading order from a pairwise score matrix, matching PP-DocLayoutV3's _get_order_seqs.
|
| 16 |
+
P(i before j) = sigmoid(S[i,j]) if i<j else 1-sigmoid(S[j,i]); votes[j]=#elements before j;
|
| 17 |
+
argsort ascending -> reading sequence (first..last). Correct for BOTH the triangular ppdoc
|
| 18 |
+
GlobalPointer AND an antisymmetric head (they coincide under this formula)."""
|
| 19 |
+
sc = torch.sigmoid(S)
|
| 20 |
+
votes = sc.triu(1).sum(0) + (1.0 - sc.t()).tril(-1).sum(0)
|
| 21 |
+
return torch.argsort(votes)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def pairwise_scores(Q, ro_q, ro_k):
|
| 25 |
+
"""Q:[B,N,d] -> antisymmetric S:[B,N,N], S_ij>0 => query i precedes j."""
|
| 26 |
+
A, K = ro_q(Q), ro_k(Q) # [B,N,r]
|
| 27 |
+
M = A @ K.transpose(-1, -2) # M_ij = q_i^T Wq^T Wk q_j
|
| 28 |
+
return (M - M.transpose(-1, -2)) / math.sqrt(A.shape[-1])
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def locality_gce(S, order, q=0.7, tau=3.0, eps=1e-6):
|
| 32 |
+
"""S:[m,m] scores over matched queries, order:[m] their GT reading_order values."""
|
| 33 |
+
m = order.numel()
|
| 34 |
+
if m < 2:
|
| 35 |
+
return S.sum() * 0.0 # nothing to order; keep graph
|
| 36 |
+
P = (order.unsqueeze(1) < order.unsqueeze(0)).float() # P_ab=1 if a precedes b
|
| 37 |
+
p = torch.sigmoid(S)
|
| 38 |
+
pc = torch.where(P > 0.5, p, 1 - p).clamp(eps, 1)
|
| 39 |
+
gce = (1 - pc.pow(q)) / q
|
| 40 |
+
pos = order.argsort().argsort().float() # dense rank positions
|
| 41 |
+
dist = (pos.unsqueeze(1) - pos.unsqueeze(0)).abs()
|
| 42 |
+
W = torch.exp(-dist / tau)
|
| 43 |
+
# only the strict UPPER triangle carries real scores (ppdoc GlobalPointer masks a>=b to -1e4);
|
| 44 |
+
# training the masked lower triangle penalizes unfixable entries. Upper-tri also suffices for
|
| 45 |
+
# an antisymmetric head. P[i,j] for i<j = (order_i < order_j).
|
| 46 |
+
mask = torch.triu(torch.ones(m, m, dtype=torch.bool, device=S.device), diagonal=1)
|
| 47 |
+
return (gce * W)[mask].sum() / W[mask].sum().clamp_min(eps)
|
idp_model_ppdoc.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/layout/modeling_ppdoc.py -- do not edit.
|
| 2 |
+
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
|
| 3 |
+
# whole install. See indic_doc_parser.py for usage.
|
| 4 |
+
|
| 5 |
+
"""IndicDocLayout: trainable PP-DocLayoutV3 (document-pretrained strong init).
|
| 6 |
+
|
| 7 |
+
HF ships PPDocLayoutV3ForObjectDetection inference-only (forward raises on labels).
|
| 8 |
+
This subclass unblocks training: it calls the inner model with labels (which builds the
|
| 9 |
+
contrastive-denoising groups), reuses the base RT-DETR detection loss on its outputs, and
|
| 10 |
+
adds our locality-weighted GCE order loss on its (pretrained) order_logits.
|
| 11 |
+
Backbone + decoder + order/mask heads start from the document-pretrained checkpoint;
|
| 12 |
+
only the class heads are re-init'd for our 37 education classes.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from dataclasses import dataclass
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import torch.nn as nn
|
| 19 |
+
from transformers import PPDocLayoutV3Config, PPDocLayoutV3ForObjectDetection
|
| 20 |
+
from transformers.loss.loss_rt_detr import RTDetrHungarianMatcher
|
| 21 |
+
from transformers.utils import ModelOutput
|
| 22 |
+
|
| 23 |
+
from idp_model_order_loss import locality_gce
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@dataclass
|
| 27 |
+
class PPDocOutput(ModelOutput):
|
| 28 |
+
loss: torch.FloatTensor | None = None
|
| 29 |
+
logits: torch.FloatTensor | None = None
|
| 30 |
+
pred_boxes: torch.FloatTensor | None = None
|
| 31 |
+
order_logits: torch.FloatTensor | None = None
|
| 32 |
+
last_hidden_state: torch.FloatTensor | None = None
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class PPDocLayoutV3Trainable(PPDocLayoutV3ForObjectDetection):
|
| 36 |
+
def __init__(self, config):
|
| 37 |
+
super().__init__(config)
|
| 38 |
+
self.lambda_order = getattr(config, "lambda_order", 5.0)
|
| 39 |
+
# Built on first use, not here: the matcher is training-only, and constructing it calls
|
| 40 |
+
# requires_backends(["scipy"]). Eagerly, that makes scipy a hard dependency of merely
|
| 41 |
+
# LOADING the detector -- so inference-only installs fail on import with a library they
|
| 42 |
+
# will never call. It holds no parameters, so this does not change the state dict.
|
| 43 |
+
self._matcher = None
|
| 44 |
+
self.loss_type = "RTDetrForObjectDetection" # base RT-DETR loss over its outputs
|
| 45 |
+
|
| 46 |
+
@classmethod
|
| 47 |
+
def build(cls, ckpt, num_labels, id2label, label2id, lambda_order=5.0):
|
| 48 |
+
config = PPDocLayoutV3Config.from_pretrained(
|
| 49 |
+
ckpt, num_labels=num_labels, id2label=id2label, label2id=label2id
|
| 50 |
+
)
|
| 51 |
+
config.lambda_order = lambda_order
|
| 52 |
+
config.loss_type = "RTDetrForObjectDetection"
|
| 53 |
+
# PP-DocLayoutV3's denoising path is buggy (embed size num_labels but pads with
|
| 54 |
+
# num_labels -> index error); it was never run since HF blocks training. Disable it
|
| 55 |
+
# (optional convergence aid). Re-enable later by resizing denoising_class_embed to +1.
|
| 56 |
+
config.num_denoising = 0
|
| 57 |
+
# RT-DETR loss/matcher fields the base config lacks
|
| 58 |
+
defaults = {
|
| 59 |
+
"use_focal_loss": True,
|
| 60 |
+
"auxiliary_loss": True,
|
| 61 |
+
"weight_loss_vfl": 1.0,
|
| 62 |
+
"weight_loss_bbox": 5.0,
|
| 63 |
+
"weight_loss_giou": 2.0,
|
| 64 |
+
"matcher_class_cost": 2.0,
|
| 65 |
+
"matcher_bbox_cost": 5.0,
|
| 66 |
+
"matcher_giou_cost": 2.0,
|
| 67 |
+
"matcher_alpha": 0.25,
|
| 68 |
+
"matcher_gamma": 2.0,
|
| 69 |
+
"focal_loss_alpha": 0.25,
|
| 70 |
+
"focal_loss_gamma": 2.0,
|
| 71 |
+
"eos_coefficient": 1e-4,
|
| 72 |
+
}
|
| 73 |
+
for k, v in defaults.items():
|
| 74 |
+
if not hasattr(config, k):
|
| 75 |
+
setattr(config, k, v)
|
| 76 |
+
model = cls.from_pretrained(ckpt, config=config, ignore_mismatched_sizes=True)
|
| 77 |
+
# re-init class heads (paddle doc classes -> our education classes); keep everything else
|
| 78 |
+
for m in model.modules():
|
| 79 |
+
if (isinstance(m, nn.Linear) and m.out_features == num_labels) or (
|
| 80 |
+
isinstance(m, nn.Embedding) and m.num_embeddings == num_labels + 1
|
| 81 |
+
):
|
| 82 |
+
m.reset_parameters()
|
| 83 |
+
return model
|
| 84 |
+
|
| 85 |
+
def _order_loss(self, order_logits, logits, pred_boxes, labels):
|
| 86 |
+
if self._matcher is None:
|
| 87 |
+
self._matcher = RTDetrHungarianMatcher(self.config) # needs scipy; training only
|
| 88 |
+
idx = self._matcher({"logits": logits, "pred_boxes": pred_boxes}, labels)
|
| 89 |
+
tot, n = 0.0, 0
|
| 90 |
+
for b, (src, tgt) in enumerate(idx):
|
| 91 |
+
if src.numel() < 2:
|
| 92 |
+
continue
|
| 93 |
+
order = labels[b]["reading_order"][tgt]
|
| 94 |
+
S = order_logits[b][src][:, src]
|
| 95 |
+
tot = tot + locality_gce(S, order)
|
| 96 |
+
n += 1
|
| 97 |
+
return tot / max(n, 1) if n else order_logits.sum() * 0.0
|
| 98 |
+
|
| 99 |
+
def forward(self, pixel_values, pixel_mask=None, labels=None, **kwargs):
|
| 100 |
+
outputs = self.model(pixel_values=pixel_values, pixel_mask=pixel_mask, labels=labels)
|
| 101 |
+
dn = outputs.denoising_meta_values if self.training else None
|
| 102 |
+
outputs_class = outputs.intermediate_logits
|
| 103 |
+
outputs_coord = outputs.intermediate_reference_points
|
| 104 |
+
logits, pred_boxes = outputs_class[:, -1], outputs_coord[:, -1]
|
| 105 |
+
order_logits = outputs.out_order_logits[:, -1] # [B, num_queries, num_queries]
|
| 106 |
+
loss = None
|
| 107 |
+
if labels is not None:
|
| 108 |
+
loss, _, _ = self.loss_function(
|
| 109 |
+
logits,
|
| 110 |
+
labels,
|
| 111 |
+
self.device,
|
| 112 |
+
pred_boxes,
|
| 113 |
+
self.config,
|
| 114 |
+
outputs_class,
|
| 115 |
+
outputs_coord,
|
| 116 |
+
enc_topk_logits=outputs.enc_topk_logits,
|
| 117 |
+
enc_topk_bboxes=outputs.enc_topk_bboxes,
|
| 118 |
+
denoising_meta_values=dn,
|
| 119 |
+
)
|
| 120 |
+
loss = loss + self.lambda_order * self._order_loss(
|
| 121 |
+
order_logits, logits, pred_boxes, labels
|
| 122 |
+
)
|
| 123 |
+
return PPDocOutput(
|
| 124 |
+
loss=loss,
|
| 125 |
+
logits=logits,
|
| 126 |
+
pred_boxes=pred_boxes,
|
| 127 |
+
order_logits=order_logits,
|
| 128 |
+
last_hidden_state=outputs.last_hidden_state,
|
| 129 |
+
)
|
idp_offline.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/engine/offline.py -- do not edit.
|
| 2 |
+
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
|
| 3 |
+
# whole install. See indic_doc_parser.py for usage.
|
| 4 |
+
|
| 5 |
+
"""The two stages, and the pipeline that runs both.
|
| 6 |
+
|
| 7 |
+
IndicDocLayout page image -> PageResult (blocks, no text)
|
| 8 |
+
IndicBlockOCR image+layout -> PageResult (blocks with text, plus markdown)
|
| 9 |
+
IndicDocParser page image -> both
|
| 10 |
+
|
| 11 |
+
Heavy imports live inside methods, so importing this module stays free.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import json
|
| 17 |
+
import os
|
| 18 |
+
|
| 19 |
+
from idp_blocks import resolve_nested_equations
|
| 20 |
+
from idp_reconstruct import reconstruct
|
| 21 |
+
from idp_types import (
|
| 22 |
+
CropConfig,
|
| 23 |
+
DedupConfig,
|
| 24 |
+
LayoutConfig,
|
| 25 |
+
PageResult,
|
| 26 |
+
RecognizerConfig,
|
| 27 |
+
)
|
| 28 |
+
from idp_contract import is_transcribed
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _open(image_path: str):
|
| 32 |
+
from PIL import Image
|
| 33 |
+
|
| 34 |
+
# Large scans and newspapers legitimately exceed PIL's decompression-bomb guard; layout
|
| 35 |
+
# resizes to img_size and crops are area-clamped, so compute stays bounded regardless.
|
| 36 |
+
Image.MAX_IMAGE_PIXELS = None
|
| 37 |
+
return Image.open(image_path).convert("RGB")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _as_page(layout: PageResult | dict | str) -> PageResult:
|
| 41 |
+
if isinstance(layout, PageResult):
|
| 42 |
+
return layout
|
| 43 |
+
if isinstance(layout, str):
|
| 44 |
+
with open(layout, encoding="utf-8") as fh:
|
| 45 |
+
layout = json.load(fh)
|
| 46 |
+
return PageResult.from_record(layout)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class IndicDocLayout:
|
| 50 |
+
"""Stage 1 -- layout and reading order. Loads torch only, never vLLM."""
|
| 51 |
+
|
| 52 |
+
def __init__(
|
| 53 |
+
self,
|
| 54 |
+
ckpt: str | None = None,
|
| 55 |
+
config: LayoutConfig | None = None,
|
| 56 |
+
dedup: DedupConfig | None = None,
|
| 57 |
+
backend=None,
|
| 58 |
+
) -> None:
|
| 59 |
+
if backend is None:
|
| 60 |
+
from idp_layout import IndicDocLayoutBackend
|
| 61 |
+
|
| 62 |
+
backend = IndicDocLayoutBackend(ckpt, config, dedup)
|
| 63 |
+
self.backend = backend
|
| 64 |
+
|
| 65 |
+
def detect(self, image_path: str) -> PageResult:
|
| 66 |
+
image = _open(image_path)
|
| 67 |
+
return PageResult(
|
| 68 |
+
image=os.path.basename(image_path),
|
| 69 |
+
width=image.width,
|
| 70 |
+
height=image.height,
|
| 71 |
+
blocks=self.backend.detect(image),
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
def close(self) -> None:
|
| 75 |
+
self.backend.close()
|
| 76 |
+
|
| 77 |
+
def __enter__(self) -> IndicDocLayout:
|
| 78 |
+
return self
|
| 79 |
+
|
| 80 |
+
def __exit__(self, *exc_info) -> None:
|
| 81 |
+
self.close()
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
class IndicBlockOCR:
|
| 85 |
+
"""Stage 2 -- per-block transcription against a layout, which may be your own."""
|
| 86 |
+
|
| 87 |
+
def __init__(
|
| 88 |
+
self,
|
| 89 |
+
ckpt: str | None = None,
|
| 90 |
+
config: RecognizerConfig | None = None,
|
| 91 |
+
dedup: DedupConfig | None = None,
|
| 92 |
+
crop: CropConfig | None = None,
|
| 93 |
+
backend=None,
|
| 94 |
+
) -> None:
|
| 95 |
+
self.config = config or RecognizerConfig()
|
| 96 |
+
self.dedup = dedup or DedupConfig()
|
| 97 |
+
self.crop = crop or CropConfig()
|
| 98 |
+
if backend is None:
|
| 99 |
+
from idp_recognizer import HfRecognizer
|
| 100 |
+
|
| 101 |
+
backend = HfRecognizer(ckpt, self.config)
|
| 102 |
+
self.backend = backend
|
| 103 |
+
|
| 104 |
+
def run(self, image_path: str, layout: PageResult | dict | str) -> PageResult:
|
| 105 |
+
"""Every block of the layout comes back, in its original order. Blocks that were not
|
| 106 |
+
transcribed carry ``text: ""`` rather than being dropped."""
|
| 107 |
+
from idp_recognizer import build_requests
|
| 108 |
+
|
| 109 |
+
image = _open(image_path)
|
| 110 |
+
page = _as_page(layout)
|
| 111 |
+
blocks = [b.copy() for b in page.blocks]
|
| 112 |
+
|
| 113 |
+
eligible = resolve_nested_equations(
|
| 114 |
+
[b for b in blocks if is_transcribed(b.label)], self.dedup
|
| 115 |
+
)
|
| 116 |
+
requests, orders = build_requests(eligible, image, self.crop, self.config.table_format)
|
| 117 |
+
texts = self.backend.transcribe(requests)
|
| 118 |
+
if len(texts) != len(orders):
|
| 119 |
+
raise RuntimeError(
|
| 120 |
+
f"recognizer returned {len(texts)} transcriptions for {len(orders)} crops; "
|
| 121 |
+
"results would be misaligned"
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
by_order = dict(zip(orders, texts, strict=True))
|
| 125 |
+
for block in blocks:
|
| 126 |
+
block.text = (by_order.get(block.order) or "").strip()
|
| 127 |
+
|
| 128 |
+
return PageResult(
|
| 129 |
+
image=page.image,
|
| 130 |
+
width=page.width,
|
| 131 |
+
height=page.height,
|
| 132 |
+
blocks=blocks,
|
| 133 |
+
markdown=reconstruct(blocks),
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
def close(self) -> None:
|
| 137 |
+
self.backend.close()
|
| 138 |
+
|
| 139 |
+
def __enter__(self) -> IndicBlockOCR:
|
| 140 |
+
return self
|
| 141 |
+
|
| 142 |
+
def __exit__(self, *exc_info) -> None:
|
| 143 |
+
self.close()
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
class IndicDocParser:
|
| 147 |
+
"""Both stages in one process."""
|
| 148 |
+
|
| 149 |
+
def __init__(
|
| 150 |
+
self,
|
| 151 |
+
layout_ckpt: str | None = None,
|
| 152 |
+
recognizer_ckpt: str | None = None,
|
| 153 |
+
layout_config: LayoutConfig | None = None,
|
| 154 |
+
recognizer_config: RecognizerConfig | None = None,
|
| 155 |
+
dedup: DedupConfig | None = None,
|
| 156 |
+
crop: CropConfig | None = None,
|
| 157 |
+
) -> None:
|
| 158 |
+
# vLLM FIRST. Its EngineCore forks/spawns at construction and must initialise CUDA
|
| 159 |
+
# before the torch layout model touches the device; reversed, the child cannot re-init.
|
| 160 |
+
self.ocr = IndicBlockOCR(recognizer_ckpt, recognizer_config, dedup, crop)
|
| 161 |
+
self.layout = IndicDocLayout(layout_ckpt, layout_config, dedup)
|
| 162 |
+
|
| 163 |
+
def detect(self, image_path: str) -> PageResult:
|
| 164 |
+
return self.layout.detect(image_path)
|
| 165 |
+
|
| 166 |
+
def parse(self, image_path: str) -> PageResult:
|
| 167 |
+
return self.ocr.run(image_path, self.layout.detect(image_path))
|
| 168 |
+
|
| 169 |
+
def close(self) -> None:
|
| 170 |
+
self.ocr.close()
|
| 171 |
+
self.layout.close()
|
| 172 |
+
|
| 173 |
+
def __enter__(self) -> IndicDocParser:
|
| 174 |
+
return self
|
| 175 |
+
|
| 176 |
+
def __exit__(self, *exc_info) -> None:
|
| 177 |
+
self.close()
|
idp_recognizer.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/engine/recognizer.py -- do not edit.
|
| 2 |
+
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
|
| 3 |
+
# whole install. See indic_doc_parser.py for usage.
|
| 4 |
+
|
| 5 |
+
"""IndicBlockOCR: crops in, transcriptions out.
|
| 6 |
+
|
| 7 |
+
Heavy imports live inside methods, so importing this module stays free -- asserted by
|
| 8 |
+
tests/ocr/test_ocr_lazy_import.py.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
from typing import TYPE_CHECKING, NamedTuple, Protocol, runtime_checkable
|
| 14 |
+
|
| 15 |
+
from idp_types import CropConfig, RecognizerConfig
|
| 16 |
+
|
| 17 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 18 |
+
from PIL.Image import Image
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class CropRequest(NamedTuple):
|
| 22 |
+
image: Image
|
| 23 |
+
prompt: str
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@runtime_checkable
|
| 27 |
+
class RecognizerBackend(Protocol):
|
| 28 |
+
"""``transcribe`` returns one string per request, in the same order."""
|
| 29 |
+
|
| 30 |
+
def transcribe(self, requests: list[CropRequest]) -> list[str]: ...
|
| 31 |
+
|
| 32 |
+
def close(self) -> None: ...
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def build_requests(blocks, page, crop_cfg: CropConfig, table_format) -> tuple[list, list]:
|
| 36 |
+
"""Crop each block and pair it with its prompt.
|
| 37 |
+
|
| 38 |
+
Returns ``(requests, orders)`` -- the reading-order rank of each request, so transcriptions
|
| 39 |
+
can be matched back. Blocks that yield no crop are simply absent from both.
|
| 40 |
+
"""
|
| 41 |
+
from idp_crops import area_clamp, crop_for
|
| 42 |
+
from idp_contract import prompt_for
|
| 43 |
+
|
| 44 |
+
requests, orders = [], []
|
| 45 |
+
for block in blocks:
|
| 46 |
+
crop = crop_for(block, page, crop_cfg)
|
| 47 |
+
if crop is None:
|
| 48 |
+
continue
|
| 49 |
+
requests.append(
|
| 50 |
+
CropRequest(area_clamp(crop, crop_cfg), prompt_for(block.type, table_format))
|
| 51 |
+
)
|
| 52 |
+
orders.append(block.order)
|
| 53 |
+
return requests, orders
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class HfRecognizer:
|
| 57 |
+
"""Reference recognizer on plain ``transformers`` -- no vLLM.
|
| 58 |
+
|
| 59 |
+
Exists so IndicDocParser can run anywhere ``transformers`` runs, including straight from the
|
| 60 |
+
Hub with ``trust_remote_code=True``. It is the *quickstart* path, not the working one:
|
| 61 |
+
without continuous batching it is orders of magnitude slower per block than
|
| 62 |
+
:class:`VllmRecognizer`, so use it to try a page, not to parse a corpus.
|
| 63 |
+
|
| 64 |
+
Output also diverges slightly from the vLLM path. Both decode greedily, but different kernels
|
| 65 |
+
give different logits, and a near-tie flips the argmax -- so do not expect byte-identical
|
| 66 |
+
transcriptions between the two backends.
|
| 67 |
+
"""
|
| 68 |
+
|
| 69 |
+
def __init__(
|
| 70 |
+
self,
|
| 71 |
+
ckpt: str | None = None,
|
| 72 |
+
config: RecognizerConfig | None = None,
|
| 73 |
+
device: str = "auto",
|
| 74 |
+
attn_implementation: str = "sdpa",
|
| 75 |
+
batch_size: int = 8,
|
| 76 |
+
) -> None:
|
| 77 |
+
import torch
|
| 78 |
+
from transformers import AutoModelForImageTextToText, AutoProcessor
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
self._torch = torch
|
| 82 |
+
self.config = config or RecognizerConfig()
|
| 83 |
+
# RecognizerConfig.batch_size sizes a vLLM chunk (~2048). Generating that many at once
|
| 84 |
+
# here would simply OOM; HF batches are bounded by memory, not by scheduler behaviour.
|
| 85 |
+
self.batch_size = batch_size
|
| 86 |
+
if ckpt is None:
|
| 87 |
+
raise ValueError(
|
| 88 |
+
"no recognizer weights given -- "
|
| 89 |
+
"IndicDocParser.from_pretrained(snapshot_download(REPO))"
|
| 90 |
+
)
|
| 91 |
+
self.ckpt = ckpt
|
| 92 |
+
|
| 93 |
+
self.processor = AutoProcessor.from_pretrained(self.ckpt)
|
| 94 |
+
tokenizer = self.processor.tokenizer
|
| 95 |
+
# Left padding so every sequence in a batch ends flush against the generation boundary.
|
| 96 |
+
tokenizer.padding_side = "left"
|
| 97 |
+
|
| 98 |
+
self.model = AutoModelForImageTextToText.from_pretrained(
|
| 99 |
+
self.ckpt,
|
| 100 |
+
dtype=getattr(torch, self.config.dtype),
|
| 101 |
+
device_map=device,
|
| 102 |
+
attn_implementation=attn_implementation,
|
| 103 |
+
)
|
| 104 |
+
self.model.eval()
|
| 105 |
+
|
| 106 |
+
# The checkpoint's generation_config carries eos_token_id 248044, which is an ordinary
|
| 107 |
+
# word piece, not a turn terminator. Left alone, generate() never stops and every block
|
| 108 |
+
# runs to max_new_tokens, repeating itself. vLLM does not hit this because it takes the
|
| 109 |
+
# tokenizer's EOS. Trust the tokenizer here too.
|
| 110 |
+
self.eos_token_id = tokenizer.eos_token_id
|
| 111 |
+
self.pad_token_id = tokenizer.pad_token_id or tokenizer.eos_token_id
|
| 112 |
+
|
| 113 |
+
def _prompt(self, text: str) -> str:
|
| 114 |
+
return self.processor.apply_chat_template(
|
| 115 |
+
[{"role": "user", "content": [{"type": "image"}, {"type": "text", "text": text}]}],
|
| 116 |
+
add_generation_prompt=True,
|
| 117 |
+
tokenize=False,
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
def transcribe(self, requests: list[CropRequest]) -> list[str]:
|
| 121 |
+
texts: list[str] = []
|
| 122 |
+
for i in range(0, len(requests), self.batch_size):
|
| 123 |
+
chunk = requests[i : i + self.batch_size]
|
| 124 |
+
inputs = self.processor(
|
| 125 |
+
text=[self._prompt(r.prompt) for r in chunk],
|
| 126 |
+
images=[r.image for r in chunk],
|
| 127 |
+
padding=True,
|
| 128 |
+
return_tensors="pt",
|
| 129 |
+
).to(self.model.device)
|
| 130 |
+
prompt_len = inputs["input_ids"].shape[-1]
|
| 131 |
+
|
| 132 |
+
with self._torch.inference_mode():
|
| 133 |
+
out = self.model.generate(
|
| 134 |
+
**inputs,
|
| 135 |
+
max_new_tokens=self.config.max_tokens,
|
| 136 |
+
do_sample=False,
|
| 137 |
+
use_cache=True,
|
| 138 |
+
eos_token_id=self.eos_token_id,
|
| 139 |
+
pad_token_id=self.pad_token_id,
|
| 140 |
+
)
|
| 141 |
+
texts.extend(self.processor.batch_decode(out[:, prompt_len:], skip_special_tokens=True))
|
| 142 |
+
return [t.strip() for t in texts]
|
| 143 |
+
|
| 144 |
+
def close(self) -> None:
|
| 145 |
+
self.model = None
|
| 146 |
+
self.processor = None
|
idp_reconstruct.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/engine/reconstruct.py -- do not edit.
|
| 2 |
+
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
|
| 3 |
+
# whole install. See indic_doc_parser.py for usage.
|
| 4 |
+
|
| 5 |
+
"""Assemble transcribed blocks into the page's markdown.
|
| 6 |
+
|
| 7 |
+
stdlib-only -- importable with no GPU stack and no PIL.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import re
|
| 13 |
+
|
| 14 |
+
from idp_types import Block
|
| 15 |
+
from idp_contract import DROP_TYPES
|
| 16 |
+
|
| 17 |
+
# Dashes as codepoints: several of the seven are indistinguishable in a source file.
|
| 18 |
+
# 002D HYPHEN-MINUS | 2010-2014 HYPHEN..EM DASH | 2212 MINUS SIGN
|
| 19 |
+
_HYPHEN_BREAK = re.compile("(\\w)[-\\u2010-\\u2014\\u2212]\\n[ \\t]*(\\w)")
|
| 20 |
+
_MATH_DELIMITERS = ("$", "\\[", "\\(")
|
| 21 |
+
|
| 22 |
+
# A math span: $$...$$ (display) or $...$ (inline).
|
| 23 |
+
_MATH_SPAN = re.compile(r"\$\$(.+?)\$\$|(?<!\$)\$(?!\$)([^$\n]+?)\$(?!\$)", re.S)
|
| 24 |
+
|
| 25 |
+
# Runs of the scripts IndicBlockOCR transcribes: Arabic (Urdu, Kashmiri, Sindhi),
|
| 26 |
+
# Devanagari..Malayalam, and Ol Chiki (Santali). Intervening spaces and the ZW(N)J joiners that
|
| 27 |
+
# Indic shaping relies on are kept inside the run so one run does not fragment into many.
|
| 28 |
+
# The run must both start and end on a script character, so a trailing space stays outside the
|
| 29 |
+
# \text{} and keeps separating it from what follows.
|
| 30 |
+
_NON_LATIN_RUN = re.compile(
|
| 31 |
+
"[\\u0600-\\u06ff\\u0900-\\u0d7f\\u1c50-\\u1c7f]"
|
| 32 |
+
"(?:[\\u0600-\\u06ff\\u0900-\\u0d7f\\u1c50-\\u1c7f \\u200c\\u200d]*"
|
| 33 |
+
"[\\u0600-\\u06ff\\u0900-\\u0d7f\\u1c50-\\u1c7f])?"
|
| 34 |
+
)
|
| 35 |
+
_TEXT_CMD = re.compile(r"\\text\{[^{}]*\}")
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def dehyphenate(text: str) -> str:
|
| 39 |
+
"""Rejoin words split by a hyphen at a line break.
|
| 40 |
+
|
| 41 |
+
Iterates to a fixpoint: re.sub matches non-overlappingly, so in "a-\\nb-\\nc" the first pass
|
| 42 |
+
consumes the "b" the second break needs.
|
| 43 |
+
"""
|
| 44 |
+
previous = None
|
| 45 |
+
while previous != text:
|
| 46 |
+
previous = text
|
| 47 |
+
text = _HYPHEN_BREAK.sub(r"\1\2", text)
|
| 48 |
+
return text
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _repair_expression(tex: str, display: bool) -> str:
|
| 52 |
+
"""Make one transcribed expression valid LaTeX.
|
| 53 |
+
|
| 54 |
+
Two repairs, both for things the recognizer emits that no LaTeX engine accepts:
|
| 55 |
+
|
| 56 |
+
* **Indic script in math mode.** ``$$প্রোটন = 9$$`` is invalid -- math mode has no glyphs for
|
| 57 |
+
those codepoints, so KaTeX, MathJax and a real TeX run all fail on it. Each run is wrapped
|
| 58 |
+
in ``\\text{}``, which is what the recognizer itself does when it gets it right. Runs
|
| 59 |
+
already inside ``\\text{}`` are left alone.
|
| 60 |
+
* **Bare newlines in display math**, which are a syntax error; ``\\\\`` is the row separator.
|
| 61 |
+
"""
|
| 62 |
+
protected: list[str] = []
|
| 63 |
+
|
| 64 |
+
def stash(match: re.Match) -> str:
|
| 65 |
+
protected.append(match.group(0))
|
| 66 |
+
return f"\x00{len(protected) - 1}\x00"
|
| 67 |
+
|
| 68 |
+
tex = _TEXT_CMD.sub(stash, tex)
|
| 69 |
+
tex = _NON_LATIN_RUN.sub(lambda m: f"\\text{{{m.group(0)}}}", tex)
|
| 70 |
+
tex = re.sub(r"\x00(\d+)\x00", lambda m: protected[int(m.group(1))], tex)
|
| 71 |
+
|
| 72 |
+
if display:
|
| 73 |
+
tex = re.sub(r"\s*\n\s*", r" \\\\ ", tex.strip())
|
| 74 |
+
return tex
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def repair_math(text: str) -> str:
|
| 78 |
+
"""Repair every math span in a markdown string. Prose outside ``$`` is untouched."""
|
| 79 |
+
|
| 80 |
+
def fix(match: re.Match) -> str:
|
| 81 |
+
display = match.group(1) is not None
|
| 82 |
+
inner = _repair_expression(match.group(1) or match.group(2), display)
|
| 83 |
+
return f"$${inner}$$" if display else f"${inner}$"
|
| 84 |
+
|
| 85 |
+
return _MATH_SPAN.sub(fix, text)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def reconstruct(blocks: list[Block], repair: bool = True) -> str:
|
| 89 |
+
"""Reading-ordered markdown. Blocks with no text contribute nothing but are not removed --
|
| 90 |
+
they still appear in the JSON with text "" .
|
| 91 |
+
|
| 92 |
+
``repair=False`` emits the recognizer's math verbatim, including expressions no LaTeX engine
|
| 93 |
+
can render. Only useful for comparing byte-for-byte against output produced before the repair
|
| 94 |
+
existed.
|
| 95 |
+
"""
|
| 96 |
+
kept = sorted((b for b in blocks if b.type not in DROP_TYPES), key=lambda b: b.order)
|
| 97 |
+
|
| 98 |
+
parts = []
|
| 99 |
+
for block in kept:
|
| 100 |
+
text = (block.text or "").strip()
|
| 101 |
+
if not text:
|
| 102 |
+
continue
|
| 103 |
+
# Bare LaTeX would render as literal source, so wrap it -- but only when the recognizer
|
| 104 |
+
# supplied no delimiters at all. Checking just the first character is not enough: an
|
| 105 |
+
# Equation block often comes back as a prose prefix followed by already-delimited math
|
| 106 |
+
# ("বা, $\\frac{a}{b}$"), and wrapping that produces "$$...$$$", which nothing can parse.
|
| 107 |
+
if block.type == "Equation" and "$" not in text and not text.startswith(_MATH_DELIMITERS):
|
| 108 |
+
text = f"$${text}$$"
|
| 109 |
+
parts.append(text)
|
| 110 |
+
|
| 111 |
+
markdown = dehyphenate("\n\n".join(parts))
|
| 112 |
+
return repair_math(markdown) if repair else markdown
|
idp_types.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GENERATED by hub/build_hub_package.py from src/bodhan_genai/ocr/engine/types.py -- do not edit.
|
| 2 |
+
# Vendored so this repo is self-contained: `pip install transformers torch pillow` is the
|
| 3 |
+
# whole install. See indic_doc_parser.py for usage.
|
| 4 |
+
|
| 5 |
+
"""Plain-data types and configuration.
|
| 6 |
+
|
| 7 |
+
Every tunable that used to be an environment variable read at import time lives here instead.
|
| 8 |
+
That fixes a real defect: callers corrected the old module-level defaults by setting os.environ
|
| 9 |
+
*before* importing the engine, two shipped callers set different values, and importing it
|
| 10 |
+
directly gave a third pipeline. These defaults ARE the canonical recipe.
|
| 11 |
+
|
| 12 |
+
stdlib-only -- importable with no GPU stack and no PIL.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import dataclasses
|
| 18 |
+
from dataclasses import dataclass, field
|
| 19 |
+
|
| 20 |
+
from idp_contract import TableFormat
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class LayoutSchemaError(ValueError):
|
| 24 |
+
"""An incoming layout record cannot be interpreted unambiguously.
|
| 25 |
+
|
| 26 |
+
Raised rather than warned because the alternative is silent and wrong. A label the taxonomy
|
| 27 |
+
does not contain falls through to ``Text``, so a table would be sent the prose prompt and
|
| 28 |
+
come back as flattened text with nothing in the output saying so. Our own detector only ever
|
| 29 |
+
emits ``layout.labels.CLASSES``, so an unrecognised label always means either a third-party
|
| 30 |
+
taxonomy -- which should declare ``type`` -- or a typo. Both are worth stopping for.
|
| 31 |
+
|
| 32 |
+
``map_label`` itself stays total: 19 of the 37 classes are deliberately absent from
|
| 33 |
+
``LABEL_TO_TYPE`` and correctly mean ``Text``, so leniency belongs there. Validation belongs
|
| 34 |
+
here, at the boundary where foreign JSON enters.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _unknown(value, candidates, kind: str) -> str:
|
| 39 |
+
"""``unknown label 'Tabel'; did you mean 'Table'?`` -- the suggestion is most of the value."""
|
| 40 |
+
import difflib
|
| 41 |
+
|
| 42 |
+
match = difflib.get_close_matches(str(value), list(candidates), n=1, cutoff=0.6)
|
| 43 |
+
hint = f"; did you mean {match[0]!r}?" if match else ""
|
| 44 |
+
return f"unknown {kind} {value!r}{hint}"
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@dataclass
|
| 48 |
+
class Block:
|
| 49 |
+
"""One detected region. ``text`` is None before OCR, and "" for blocks deliberately not
|
| 50 |
+
transcribed (kept in place rather than deleted)."""
|
| 51 |
+
|
| 52 |
+
order: int
|
| 53 |
+
label: str
|
| 54 |
+
type: str
|
| 55 |
+
bbox_xyxy: list[float]
|
| 56 |
+
conf: float
|
| 57 |
+
text: str | None = None
|
| 58 |
+
|
| 59 |
+
def as_record(self) -> dict:
|
| 60 |
+
# Key order is load-bearing: json.dump writes insertion order and the regression gate
|
| 61 |
+
# compares byte for byte. Do not reorder.
|
| 62 |
+
record: dict = {
|
| 63 |
+
"order": self.order,
|
| 64 |
+
"label": self.label,
|
| 65 |
+
"type": self.type,
|
| 66 |
+
"bbox_xyxy": [round(float(v), 1) for v in self.bbox_xyxy],
|
| 67 |
+
"conf": round(float(self.conf), 3),
|
| 68 |
+
}
|
| 69 |
+
if self.text is not None:
|
| 70 |
+
record["text"] = self.text
|
| 71 |
+
return record
|
| 72 |
+
|
| 73 |
+
@classmethod
|
| 74 |
+
def problems(cls, record: dict) -> list[str]:
|
| 75 |
+
"""Everything wrong with one incoming block record, as readable strings.
|
| 76 |
+
|
| 77 |
+
Returns rather than raises so a whole page can be reported at once. Fixing a 60-block
|
| 78 |
+
layout one exception at a time is the difference between one edit and sixty.
|
| 79 |
+
"""
|
| 80 |
+
from idp_model_labels import CLASSES
|
| 81 |
+
from idp_contract import DROP_TYPES, KEPT_BLOCK_TYPES
|
| 82 |
+
|
| 83 |
+
found: list[str] = []
|
| 84 |
+
for key in ("order", "bbox_xyxy"):
|
| 85 |
+
if key not in record:
|
| 86 |
+
found.append(f"missing required key {key!r}")
|
| 87 |
+
|
| 88 |
+
bbox = record.get("bbox_xyxy")
|
| 89 |
+
if bbox is not None:
|
| 90 |
+
try:
|
| 91 |
+
if len([float(v) for v in bbox]) != 4:
|
| 92 |
+
found.append(f"bbox_xyxy must be 4 numbers [x0, y0, x1, y1], got {bbox!r}")
|
| 93 |
+
except (TypeError, ValueError):
|
| 94 |
+
found.append(f"bbox_xyxy must be 4 numbers [x0, y0, x1, y1], got {bbox!r}")
|
| 95 |
+
|
| 96 |
+
# An explicit `type` is the documented escape hatch for a foreign taxonomy, so the label
|
| 97 |
+
# is not checked when one is given -- but the type itself is. Left unchecked, `Tabel`
|
| 98 |
+
# is accepted verbatim and never matches `Table`, which is the same silent failure one
|
| 99 |
+
# level up.
|
| 100 |
+
declared = record.get("type")
|
| 101 |
+
if declared:
|
| 102 |
+
valid = tuple(KEPT_BLOCK_TYPES) + tuple(sorted(DROP_TYPES))
|
| 103 |
+
if str(declared) not in valid:
|
| 104 |
+
found.append(_unknown(declared, valid, "type"))
|
| 105 |
+
else:
|
| 106 |
+
label = str(record.get("label", "")).strip()
|
| 107 |
+
if label.lower() not in {c.strip().lower() for c in CLASSES}:
|
| 108 |
+
found.append(
|
| 109 |
+
_unknown(label, CLASSES, "label")
|
| 110 |
+
+ ' -- use a spelling from layout.labels.CLASSES, or declare "type"'
|
| 111 |
+
" explicitly if your detector has its own taxonomy"
|
| 112 |
+
)
|
| 113 |
+
return found
|
| 114 |
+
|
| 115 |
+
@classmethod
|
| 116 |
+
def from_record(cls, record: dict, *, strict: bool = True) -> Block:
|
| 117 |
+
# `type` is derived when absent, so a third-party layout carrying only labels works.
|
| 118 |
+
from idp_contract import map_label
|
| 119 |
+
|
| 120 |
+
if strict:
|
| 121 |
+
found = cls.problems(record)
|
| 122 |
+
if found:
|
| 123 |
+
raise LayoutSchemaError("; ".join(found))
|
| 124 |
+
|
| 125 |
+
label = record.get("label", "")
|
| 126 |
+
return cls(
|
| 127 |
+
order=int(record["order"]),
|
| 128 |
+
label=str(label),
|
| 129 |
+
type=str(record.get("type") or map_label(label)),
|
| 130 |
+
bbox_xyxy=[float(v) for v in record["bbox_xyxy"]],
|
| 131 |
+
conf=float(record.get("conf", 1.0)),
|
| 132 |
+
text=record.get("text"),
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
def copy(self) -> Block:
|
| 136 |
+
return dataclasses.replace(self, bbox_xyxy=list(self.bbox_xyxy))
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
@dataclass
|
| 140 |
+
class PageResult:
|
| 141 |
+
image: str
|
| 142 |
+
width: int
|
| 143 |
+
height: int
|
| 144 |
+
blocks: list[Block] = field(default_factory=list)
|
| 145 |
+
markdown: str | None = None
|
| 146 |
+
|
| 147 |
+
def as_record(self) -> dict:
|
| 148 |
+
return {
|
| 149 |
+
"image": self.image,
|
| 150 |
+
"width": self.width,
|
| 151 |
+
"height": self.height,
|
| 152 |
+
"blocks": [b.as_record() for b in self.blocks],
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
@classmethod
|
| 156 |
+
def from_record(cls, record: dict, *, strict: bool = True) -> PageResult:
|
| 157 |
+
"""``strict=False`` replays a layout without validating it -- for reading back files
|
| 158 |
+
written before validation existed, not for new integrations."""
|
| 159 |
+
blocks = list(record.get("blocks", []))
|
| 160 |
+
|
| 161 |
+
if strict:
|
| 162 |
+
errors = [
|
| 163 |
+
f" block[{i}] (order={b.get('order', '?')!r}): {problem}"
|
| 164 |
+
for i, b in enumerate(blocks)
|
| 165 |
+
for problem in Block.problems(b)
|
| 166 |
+
]
|
| 167 |
+
if errors:
|
| 168 |
+
raise LayoutSchemaError(
|
| 169 |
+
f"{len(errors)} problem(s) in layout for "
|
| 170 |
+
f"{record.get('image', '<unknown>')!r}:\n" + "\n".join(errors)
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
return cls(
|
| 174 |
+
image=str(record["image"]),
|
| 175 |
+
width=int(record["width"]),
|
| 176 |
+
height=int(record["height"]),
|
| 177 |
+
# Already validated above; re-checking each block would repeat the work.
|
| 178 |
+
blocks=[Block.from_record(b, strict=False) for b in blocks],
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
@dataclass(frozen=True)
|
| 183 |
+
class LayoutConfig:
|
| 184 |
+
conf: float = 0.5 # below ~0.4, stains and page borders start scoring as blocks
|
| 185 |
+
img_size: int = 1024
|
| 186 |
+
device: str = "cuda"
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
@dataclass(frozen=True)
|
| 190 |
+
class CropConfig:
|
| 191 |
+
"""The clamp is on area, not on a side: pinning a side exploded elongated crops (a 122:1
|
| 192 |
+
rule line became ~32k image tokens and wedged the engine)."""
|
| 193 |
+
|
| 194 |
+
min_px_side: int = 256 # largest single measured win; 0 disables upscaling
|
| 195 |
+
max_px_side: int = 1536 # token ceiling
|
| 196 |
+
pad_px: int = 0
|
| 197 |
+
|
| 198 |
+
@property
|
| 199 |
+
def min_px(self) -> int:
|
| 200 |
+
return self.min_px_side**2
|
| 201 |
+
|
| 202 |
+
@property
|
| 203 |
+
def max_px(self) -> int:
|
| 204 |
+
return self.max_px_side**2
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
#: both -- text-like OR a larger Equation | text_only -- text-like only | eq_only -- Equation only
|
| 208 |
+
DEDUP_MODES = ("both", "text_only", "eq_only")
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
@dataclass(frozen=True)
|
| 212 |
+
class DedupConfig:
|
| 213 |
+
"""IndicDocLayout over-produces equation boxes nested inside the paragraphs and display
|
| 214 |
+
arrays that already contain them; transcribing both emits the same math twice."""
|
| 215 |
+
|
| 216 |
+
nest: bool = True
|
| 217 |
+
mode: str = "both"
|
| 218 |
+
contain: float = 0.90 # duplicate threshold in clean_layout
|
| 219 |
+
wrap: float = 0.5 # a header counts as occupied at this containment
|
| 220 |
+
nested: float = 0.70 # nested-equation threshold
|
| 221 |
+
|
| 222 |
+
def __post_init__(self) -> None:
|
| 223 |
+
if self.mode not in DEDUP_MODES:
|
| 224 |
+
raise ValueError(f"DedupConfig.mode must be one of {DEDUP_MODES}, got {self.mode!r}")
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
@dataclass(frozen=True)
|
| 228 |
+
class RecognizerConfig:
|
| 229 |
+
max_model_len: int = 8192
|
| 230 |
+
max_tokens: int = 2048
|
| 231 |
+
temperature: float = 0.0 # greedy: the only setting reproducible run to run
|
| 232 |
+
gpu_memory_utilization: float = 0.80
|
| 233 |
+
dtype: str = "bfloat16"
|
| 234 |
+
# One giant batch over tens of thousands of multi-modal requests wedges the vLLM V1
|
| 235 |
+
# scheduler at 100% util with no progress; ~2k chunks run clean.
|
| 236 |
+
batch_size: int = 2048
|
| 237 |
+
enforce_eager: bool = True # skips ~4 min of torch.compile on a 0.8B model
|
| 238 |
+
table_format: TableFormat = TableFormat.HTML
|
| 239 |
+
|
| 240 |
+
def merged(self, **overrides) -> RecognizerConfig:
|
| 241 |
+
"""New config with non-None overrides applied; unknown keys raise."""
|
| 242 |
+
known = {f.name for f in dataclasses.fields(self)}
|
| 243 |
+
unknown = sorted(set(overrides) - known)
|
| 244 |
+
if unknown:
|
| 245 |
+
raise TypeError(f"Unknown RecognizerConfig field(s): {', '.join(unknown)}")
|
| 246 |
+
return dataclasses.replace(self, **{k: v for k, v in overrides.items() if v is not None})
|
indic_doc_parser.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""IndicDocParser -- page image in, reading-ordered Markdown and per-block JSON out.
|
| 2 |
+
|
| 3 |
+
import sys
|
| 4 |
+
from huggingface_hub import snapshot_download
|
| 5 |
+
|
| 6 |
+
repo = snapshot_download("bodhan-ai/indic-doc-parser")
|
| 7 |
+
sys.path.insert(0, repo) # the code ships in the repo
|
| 8 |
+
from indic_doc_parser import IndicDocParser
|
| 9 |
+
|
| 10 |
+
parser = IndicDocParser.from_pretrained(repo)
|
| 11 |
+
print(parser("page.png")) # markdown
|
| 12 |
+
|
| 13 |
+
``sys.path.insert`` is needed because ``snapshot_download`` returns a cache directory, which is
|
| 14 |
+
not importable on its own. With the path added, this is ordinary Python -- no
|
| 15 |
+
``trust_remote_code``, and nothing of ours to install.
|
| 16 |
+
|
| 17 |
+
The two stages are also public, and each runs on its own:
|
| 18 |
+
|
| 19 |
+
IndicDocLayout(f"{repo}/weights/layout").detect("page.png") # blocks and reading order
|
| 20 |
+
IndicBlockOCR(f"{repo}/weights/ocr").run("page.png", layout) # layout may be your own
|
| 21 |
+
|
| 22 |
+
``layout`` there can be a PageResult, a dict, or the path to a layout JSON file, so the stages
|
| 23 |
+
compose across processes and you can correct a layout before transcribing it.
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
from __future__ import annotations
|
| 27 |
+
|
| 28 |
+
import importlib
|
| 29 |
+
from pathlib import Path
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _preflight() -> None:
|
| 33 |
+
"""One clear error instead of a cascade of ImportErrors from inside transformers.
|
| 34 |
+
|
| 35 |
+
Every failure here is one someone has actually hit: a torch built for a CUDA line the driver
|
| 36 |
+
cannot run (silently CPU), a torchvision that does not match its torch, or a transformers
|
| 37 |
+
older than PPDocLayoutV3.
|
| 38 |
+
"""
|
| 39 |
+
fix = "./install.sh (or see the Installation section of the model card)"
|
| 40 |
+
for name, floor in (("torch", (2, 4)), ("transformers", (5, 7))):
|
| 41 |
+
try:
|
| 42 |
+
mod = importlib.import_module(name)
|
| 43 |
+
except ImportError:
|
| 44 |
+
raise ImportError(f"IndicDocParser needs {name}. Run: {fix}") from None
|
| 45 |
+
got = tuple(int(p) for p in mod.__version__.split(".")[:2] if p.isdigit())
|
| 46 |
+
if got < floor:
|
| 47 |
+
raise ImportError(
|
| 48 |
+
f"IndicDocParser needs {name}>={'.'.join(map(str, floor))}, "
|
| 49 |
+
f"found {mod.__version__}. Run: {fix}"
|
| 50 |
+
)
|
| 51 |
+
# torchvision is stage 2 only, so its absence is not fatal here -- the layout stage runs
|
| 52 |
+
# without it. It is checked where the recognizer is built.
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
_preflight()
|
| 56 |
+
|
| 57 |
+
# ruff: noqa: E402 -- the preflight has to run before transformers is imported, which is the
|
| 58 |
+
# whole point of it; these imports pull transformers in transitively.
|
| 59 |
+
from idp_offline import IndicBlockOCR, IndicDocLayout
|
| 60 |
+
from idp_types import CropConfig, DedupConfig, LayoutConfig, RecognizerConfig, TableFormat
|
| 61 |
+
|
| 62 |
+
#: The two stages are public: either runs on its own, and IndicBlockOCR accepts a layout you
|
| 63 |
+
#: produced or corrected yourself.
|
| 64 |
+
__all__ = ["IndicBlockOCR", "IndicDocLayout", "IndicDocParser"]
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class IndicDocParser:
|
| 68 |
+
"""Both stages. Construct with :meth:`from_pretrained`, then call it on a page image."""
|
| 69 |
+
|
| 70 |
+
def __init__(
|
| 71 |
+
self,
|
| 72 |
+
path: str | Path,
|
| 73 |
+
device: str = "cuda",
|
| 74 |
+
table_format: str = "html",
|
| 75 |
+
dedup_mode: str = "both",
|
| 76 |
+
contain: float = 0.90,
|
| 77 |
+
min_px_side: int = 256,
|
| 78 |
+
max_new_tokens: int = 2048,
|
| 79 |
+
) -> None:
|
| 80 |
+
self.path = Path(path)
|
| 81 |
+
self._device = device
|
| 82 |
+
self._layout_cfg = LayoutConfig(device=device)
|
| 83 |
+
self._dedup = DedupConfig(mode=dedup_mode, contain=contain)
|
| 84 |
+
self._crop = CropConfig(min_px_side=min_px_side)
|
| 85 |
+
self._rec_cfg = RecognizerConfig(
|
| 86 |
+
max_tokens=max_new_tokens, table_format=TableFormat(table_format)
|
| 87 |
+
)
|
| 88 |
+
# Both stages are built on first use, so detect() never loads the 1.7 GB recognizer.
|
| 89 |
+
self._layout = None
|
| 90 |
+
self._ocr = None
|
| 91 |
+
|
| 92 |
+
@classmethod
|
| 93 |
+
def from_pretrained(cls, path: str | Path | None = None, **kwargs) -> IndicDocParser:
|
| 94 |
+
"""Load from a downloaded snapshot. Defaults to the directory this file lives in, which
|
| 95 |
+
is the snapshot itself -- so ``IndicDocParser.from_pretrained()`` also works."""
|
| 96 |
+
return cls(Path(path) if path else Path(__file__).resolve().parent, **kwargs)
|
| 97 |
+
|
| 98 |
+
# -- stages ------------------------------------------------------------ #
|
| 99 |
+
|
| 100 |
+
@property
|
| 101 |
+
def layout(self):
|
| 102 |
+
if self._layout is None:
|
| 103 |
+
self._layout = IndicDocLayout(
|
| 104 |
+
ckpt=str(self.path / "weights" / "layout"),
|
| 105 |
+
config=self._layout_cfg,
|
| 106 |
+
dedup=self._dedup,
|
| 107 |
+
)
|
| 108 |
+
return self._layout
|
| 109 |
+
|
| 110 |
+
@property
|
| 111 |
+
def recognizer(self):
|
| 112 |
+
if self._ocr is None:
|
| 113 |
+
if importlib.util.find_spec("torchvision") is None:
|
| 114 |
+
raise ImportError(
|
| 115 |
+
"The recognizer needs torchvision (its image processor uses it); the layout "
|
| 116 |
+
"stage does not, so detect() still works. Install it together with torch, "
|
| 117 |
+
"from the same index -- torchvision pins an exact torch version. See "
|
| 118 |
+
"./install.sh"
|
| 119 |
+
)
|
| 120 |
+
from idp_recognizer import HfRecognizer
|
| 121 |
+
|
| 122 |
+
self._ocr = IndicBlockOCR(
|
| 123 |
+
backend=HfRecognizer(
|
| 124 |
+
ckpt=str(self.path / "weights" / "ocr"),
|
| 125 |
+
config=self._rec_cfg,
|
| 126 |
+
device=self._device,
|
| 127 |
+
),
|
| 128 |
+
config=self._rec_cfg,
|
| 129 |
+
dedup=self._dedup,
|
| 130 |
+
crop=self._crop,
|
| 131 |
+
)
|
| 132 |
+
return self._ocr
|
| 133 |
+
|
| 134 |
+
# -- public API -------------------------------------------------------- #
|
| 135 |
+
|
| 136 |
+
def detect(self, image_path: str) -> dict:
|
| 137 |
+
"""Stage 1 only -- blocks, labels, reading order. Loads no recognizer."""
|
| 138 |
+
return self.layout.detect(image_path).as_record()
|
| 139 |
+
|
| 140 |
+
def parse(self, image_path: str) -> dict:
|
| 141 |
+
"""Both stages -> ``{image, width, height, blocks, markdown}``."""
|
| 142 |
+
page = self.recognizer.run(image_path, self.layout.detect(image_path))
|
| 143 |
+
return {**page.as_record(), "markdown": page.markdown}
|
| 144 |
+
|
| 145 |
+
def __call__(self, image_path: str) -> str:
|
| 146 |
+
"""The markdown for a page. ``parse()`` if you also want the per-block JSON."""
|
| 147 |
+
return self.parse(image_path)["markdown"]
|
install.sh
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Install IndicDocParser's dependencies into the active Python environment.
|
| 3 |
+
#
|
| 4 |
+
# ./install.sh # pick the CUDA wheels from your driver
|
| 5 |
+
# IDP_CUDA=cu121 ./install.sh # force a CUDA line
|
| 6 |
+
# IDP_CUDA=cpu ./install.sh # CPU only -- fine for layout, slow for the recognizer
|
| 7 |
+
#
|
| 8 |
+
# The one thing pip cannot work out for itself is which CUDA build you need. `pip install torch`
|
| 9 |
+
# takes the newest wheel, which on an older driver initialises to CPU with nothing but a warning --
|
| 10 |
+
# you get a working install that silently never touches the GPU. And torchvision pins an exact
|
| 11 |
+
# torch, so resolving the two separately is how you end up with a mismatched pair and a run of
|
| 12 |
+
# import errors. This installs both together, from one index.
|
| 13 |
+
set -euo pipefail
|
| 14 |
+
|
| 15 |
+
cd "$(dirname "$0")"
|
| 16 |
+
PY=${PYTHON:-python3}
|
| 17 |
+
|
| 18 |
+
# `uv venv` creates environments without pip, so `$PY -m pip` is not a safe assumption. Prefer
|
| 19 |
+
# whatever the environment actually has; uv is also just faster.
|
| 20 |
+
if command -v uv >/dev/null 2>&1; then
|
| 21 |
+
pip_install() { uv pip install --python "$PY" "$@"; }
|
| 22 |
+
elif "$PY" -m pip --version >/dev/null 2>&1; then
|
| 23 |
+
pip_install() { "$PY" -m pip install "$@"; }
|
| 24 |
+
elif "$PY" -m ensurepip --upgrade >/dev/null 2>&1; then
|
| 25 |
+
pip_install() { "$PY" -m pip install "$@"; }
|
| 26 |
+
else
|
| 27 |
+
echo "no installer found for $PY -- install pip (python -m ensurepip) or uv, then re-run" >&2
|
| 28 |
+
exit 1
|
| 29 |
+
fi
|
| 30 |
+
|
| 31 |
+
detect_cuda() {
|
| 32 |
+
command -v nvidia-smi >/dev/null 2>&1 || { echo cpu; return; }
|
| 33 |
+
local v
|
| 34 |
+
v=$(nvidia-smi 2>/dev/null | sed -n 's/.*CUDA Version: \([0-9]*\)\.\([0-9]*\).*/\1/p' | head -1)
|
| 35 |
+
case "$v" in
|
| 36 |
+
# CUDA 12.x drivers run any cu12x wheel (minor version compatibility), so one build covers
|
| 37 |
+
# the whole line. A 13.x wheel on a 12.x driver does NOT work -- that is the trap.
|
| 38 |
+
12) echo cu124 ;;
|
| 39 |
+
13|1[4-9]) echo cu130 ;;
|
| 40 |
+
"") echo cpu ;;
|
| 41 |
+
*) echo cu124 ;;
|
| 42 |
+
esac
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
CUDA=${IDP_CUDA:-$(detect_cuda)}
|
| 46 |
+
INDEX=https://download.pytorch.org/whl/$CUDA
|
| 47 |
+
|
| 48 |
+
echo "==> target: $CUDA"
|
| 49 |
+
[ "$CUDA" = cpu ] && echo " (no GPU detected -- layout runs fine on CPU, the recognizer is slow)"
|
| 50 |
+
|
| 51 |
+
echo "==> torch + torchvision, together, from $INDEX"
|
| 52 |
+
pip_install torch torchvision --index-url "$INDEX"
|
| 53 |
+
|
| 54 |
+
echo "==> everything else"
|
| 55 |
+
pip_install -r requirements.txt
|
| 56 |
+
|
| 57 |
+
echo "==> checking"
|
| 58 |
+
$PY - <<'PY'
|
| 59 |
+
import torch, torchvision, transformers
|
| 60 |
+
print(f" torch {torch.__version__}")
|
| 61 |
+
print(f" torchvision {torchvision.__version__}")
|
| 62 |
+
print(f" transformers {transformers.__version__}")
|
| 63 |
+
print(f" cuda {'yes, ' + torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'NO -- running on CPU'}")
|
| 64 |
+
import sys; sys.path.insert(0, ".")
|
| 65 |
+
from indic_doc_parser import IndicDocParser # noqa: F401
|
| 66 |
+
print(" import ok")
|
| 67 |
+
PY
|
| 68 |
+
echo "==> done"
|
requirements.txt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# The model code ships in this repo; these are its only third-party dependencies.
|
| 2 |
+
#
|
| 3 |
+
# Install torch and torchvision TOGETHER, from the index for your CUDA version -- torchvision pins
|
| 4 |
+
# an exact torch, so resolving them separately is how you end up with a mismatched pair:
|
| 5 |
+
#
|
| 6 |
+
# pip install torch torchvision --index-url https://download.pytorch.org/whl/cu124
|
| 7 |
+
# pip install -r requirements.txt
|
| 8 |
+
#
|
| 9 |
+
# ./install.sh does this for you.
|
| 10 |
+
|
| 11 |
+
torch>=2.4 # transformers 5.7 floor
|
| 12 |
+
torchvision # stage 2 only; MUST match torch (see above)
|
| 13 |
+
transformers>=5.7 # PPDocLayoutV3
|
| 14 |
+
accelerate>=1.1
|
| 15 |
+
numpy>=1.17
|
| 16 |
+
pillow>=10.0.1
|
| 17 |
+
huggingface_hub>=1.0
|
| 18 |
+
|
| 19 |
+
# scipy is NOT needed for inference. Finetuning the detector needs it (the Hungarian matcher).
|
schemas/layout_output.schema.json
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
| 3 |
+
"$id": "https://huggingface.co/bodhan-ai/indic-doc-parser/raw/main/schemas/layout_output.schema.json",
|
| 4 |
+
"title": "Layout JSON (stage-1 output, stage-2 input)",
|
| 5 |
+
"description": "Blocks carry no 'text' key. Absence means not transcribed yet, which is distinct from text: \"\" in a parsed page.",
|
| 6 |
+
"type": "object",
|
| 7 |
+
"required": [
|
| 8 |
+
"image",
|
| 9 |
+
"width",
|
| 10 |
+
"height",
|
| 11 |
+
"blocks"
|
| 12 |
+
],
|
| 13 |
+
"properties": {
|
| 14 |
+
"image": {
|
| 15 |
+
"type": "string"
|
| 16 |
+
},
|
| 17 |
+
"width": {
|
| 18 |
+
"type": "integer",
|
| 19 |
+
"exclusiveMinimum": 0
|
| 20 |
+
},
|
| 21 |
+
"height": {
|
| 22 |
+
"type": "integer",
|
| 23 |
+
"exclusiveMinimum": 0
|
| 24 |
+
},
|
| 25 |
+
"blocks": {
|
| 26 |
+
"type": "array",
|
| 27 |
+
"items": {
|
| 28 |
+
"$ref": "#/$defs/block"
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
},
|
| 32 |
+
"$defs": {
|
| 33 |
+
"block": {
|
| 34 |
+
"type": "object",
|
| 35 |
+
"required": [
|
| 36 |
+
"order",
|
| 37 |
+
"bbox_xyxy"
|
| 38 |
+
],
|
| 39 |
+
"properties": {
|
| 40 |
+
"order": {
|
| 41 |
+
"type": "integer",
|
| 42 |
+
"minimum": 0,
|
| 43 |
+
"description": "Reading-order rank. Gap-free and 0-based across the page."
|
| 44 |
+
},
|
| 45 |
+
"label": {
|
| 46 |
+
"type": "string",
|
| 47 |
+
"description": "IndicDocLayout class. Restricted to the taxonomy unless 'type' is given."
|
| 48 |
+
},
|
| 49 |
+
"type": {
|
| 50 |
+
"type": "string",
|
| 51 |
+
"enum": [
|
| 52 |
+
"Text",
|
| 53 |
+
"Title",
|
| 54 |
+
"SectionHeader",
|
| 55 |
+
"Table",
|
| 56 |
+
"Equation",
|
| 57 |
+
"Caption",
|
| 58 |
+
"Footnote",
|
| 59 |
+
"PageHeader",
|
| 60 |
+
"PageFooter",
|
| 61 |
+
"PageNumber",
|
| 62 |
+
"Figure",
|
| 63 |
+
"Picture"
|
| 64 |
+
],
|
| 65 |
+
"description": "Pipeline category; selects the prompt. Derived from 'label' when omitted."
|
| 66 |
+
},
|
| 67 |
+
"bbox_xyxy": {
|
| 68 |
+
"type": "array",
|
| 69 |
+
"items": {
|
| 70 |
+
"type": "number"
|
| 71 |
+
},
|
| 72 |
+
"minItems": 4,
|
| 73 |
+
"maxItems": 4,
|
| 74 |
+
"description": "Pixel [x0, y0, x1, y1], clamped to the page."
|
| 75 |
+
},
|
| 76 |
+
"conf": {
|
| 77 |
+
"type": "number",
|
| 78 |
+
"minimum": 0,
|
| 79 |
+
"maximum": 1
|
| 80 |
+
}
|
| 81 |
+
},
|
| 82 |
+
"allOf": [
|
| 83 |
+
{
|
| 84 |
+
"if": {
|
| 85 |
+
"not": {
|
| 86 |
+
"required": [
|
| 87 |
+
"type"
|
| 88 |
+
]
|
| 89 |
+
}
|
| 90 |
+
},
|
| 91 |
+
"then": {
|
| 92 |
+
"required": [
|
| 93 |
+
"label"
|
| 94 |
+
],
|
| 95 |
+
"properties": {
|
| 96 |
+
"label": {
|
| 97 |
+
"enum": [
|
| 98 |
+
"Question",
|
| 99 |
+
"Paragraph",
|
| 100 |
+
"Answer",
|
| 101 |
+
"List",
|
| 102 |
+
"Title",
|
| 103 |
+
"Section-title",
|
| 104 |
+
"Equation",
|
| 105 |
+
"Table",
|
| 106 |
+
"Diagram",
|
| 107 |
+
"Image",
|
| 108 |
+
"MCQ",
|
| 109 |
+
"Infobox",
|
| 110 |
+
"Sub-section-title",
|
| 111 |
+
"Expression",
|
| 112 |
+
"Image-caption",
|
| 113 |
+
"Placeholder-text",
|
| 114 |
+
"Chart",
|
| 115 |
+
"Solved-example",
|
| 116 |
+
"Footnote",
|
| 117 |
+
"Table-caption",
|
| 118 |
+
"Sub-sub-section-title",
|
| 119 |
+
"Footer",
|
| 120 |
+
"Header",
|
| 121 |
+
"Code",
|
| 122 |
+
"Page-number",
|
| 123 |
+
"Chapter-title",
|
| 124 |
+
"Chapter-end-section",
|
| 125 |
+
"Folio",
|
| 126 |
+
"Reference",
|
| 127 |
+
"Table-of-contents",
|
| 128 |
+
"Index",
|
| 129 |
+
"Advertisement",
|
| 130 |
+
"Author",
|
| 131 |
+
"Dateline",
|
| 132 |
+
"Contact-info",
|
| 133 |
+
"Website-link",
|
| 134 |
+
"Flag"
|
| 135 |
+
]
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
],
|
| 141 |
+
"not": {
|
| 142 |
+
"required": [
|
| 143 |
+
"text"
|
| 144 |
+
]
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
}
|
schemas/parse_output.schema.json
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
| 3 |
+
"$id": "https://huggingface.co/bodhan-ai/indic-doc-parser/raw/main/schemas/parse_output.schema.json",
|
| 4 |
+
"title": "Parsed page JSON (final output)",
|
| 5 |
+
"description": "Every block carries 'text'.",
|
| 6 |
+
"type": "object",
|
| 7 |
+
"required": [
|
| 8 |
+
"image",
|
| 9 |
+
"width",
|
| 10 |
+
"height",
|
| 11 |
+
"blocks"
|
| 12 |
+
],
|
| 13 |
+
"properties": {
|
| 14 |
+
"image": {
|
| 15 |
+
"type": "string"
|
| 16 |
+
},
|
| 17 |
+
"width": {
|
| 18 |
+
"type": "integer",
|
| 19 |
+
"exclusiveMinimum": 0
|
| 20 |
+
},
|
| 21 |
+
"height": {
|
| 22 |
+
"type": "integer",
|
| 23 |
+
"exclusiveMinimum": 0
|
| 24 |
+
},
|
| 25 |
+
"blocks": {
|
| 26 |
+
"type": "array",
|
| 27 |
+
"items": {
|
| 28 |
+
"$ref": "#/$defs/block"
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
},
|
| 32 |
+
"$defs": {
|
| 33 |
+
"block": {
|
| 34 |
+
"type": "object",
|
| 35 |
+
"required": [
|
| 36 |
+
"order",
|
| 37 |
+
"bbox_xyxy",
|
| 38 |
+
"text"
|
| 39 |
+
],
|
| 40 |
+
"properties": {
|
| 41 |
+
"order": {
|
| 42 |
+
"type": "integer",
|
| 43 |
+
"minimum": 0,
|
| 44 |
+
"description": "Reading-order rank. Gap-free and 0-based across the page."
|
| 45 |
+
},
|
| 46 |
+
"label": {
|
| 47 |
+
"type": "string",
|
| 48 |
+
"description": "IndicDocLayout class. Restricted to the taxonomy unless 'type' is given."
|
| 49 |
+
},
|
| 50 |
+
"type": {
|
| 51 |
+
"type": "string",
|
| 52 |
+
"enum": [
|
| 53 |
+
"Text",
|
| 54 |
+
"Title",
|
| 55 |
+
"SectionHeader",
|
| 56 |
+
"Table",
|
| 57 |
+
"Equation",
|
| 58 |
+
"Caption",
|
| 59 |
+
"Footnote",
|
| 60 |
+
"PageHeader",
|
| 61 |
+
"PageFooter",
|
| 62 |
+
"PageNumber",
|
| 63 |
+
"Figure",
|
| 64 |
+
"Picture"
|
| 65 |
+
],
|
| 66 |
+
"description": "Pipeline category; selects the prompt. Derived from 'label' when omitted."
|
| 67 |
+
},
|
| 68 |
+
"bbox_xyxy": {
|
| 69 |
+
"type": "array",
|
| 70 |
+
"items": {
|
| 71 |
+
"type": "number"
|
| 72 |
+
},
|
| 73 |
+
"minItems": 4,
|
| 74 |
+
"maxItems": 4,
|
| 75 |
+
"description": "Pixel [x0, y0, x1, y1], clamped to the page."
|
| 76 |
+
},
|
| 77 |
+
"conf": {
|
| 78 |
+
"type": "number",
|
| 79 |
+
"minimum": 0,
|
| 80 |
+
"maximum": 1
|
| 81 |
+
},
|
| 82 |
+
"text": {
|
| 83 |
+
"type": "string",
|
| 84 |
+
"description": "Transcription. \"\" means detected but deliberately not sent to the recognizer; the block is kept, not dropped."
|
| 85 |
+
}
|
| 86 |
+
},
|
| 87 |
+
"allOf": [
|
| 88 |
+
{
|
| 89 |
+
"if": {
|
| 90 |
+
"not": {
|
| 91 |
+
"required": [
|
| 92 |
+
"type"
|
| 93 |
+
]
|
| 94 |
+
}
|
| 95 |
+
},
|
| 96 |
+
"then": {
|
| 97 |
+
"required": [
|
| 98 |
+
"label"
|
| 99 |
+
],
|
| 100 |
+
"properties": {
|
| 101 |
+
"label": {
|
| 102 |
+
"enum": [
|
| 103 |
+
"Question",
|
| 104 |
+
"Paragraph",
|
| 105 |
+
"Answer",
|
| 106 |
+
"List",
|
| 107 |
+
"Title",
|
| 108 |
+
"Section-title",
|
| 109 |
+
"Equation",
|
| 110 |
+
"Table",
|
| 111 |
+
"Diagram",
|
| 112 |
+
"Image",
|
| 113 |
+
"MCQ",
|
| 114 |
+
"Infobox",
|
| 115 |
+
"Sub-section-title",
|
| 116 |
+
"Expression",
|
| 117 |
+
"Image-caption",
|
| 118 |
+
"Placeholder-text",
|
| 119 |
+
"Chart",
|
| 120 |
+
"Solved-example",
|
| 121 |
+
"Footnote",
|
| 122 |
+
"Table-caption",
|
| 123 |
+
"Sub-sub-section-title",
|
| 124 |
+
"Footer",
|
| 125 |
+
"Header",
|
| 126 |
+
"Code",
|
| 127 |
+
"Page-number",
|
| 128 |
+
"Chapter-title",
|
| 129 |
+
"Chapter-end-section",
|
| 130 |
+
"Folio",
|
| 131 |
+
"Reference",
|
| 132 |
+
"Table-of-contents",
|
| 133 |
+
"Index",
|
| 134 |
+
"Advertisement",
|
| 135 |
+
"Author",
|
| 136 |
+
"Dateline",
|
| 137 |
+
"Contact-info",
|
| 138 |
+
"Website-link",
|
| 139 |
+
"Flag"
|
| 140 |
+
]
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
]
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
}
|
weights/layout/config.json
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"activation_dropout": 0.0,
|
| 3 |
+
"activation_function": "silu",
|
| 4 |
+
"anchor_image_size": null,
|
| 5 |
+
"architectures": [
|
| 6 |
+
"PPDocLayoutV3Trainable"
|
| 7 |
+
],
|
| 8 |
+
"attention_dropout": 0.0,
|
| 9 |
+
"auxiliary_loss": true,
|
| 10 |
+
"backbone": null,
|
| 11 |
+
"backbone_config": {
|
| 12 |
+
"arch": "L",
|
| 13 |
+
"depths": [
|
| 14 |
+
3,
|
| 15 |
+
4,
|
| 16 |
+
6,
|
| 17 |
+
3
|
| 18 |
+
],
|
| 19 |
+
"dtype": "float32",
|
| 20 |
+
"embedding_size": 64,
|
| 21 |
+
"freeze_at": 0,
|
| 22 |
+
"freeze_norm": true,
|
| 23 |
+
"freeze_stem_only": true,
|
| 24 |
+
"hidden_act": "relu",
|
| 25 |
+
"hidden_sizes": [
|
| 26 |
+
256,
|
| 27 |
+
512,
|
| 28 |
+
1024,
|
| 29 |
+
2048
|
| 30 |
+
],
|
| 31 |
+
"initializer_range": 0.02,
|
| 32 |
+
"lr_mult_list": [
|
| 33 |
+
0,
|
| 34 |
+
0.05,
|
| 35 |
+
0.05,
|
| 36 |
+
0.05,
|
| 37 |
+
0.05
|
| 38 |
+
],
|
| 39 |
+
"model_type": "hgnet_v2",
|
| 40 |
+
"num_channels": 3,
|
| 41 |
+
"out_features": [
|
| 42 |
+
"stage1",
|
| 43 |
+
"stage2",
|
| 44 |
+
"stage3",
|
| 45 |
+
"stage4"
|
| 46 |
+
],
|
| 47 |
+
"out_indices": [
|
| 48 |
+
1,
|
| 49 |
+
2,
|
| 50 |
+
3,
|
| 51 |
+
4
|
| 52 |
+
],
|
| 53 |
+
"return_idx": [
|
| 54 |
+
0,
|
| 55 |
+
1,
|
| 56 |
+
2,
|
| 57 |
+
3
|
| 58 |
+
],
|
| 59 |
+
"stage_downsample": [
|
| 60 |
+
false,
|
| 61 |
+
true,
|
| 62 |
+
true,
|
| 63 |
+
true
|
| 64 |
+
],
|
| 65 |
+
"stage_downsample_strides": [
|
| 66 |
+
2,
|
| 67 |
+
2,
|
| 68 |
+
2,
|
| 69 |
+
2
|
| 70 |
+
],
|
| 71 |
+
"stage_in_channels": [
|
| 72 |
+
48,
|
| 73 |
+
128,
|
| 74 |
+
512,
|
| 75 |
+
1024
|
| 76 |
+
],
|
| 77 |
+
"stage_kernel_size": [
|
| 78 |
+
3,
|
| 79 |
+
3,
|
| 80 |
+
5,
|
| 81 |
+
5
|
| 82 |
+
],
|
| 83 |
+
"stage_light_block": [
|
| 84 |
+
false,
|
| 85 |
+
false,
|
| 86 |
+
true,
|
| 87 |
+
true
|
| 88 |
+
],
|
| 89 |
+
"stage_mid_channels": [
|
| 90 |
+
48,
|
| 91 |
+
96,
|
| 92 |
+
192,
|
| 93 |
+
384
|
| 94 |
+
],
|
| 95 |
+
"stage_names": [
|
| 96 |
+
"stem",
|
| 97 |
+
"stage1",
|
| 98 |
+
"stage2",
|
| 99 |
+
"stage3",
|
| 100 |
+
"stage4"
|
| 101 |
+
],
|
| 102 |
+
"stage_num_blocks": [
|
| 103 |
+
1,
|
| 104 |
+
1,
|
| 105 |
+
3,
|
| 106 |
+
1
|
| 107 |
+
],
|
| 108 |
+
"stage_numb_of_layers": [
|
| 109 |
+
6,
|
| 110 |
+
6,
|
| 111 |
+
6,
|
| 112 |
+
6
|
| 113 |
+
],
|
| 114 |
+
"stage_out_channels": [
|
| 115 |
+
128,
|
| 116 |
+
512,
|
| 117 |
+
1024,
|
| 118 |
+
2048
|
| 119 |
+
],
|
| 120 |
+
"stem_channels": [
|
| 121 |
+
3,
|
| 122 |
+
32,
|
| 123 |
+
48
|
| 124 |
+
],
|
| 125 |
+
"stem_strides": [
|
| 126 |
+
2,
|
| 127 |
+
1,
|
| 128 |
+
1,
|
| 129 |
+
2,
|
| 130 |
+
1
|
| 131 |
+
],
|
| 132 |
+
"use_learnable_affine_block": false
|
| 133 |
+
},
|
| 134 |
+
"batch_norm_eps": 1e-05,
|
| 135 |
+
"box_noise_scale": 1.0,
|
| 136 |
+
"d_model": 256,
|
| 137 |
+
"decoder_activation_function": "relu",
|
| 138 |
+
"decoder_attention_heads": 8,
|
| 139 |
+
"decoder_ffn_dim": 1024,
|
| 140 |
+
"decoder_in_channels": [
|
| 141 |
+
256,
|
| 142 |
+
256,
|
| 143 |
+
256
|
| 144 |
+
],
|
| 145 |
+
"decoder_layers": 6,
|
| 146 |
+
"decoder_n_points": 4,
|
| 147 |
+
"disable_custom_kernels": true,
|
| 148 |
+
"dropout": 0.0,
|
| 149 |
+
"dtype": "float32",
|
| 150 |
+
"encode_proj_layers": [
|
| 151 |
+
2
|
| 152 |
+
],
|
| 153 |
+
"encoder_activation_function": "gelu",
|
| 154 |
+
"encoder_attention_heads": 8,
|
| 155 |
+
"encoder_ffn_dim": 1024,
|
| 156 |
+
"encoder_hidden_dim": 256,
|
| 157 |
+
"encoder_in_channels": [
|
| 158 |
+
512,
|
| 159 |
+
1024,
|
| 160 |
+
2048
|
| 161 |
+
],
|
| 162 |
+
"encoder_layers": 1,
|
| 163 |
+
"eos_coefficient": 0.0001,
|
| 164 |
+
"eval_size": null,
|
| 165 |
+
"feat_strides": [
|
| 166 |
+
8,
|
| 167 |
+
16,
|
| 168 |
+
32
|
| 169 |
+
],
|
| 170 |
+
"feature_strides": [
|
| 171 |
+
8,
|
| 172 |
+
16,
|
| 173 |
+
32
|
| 174 |
+
],
|
| 175 |
+
"focal_loss_alpha": 0.25,
|
| 176 |
+
"focal_loss_gamma": 2.0,
|
| 177 |
+
"freeze_backbone_batch_norms": true,
|
| 178 |
+
"global_pointer_head_size": 64,
|
| 179 |
+
"gp_dropout_value": 0.1,
|
| 180 |
+
"hidden_expansion": 1.0,
|
| 181 |
+
"id2label": {
|
| 182 |
+
"0": "Question",
|
| 183 |
+
"1": "Paragraph",
|
| 184 |
+
"2": "Answer",
|
| 185 |
+
"3": "List",
|
| 186 |
+
"4": "Title",
|
| 187 |
+
"5": "Section-title",
|
| 188 |
+
"6": "Equation",
|
| 189 |
+
"7": "Table",
|
| 190 |
+
"8": "Diagram",
|
| 191 |
+
"9": "Image",
|
| 192 |
+
"10": "MCQ",
|
| 193 |
+
"11": "Infobox",
|
| 194 |
+
"12": "Sub-section-title",
|
| 195 |
+
"13": "Expression",
|
| 196 |
+
"14": "Image-caption",
|
| 197 |
+
"15": "Placeholder-text",
|
| 198 |
+
"16": "Chart",
|
| 199 |
+
"17": "Solved-example",
|
| 200 |
+
"18": "Footnote",
|
| 201 |
+
"19": "Table-caption",
|
| 202 |
+
"20": "Sub-sub-section-title",
|
| 203 |
+
"21": "Footer",
|
| 204 |
+
"22": "Header",
|
| 205 |
+
"23": "Code",
|
| 206 |
+
"24": "Page-number",
|
| 207 |
+
"25": "Chapter-title",
|
| 208 |
+
"26": "Chapter-end-section",
|
| 209 |
+
"27": "Folio",
|
| 210 |
+
"28": "Reference",
|
| 211 |
+
"29": "Table-of-contents",
|
| 212 |
+
"30": "Index",
|
| 213 |
+
"31": "Advertisement",
|
| 214 |
+
"32": "Author",
|
| 215 |
+
"33": "Dateline",
|
| 216 |
+
"34": "Contact-info",
|
| 217 |
+
"35": "Website-link",
|
| 218 |
+
"36": "Flag"
|
| 219 |
+
},
|
| 220 |
+
"initializer_bias_prior_prob": null,
|
| 221 |
+
"initializer_range": 0.01,
|
| 222 |
+
"is_encoder_decoder": true,
|
| 223 |
+
"label2id": {
|
| 224 |
+
"Advertisement": 31,
|
| 225 |
+
"Answer": 2,
|
| 226 |
+
"Author": 32,
|
| 227 |
+
"Chapter-end-section": 26,
|
| 228 |
+
"Chapter-title": 25,
|
| 229 |
+
"Chart": 16,
|
| 230 |
+
"Code": 23,
|
| 231 |
+
"Contact-info": 34,
|
| 232 |
+
"Dateline": 33,
|
| 233 |
+
"Diagram": 8,
|
| 234 |
+
"Equation": 6,
|
| 235 |
+
"Expression": 13,
|
| 236 |
+
"Flag": 36,
|
| 237 |
+
"Folio": 27,
|
| 238 |
+
"Footer": 21,
|
| 239 |
+
"Footnote": 18,
|
| 240 |
+
"Header": 22,
|
| 241 |
+
"Image": 9,
|
| 242 |
+
"Image-caption": 14,
|
| 243 |
+
"Index": 30,
|
| 244 |
+
"Infobox": 11,
|
| 245 |
+
"List": 3,
|
| 246 |
+
"MCQ": 10,
|
| 247 |
+
"Page-number": 24,
|
| 248 |
+
"Paragraph": 1,
|
| 249 |
+
"Placeholder-text": 15,
|
| 250 |
+
"Question": 0,
|
| 251 |
+
"Reference": 28,
|
| 252 |
+
"Section-title": 5,
|
| 253 |
+
"Solved-example": 17,
|
| 254 |
+
"Sub-section-title": 12,
|
| 255 |
+
"Sub-sub-section-title": 20,
|
| 256 |
+
"Table": 7,
|
| 257 |
+
"Table-caption": 19,
|
| 258 |
+
"Table-of-contents": 29,
|
| 259 |
+
"Title": 4,
|
| 260 |
+
"Website-link": 35
|
| 261 |
+
},
|
| 262 |
+
"label_noise_ratio": 0.5,
|
| 263 |
+
"lambda_order": 5.0,
|
| 264 |
+
"layer_norm_eps": 1e-05,
|
| 265 |
+
"learn_initial_query": false,
|
| 266 |
+
"loss_type": "RTDetrForObjectDetection",
|
| 267 |
+
"mask_enhanced": true,
|
| 268 |
+
"mask_feature_channels": [
|
| 269 |
+
64,
|
| 270 |
+
64
|
| 271 |
+
],
|
| 272 |
+
"matcher_alpha": 0.25,
|
| 273 |
+
"matcher_bbox_cost": 5.0,
|
| 274 |
+
"matcher_class_cost": 2.0,
|
| 275 |
+
"matcher_gamma": 2.0,
|
| 276 |
+
"matcher_giou_cost": 2.0,
|
| 277 |
+
"model_type": "pp_doclayout_v3",
|
| 278 |
+
"normalize_before": false,
|
| 279 |
+
"num_denoising": 0,
|
| 280 |
+
"num_feature_levels": 3,
|
| 281 |
+
"num_prototypes": 32,
|
| 282 |
+
"num_queries": 300,
|
| 283 |
+
"positional_encoding_temperature": 10000,
|
| 284 |
+
"tie_word_embeddings": true,
|
| 285 |
+
"transformers_version": "5.8.1",
|
| 286 |
+
"use_cache": false,
|
| 287 |
+
"use_focal_loss": true,
|
| 288 |
+
"weight_loss_bbox": 5.0,
|
| 289 |
+
"weight_loss_giou": 2.0,
|
| 290 |
+
"weight_loss_vfl": 1.0,
|
| 291 |
+
"x4_feat_dim": 128
|
| 292 |
+
}
|
weights/layout/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:051fa9741b713f164eef74ef25b88defa87670ab64995cfe431d413685bf0a3f
|
| 3 |
+
size 133301428
|
weights/ocr/chat_template.jinja
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{%- set image_count = namespace(value=0) %}
|
| 2 |
+
{%- set video_count = namespace(value=0) %}
|
| 3 |
+
{%- macro render_content(content, do_vision_count, is_system_content=false) %}
|
| 4 |
+
{%- if content is string %}
|
| 5 |
+
{{- content }}
|
| 6 |
+
{%- elif content is iterable and content is not mapping %}
|
| 7 |
+
{%- for item in content %}
|
| 8 |
+
{%- if 'image' in item or 'image_url' in item or item.type == 'image' %}
|
| 9 |
+
{%- if is_system_content %}
|
| 10 |
+
{{- raise_exception('System message cannot contain images.') }}
|
| 11 |
+
{%- endif %}
|
| 12 |
+
{%- if do_vision_count %}
|
| 13 |
+
{%- set image_count.value = image_count.value + 1 %}
|
| 14 |
+
{%- endif %}
|
| 15 |
+
{%- if add_vision_id %}
|
| 16 |
+
{{- 'Picture ' ~ image_count.value ~ ': ' }}
|
| 17 |
+
{%- endif %}
|
| 18 |
+
{{- '<|vision_start|><|image_pad|><|vision_end|>' }}
|
| 19 |
+
{%- elif 'video' in item or item.type == 'video' %}
|
| 20 |
+
{%- if is_system_content %}
|
| 21 |
+
{{- raise_exception('System message cannot contain videos.') }}
|
| 22 |
+
{%- endif %}
|
| 23 |
+
{%- if do_vision_count %}
|
| 24 |
+
{%- set video_count.value = video_count.value + 1 %}
|
| 25 |
+
{%- endif %}
|
| 26 |
+
{%- if add_vision_id %}
|
| 27 |
+
{{- 'Video ' ~ video_count.value ~ ': ' }}
|
| 28 |
+
{%- endif %}
|
| 29 |
+
{{- '<|vision_start|><|video_pad|><|vision_end|>' }}
|
| 30 |
+
{%- elif 'text' in item %}
|
| 31 |
+
{{- item.text }}
|
| 32 |
+
{%- else %}
|
| 33 |
+
{{- raise_exception('Unexpected item type in content.') }}
|
| 34 |
+
{%- endif %}
|
| 35 |
+
{%- endfor %}
|
| 36 |
+
{%- elif content is none or content is undefined %}
|
| 37 |
+
{{- '' }}
|
| 38 |
+
{%- else %}
|
| 39 |
+
{{- raise_exception('Unexpected content type.') }}
|
| 40 |
+
{%- endif %}
|
| 41 |
+
{%- endmacro %}
|
| 42 |
+
{%- if not messages %}
|
| 43 |
+
{{- raise_exception('No messages provided.') }}
|
| 44 |
+
{%- endif %}
|
| 45 |
+
{%- if tools and tools is iterable and tools is not mapping %}
|
| 46 |
+
{{- '<|im_start|>system\n' }}
|
| 47 |
+
{{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
|
| 48 |
+
{%- for tool in tools %}
|
| 49 |
+
{{- "\n" }}
|
| 50 |
+
{{- tool | tojson }}
|
| 51 |
+
{%- endfor %}
|
| 52 |
+
{{- "\n</tools>" }}
|
| 53 |
+
{{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
|
| 54 |
+
{%- if messages[0].role == 'system' %}
|
| 55 |
+
{%- set content = render_content(messages[0].content, false, true)|trim %}
|
| 56 |
+
{%- if content %}
|
| 57 |
+
{{- '\n\n' + content }}
|
| 58 |
+
{%- endif %}
|
| 59 |
+
{%- endif %}
|
| 60 |
+
{{- '<|im_end|>\n' }}
|
| 61 |
+
{%- else %}
|
| 62 |
+
{%- if messages[0].role == 'system' %}
|
| 63 |
+
{%- set content = render_content(messages[0].content, false, true)|trim %}
|
| 64 |
+
{{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
|
| 65 |
+
{%- endif %}
|
| 66 |
+
{%- endif %}
|
| 67 |
+
{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
|
| 68 |
+
{%- for message in messages[::-1] %}
|
| 69 |
+
{%- set index = (messages|length - 1) - loop.index0 %}
|
| 70 |
+
{%- if ns.multi_step_tool and message.role == "user" %}
|
| 71 |
+
{%- set content = render_content(message.content, false)|trim %}
|
| 72 |
+
{%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
|
| 73 |
+
{%- set ns.multi_step_tool = false %}
|
| 74 |
+
{%- set ns.last_query_index = index %}
|
| 75 |
+
{%- endif %}
|
| 76 |
+
{%- endif %}
|
| 77 |
+
{%- endfor %}
|
| 78 |
+
{%- if ns.multi_step_tool %}
|
| 79 |
+
{{- raise_exception('No user query found in messages.') }}
|
| 80 |
+
{%- endif %}
|
| 81 |
+
{%- for message in messages %}
|
| 82 |
+
{%- set content = render_content(message.content, true)|trim %}
|
| 83 |
+
{%- if message.role == "system" %}
|
| 84 |
+
{%- if not loop.first %}
|
| 85 |
+
{{- raise_exception('System message must be at the beginning.') }}
|
| 86 |
+
{%- endif %}
|
| 87 |
+
{%- elif message.role == "user" %}
|
| 88 |
+
{{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
|
| 89 |
+
{%- elif message.role == "assistant" %}
|
| 90 |
+
{%- set reasoning_content = '' %}
|
| 91 |
+
{%- if message.reasoning_content is string %}
|
| 92 |
+
{%- set reasoning_content = message.reasoning_content %}
|
| 93 |
+
{%- else %}
|
| 94 |
+
{%- if '</think>' in content %}
|
| 95 |
+
{%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
|
| 96 |
+
{%- set content = content.split('</think>')[-1].lstrip('\n') %}
|
| 97 |
+
{%- endif %}
|
| 98 |
+
{%- endif %}
|
| 99 |
+
{%- set reasoning_content = reasoning_content|trim %}
|
| 100 |
+
{%- if loop.index0 > ns.last_query_index %}
|
| 101 |
+
{{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
|
| 102 |
+
{%- else %}
|
| 103 |
+
{{- '<|im_start|>' + message.role + '\n' + content }}
|
| 104 |
+
{%- endif %}
|
| 105 |
+
{%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
|
| 106 |
+
{%- for tool_call in message.tool_calls %}
|
| 107 |
+
{%- if tool_call.function is defined %}
|
| 108 |
+
{%- set tool_call = tool_call.function %}
|
| 109 |
+
{%- endif %}
|
| 110 |
+
{%- if loop.first %}
|
| 111 |
+
{%- if content|trim %}
|
| 112 |
+
{{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 113 |
+
{%- else %}
|
| 114 |
+
{{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 115 |
+
{%- endif %}
|
| 116 |
+
{%- else %}
|
| 117 |
+
{{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 118 |
+
{%- endif %}
|
| 119 |
+
{%- if tool_call.arguments is defined %}
|
| 120 |
+
{%- for args_name, args_value in tool_call.arguments|items %}
|
| 121 |
+
{{- '<parameter=' + args_name + '>\n' }}
|
| 122 |
+
{%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
|
| 123 |
+
{{- args_value }}
|
| 124 |
+
{{- '\n</parameter>\n' }}
|
| 125 |
+
{%- endfor %}
|
| 126 |
+
{%- endif %}
|
| 127 |
+
{{- '</function>\n</tool_call>' }}
|
| 128 |
+
{%- endfor %}
|
| 129 |
+
{%- endif %}
|
| 130 |
+
{{- '<|im_end|>\n' }}
|
| 131 |
+
{%- elif message.role == "tool" %}
|
| 132 |
+
{%- if loop.previtem and loop.previtem.role != "tool" %}
|
| 133 |
+
{{- '<|im_start|>user' }}
|
| 134 |
+
{%- endif %}
|
| 135 |
+
{{- '\n<tool_response>\n' }}
|
| 136 |
+
{{- content }}
|
| 137 |
+
{{- '\n</tool_response>' }}
|
| 138 |
+
{%- if not loop.last and loop.nextitem.role != "tool" %}
|
| 139 |
+
{{- '<|im_end|>\n' }}
|
| 140 |
+
{%- elif loop.last %}
|
| 141 |
+
{{- '<|im_end|>\n' }}
|
| 142 |
+
{%- endif %}
|
| 143 |
+
{%- else %}
|
| 144 |
+
{{- raise_exception('Unexpected message role.') }}
|
| 145 |
+
{%- endif %}
|
| 146 |
+
{%- endfor %}
|
| 147 |
+
{%- if add_generation_prompt %}
|
| 148 |
+
{{- '<|im_start|>assistant\n' }}
|
| 149 |
+
{%- if enable_thinking is defined and enable_thinking is true %}
|
| 150 |
+
{{- '<think>\n' }}
|
| 151 |
+
{%- else %}
|
| 152 |
+
{{- '<think>\n\n</think>\n\n' }}
|
| 153 |
+
{%- endif %}
|
| 154 |
+
{%- endif %}
|
weights/ocr/config.json
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_name_or_path": "/projects/data/visionteam/sherry/qwen08b/ckpts/merges_v5/S2b",
|
| 3 |
+
"architectures": [
|
| 4 |
+
"Qwen3_5ForConditionalGeneration"
|
| 5 |
+
],
|
| 6 |
+
"chunk_size_feed_forward": 0,
|
| 7 |
+
"dtype": "bfloat16",
|
| 8 |
+
"id2label": {
|
| 9 |
+
"0": "LABEL_0",
|
| 10 |
+
"1": "LABEL_1"
|
| 11 |
+
},
|
| 12 |
+
"image_token_id": 262155,
|
| 13 |
+
"is_encoder_decoder": false,
|
| 14 |
+
"label2id": {
|
| 15 |
+
"LABEL_0": 0,
|
| 16 |
+
"LABEL_1": 1
|
| 17 |
+
},
|
| 18 |
+
"model_type": "qwen3_5",
|
| 19 |
+
"output_attentions": false,
|
| 20 |
+
"output_hidden_states": false,
|
| 21 |
+
"problem_type": null,
|
| 22 |
+
"return_dict": true,
|
| 23 |
+
"text_config": {
|
| 24 |
+
"_name_or_path": "",
|
| 25 |
+
"architectures": null,
|
| 26 |
+
"attention_bias": false,
|
| 27 |
+
"attention_dropout": 0.0,
|
| 28 |
+
"attn_output_gate": true,
|
| 29 |
+
"bos_token_id": null,
|
| 30 |
+
"chunk_size_feed_forward": 0,
|
| 31 |
+
"dtype": "bfloat16",
|
| 32 |
+
"eos_token_id": 262146,
|
| 33 |
+
"full_attention_interval": 4,
|
| 34 |
+
"head_dim": 256,
|
| 35 |
+
"hidden_act": "silu",
|
| 36 |
+
"hidden_size": 1024,
|
| 37 |
+
"id2label": {
|
| 38 |
+
"0": "LABEL_0",
|
| 39 |
+
"1": "LABEL_1"
|
| 40 |
+
},
|
| 41 |
+
"initializer_range": 0.02,
|
| 42 |
+
"intermediate_size": 3584,
|
| 43 |
+
"is_encoder_decoder": false,
|
| 44 |
+
"label2id": {
|
| 45 |
+
"LABEL_0": 0,
|
| 46 |
+
"LABEL_1": 1
|
| 47 |
+
},
|
| 48 |
+
"layer_types": [
|
| 49 |
+
"linear_attention",
|
| 50 |
+
"linear_attention",
|
| 51 |
+
"linear_attention",
|
| 52 |
+
"full_attention",
|
| 53 |
+
"linear_attention",
|
| 54 |
+
"linear_attention",
|
| 55 |
+
"linear_attention",
|
| 56 |
+
"full_attention",
|
| 57 |
+
"linear_attention",
|
| 58 |
+
"linear_attention",
|
| 59 |
+
"linear_attention",
|
| 60 |
+
"full_attention",
|
| 61 |
+
"linear_attention",
|
| 62 |
+
"linear_attention",
|
| 63 |
+
"linear_attention",
|
| 64 |
+
"full_attention",
|
| 65 |
+
"linear_attention",
|
| 66 |
+
"linear_attention",
|
| 67 |
+
"linear_attention",
|
| 68 |
+
"full_attention",
|
| 69 |
+
"linear_attention",
|
| 70 |
+
"linear_attention",
|
| 71 |
+
"linear_attention",
|
| 72 |
+
"full_attention"
|
| 73 |
+
],
|
| 74 |
+
"linear_conv_kernel_dim": 4,
|
| 75 |
+
"linear_key_head_dim": 128,
|
| 76 |
+
"linear_num_key_heads": 16,
|
| 77 |
+
"linear_num_value_heads": 16,
|
| 78 |
+
"linear_value_head_dim": 128,
|
| 79 |
+
"mamba_ssm_dtype": "float32",
|
| 80 |
+
"max_position_embeddings": 262144,
|
| 81 |
+
"mlp_only_layers": [],
|
| 82 |
+
"model_type": "qwen3_5_text",
|
| 83 |
+
"mtp_num_hidden_layers": 1,
|
| 84 |
+
"mtp_use_dedicated_embeddings": false,
|
| 85 |
+
"num_attention_heads": 8,
|
| 86 |
+
"num_hidden_layers": 24,
|
| 87 |
+
"num_key_value_heads": 2,
|
| 88 |
+
"output_attentions": false,
|
| 89 |
+
"output_hidden_states": false,
|
| 90 |
+
"pad_token_id": 262144,
|
| 91 |
+
"partial_rotary_factor": 0.25,
|
| 92 |
+
"problem_type": null,
|
| 93 |
+
"return_dict": true,
|
| 94 |
+
"rms_norm_eps": 1e-06,
|
| 95 |
+
"rope_parameters": {
|
| 96 |
+
"mrope_interleaved": true,
|
| 97 |
+
"mrope_section": [
|
| 98 |
+
11,
|
| 99 |
+
11,
|
| 100 |
+
10
|
| 101 |
+
],
|
| 102 |
+
"partial_rotary_factor": 0.25,
|
| 103 |
+
"rope_theta": 10000000,
|
| 104 |
+
"rope_type": "default"
|
| 105 |
+
},
|
| 106 |
+
"tie_word_embeddings": true,
|
| 107 |
+
"use_cache": false,
|
| 108 |
+
"vocab_size": 262157
|
| 109 |
+
},
|
| 110 |
+
"tie_word_embeddings": true,
|
| 111 |
+
"transformers_version": "5.6.2",
|
| 112 |
+
"use_cache": false,
|
| 113 |
+
"video_token_id": 262156,
|
| 114 |
+
"vision_config": {
|
| 115 |
+
"_name_or_path": "",
|
| 116 |
+
"architectures": null,
|
| 117 |
+
"chunk_size_feed_forward": 0,
|
| 118 |
+
"deepstack_visual_indexes": [],
|
| 119 |
+
"depth": 12,
|
| 120 |
+
"dtype": "bfloat16",
|
| 121 |
+
"hidden_act": "gelu_pytorch_tanh",
|
| 122 |
+
"hidden_size": 768,
|
| 123 |
+
"id2label": {
|
| 124 |
+
"0": "LABEL_0",
|
| 125 |
+
"1": "LABEL_1"
|
| 126 |
+
},
|
| 127 |
+
"in_channels": 3,
|
| 128 |
+
"initializer_range": 0.02,
|
| 129 |
+
"intermediate_size": 3072,
|
| 130 |
+
"is_encoder_decoder": false,
|
| 131 |
+
"label2id": {
|
| 132 |
+
"LABEL_0": 0,
|
| 133 |
+
"LABEL_1": 1
|
| 134 |
+
},
|
| 135 |
+
"model_type": "qwen3_5_vision",
|
| 136 |
+
"num_heads": 12,
|
| 137 |
+
"num_position_embeddings": 2304,
|
| 138 |
+
"out_hidden_size": 1024,
|
| 139 |
+
"output_attentions": false,
|
| 140 |
+
"output_hidden_states": false,
|
| 141 |
+
"patch_size": 16,
|
| 142 |
+
"problem_type": null,
|
| 143 |
+
"return_dict": true,
|
| 144 |
+
"spatial_merge_size": 2,
|
| 145 |
+
"temporal_patch_size": 2
|
| 146 |
+
},
|
| 147 |
+
"vision_end_token_id": 262154,
|
| 148 |
+
"vision_start_token_id": 262153
|
| 149 |
+
}
|
weights/ocr/generation_config.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_from_model_config": true,
|
| 3 |
+
"eos_token_id": 262146,
|
| 4 |
+
"transformers_version": "5.6.2",
|
| 5 |
+
"use_cache": true
|
| 6 |
+
}
|
weights/ocr/model-00001-of-00001.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:40f6a5ce321d65ce18db429c9e8c8b2d2d22d4ec7d71737d150cc0a6edf9ae5f
|
| 3 |
+
size 1734372747
|
weights/ocr/model.safetensors.index.json
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"metadata": {
|
| 3 |
+
"total_size": 1734310016
|
| 4 |
+
},
|
| 5 |
+
"weight_map": {
|
| 6 |
+
"model.language_model.layers.0.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 7 |
+
"model.language_model.layers.0.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 8 |
+
"model.language_model.layers.1.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 9 |
+
"model.language_model.layers.1.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 10 |
+
"model.language_model.layers.2.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 11 |
+
"model.language_model.layers.2.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 12 |
+
"model.language_model.layers.4.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 13 |
+
"model.language_model.layers.4.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 14 |
+
"model.language_model.layers.5.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 15 |
+
"model.language_model.layers.5.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 16 |
+
"model.language_model.layers.6.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 17 |
+
"model.language_model.layers.6.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 18 |
+
"model.language_model.layers.8.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 19 |
+
"model.language_model.layers.8.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 20 |
+
"model.language_model.layers.9.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 21 |
+
"model.language_model.layers.9.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 22 |
+
"model.language_model.layers.10.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 23 |
+
"model.language_model.layers.10.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 24 |
+
"model.language_model.layers.12.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 25 |
+
"model.language_model.layers.12.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 26 |
+
"model.language_model.layers.13.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 27 |
+
"model.language_model.layers.13.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 28 |
+
"model.language_model.layers.14.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 29 |
+
"model.language_model.layers.14.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 30 |
+
"model.language_model.layers.16.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 31 |
+
"model.language_model.layers.16.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 32 |
+
"model.language_model.layers.17.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 33 |
+
"model.language_model.layers.17.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 34 |
+
"model.language_model.layers.18.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 35 |
+
"model.language_model.layers.18.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 36 |
+
"model.language_model.layers.20.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 37 |
+
"model.language_model.layers.20.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 38 |
+
"model.language_model.layers.21.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 39 |
+
"model.language_model.layers.21.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 40 |
+
"model.language_model.layers.22.linear_attn.dt_bias": "model-00001-of-00001.safetensors",
|
| 41 |
+
"model.language_model.layers.22.linear_attn.A_log": "model-00001-of-00001.safetensors",
|
| 42 |
+
"model.language_model.layers.0.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 43 |
+
"model.language_model.layers.1.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 44 |
+
"model.language_model.layers.2.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 45 |
+
"model.language_model.layers.4.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 46 |
+
"model.language_model.layers.5.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 47 |
+
"model.language_model.layers.6.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 48 |
+
"model.language_model.layers.8.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 49 |
+
"model.language_model.layers.9.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 50 |
+
"model.language_model.layers.10.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 51 |
+
"model.language_model.layers.12.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 52 |
+
"model.language_model.layers.13.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 53 |
+
"model.language_model.layers.14.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 54 |
+
"model.language_model.layers.16.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 55 |
+
"model.language_model.layers.17.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 56 |
+
"model.language_model.layers.18.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 57 |
+
"model.language_model.layers.20.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 58 |
+
"model.language_model.layers.21.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 59 |
+
"model.language_model.layers.22.linear_attn.norm.weight": "model-00001-of-00001.safetensors",
|
| 60 |
+
"model.language_model.layers.3.self_attn.q_norm.weight": "model-00001-of-00001.safetensors",
|
| 61 |
+
"model.language_model.layers.3.self_attn.k_norm.weight": "model-00001-of-00001.safetensors",
|
| 62 |
+
"model.language_model.layers.7.self_attn.q_norm.weight": "model-00001-of-00001.safetensors",
|
| 63 |
+
"model.language_model.layers.7.self_attn.k_norm.weight": "model-00001-of-00001.safetensors",
|
| 64 |
+
"model.language_model.layers.11.self_attn.q_norm.weight": "model-00001-of-00001.safetensors",
|
| 65 |
+
"model.language_model.layers.11.self_attn.k_norm.weight": "model-00001-of-00001.safetensors",
|
| 66 |
+
"model.language_model.layers.15.self_attn.q_norm.weight": "model-00001-of-00001.safetensors",
|
| 67 |
+
"model.language_model.layers.15.self_attn.k_norm.weight": "model-00001-of-00001.safetensors",
|
| 68 |
+
"model.language_model.layers.19.self_attn.q_norm.weight": "model-00001-of-00001.safetensors",
|
| 69 |
+
"model.language_model.layers.19.self_attn.k_norm.weight": "model-00001-of-00001.safetensors",
|
| 70 |
+
"model.language_model.layers.23.self_attn.q_norm.weight": "model-00001-of-00001.safetensors",
|
| 71 |
+
"model.language_model.layers.23.self_attn.k_norm.weight": "model-00001-of-00001.safetensors",
|
| 72 |
+
"model.visual.patch_embed.proj.bias": "model-00001-of-00001.safetensors",
|
| 73 |
+
"model.visual.blocks.0.norm1.weight": "model-00001-of-00001.safetensors",
|
| 74 |
+
"model.visual.blocks.0.norm1.bias": "model-00001-of-00001.safetensors",
|
| 75 |
+
"model.visual.blocks.0.norm2.weight": "model-00001-of-00001.safetensors",
|
| 76 |
+
"model.visual.blocks.0.norm2.bias": "model-00001-of-00001.safetensors",
|
| 77 |
+
"model.visual.blocks.0.attn.proj.bias": "model-00001-of-00001.safetensors",
|
| 78 |
+
"model.visual.blocks.0.mlp.linear_fc2.bias": "model-00001-of-00001.safetensors",
|
| 79 |
+
"model.visual.blocks.1.norm1.weight": "model-00001-of-00001.safetensors",
|
| 80 |
+
"model.visual.blocks.1.norm1.bias": "model-00001-of-00001.safetensors",
|
| 81 |
+
"model.visual.blocks.1.norm2.weight": "model-00001-of-00001.safetensors",
|
| 82 |
+
"model.visual.blocks.1.norm2.bias": "model-00001-of-00001.safetensors",
|
| 83 |
+
"model.visual.blocks.1.attn.proj.bias": "model-00001-of-00001.safetensors",
|
| 84 |
+
"model.visual.blocks.1.mlp.linear_fc2.bias": "model-00001-of-00001.safetensors",
|
| 85 |
+
"model.visual.blocks.2.norm1.weight": "model-00001-of-00001.safetensors",
|
| 86 |
+
"model.visual.blocks.2.norm1.bias": "model-00001-of-00001.safetensors",
|
| 87 |
+
"model.visual.blocks.2.norm2.weight": "model-00001-of-00001.safetensors",
|
| 88 |
+
"model.visual.blocks.2.norm2.bias": "model-00001-of-00001.safetensors",
|
| 89 |
+
"model.visual.blocks.2.attn.proj.bias": "model-00001-of-00001.safetensors",
|
| 90 |
+
"model.visual.blocks.2.mlp.linear_fc2.bias": "model-00001-of-00001.safetensors",
|
| 91 |
+
"model.visual.blocks.3.norm1.weight": "model-00001-of-00001.safetensors",
|
| 92 |
+
"model.visual.blocks.3.norm1.bias": "model-00001-of-00001.safetensors",
|
| 93 |
+
"model.visual.blocks.3.norm2.weight": "model-00001-of-00001.safetensors",
|
| 94 |
+
"model.visual.blocks.3.norm2.bias": "model-00001-of-00001.safetensors",
|
| 95 |
+
"model.visual.blocks.3.attn.proj.bias": "model-00001-of-00001.safetensors",
|
| 96 |
+
"model.visual.blocks.3.mlp.linear_fc2.bias": "model-00001-of-00001.safetensors",
|
| 97 |
+
"model.visual.blocks.4.norm1.weight": "model-00001-of-00001.safetensors",
|
| 98 |
+
"model.visual.blocks.4.norm1.bias": "model-00001-of-00001.safetensors",
|
| 99 |
+
"model.visual.blocks.4.norm2.weight": "model-00001-of-00001.safetensors",
|
| 100 |
+
"model.visual.blocks.4.norm2.bias": "model-00001-of-00001.safetensors",
|
| 101 |
+
"model.visual.blocks.4.attn.proj.bias": "model-00001-of-00001.safetensors",
|
| 102 |
+
"model.visual.blocks.4.mlp.linear_fc2.bias": "model-00001-of-00001.safetensors",
|
| 103 |
+
"model.visual.blocks.5.norm1.weight": "model-00001-of-00001.safetensors",
|
| 104 |
+
"model.visual.blocks.5.norm1.bias": "model-00001-of-00001.safetensors",
|
| 105 |
+
"model.visual.blocks.5.norm2.weight": "model-00001-of-00001.safetensors",
|
| 106 |
+
"model.visual.blocks.5.norm2.bias": "model-00001-of-00001.safetensors",
|
| 107 |
+
"model.visual.blocks.5.attn.proj.bias": "model-00001-of-00001.safetensors",
|
| 108 |
+
"model.visual.blocks.5.mlp.linear_fc2.bias": "model-00001-of-00001.safetensors",
|
| 109 |
+
"model.visual.blocks.6.norm1.weight": "model-00001-of-00001.safetensors",
|
| 110 |
+
"model.visual.blocks.6.norm1.bias": "model-00001-of-00001.safetensors",
|
| 111 |
+
"model.visual.blocks.6.norm2.weight": "model-00001-of-00001.safetensors",
|
| 112 |
+
"model.visual.blocks.6.norm2.bias": "model-00001-of-00001.safetensors",
|
| 113 |
+
"model.visual.blocks.6.attn.proj.bias": "model-00001-of-00001.safetensors",
|
| 114 |
+
"model.visual.blocks.6.mlp.linear_fc2.bias": "model-00001-of-00001.safetensors",
|
| 115 |
+
"model.visual.blocks.7.norm1.weight": "model-00001-of-00001.safetensors",
|
| 116 |
+
"model.visual.blocks.7.norm1.bias": "model-00001-of-00001.safetensors",
|
| 117 |
+
"model.visual.blocks.7.norm2.weight": "model-00001-of-00001.safetensors",
|
| 118 |
+
"model.visual.blocks.7.norm2.bias": "model-00001-of-00001.safetensors",
|
| 119 |
+
"model.visual.blocks.7.attn.proj.bias": "model-00001-of-00001.safetensors",
|
| 120 |
+
"model.visual.blocks.7.mlp.linear_fc2.bias": "model-00001-of-00001.safetensors",
|
| 121 |
+
"model.visual.blocks.8.norm1.weight": "model-00001-of-00001.safetensors",
|
| 122 |
+
"model.visual.blocks.8.norm1.bias": "model-00001-of-00001.safetensors",
|
| 123 |
+
"model.visual.blocks.8.norm2.weight": "model-00001-of-00001.safetensors",
|
| 124 |
+
"model.visual.blocks.8.norm2.bias": "model-00001-of-00001.safetensors",
|
| 125 |
+
"model.visual.blocks.8.attn.proj.bias": "model-00001-of-00001.safetensors",
|
| 126 |
+
"model.visual.blocks.8.mlp.linear_fc2.bias": "model-00001-of-00001.safetensors",
|
| 127 |
+
"model.visual.blocks.9.norm1.weight": "model-00001-of-00001.safetensors",
|
| 128 |
+
"model.visual.blocks.9.norm1.bias": "model-00001-of-00001.safetensors",
|
| 129 |
+
"model.visual.blocks.9.norm2.weight": "model-00001-of-00001.safetensors",
|
| 130 |
+
"model.visual.blocks.9.norm2.bias": "model-00001-of-00001.safetensors",
|
| 131 |
+
"model.visual.blocks.9.attn.proj.bias": "model-00001-of-00001.safetensors",
|
| 132 |
+
"model.visual.blocks.9.mlp.linear_fc2.bias": "model-00001-of-00001.safetensors",
|
| 133 |
+
"model.visual.blocks.10.norm1.weight": "model-00001-of-00001.safetensors",
|
| 134 |
+
"model.visual.blocks.10.norm1.bias": "model-00001-of-00001.safetensors",
|
| 135 |
+
"model.visual.blocks.10.norm2.weight": "model-00001-of-00001.safetensors",
|
| 136 |
+
"model.visual.blocks.10.norm2.bias": "model-00001-of-00001.safetensors",
|
| 137 |
+
"model.visual.blocks.10.attn.proj.bias": "model-00001-of-00001.safetensors",
|
| 138 |
+
"model.visual.blocks.10.mlp.linear_fc2.bias": "model-00001-of-00001.safetensors",
|
| 139 |
+
"model.visual.blocks.11.norm1.weight": "model-00001-of-00001.safetensors",
|
| 140 |
+
"model.visual.blocks.11.norm1.bias": "model-00001-of-00001.safetensors",
|
| 141 |
+
"model.visual.blocks.11.norm2.weight": "model-00001-of-00001.safetensors",
|
| 142 |
+
"model.visual.blocks.11.norm2.bias": "model-00001-of-00001.safetensors",
|
| 143 |
+
"model.visual.blocks.11.attn.proj.bias": "model-00001-of-00001.safetensors",
|
| 144 |
+
"model.visual.blocks.11.mlp.linear_fc2.bias": "model-00001-of-00001.safetensors",
|
| 145 |
+
"model.visual.merger.norm.weight": "model-00001-of-00001.safetensors",
|
| 146 |
+
"model.visual.merger.norm.bias": "model-00001-of-00001.safetensors",
|
| 147 |
+
"model.visual.merger.linear_fc2.bias": "model-00001-of-00001.safetensors",
|
| 148 |
+
"model.language_model.layers.0.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 149 |
+
"model.language_model.layers.0.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 150 |
+
"model.language_model.layers.1.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 151 |
+
"model.language_model.layers.1.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 152 |
+
"model.language_model.layers.2.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 153 |
+
"model.language_model.layers.2.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 154 |
+
"model.language_model.layers.3.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 155 |
+
"model.language_model.layers.3.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 156 |
+
"model.language_model.layers.4.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 157 |
+
"model.language_model.layers.4.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 158 |
+
"model.language_model.layers.5.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 159 |
+
"model.language_model.layers.5.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 160 |
+
"model.language_model.layers.6.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 161 |
+
"model.language_model.layers.6.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 162 |
+
"model.language_model.layers.7.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 163 |
+
"model.language_model.layers.7.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 164 |
+
"model.language_model.layers.8.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 165 |
+
"model.language_model.layers.8.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 166 |
+
"model.language_model.layers.9.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 167 |
+
"model.language_model.layers.9.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 168 |
+
"model.language_model.layers.10.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 169 |
+
"model.language_model.layers.10.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 170 |
+
"model.language_model.layers.11.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 171 |
+
"model.language_model.layers.11.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 172 |
+
"model.language_model.layers.12.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 173 |
+
"model.language_model.layers.12.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 174 |
+
"model.language_model.layers.13.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 175 |
+
"model.language_model.layers.13.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 176 |
+
"model.language_model.layers.14.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 177 |
+
"model.language_model.layers.14.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 178 |
+
"model.language_model.layers.15.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 179 |
+
"model.language_model.layers.15.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 180 |
+
"model.language_model.layers.16.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 181 |
+
"model.language_model.layers.16.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 182 |
+
"model.language_model.layers.17.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 183 |
+
"model.language_model.layers.17.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 184 |
+
"model.language_model.layers.18.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 185 |
+
"model.language_model.layers.18.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 186 |
+
"model.language_model.layers.19.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 187 |
+
"model.language_model.layers.19.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 188 |
+
"model.language_model.layers.20.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 189 |
+
"model.language_model.layers.20.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 190 |
+
"model.language_model.layers.21.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 191 |
+
"model.language_model.layers.21.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 192 |
+
"model.language_model.layers.22.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 193 |
+
"model.language_model.layers.22.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 194 |
+
"model.language_model.layers.23.input_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 195 |
+
"model.language_model.layers.23.post_attention_layernorm.weight": "model-00001-of-00001.safetensors",
|
| 196 |
+
"model.language_model.norm.weight": "model-00001-of-00001.safetensors",
|
| 197 |
+
"model.visual.blocks.0.attn.qkv.bias": "model-00001-of-00001.safetensors",
|
| 198 |
+
"model.visual.blocks.1.attn.qkv.bias": "model-00001-of-00001.safetensors",
|
| 199 |
+
"model.visual.blocks.2.attn.qkv.bias": "model-00001-of-00001.safetensors",
|
| 200 |
+
"model.visual.blocks.3.attn.qkv.bias": "model-00001-of-00001.safetensors",
|
| 201 |
+
"model.visual.blocks.4.attn.qkv.bias": "model-00001-of-00001.safetensors",
|
| 202 |
+
"model.visual.blocks.5.attn.qkv.bias": "model-00001-of-00001.safetensors",
|
| 203 |
+
"model.visual.blocks.6.attn.qkv.bias": "model-00001-of-00001.safetensors",
|
| 204 |
+
"model.visual.blocks.7.attn.qkv.bias": "model-00001-of-00001.safetensors",
|
| 205 |
+
"model.visual.blocks.8.attn.qkv.bias": "model-00001-of-00001.safetensors",
|
| 206 |
+
"model.visual.blocks.9.attn.qkv.bias": "model-00001-of-00001.safetensors",
|
| 207 |
+
"model.visual.blocks.10.attn.qkv.bias": "model-00001-of-00001.safetensors",
|
| 208 |
+
"model.visual.blocks.11.attn.qkv.bias": "model-00001-of-00001.safetensors",
|
| 209 |
+
"model.visual.blocks.0.mlp.linear_fc1.bias": "model-00001-of-00001.safetensors",
|
| 210 |
+
"model.visual.blocks.1.mlp.linear_fc1.bias": "model-00001-of-00001.safetensors",
|
| 211 |
+
"model.visual.blocks.2.mlp.linear_fc1.bias": "model-00001-of-00001.safetensors",
|
| 212 |
+
"model.visual.blocks.3.mlp.linear_fc1.bias": "model-00001-of-00001.safetensors",
|
| 213 |
+
"model.visual.blocks.4.mlp.linear_fc1.bias": "model-00001-of-00001.safetensors",
|
| 214 |
+
"model.visual.blocks.5.mlp.linear_fc1.bias": "model-00001-of-00001.safetensors",
|
| 215 |
+
"model.visual.blocks.6.mlp.linear_fc1.bias": "model-00001-of-00001.safetensors",
|
| 216 |
+
"model.visual.blocks.7.mlp.linear_fc1.bias": "model-00001-of-00001.safetensors",
|
| 217 |
+
"model.visual.blocks.8.mlp.linear_fc1.bias": "model-00001-of-00001.safetensors",
|
| 218 |
+
"model.visual.blocks.9.mlp.linear_fc1.bias": "model-00001-of-00001.safetensors",
|
| 219 |
+
"model.visual.blocks.10.mlp.linear_fc1.bias": "model-00001-of-00001.safetensors",
|
| 220 |
+
"model.visual.blocks.11.mlp.linear_fc1.bias": "model-00001-of-00001.safetensors",
|
| 221 |
+
"model.visual.merger.linear_fc1.bias": "model-00001-of-00001.safetensors",
|
| 222 |
+
"model.language_model.layers.0.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 223 |
+
"model.language_model.layers.1.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 224 |
+
"model.language_model.layers.2.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 225 |
+
"model.language_model.layers.4.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 226 |
+
"model.language_model.layers.5.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 227 |
+
"model.language_model.layers.6.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 228 |
+
"model.language_model.layers.8.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 229 |
+
"model.language_model.layers.9.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 230 |
+
"model.language_model.layers.10.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 231 |
+
"model.language_model.layers.12.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 232 |
+
"model.language_model.layers.13.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 233 |
+
"model.language_model.layers.14.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 234 |
+
"model.language_model.layers.16.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 235 |
+
"model.language_model.layers.17.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 236 |
+
"model.language_model.layers.18.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 237 |
+
"model.language_model.layers.20.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 238 |
+
"model.language_model.layers.21.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 239 |
+
"model.language_model.layers.22.linear_attn.conv1d.weight": "model-00001-of-00001.safetensors",
|
| 240 |
+
"model.language_model.layers.0.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 241 |
+
"model.language_model.layers.0.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 242 |
+
"model.language_model.layers.1.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 243 |
+
"model.language_model.layers.1.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 244 |
+
"model.language_model.layers.2.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 245 |
+
"model.language_model.layers.2.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 246 |
+
"model.language_model.layers.4.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 247 |
+
"model.language_model.layers.4.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 248 |
+
"model.language_model.layers.5.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 249 |
+
"model.language_model.layers.5.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 250 |
+
"model.language_model.layers.6.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 251 |
+
"model.language_model.layers.6.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 252 |
+
"model.language_model.layers.8.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 253 |
+
"model.language_model.layers.8.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 254 |
+
"model.language_model.layers.9.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 255 |
+
"model.language_model.layers.9.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 256 |
+
"model.language_model.layers.10.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 257 |
+
"model.language_model.layers.10.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 258 |
+
"model.language_model.layers.12.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 259 |
+
"model.language_model.layers.12.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 260 |
+
"model.language_model.layers.13.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 261 |
+
"model.language_model.layers.13.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 262 |
+
"model.language_model.layers.14.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 263 |
+
"model.language_model.layers.14.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 264 |
+
"model.language_model.layers.16.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 265 |
+
"model.language_model.layers.16.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 266 |
+
"model.language_model.layers.17.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 267 |
+
"model.language_model.layers.17.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 268 |
+
"model.language_model.layers.18.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 269 |
+
"model.language_model.layers.18.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 270 |
+
"model.language_model.layers.20.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 271 |
+
"model.language_model.layers.20.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 272 |
+
"model.language_model.layers.21.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 273 |
+
"model.language_model.layers.21.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 274 |
+
"model.language_model.layers.22.linear_attn.in_proj_b.weight": "model-00001-of-00001.safetensors",
|
| 275 |
+
"model.language_model.layers.22.linear_attn.in_proj_a.weight": "model-00001-of-00001.safetensors",
|
| 276 |
+
"model.language_model.layers.3.self_attn.k_proj.weight": "model-00001-of-00001.safetensors",
|
| 277 |
+
"model.language_model.layers.3.self_attn.v_proj.weight": "model-00001-of-00001.safetensors",
|
| 278 |
+
"model.language_model.layers.7.self_attn.k_proj.weight": "model-00001-of-00001.safetensors",
|
| 279 |
+
"model.language_model.layers.7.self_attn.v_proj.weight": "model-00001-of-00001.safetensors",
|
| 280 |
+
"model.language_model.layers.11.self_attn.k_proj.weight": "model-00001-of-00001.safetensors",
|
| 281 |
+
"model.language_model.layers.11.self_attn.v_proj.weight": "model-00001-of-00001.safetensors",
|
| 282 |
+
"model.language_model.layers.15.self_attn.k_proj.weight": "model-00001-of-00001.safetensors",
|
| 283 |
+
"model.language_model.layers.15.self_attn.v_proj.weight": "model-00001-of-00001.safetensors",
|
| 284 |
+
"model.language_model.layers.19.self_attn.k_proj.weight": "model-00001-of-00001.safetensors",
|
| 285 |
+
"model.language_model.layers.19.self_attn.v_proj.weight": "model-00001-of-00001.safetensors",
|
| 286 |
+
"model.language_model.layers.23.self_attn.k_proj.weight": "model-00001-of-00001.safetensors",
|
| 287 |
+
"model.language_model.layers.23.self_attn.v_proj.weight": "model-00001-of-00001.safetensors",
|
| 288 |
+
"model.visual.blocks.0.attn.proj.weight": "model-00001-of-00001.safetensors",
|
| 289 |
+
"model.visual.blocks.1.attn.proj.weight": "model-00001-of-00001.safetensors",
|
| 290 |
+
"model.visual.blocks.2.attn.proj.weight": "model-00001-of-00001.safetensors",
|
| 291 |
+
"model.visual.blocks.3.attn.proj.weight": "model-00001-of-00001.safetensors",
|
| 292 |
+
"model.visual.blocks.4.attn.proj.weight": "model-00001-of-00001.safetensors",
|
| 293 |
+
"model.visual.blocks.5.attn.proj.weight": "model-00001-of-00001.safetensors",
|
| 294 |
+
"model.visual.blocks.6.attn.proj.weight": "model-00001-of-00001.safetensors",
|
| 295 |
+
"model.visual.blocks.7.attn.proj.weight": "model-00001-of-00001.safetensors",
|
| 296 |
+
"model.visual.blocks.8.attn.proj.weight": "model-00001-of-00001.safetensors",
|
| 297 |
+
"model.visual.blocks.9.attn.proj.weight": "model-00001-of-00001.safetensors",
|
| 298 |
+
"model.visual.blocks.10.attn.proj.weight": "model-00001-of-00001.safetensors",
|
| 299 |
+
"model.visual.blocks.11.attn.proj.weight": "model-00001-of-00001.safetensors",
|
| 300 |
+
"model.visual.patch_embed.proj.weight": "model-00001-of-00001.safetensors",
|
| 301 |
+
"model.visual.pos_embed.weight": "model-00001-of-00001.safetensors",
|
| 302 |
+
"model.visual.blocks.0.attn.qkv.weight": "model-00001-of-00001.safetensors",
|
| 303 |
+
"model.visual.blocks.1.attn.qkv.weight": "model-00001-of-00001.safetensors",
|
| 304 |
+
"model.visual.blocks.2.attn.qkv.weight": "model-00001-of-00001.safetensors",
|
| 305 |
+
"model.visual.blocks.3.attn.qkv.weight": "model-00001-of-00001.safetensors",
|
| 306 |
+
"model.visual.blocks.4.attn.qkv.weight": "model-00001-of-00001.safetensors",
|
| 307 |
+
"model.visual.blocks.5.attn.qkv.weight": "model-00001-of-00001.safetensors",
|
| 308 |
+
"model.visual.blocks.6.attn.qkv.weight": "model-00001-of-00001.safetensors",
|
| 309 |
+
"model.visual.blocks.7.attn.qkv.weight": "model-00001-of-00001.safetensors",
|
| 310 |
+
"model.visual.blocks.8.attn.qkv.weight": "model-00001-of-00001.safetensors",
|
| 311 |
+
"model.visual.blocks.9.attn.qkv.weight": "model-00001-of-00001.safetensors",
|
| 312 |
+
"model.visual.blocks.10.attn.qkv.weight": "model-00001-of-00001.safetensors",
|
| 313 |
+
"model.visual.blocks.11.attn.qkv.weight": "model-00001-of-00001.safetensors",
|
| 314 |
+
"model.language_model.layers.0.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 315 |
+
"model.language_model.layers.0.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 316 |
+
"model.language_model.layers.1.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 317 |
+
"model.language_model.layers.1.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 318 |
+
"model.language_model.layers.2.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 319 |
+
"model.language_model.layers.2.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 320 |
+
"model.language_model.layers.3.self_attn.o_proj.weight": "model-00001-of-00001.safetensors",
|
| 321 |
+
"model.language_model.layers.4.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 322 |
+
"model.language_model.layers.4.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 323 |
+
"model.language_model.layers.5.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 324 |
+
"model.language_model.layers.5.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 325 |
+
"model.language_model.layers.6.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 326 |
+
"model.language_model.layers.6.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 327 |
+
"model.language_model.layers.7.self_attn.o_proj.weight": "model-00001-of-00001.safetensors",
|
| 328 |
+
"model.language_model.layers.8.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 329 |
+
"model.language_model.layers.8.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 330 |
+
"model.language_model.layers.9.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 331 |
+
"model.language_model.layers.9.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 332 |
+
"model.language_model.layers.10.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 333 |
+
"model.language_model.layers.10.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 334 |
+
"model.language_model.layers.11.self_attn.o_proj.weight": "model-00001-of-00001.safetensors",
|
| 335 |
+
"model.language_model.layers.12.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 336 |
+
"model.language_model.layers.12.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 337 |
+
"model.language_model.layers.13.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 338 |
+
"model.language_model.layers.13.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 339 |
+
"model.language_model.layers.14.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 340 |
+
"model.language_model.layers.14.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 341 |
+
"model.language_model.layers.15.self_attn.o_proj.weight": "model-00001-of-00001.safetensors",
|
| 342 |
+
"model.language_model.layers.16.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 343 |
+
"model.language_model.layers.16.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 344 |
+
"model.language_model.layers.17.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 345 |
+
"model.language_model.layers.17.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 346 |
+
"model.language_model.layers.18.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 347 |
+
"model.language_model.layers.18.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 348 |
+
"model.language_model.layers.19.self_attn.o_proj.weight": "model-00001-of-00001.safetensors",
|
| 349 |
+
"model.language_model.layers.20.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 350 |
+
"model.language_model.layers.20.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 351 |
+
"model.language_model.layers.21.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 352 |
+
"model.language_model.layers.21.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 353 |
+
"model.language_model.layers.22.linear_attn.out_proj.weight": "model-00001-of-00001.safetensors",
|
| 354 |
+
"model.language_model.layers.22.linear_attn.in_proj_z.weight": "model-00001-of-00001.safetensors",
|
| 355 |
+
"model.language_model.layers.23.self_attn.o_proj.weight": "model-00001-of-00001.safetensors",
|
| 356 |
+
"model.visual.blocks.0.mlp.linear_fc1.weight": "model-00001-of-00001.safetensors",
|
| 357 |
+
"model.visual.blocks.0.mlp.linear_fc2.weight": "model-00001-of-00001.safetensors",
|
| 358 |
+
"model.visual.blocks.1.mlp.linear_fc1.weight": "model-00001-of-00001.safetensors",
|
| 359 |
+
"model.visual.blocks.1.mlp.linear_fc2.weight": "model-00001-of-00001.safetensors",
|
| 360 |
+
"model.visual.blocks.2.mlp.linear_fc1.weight": "model-00001-of-00001.safetensors",
|
| 361 |
+
"model.visual.blocks.2.mlp.linear_fc2.weight": "model-00001-of-00001.safetensors",
|
| 362 |
+
"model.visual.blocks.3.mlp.linear_fc1.weight": "model-00001-of-00001.safetensors",
|
| 363 |
+
"model.visual.blocks.3.mlp.linear_fc2.weight": "model-00001-of-00001.safetensors",
|
| 364 |
+
"model.visual.blocks.4.mlp.linear_fc1.weight": "model-00001-of-00001.safetensors",
|
| 365 |
+
"model.visual.blocks.4.mlp.linear_fc2.weight": "model-00001-of-00001.safetensors",
|
| 366 |
+
"model.visual.blocks.5.mlp.linear_fc1.weight": "model-00001-of-00001.safetensors",
|
| 367 |
+
"model.visual.blocks.5.mlp.linear_fc2.weight": "model-00001-of-00001.safetensors",
|
| 368 |
+
"model.visual.blocks.6.mlp.linear_fc1.weight": "model-00001-of-00001.safetensors",
|
| 369 |
+
"model.visual.blocks.6.mlp.linear_fc2.weight": "model-00001-of-00001.safetensors",
|
| 370 |
+
"model.visual.blocks.7.mlp.linear_fc1.weight": "model-00001-of-00001.safetensors",
|
| 371 |
+
"model.visual.blocks.7.mlp.linear_fc2.weight": "model-00001-of-00001.safetensors",
|
| 372 |
+
"model.visual.blocks.8.mlp.linear_fc1.weight": "model-00001-of-00001.safetensors",
|
| 373 |
+
"model.visual.blocks.8.mlp.linear_fc2.weight": "model-00001-of-00001.safetensors",
|
| 374 |
+
"model.visual.blocks.9.mlp.linear_fc1.weight": "model-00001-of-00001.safetensors",
|
| 375 |
+
"model.visual.blocks.9.mlp.linear_fc2.weight": "model-00001-of-00001.safetensors",
|
| 376 |
+
"model.visual.blocks.10.mlp.linear_fc1.weight": "model-00001-of-00001.safetensors",
|
| 377 |
+
"model.visual.blocks.10.mlp.linear_fc2.weight": "model-00001-of-00001.safetensors",
|
| 378 |
+
"model.visual.blocks.11.mlp.linear_fc1.weight": "model-00001-of-00001.safetensors",
|
| 379 |
+
"model.visual.blocks.11.mlp.linear_fc2.weight": "model-00001-of-00001.safetensors",
|
| 380 |
+
"model.visual.merger.linear_fc2.weight": "model-00001-of-00001.safetensors",
|
| 381 |
+
"model.language_model.layers.0.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 382 |
+
"model.language_model.layers.0.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 383 |
+
"model.language_model.layers.0.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 384 |
+
"model.language_model.layers.1.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 385 |
+
"model.language_model.layers.1.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 386 |
+
"model.language_model.layers.1.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 387 |
+
"model.language_model.layers.2.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 388 |
+
"model.language_model.layers.2.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 389 |
+
"model.language_model.layers.2.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 390 |
+
"model.language_model.layers.3.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 391 |
+
"model.language_model.layers.3.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 392 |
+
"model.language_model.layers.3.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 393 |
+
"model.language_model.layers.4.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 394 |
+
"model.language_model.layers.4.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 395 |
+
"model.language_model.layers.4.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 396 |
+
"model.language_model.layers.5.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 397 |
+
"model.language_model.layers.5.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 398 |
+
"model.language_model.layers.5.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 399 |
+
"model.language_model.layers.6.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 400 |
+
"model.language_model.layers.6.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 401 |
+
"model.language_model.layers.6.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 402 |
+
"model.language_model.layers.7.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 403 |
+
"model.language_model.layers.7.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 404 |
+
"model.language_model.layers.7.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 405 |
+
"model.language_model.layers.8.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 406 |
+
"model.language_model.layers.8.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 407 |
+
"model.language_model.layers.8.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 408 |
+
"model.language_model.layers.9.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 409 |
+
"model.language_model.layers.9.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 410 |
+
"model.language_model.layers.9.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 411 |
+
"model.language_model.layers.10.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 412 |
+
"model.language_model.layers.10.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 413 |
+
"model.language_model.layers.10.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 414 |
+
"model.language_model.layers.11.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 415 |
+
"model.language_model.layers.11.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 416 |
+
"model.language_model.layers.11.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 417 |
+
"model.language_model.layers.12.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 418 |
+
"model.language_model.layers.12.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 419 |
+
"model.language_model.layers.12.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 420 |
+
"model.language_model.layers.13.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 421 |
+
"model.language_model.layers.13.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 422 |
+
"model.language_model.layers.13.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 423 |
+
"model.language_model.layers.14.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 424 |
+
"model.language_model.layers.14.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 425 |
+
"model.language_model.layers.14.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 426 |
+
"model.language_model.layers.15.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 427 |
+
"model.language_model.layers.15.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 428 |
+
"model.language_model.layers.15.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 429 |
+
"model.language_model.layers.16.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 430 |
+
"model.language_model.layers.16.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 431 |
+
"model.language_model.layers.16.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 432 |
+
"model.language_model.layers.17.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 433 |
+
"model.language_model.layers.17.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 434 |
+
"model.language_model.layers.17.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 435 |
+
"model.language_model.layers.18.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 436 |
+
"model.language_model.layers.18.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 437 |
+
"model.language_model.layers.18.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 438 |
+
"model.language_model.layers.19.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 439 |
+
"model.language_model.layers.19.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 440 |
+
"model.language_model.layers.19.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 441 |
+
"model.language_model.layers.20.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 442 |
+
"model.language_model.layers.20.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 443 |
+
"model.language_model.layers.20.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 444 |
+
"model.language_model.layers.21.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 445 |
+
"model.language_model.layers.21.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 446 |
+
"model.language_model.layers.21.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 447 |
+
"model.language_model.layers.22.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 448 |
+
"model.language_model.layers.22.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 449 |
+
"model.language_model.layers.22.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 450 |
+
"model.language_model.layers.23.mlp.gate_proj.weight": "model-00001-of-00001.safetensors",
|
| 451 |
+
"model.language_model.layers.23.mlp.up_proj.weight": "model-00001-of-00001.safetensors",
|
| 452 |
+
"model.language_model.layers.23.mlp.down_proj.weight": "model-00001-of-00001.safetensors",
|
| 453 |
+
"model.language_model.layers.3.self_attn.q_proj.weight": "model-00001-of-00001.safetensors",
|
| 454 |
+
"model.language_model.layers.7.self_attn.q_proj.weight": "model-00001-of-00001.safetensors",
|
| 455 |
+
"model.language_model.layers.11.self_attn.q_proj.weight": "model-00001-of-00001.safetensors",
|
| 456 |
+
"model.language_model.layers.15.self_attn.q_proj.weight": "model-00001-of-00001.safetensors",
|
| 457 |
+
"model.language_model.layers.19.self_attn.q_proj.weight": "model-00001-of-00001.safetensors",
|
| 458 |
+
"model.language_model.layers.23.self_attn.q_proj.weight": "model-00001-of-00001.safetensors",
|
| 459 |
+
"model.language_model.layers.0.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 460 |
+
"model.language_model.layers.1.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 461 |
+
"model.language_model.layers.2.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 462 |
+
"model.language_model.layers.4.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 463 |
+
"model.language_model.layers.5.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 464 |
+
"model.language_model.layers.6.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 465 |
+
"model.language_model.layers.8.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 466 |
+
"model.language_model.layers.9.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 467 |
+
"model.language_model.layers.10.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 468 |
+
"model.language_model.layers.12.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 469 |
+
"model.language_model.layers.13.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 470 |
+
"model.language_model.layers.14.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 471 |
+
"model.language_model.layers.16.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 472 |
+
"model.language_model.layers.17.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 473 |
+
"model.language_model.layers.18.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 474 |
+
"model.language_model.layers.20.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 475 |
+
"model.language_model.layers.21.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 476 |
+
"model.language_model.layers.22.linear_attn.in_proj_qkv.weight": "model-00001-of-00001.safetensors",
|
| 477 |
+
"model.visual.merger.linear_fc1.weight": "model-00001-of-00001.safetensors",
|
| 478 |
+
"model.language_model.embed_tokens.weight": "model-00001-of-00001.safetensors"
|
| 479 |
+
}
|
| 480 |
+
}
|
weights/ocr/processor_config.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"image_processor": {
|
| 3 |
+
"do_convert_rgb": true,
|
| 4 |
+
"do_normalize": true,
|
| 5 |
+
"do_rescale": true,
|
| 6 |
+
"do_resize": true,
|
| 7 |
+
"image_mean": [
|
| 8 |
+
0.5,
|
| 9 |
+
0.5,
|
| 10 |
+
0.5
|
| 11 |
+
],
|
| 12 |
+
"image_processor_type": "Qwen2VLImageProcessor",
|
| 13 |
+
"image_std": [
|
| 14 |
+
0.5,
|
| 15 |
+
0.5,
|
| 16 |
+
0.5
|
| 17 |
+
],
|
| 18 |
+
"merge_size": 2,
|
| 19 |
+
"patch_size": 16,
|
| 20 |
+
"resample": 3,
|
| 21 |
+
"rescale_factor": 0.00392156862745098,
|
| 22 |
+
"size": {
|
| 23 |
+
"longest_edge": 16777216,
|
| 24 |
+
"shortest_edge": 65536
|
| 25 |
+
},
|
| 26 |
+
"temporal_patch_size": 2
|
| 27 |
+
},
|
| 28 |
+
"processor_class": "Qwen3VLProcessor",
|
| 29 |
+
"video_processor": {
|
| 30 |
+
"do_convert_rgb": true,
|
| 31 |
+
"do_normalize": true,
|
| 32 |
+
"do_rescale": true,
|
| 33 |
+
"do_resize": true,
|
| 34 |
+
"do_sample_frames": true,
|
| 35 |
+
"fps": 2,
|
| 36 |
+
"image_mean": [
|
| 37 |
+
0.5,
|
| 38 |
+
0.5,
|
| 39 |
+
0.5
|
| 40 |
+
],
|
| 41 |
+
"image_std": [
|
| 42 |
+
0.5,
|
| 43 |
+
0.5,
|
| 44 |
+
0.5
|
| 45 |
+
],
|
| 46 |
+
"max_frames": 768,
|
| 47 |
+
"merge_size": 2,
|
| 48 |
+
"min_frames": 4,
|
| 49 |
+
"patch_size": 16,
|
| 50 |
+
"resample": 3,
|
| 51 |
+
"rescale_factor": 0.00392156862745098,
|
| 52 |
+
"return_metadata": false,
|
| 53 |
+
"size": {
|
| 54 |
+
"longest_edge": 25165824,
|
| 55 |
+
"shortest_edge": 4096
|
| 56 |
+
},
|
| 57 |
+
"temporal_patch_size": 2,
|
| 58 |
+
"video_processor_type": "Qwen3VLVideoProcessor"
|
| 59 |
+
}
|
| 60 |
+
}
|
weights/ocr/tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:aa502d4d33e68344b0702e5dfd90466bc1e152d36ed906bad34c48dfd3e06d41
|
| 3 |
+
size 33629731
|
weights/ocr/tokenizer_config.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"backend": "tokenizers",
|
| 3 |
+
"boi_token": "<|start_of_image|>",
|
| 4 |
+
"bos_token": "[@BOS@]",
|
| 5 |
+
"clean_up_tokenization_spaces": false,
|
| 6 |
+
"eoi_token": "<|end_of_image|>",
|
| 7 |
+
"eos_token": "<|im_end|>",
|
| 8 |
+
"extra_special_tokens": [
|
| 9 |
+
"<|endoftext|>",
|
| 10 |
+
"<|im_start|>",
|
| 11 |
+
"<|im_end|>",
|
| 12 |
+
"<|object_ref_start|>",
|
| 13 |
+
"<|object_ref_end|>",
|
| 14 |
+
"<|box_start|>",
|
| 15 |
+
"<|box_end|>",
|
| 16 |
+
"<|quad_start|>",
|
| 17 |
+
"<|quad_end|>",
|
| 18 |
+
"<|vision_start|>",
|
| 19 |
+
"<|vision_end|>",
|
| 20 |
+
"<|image_pad|>",
|
| 21 |
+
"<|video_pad|>"
|
| 22 |
+
],
|
| 23 |
+
"image_token": "<|image_pad|>",
|
| 24 |
+
"is_local": true,
|
| 25 |
+
"local_files_only": true,
|
| 26 |
+
"model_max_length": 1000000000000000019884624838656,
|
| 27 |
+
"model_specific_special_tokens": {
|
| 28 |
+
"boi_token": "<|start_of_image|>",
|
| 29 |
+
"eoi_token": "<|end_of_image|>",
|
| 30 |
+
"image_token": "<|image_pad|>"
|
| 31 |
+
},
|
| 32 |
+
"pad_token": "<|endoftext|>",
|
| 33 |
+
"padding_side": "right",
|
| 34 |
+
"processor_class": "Qwen3VLProcessor",
|
| 35 |
+
"sp_model_kwargs": null,
|
| 36 |
+
"spaces_between_special_tokens": false,
|
| 37 |
+
"tokenizer_class": "TokenizersBackend",
|
| 38 |
+
"unk_token": "<unk>",
|
| 39 |
+
"use_default_system_prompt": false
|
| 40 |
+
}
|