File size: 3,992 Bytes
7b2177e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# Architecture

Two models with a JSON file between them.

```text
page image ──> IndicDocLayout ──> layout JSON ──> IndicBlockOCR ──> page JSON + markdown
```

`IndicDocLayout` finds the blocks and orders them. `IndicBlockOCR` transcribes one block crop
per request. Everything between the two models (cropping, prompt selection, reassembly) is this
package.

## The call path

One page, top to bottom, with the file that owns each step.

```text
IndicDocParser.parse(image)                              idp_offline.py

  IndicDocLayout.detect(image)                           idp_offline.py
    _open                     image -> PIL RGB           idp_offline.py
    IndicDocLayoutBackend.detect                         idp_layout.py
      infer                   -> [y0,x0,y1,x1] at 0-1000 idp_model_infer.py
      convert + clamp_to_page -> pixel [x0,y0,x1,y1]     idp_layout.py, blocks.py
      clean_layout            drop duplicate boxes       idp_blocks.py
      _densify                order -> gap-free 0..n-1   idp_layout.py
    = PageResult, every block has text None              idp_types.py

  IndicBlockOCR.run(image, layout)                       idp_offline.py
    _as_page                  dict -> PageResult, validated
    is_transcribed            drop OCR_SKIP_LABELS       idp_contract.py
    resolve_nested_equations  drop nested equations      idp_blocks.py
    build_requests            -> [CropRequest]           idp_recognizer.py
      crop_for + area_clamp   block -> image             idp_crops.py
      prompt_for(block.type)  type  -> prompt            idp_contract.py
    backend.transcribe        [CropRequest] -> [str]     a recognizer backend
    match by order            texts -> blocks            idp_offline.py
    reconstruct               blocks -> markdown         idp_reconstruct.py
  = PageResult, every block has text
```

Blocks that were skipped are not deleted. They come back with `text: ""`.

## Files by role

**Contract and data.** The vocabulary everything else shares.

| file | holds |
| --- | --- |
| `idp_contract.py` | prompts, the label to type map, which labels are skipped |
| `idp_types.py` | `Block`, `PageResult`, and every tunable config |

**The layout model.** Torch lives here and nowhere else.

| file | holds |
| --- | --- |
| `idp_model_infer.py` | preprocessing and decode for one page |
| `idp_model_labels.py` | the 37 classes, pure Python |
| `idp_model_ppdoc.py` | the PP-DocLayoutV3 subclass |
| `idp_model_order_loss.py` | reading-order decode, training loss |

**Geometry and crops.** No model, no network.

| file | holds |
| --- | --- |
| `idp_blocks.py` | box math, `clean_layout`, nested-equation dedup |
| `idp_crops.py` | `crop_for` and `area_clamp` |

**Backends.** Two Protocols, five implementations, one per deployment.

| class | file | used by |
| --- | --- | --- |
| `IndicDocLayoutBackend` | `idp_layout.py` | the real detector |
| `JsonLayoutBackend` | `idp_layout.py` | replaying a layout file, no torch |
| `HfRecognizer` | `idp_recognizer.py` | plain transformers, the Hub package |


**Orchestration.**

| file | holds |
| --- | --- |
| `idp_offline.py` | `IndicDocLayout`, `IndicBlockOCR`, `IndicDocParser` |
| `idp_recognizer.py` | `CropRequest`, `build_requests`, the recognizer Protocol |
| `idp_reconstruct.py` | blocks to markdown, math and hyphen repair |

## Invariants

1. **Two box conventions.** The model emits `[y0, x0, y1, x1]` normalised to 0-1000. The
   pipeline uses pixel `[x0, y0, x1, y1]`. 
2. **`order` must be gap-free and 0-based.** Transcriptions are matched back to blocks by
   position, so a gap or a duplicate moves text onto the wrong block.
3. **The crop clamp is on area, not on a side.** Pinning a side explodes elongated crops. See
   `CropConfig` in `idp_types.py`.
4. **`type` selects the prompt.** A wrong `type` changes what the model was asked to do, not
   just how a block is labelled. Unrecognised labels are rejected for this reason.