Instructions to use small-models-for-glam/text-page-filter with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- timm
How to use small-models-for-glam/text-page-filter with timm:
import timm model = timm.create_model("hf-hub:small-models-for-glam/text-page-filter", pretrained=True) - Notebooks
- Google Colab
- Kaggle
Text page filter
A small image model that answers one question about a scanned page: is this a page of body text?
It keeps pages with running text, lists and tables, and drops pages without it: blank paper, show-through from the other side of the leaf, library stamps, plates with a short caption, and pages with only a few lines. Use it to build text corpora from page scans, or to pick the pages worth sending to an expensive OCR model. It was trained mostly on 19th- and 20th-century printed books from the Biodiversity Heritage Library (BHL), so check it on a sample of your own pages before you rely on it.
MobileNetV4-conv-small, 2.5M parameters, 384 × 384 input, about 50 pages per second on one CPU thread. For the looser question "is there any text here?" (captions and page numbers count) plus OCR-risk flags, use the companion model page-ocr-flags.
How to use
The model has one sigmoid output, not_proper_text: the probability that the page has no body text. Keep pages with not_proper_text < 0.5.
import timm, torch
from PIL import Image
model = timm.create_model("hf-hub:small-models-for-glam/text-page-filter", pretrained=True).eval()
cfg = timm.data.resolve_data_config({}, model=model) # 384 x 384, squash (no crop), ImageNet mean/std
transform = timm.data.create_transform(**cfg)
page = Image.open("page.jpg").convert("L").convert("RGB") # trained on grey pages
with torch.inference_mode():
p = torch.sigmoid(model(transform(page).unsqueeze(0)))[0, 0].item()
print("body text" if p < 0.5 else "no body text", round(p, 3))
Convert pages to grey first, as in the example: the model was trained on grey pages.
Batch inference on the GPU (NVIDIA DALI, JPEG bytes in)
import numpy as np, timm, torch
from nvidia.dali import fn, pipeline_def, types # pip install nvidia-dali-cuda130 --extra-index-url https://pypi.nvidia.com
from torch.utils.dlpack import from_dlpack
BATCH = 64
@pipeline_def(batch_size=BATCH, num_threads=4, device_id=0)
def pages():
enc = fn.external_source(name="enc", dtype=types.UINT8)
img = fn.decoders.image(enc, device="mixed", output_type=types.GRAY) # decode on the GPU
return fn.resize(img, resize_x=384, resize_y=384, interp_type=types.INTERP_LINEAR, antialias=True)
pipe = pages(); pipe.build()
model = timm.create_model("hf-hub:small-models-for-glam/text-page-filter", pretrained=True).eval().cuda().half()
mean = torch.tensor([0.485, 0.456, 0.406], device="cuda").view(1, 3, 1, 1)
std = torch.tensor([0.229, 0.224, 0.225], device="cuda").view(1, 3, 1, 1)
def score(jpeg_bytes): # a list of BATCH encoded JPEGs
pipe.feed_input("enc", [np.frombuffer(b, dtype=np.uint8) for b in jpeg_bytes])
out, = pipe.run()
x = from_dlpack(out.as_tensor()).permute(0, 3, 1, 2).float() / 255 # (B, 1, 384, 384)
x = ((x.expand(-1, 3, -1, -1) - mean) / std).half()
with torch.inference_mode():
return torch.sigmoid(model(x).float()).cpu()
What counts as "body text"
A page counts as body text when a page-question model says it has text worth transcribing and LightOnOCR-3-0.8B writes at least 100 characters on it. So:
| Page | Decision (by the target definition; not measured per type) |
|---|---|
| Running text, lists, indexes, tables | kept |
| Blank paper, show-through only, stamp only | dropped |
| Plate with a short caption (about 100 characters or less) | dropped |
| Page number, running head, half-title only | dropped |
Evaluation
| Test set, cut at 0.5 | Result |
|---|---|
| 2,165 BHL pages with ground-truth transcriptions: text pages (≥ 80 characters) kept | 0.941 |
| Same pages: empty and few-line pages (under 80 characters) removed | 0.986 (mean of 3 training runs) |
| 250 hand-labelled hard pages (one labeller; chosen deliberately, so not a random sample): non-text pages removed | 0.951 (mean of 3 training runs) |
Almost all the text pages it drops are plates with captions: 88 of the 102 come from one volume of shell plates (median 113 characters of ground truth). This is the intended behaviour, but check it fits your definition of a text page.
Full evaluation tables
Numbers are for the released checkpoint (seed 1), plus mean ± SD over three training seeds where shown.
Pages with ground-truth transcriptions: 2,165 BHL pages.
| Result | |
|---|---|
| AUROC, text pages (≥ 80 characters) vs empty pages | 0.999 |
| Pages under 80 characters (empty and few-line) removed at 0.5 | 0.986 ± 0.002 (3 seeds) |
| Text pages kept at 0.5 | 0.941 (3 seeds: 0.943 ± 0.010) |
Hand-labelled hard pages: 250 pages, one labeller. Text = the labeller's "text" class, which includes short pages and captions, so this definition is looser than the model's.
| Result | |
|---|---|
| AUROC, text vs everything else | 0.952 (round 1, 78 pages), 0.935 (round 2, 172 pages) |
| Non-text pages removed at 0.5 (3 seeds) | 0.951 ± 0.006 |
| Labelled text pages kept at 0.5 (3 seeds) | 0.735 ± 0.028 (short pages and captions are dropped by design) |
Speed
Decoding the page image usually takes longer than running the model.
| Setup | Pages per second |
|---|---|
| Model only, 1 CPU thread (PyTorch / ONNX Runtime, fp32) | ~50 / ~85 |
| Model only, NVIDIA L4 (fp16, batch 64) | ~3,000–4,000 |
| JPEG in, decoded on the GPU with NVIDIA DALI (L4 / RTX PRO 6000) | ~300 / ~430 |
| JPEG 2000 in, decoded on the CPU (Pillow/OpenJPEG) | ~1.5–3 per CPU core |
More speed figures and tips for large runs
| Setup | Pages per second |
|---|---|
Model only, NVIDIA RTX PRO 6000 (fp16, torch.compile) |
~40,000 |
| JPEG derivative, 1,024 px, decoded with DALI (A10G) | ~2,000 |
| JPEG 2000, decoded on the GPU with nvImageCodec (nvJPEG2000) (L4 / RTX PRO 6000) | ~80 / ~120 |
- 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. 512 px is about twice as fast but agrees slightly less.
- 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.
- 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. - ONNX: fp32 export gives identical scores. Static int8 quantisation changed the scores a lot in our test, so we do not recommend it.
Limitations
- Domain: mostly 19th–20th-century printed books. Manuscripts, newspapers and modern documents are untested.
- The 100-character line is a choice. Pages near it (a short poem, a chapter opening, a caption-heavy figure page) can go either way.
- Show-through is the hardest case. The model removes most show-through pages, but some with strong mirrored text can pass as text.
Training
- Data: 17,653 training pages from about 3,000 public-domain BHL books. Books with hand-labelled pages are held out.
- Target: a VLM page-question teacher (openjev 4B, MIT, with a LoRA adapter) combined with the length of the output of LightOnOCR-3-0.8B (Apache-2.0), as described above.
- Recipe: MobileNetV4-conv-small from ImageNet, grey pages squashed to 384 × 384, BCE on soft targets, 8 epochs, 104 s on one A100.
- 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.
Other kinds of material
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 Training section is the starting point.
- Downloads last month
- 7
Model tree for small-models-for-glam/text-page-filter
Base model
timm/mobilenetv4_conv_small.e2400_r224_in1k