|
1 | | -# Findings: LightOnOCR-2-1B vs pdfplumber + xy_cut (2026-07-16) |
| 1 | +# Findings: LightOnOCR-2-1B vs pdfplumber + xy_cut (2026-07-17) |
2 | 2 |
|
3 | | -First run, 19 corpus posters + gasimova. Model `lightonai/LightOnOCR-2-1B`, |
4 | | -200 DPI, longest side 1540px, bf16, greedy. **3.0 GB peak VRAM, ~60s/poster** |
5 | | -on one GPU alongside the running ollama/vLLM services. |
| 3 | +19 corpus posters + gasimova. `lightonai/LightOnOCR-2-1B`, bf16, greedy, |
| 4 | +3.0 GB peak VRAM, ~60s/poster, on one GPU alongside the running ollama/vLLM |
| 5 | +services. |
6 | 6 |
|
7 | 7 | ## Verdict |
8 | 8 |
|
9 | | -**Not a drop-in replacement — it fabricates identifiers. But it reads poster |
10 | | -structure markedly better than we do, and that is worth having.** |
| 9 | +**A serious candidate for structure, resolution-starved on posters, and not yet |
| 10 | +usable as a drop-in.** It reads banners far better than we do. It loses fine |
| 11 | +text because its vision tower is fixed at a page-sized 1540px and a poster is |
| 12 | +four feet. Its identifier hallucinations, which the first version of this file |
| 13 | +called disqualifying, are **irrelevant to this pipeline** -- see the retraction. |
11 | 14 |
|
12 | | -Do not read the headline averages alone; they say the opposite of what matters. |
| 15 | +## RETRACTED: the identifier charge |
13 | 16 |
|
14 | | -## It reads better |
| 17 | +The first version of this file led with "it fabricates identifiers -- 6 of 10 |
| 18 | +ORCIDs wrong -- therefore disqualifying". **That was wrong, and it was wrong |
| 19 | +because I did not read the pipeline before measuring it.** |
| 20 | + |
| 21 | +poster2json never reads an ORCID off a poster. `orcid.enrich_creators_orcid` |
| 22 | +queries the ORCID API with `given-names` + `family-name` + |
| 23 | +`affiliation-org-name` and attaches an id only on a single unambiguous hit; ROR |
| 24 | +resolves institutions the same way. A hallucinated ORCID in the raw text is |
| 25 | +simply never consulted, so counting hallucinated ORCIDs measured nothing about |
| 26 | +this system. The lesson is the same one this corpus keeps teaching: **measure |
| 27 | +the thing the pipeline actually consumes.** |
| 28 | + |
| 29 | +What the pipeline consumes is the **lookup keys** -- author names and |
| 30 | +affiliation strings. Re-measured on those (`keys_check.py`): |
| 31 | + |
| 32 | +| extractor | authors found | affils found | |
| 33 | +|---|---|---| |
| 34 | +| pdfplumber | 90/93 = 0.968 | 43/47 = **0.915** | |
| 35 | +| LightOnOCR | 91/93 = **0.978** | 40/47 = 0.851 | |
| 36 | + |
| 37 | +The VLM is *slightly better* at recovering author names, and worse on |
| 38 | +affiliations — where **all four of its extra losses are isporeu2023**, the one |
| 39 | +poster it failed outright. Its other losses are the same abbreviation cases |
| 40 | +pdfplumber loses (VTT, CARL, STScI, Perdomo García, Münz-Manor). It actually |
| 41 | +recovered `the RECONS Team` and `University of Kent`, which pdfplumber missed. |
| 42 | + |
| 43 | +And because enrichment demands an exact unambiguous match (precision over |
| 44 | +coverage, 0.9.17), a corrupted key fails **safe**: no id attached, rather than |
| 45 | +the wrong person's id. So the real cost of VLM corruption is coverage, not |
| 46 | +misattribution. A far smaller charge than the one I filed. |
| 47 | + |
| 48 | +## It reads structure better |
15 | 49 |
|
16 | 50 | | metric | pdfplumber + xy_cut | LightOnOCR-2-1B | |
17 | 51 | |---|---|---| |
18 | 52 | | `w` (word capture) | **0.976** | 0.936 | |
19 | 53 | | `rGlobal` | **0.835** | 0.788 | |
20 | 54 | | `rField` (length-normalized) | 0.741 | **0.765** | |
21 | 55 |
|
22 | | -rField, the metric we treat as the headline, favours the VLM. Head-to-head it |
23 | | -wins 12 of 19. On the **banner** — `authors+affiliations`, the field that cost |
24 | | -this project Track A, Track B and approach A — it wins **15 of 19** (2 ties, 2 |
25 | | -losses), mean **+0.179**: |
26 | | - |
27 | | - gasimova 0.742 -> 1.000 4607450 0.244 -> 0.909 |
28 | | - 4560930 0.600 -> 1.000 aysaekanger 0.429 -> 0.880 |
29 | | - 4446908 0.667 -> 1.000 10890106 0.600 -> 0.925 |
30 | | - 42 0.640 -> 0.913 AISec 0.696 -> 0.929 |
31 | | - |
32 | | -It gets these right for free, from pixels, with no xy_cut, no |
33 | | -`_flatten_top_band`, no superscript-row merge, no marker parsing. It also |
34 | | -returns wrapped titles in one piece (10890106, which our block grouper still |
35 | | -splits) and emits its own markdown headers. |
36 | | - |
37 | | -## It invents identifiers |
38 | | - |
39 | | -This is disqualifying for a metadata pipeline and ROUGE cannot see it. Exact |
40 | | -strings checked against the human transcription (`fidelity_check.py`): |
41 | | - |
42 | | -| extractor | kind | recovered | missed | **invented** | |
43 | | -|---|---|---|---|---| |
44 | | -| pdfplumber | orcid | 9 | 1 | **0** | |
45 | | -| pdfplumber | doi | 10 | 0 | **0** | |
46 | | -| pdfplumber | email | 20 | 2 | 1* | |
47 | | -| LightOnOCR | orcid | 4 | 6 | **6** | |
48 | | -| LightOnOCR | doi | 7 | 3 | **2** | |
49 | | -| LightOnOCR | email | 18 | 4 | 2 | |
50 | | - |
51 | | -\* not a fabrication: the poster prints `1aperdomo@iac.es` where the `1` is a |
52 | | -superscript affiliation marker glued to the address. Our extractor correctly |
53 | | -splits it; the checker counts the clean address as "not in the reference". |
54 | | - |
55 | | -**6 of 10 ORCIDs are wrong.** Actual corruptions: |
56 | | - |
57 | | - DOI 10.1101/2024.08.13.24311948 -> 10.1105/2024.08.13.24311348 |
58 | | - DOI 10.1007/978-3-031-02170-1 -> 10.1007/978-3-031-02701-1 |
59 | | - email joneilliii@sdsu.edu -> joneilliii@sdssu.edu |
60 | | - ORCID 0000-0002-2862-7302 -> 0000-0002-3982-7202 |
61 | | - |
62 | | -These are not near-misses, they are different identifiers. A corrupted ORCID |
63 | | -attributes a poster to another researcher; a corrupted DOI resolves to the |
64 | | -wrong paper or nowhere. Silent, plausible, and worse than no value at all. |
65 | | -A text-layer extractor cannot do this: it can only miss. |
66 | | - |
67 | | -The reason is structural, not a tuning problem. The VLM re-renders every glyph |
68 | | -from pixels, so an identifier is a prediction. pdfplumber copies bytes the |
69 | | -author embedded. |
70 | | - |
71 | | -## Other failure modes seen |
72 | | - |
73 | | -- **isporeu2023** is a genuine failure: it dropped 4 of 8 authors, mis-assigned |
74 | | - markers (Ciccarone 3 -> 2, Schlichting 4 -> 1), read "Delta Hat Ltd" as |
75 | | - "Delta et Ltd", and hallucinated 3 ORCIDs. It also never terminated: 6144 |
76 | | - tokens truncated, and at 16384 it was STILL going (471s), emitting HTML |
77 | | - tables. Our pipeline scores 0.849 rField on this poster; the VLM 0.509. |
78 | | -- **8228476** (RTL Hebrew) is worse under the VLM too (rField 0.503 vs 0.692), |
79 | | - so approach D is not solved by switching extractor. |
80 | | -- Superscripts come back as LaTeX (`$^{1,2}$`). Harmless for raw-text scoring |
81 | | - (`_alpha()` reduces it to `12`, matching the reference's NFKD-normalized |
82 | | - `¹˒²`) but the affiliation corrector would need to read it. |
| 56 | +On the **banner** — `authors+affiliations`, the field that cost this project |
| 57 | +Track A, Track B and approach A — it wins **15 of 19** (2 ties, 2 losses), mean |
| 58 | +**+0.179**: gasimova 0.742 -> 1.000, 4607450 0.244 -> 0.909, 4560930 0.600 -> |
| 59 | +1.000. From pixels, with no xy_cut, no `_flatten_top_band`, no superscript-row |
| 60 | +merge, no marker parsing. It also returns wrapped titles in one piece. |
| 61 | + |
| 62 | +## Resolution: the ceiling is architectural, and posters fall off it |
| 63 | + |
| 64 | +**1540px is not a setting.** `PixtralVisionConfig.image_size = 1540`, |
| 65 | +`patch_size = 14`, so the vision tower's 2D RoPE table holds 110 patch |
| 66 | +positions per axis. Hand it a bigger image and it indexes off the end: |
| 67 | + |
| 68 | + modeling_pixtral.py:126 freqs = self.inv_freq[position_ids] |
| 69 | + CUDA error: device-side assert triggered |
| 70 | + |
| 71 | +Two traps here, both of which cost me a sweep: |
| 72 | + |
| 73 | +1. `PixtralImageProcessor` ships `size={"longest_edge": 1540}, do_resize=True`, |
| 74 | + so it **silently rescales whatever you give it back to 1540**. Rendering |
| 75 | + larger and feeding it in does not raise resolution — it only resamples |
| 76 | + twice, and measurably hurts (10890106 `w` 0.942 -> 0.723 at "2048"). |
| 77 | + The knob is the processor's `size`, not the render. |
| 78 | +2. Raising the processor's `size` past 1540 asserts (above). The failure is |
| 79 | + async, so the *first* oversized page may appear to succeed while computing |
| 80 | + on out-of-bounds indices; do not trust it. |
| 81 | + |
| 82 | +**What 1540px means for a poster** (`eff_dpi.py`): |
| 83 | + |
| 84 | +| | effective DPI at 1540px | |
| 85 | +|---|---| |
| 86 | +| A4 (the model card's design point) | 132 | |
| 87 | +| poster mean | **48** | |
| 88 | +| 17268692 (60 x 44 in) | **26** | |
| 89 | +| **17 of 20 posters** | **under 80** | |
| 90 | + |
| 91 | +The model card's "200 DPI, longest dimension 1540px" is self-consistent for a |
| 92 | +page. A conference poster is 3-4 feet, so the same 1540px spreads to ~33 DPI. |
| 93 | +**We are asking it to read 8pt body text at a quarter of the resolution it was |
| 94 | +built for.** |
| 95 | + |
| 96 | +Supporting evidence, honestly weak (`dpi_corr.py`): on the 3 page-sized |
| 97 | +documents in the corpus (>=100 DPI) the VLM's recall gap **vanishes** |
| 98 | +(mean dW **+0.012**); on the 17 poster-sized ones it is **-0.048**. |
| 99 | +r(DPI, dW) = +0.313 — positive but weak, n=3 in the high group. Suggestive, |
| 100 | +not proven. |
| 101 | + |
| 102 | +## Tiling: confirms the diagnosis, does not yet fix it |
| 103 | + |
| 104 | +Tiling is the only lever left, so `--tiles N` renders an NxN grid (6% overlap) |
| 105 | +and OCRs each tile at 1540, multiplying effective DPI by N. At 2x2, on the |
| 106 | +biggest posters, **recall improves exactly as the theory predicts**: |
| 107 | + |
| 108 | + 17268692 (26 DPI) w 0.916 -> 0.993 (beats pdfplumber's 0.986) |
| 109 | + 42 (32 DPI) w 0.897 -> 0.972 |
| 110 | + 15963941 (39 DPI) w 0.945 -> 0.987 (beats pdfplumber's 0.958) |
| 111 | + |
| 112 | +But it is not uniform (4 of 8 gained, 4 lost: 4448680 0.923 -> 0.828, gasimova |
| 113 | +0.956 -> 0.906), and **it destroys reading order**: rGlobal 0.788 -> 0.471, |
| 114 | +rField 0.765 -> 0.547, because concatenating quadrants chops multi-column text |
| 115 | +mid-flow and duplicates the overlaps. Naive grid tiling is not usable. |
83 | 116 |
|
84 | 117 | ## Where this points |
85 | 118 |
|
86 | | -A hybrid is the obvious shape, and the numbers support it: **take structure |
87 | | -from the VLM, take exact strings from the text layer.** The VLM is good at |
88 | | -precisely what xy_cut finds hard (which text belongs to which line, in what |
89 | | -order) and bad at precisely what pdfplumber gets for free (reproducing a string |
90 | | -exactly). They fail in opposite directions. |
91 | | - |
92 | | -Concretely, worth testing next: |
93 | | - |
94 | | -1. VLM output as the reading-order source, then verify/replace every ORCID, |
95 | | - DOI and email against the PDF text layer — reject any identifier the text |
96 | | - layer does not contain verbatim. This bounds the fabrication to zero while |
97 | | - keeping the banner gains. |
98 | | -2. Or narrower and safer: keep our pipeline, and use the VLM only for the |
99 | | - banner region, where it wins by +0.179 and where identifiers can be |
100 | | - cross-checked against a small, well-defined slice of text. |
101 | | -3. Re-run with `--dpi 300` before concluding on recall; `w` is 0.936 vs our |
102 | | - 0.976 and some of that gap may be resolution, not the model. |
103 | | - |
104 | | -Do NOT wire this into the pipeline on the strength of rField=0.765. |
| 119 | +The two extractors fail in opposite directions, and so do the two VLM modes: |
| 120 | + |
| 121 | +- pdfplumber has the text exactly but has to *infer* layout (all of xy_cut). |
| 122 | +- the VLM sees layout natively but has to *predict* every glyph, at 33 DPI. |
| 123 | +- a full-page VLM pass has the order but starves on detail; tiles have the |
| 124 | + detail but lose the order. |
| 125 | + |
| 126 | +Worth trying next, in order: |
| 127 | + |
| 128 | +1. **Full-page pass for order + tiles for recall.** Use the full-page output as |
| 129 | + the skeleton and tiles only to recover text the full pass missed. Keeps |
| 130 | + rGlobal while capturing the +0.077 recall. |
| 131 | +2. **Layout-aware tiling** — tile on column boundaries (xy_cut already finds |
| 132 | + them) instead of a blind grid, so no tile cuts a column mid-flow. |
| 133 | +3. **Banner-only VLM.** Narrowest and safest: our pipeline everywhere, the VLM |
| 134 | + on the banner crop alone, where it wins +0.179 and where a crop is naturally |
| 135 | + page-sized so resolution starvation disappears. |
| 136 | +4. Anything that consumes VLM text must read LaTeX superscripts (`$^{1,2}$`) |
| 137 | + before the affiliation corrector can use it. |
| 138 | + |
| 139 | +## Other failure modes |
| 140 | + |
| 141 | +- **isporeu2023** fails outright: dropped 4 of 8 authors, mis-assigned markers |
| 142 | + (Ciccarone 3->2, Schlichting 4->1), read "Delta Hat Ltd" as "Delta et Ltd", |
| 143 | + and never terminated — 6144 tokens truncated, still going at 16384 (471s), |
| 144 | + emitting HTML tables. Ours scores rField 0.849 there; the VLM 0.509. |
| 145 | +- **8228476** (RTL Hebrew) is worse under the VLM too (0.503 vs 0.692), so |
| 146 | + approach D is not solved by switching extractor. |
0 commit comments