davanstrien HF Staff commited on
Commit
83a515e
·
verified ·
1 Parent(s): bb27b5a

Card: more general framing; reviewer fixes

Browse files
Files changed (1) hide show
  1. README.md +32 -23
README.md CHANGED
@@ -10,20 +10,26 @@ tags:
10
  - page-classification
11
  - blank-page-detection
12
  - multi-label
 
 
13
  ---
14
 
15
  # Page OCR flags
16
 
17
- A small image model that looks at a scanned book page **before** OCR and answers two questions:
18
 
19
  1. **Is there any text on this side of the leaf to transcribe?** (`no_text`)
20
- 2. **Is a VLM OCR model likely to fail on this page?** (`lo_blank_write`, `lo_trunc`, `lo_loop`; trained on the failures of [LightOnOCR-3-0.8B](https://huggingface.co/lightonai/LightOnOCR-3-0.8B))
21
 
22
- Use it to skip pages with nothing to read, to route risky pages to a different OCR setup, or to explain bad OCR output after the fact ("this page only has show-through, so the text in the output was read from the other side of the leaf").
23
 
24
- MobileNetV4-conv-small, 2.5M parameters, 384 × 384 input, about **23 ms per page on one CPU thread**. A companion model, [text-page-filter](https://huggingface.co/davanstrien/text-page-filter), answers the stricter question "is this a page of body text?".
25
 
26
- ![Examples: no_text on labelled pages](assets/flags_notext.png)
 
 
 
 
27
 
28
  ## Outputs
29
 
@@ -33,12 +39,12 @@ Seven independent sigmoid outputs (multi-label, not a softmax):
33
  |---|---|---|
34
  | `no_text` | No text to transcribe on this side of the leaf: blank paper, show-through only, a library stamp only | Skip OCR, or treat OCR output as suspect |
35
  | `lo_blank_write` | Page has no text, but LightOnOCR-3 writes text anyway (usually it reads show-through) | Explain invented output |
36
- | `lo_trunc` | LightOnOCR-3 stops at its 4,096-token limit (long tables, or repetition) | Route to a larger token budget or another engine |
37
  | `lo_loop` | LightOnOCR-3 repeats itself (dot leaders, repeated lines) | Route or re-run |
38
  | `table`, `dense` | Page has a table / dense text (soft teacher answers) | Routing hints |
39
  | `faint` | **Experimental, do not use** (see limitations) | – |
40
 
41
- ![Examples: LightOn risk outputs](assets/flags_lighton.png)
42
 
43
  ## How to use
44
 
@@ -61,17 +67,15 @@ The whole page is squashed to 384 × 384 (no centre crop, so margins stay in vie
61
 
62
  ### Suggested thresholds for `no_text`
63
 
64
- | Score | Decision | On the labelled hard pages below |
65
  |---|---|---|
66
  | > 0.8 | No text: skip, or flag any OCR output as invented | 67/67 blank, 83/90 show-through, 1/54 text |
67
- | 0.2 – 0.8 | Unsure: route or check | most faint, stamp and picture pages |
68
  | < 0.2 | Text: OCR normally | 49/54 text |
69
 
70
- At a single cut of 0.5 on the BHL board pages: 99.9% of text pages kept, 99.4% of empty pages removed.
71
-
72
  ## Speed
73
 
74
- The model itself is cheap; decoding the page image usually costs more than running it.
75
 
76
  | Setup | Pages per second, model only |
77
  |---|---|
@@ -88,11 +92,12 @@ End to end (encoded page in, scores out), the decoder sets the pace:
88
  | JPEG 2000, decoded on the GPU with nvImageCodec (nvJPEG2000) (L4 / RTX PRO 6000) | ~80 / ~120 |
89
  | JPEG 2000, decoded on the CPU (Pillow/OpenJPEG) | ~1.5–3 per CPU core |
90
 
91
- Notes from testing on Biodiversity Heritage Library pages:
92
 
93
- - NVIDIA's GPU JPEG 2000 decoder (nvJPEG2000, via nvImageCodec or DALI) decoded all 60 BHL JP2 files we tested, from Kakadu, LuraWave, JasPer and OpenJPEG encoders. In a batch, one unreadable file can fail the whole batch: check files first (a complete codestream ends with the EOC marker `FF D9`) and decode any page the GPU returns empty on the CPU. For repeated runs over one collection, store small JPEG derivatives once: at 1,024 px (long side) DALI reaches ~2,000 pages per second on one A10G with the same decisions as full-size input; 512 px is about twice as fast but agrees slightly less.
94
- - ONNX export (fp32) gives identical scores. Static int8 quantization changed the scores a lot in our test; we do not recommend it.
95
- - GPU preprocessing agrees with the reference preprocessing: DALI with JPEG input gave the same decision on every test page (largest score difference 0.015); nvImageCodec with JPEG 2000 input and an area resize on 99.6% of pages.
 
96
 
97
  Batch inference with GPU decoding (JPEG bytes in):
98
 
@@ -125,9 +130,9 @@ def score(jpeg_bytes): # a list of BATCH encode
125
 
126
  ## Evaluation
127
 
128
- All numbers are for the released checkpoint (seed 1). Mean ± SD over three training seeds where shown.
129
 
130
- **BHL board** (2,165 Biodiversity Heritage Library pages with ground-truth transcriptions; text = at least 80 characters of ground truth):
131
 
132
  | | Result |
133
  |---|---|
@@ -136,12 +141,12 @@ All numbers are for the released checkpoint (seed 1). Mean ± SD over three trai
136
  | Empty pages removed at 0.5 | 0.994 |
137
  | Pages with 1–79 characters (mostly plates with captions) | kept as text (removed: 0.047) |
138
 
139
- **Human labels** (one labeller; 250 deliberately hard pages chosen where page judges disagreed or flagged something, plus random audits; not a random sample):
140
 
141
  | | AUROC, no text vs text |
142
  |---|---|
143
  | Round 1, 78 pages (BHL, Library of Congress and London Medical Officer of Health reports) | 0.970 (3 seeds: 0.976 ± 0.005) |
144
- | Round 2, 172 pages (BHL, MOH; held-out books) | 0.935 (3 seeds: 0.923 ± 0.015) |
145
 
146
  Confusion on these 250 pages (label × `no_text` score):
147
 
@@ -154,7 +159,7 @@ Confusion on these 250 pages (label × `no_text` score):
154
  | blank | 0 | 0 | 67 |
155
  | show-through only | 2 | 5 | 83 |
156
 
157
- **LightOnOCR-3 failures** on 2,086 pages from 133 held-out books (the real LightOnOCR-3-0.8B outputs on these pages are the reference):
158
 
159
  | Output | Positives | AUROC | Precision @0.5 | Recall @0.5 | AUROC, 3 seeds |
160
  |---|---|---|---|---|---|
@@ -166,7 +171,7 @@ The loop reference is a simple rule (tail compression, repeated characters). It
166
 
167
  ## Training
168
 
169
- - **Pages:** 17,653 training and 1,124 validation pages from about 3,000 public-domain Biodiversity Heritage Library books, stratified by BHL page-type metadata (random, Blank, plate versos, plates). Books that hold any human-labelled page are held out. Medical Officer of Health pages (CC-BY-NC images) are not used for training.
170
  - **Targets** (soft, no human labels in training):
171
  - `no_text`: [openjev](https://huggingface.co/AlexWortega/openjev) 4B (MIT) with a page-question LoRA adapter, asked "The page has foreground text worth transcribing." at 896 × 1280, softened around a cut chosen on round-1 human labels.
172
  - `lo_*`: the actual outputs of LightOnOCR-3-0.8B (Apache-2.0) on every training page (truncation = `finish_reason == "length"`; loop = compression rule; blank-write = no-text page with ≥ 20 characters of output).
@@ -180,5 +185,9 @@ The loop reference is a simple rule (tail compression, repeated characters). It
180
  - **Labels:** one labeller, one pass. The boundary between blank and show-through, and between faint text and blank, is fuzzy.
181
  - **Conventions:** a page with only a library stamp, or an inserted "BLANK PAGE" card, counts as no text. Short captions and page numbers count as text: use [text-page-filter](https://huggingface.co/davanstrien/text-page-filter) to remove those.
182
  - **`faint` is not a faint-text detector.** It copies the teacher's "faded" answer, which fires on faded paper and show-through. On human faint-text labels it ranks backwards. Do not use it.
183
- - **LightOn heads are engine-specific.** They predict LightOnOCR-3-0.8B (vLLM, temperature 0.2, 4,096 tokens) failures. Other VLMs fail differently: in tests, GLM-OCR and PaddleOCR-VL wrote text on far more no-text pages.
184
  - **Teacher provenance:** the openjev page-question adapter was trained on DocLayNet, BHL page types and RVL-CDIP pages. RVL-CDIP has unclear licence terms. This model was trained on BHL images and the teacher's answers only.
 
 
 
 
 
10
  - page-classification
11
  - blank-page-detection
12
  - multi-label
13
+ - document-ai
14
+ - digitised-books
15
  ---
16
 
17
  # Page OCR flags
18
 
19
+ A small image model that looks at a scanned page **before** OCR and answers two questions:
20
 
21
  1. **Is there any text on this side of the leaf to transcribe?** (`no_text`)
22
+ 2. **Is [LightOnOCR-3-0.8B](https://huggingface.co/lightonai/LightOnOCR-3-0.8B) likely to fail on this page?** (`lo_blank_write`, `lo_trunc`, `lo_loop`, trained on that model's real outputs)
23
 
24
+ It is for libraries, archives and anyone else running OCR over a large number of page scans, for example to build a text corpus. Use it to skip pages with nothing to read, to send risky pages to a different OCR setup, or to explain bad OCR output after the fact ("this page only has show-through, so the text in the output was read from the other side of the leaf").
25
 
26
+ It was trained on pages from about 3,000 public-domain books in the [Biodiversity Heritage Library](https://www.biodiversitylibrary.org/) (BHL), mostly printed in the 19th and 20th centuries, and most of the evaluation pages come from BHL too. Check it on a sample of your own pages before you rely on it.
27
 
28
+ MobileNetV4-conv-small, 2.5M parameters, 384 × 384 input, about 50 pages per second on one CPU thread. A companion model, [text-page-filter](https://huggingface.co/davanstrien/text-page-filter), answers the stricter question "is this a page of body text?".
29
+
30
+ ![Hand-labelled example pages with the model's no_text score](assets/flags_notext.png)
31
+
32
+ A score near 1 means no text. The picture page is a miss: most labelled picture-only pages score as text.
33
 
34
  ## Outputs
35
 
 
39
  |---|---|---|
40
  | `no_text` | No text to transcribe on this side of the leaf: blank paper, show-through only, a library stamp only | Skip OCR, or treat OCR output as suspect |
41
  | `lo_blank_write` | Page has no text, but LightOnOCR-3 writes text anyway (usually it reads show-through) | Explain invented output |
42
+ | `lo_trunc` | LightOnOCR-3 reaches the 4,096-token output limit we used (long tables, or repetition) | Route to a larger token budget or another engine |
43
  | `lo_loop` | LightOnOCR-3 repeats itself (dot leaders, repeated lines) | Route or re-run |
44
  | `table`, `dense` | Page has a table / dense text (soft teacher answers) | Routing hints |
45
  | `faint` | **Experimental, do not use** (see limitations) | – |
46
 
47
+ ![Example pages where LightOnOCR-3 truncated, looped or wrote text on a no-text page, with the model's scores](assets/flags_lighton.png)
48
 
49
  ## How to use
50
 
 
67
 
68
  ### Suggested thresholds for `no_text`
69
 
70
+ | Score | Decision | On the 250 hand-labelled hard pages (see Evaluation) |
71
  |---|---|---|
72
  | > 0.8 | No text: skip, or flag any OCR output as invented | 67/67 blank, 83/90 show-through, 1/54 text |
73
+ | 0.2 – 0.8 | Unsure: route or check | a mix: 5 faint, 5 show-through, 4 text, 3 stamp, 1 picture |
74
  | < 0.2 | Text: OCR normally | 49/54 text |
75
 
 
 
76
  ## Speed
77
 
78
+ The model itself is fast. Decoding the page image usually takes longer than running it.
79
 
80
  | Setup | Pages per second, model only |
81
  |---|---|
 
92
  | JPEG 2000, decoded on the GPU with nvImageCodec (nvJPEG2000) (L4 / RTX PRO 6000) | ~80 / ~120 |
93
  | JPEG 2000, decoded on the CPU (Pillow/OpenJPEG) | ~1.5–3 per CPU core |
94
 
95
+ ### Running at scale (notes from testing on BHL pages)
96
 
97
+ - **JPEG derivatives:** for repeated runs over one collection, store small JPEG derivatives once. At 1,024 px (long side) they give the same decisions as full-size input (see the A10G row above). 512 px is about twice as fast but agrees slightly less.
98
+ - **GPU preprocessing** matches the reference: the same decision on every test page with DALI and JPEG input (largest score difference 0.015), and on 99.6% of pages with nvImageCodec, JPEG 2000 input and an area resize.
99
+ - **JPEG 2000 on the GPU:** nvJPEG2000 (via nvImageCodec or DALI) decoded all 60 BHL files we tested. Before batching, check that each file is complete (a complete codestream ends with `FF D9`), and decode on the CPU any page the GPU returns empty.
100
+ - **ONNX:** fp32 export gives identical scores. Static int8 quantisation changed the scores a lot in our test, so we do not recommend it.
101
 
102
  Batch inference with GPU decoding (JPEG bytes in):
103
 
 
130
 
131
  ## Evaluation
132
 
133
+ Numbers are for the released checkpoint (seed 1), plus mean ± SD over three training seeds where shown.
134
 
135
+ **Pages with ground-truth transcriptions:** 2,165 BHL pages. Text = at least 80 characters of ground truth.
136
 
137
  | | Result |
138
  |---|---|
 
141
  | Empty pages removed at 0.5 | 0.994 |
142
  | Pages with 1–79 characters (mostly plates with captions) | kept as text (removed: 0.047) |
143
 
144
+ **Hand-labelled hard pages:** 250 pages, one labeller. They were chosen where page models disagreed or flagged something, plus random audits, so they are not a random sample. Round 1 labels were also used to set the teacher's cut (see Training).
145
 
146
  | | AUROC, no text vs text |
147
  |---|---|
148
  | Round 1, 78 pages (BHL, Library of Congress and London Medical Officer of Health reports) | 0.970 (3 seeds: 0.976 ± 0.005) |
149
+ | Round 2, 172 pages (BHL and Medical Officer of Health reports; books not used in training) | 0.935 (3 seeds: 0.923 ± 0.015) |
150
 
151
  Confusion on these 250 pages (label × `no_text` score):
152
 
 
159
  | blank | 0 | 0 | 67 |
160
  | show-through only | 2 | 5 | 83 |
161
 
162
+ **LightOnOCR-3 failures:** 2,086 pages from 133 books not used in training. The reference is the real LightOnOCR-3-0.8B output on each page.
163
 
164
  | Output | Positives | AUROC | Precision @0.5 | Recall @0.5 | AUROC, 3 seeds |
165
  |---|---|---|---|---|---|
 
171
 
172
  ## Training
173
 
174
+ - **Pages:** 17,653 training and 1,124 validation pages from about 3,000 public-domain BHL books, sampled using BHL's page-type metadata (random pages, plus pages marked Blank, plate versos and plates). Books that hold any human-labelled page are held out. Medical Officer of Health pages (CC-BY-NC images) are not used for training.
175
  - **Targets** (soft, no human labels in training):
176
  - `no_text`: [openjev](https://huggingface.co/AlexWortega/openjev) 4B (MIT) with a page-question LoRA adapter, asked "The page has foreground text worth transcribing." at 896 × 1280, softened around a cut chosen on round-1 human labels.
177
  - `lo_*`: the actual outputs of LightOnOCR-3-0.8B (Apache-2.0) on every training page (truncation = `finish_reason == "length"`; loop = compression rule; blank-write = no-text page with ≥ 20 characters of output).
 
185
  - **Labels:** one labeller, one pass. The boundary between blank and show-through, and between faint text and blank, is fuzzy.
186
  - **Conventions:** a page with only a library stamp, or an inserted "BLANK PAGE" card, counts as no text. Short captions and page numbers count as text: use [text-page-filter](https://huggingface.co/davanstrien/text-page-filter) to remove those.
187
  - **`faint` is not a faint-text detector.** It copies the teacher's "faded" answer, which fires on faded paper and show-through. On human faint-text labels it ranks backwards. Do not use it.
188
+ - **The LightOn outputs only apply to one OCR model.** They predict LightOnOCR-3-0.8B (vLLM, temperature 0.2, 4,096 tokens) failures. Other OCR models fail in other ways, so do not use these outputs for them.
189
  - **Teacher provenance:** the openjev page-question adapter was trained on DocLayNet, BHL page types and RVL-CDIP pages. RVL-CDIP has unclear licence terms. This model was trained on BHL images and the teacher's answers only.
190
+
191
+ ## Other kinds of material
192
+
193
+ This model was built for printed books. If a more general version (manuscripts, newspapers, modern documents) would be useful to you, feel free to open a discussion in the Community tab. Or, even better, train one yourself! The recipe under Training is the starting point. For a different OCR model, use its outputs on your training pages as the `lo_*` targets.