Image Classification
timm
Safetensors
glam
ocr
historical-documents
page-classification
blank-page-detection
multi-label
document-ai
digitised-books
Instructions to use small-models-for-glam/page-ocr-flags with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- timm
How to use small-models-for-glam/page-ocr-flags with timm:
import timm model = timm.create_model("hf-hub:small-models-for-glam/page-ocr-flags", pretrained=True) - Notebooks
- Google Colab
- Kaggle
Card: more general framing; reviewer fixes
Browse files
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
|
| 18 |
|
| 19 |
1. **Is there any text on this side of the leaf to transcribe?** (`no_text`)
|
| 20 |
-
2. **Is
|
| 21 |
|
| 22 |
-
Use it to skip pages with nothing to read, to
|
| 23 |
|
| 24 |
-
|
| 25 |
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
 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
|
| 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 |
+

|
| 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 |
+

|
| 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.
|