page-classification reads the image of a scanned archival page and says what kind of page it is. It chooses one of eleven categories, such as running text, a table or form, a drawing or a photograph, with handwritten, printed or typed text. The category tells which processing the page needs next, for example OCR or table extraction. Use it on scans of paper documents; the labels it gives born-digital pages have not been checked.
Releases · MIT licence · ATRIUM documentation · Models on Hugging Face
| Input | Output |
|---|---|
a page image (any format Pillow reads: PNG, JPEG, TIFF, …), a folder or a tree of them, named <document>-<page>.png |
per page, the best categories with their scores: a CSV (FILE, PAGE, CLASS-1…N, SCORE-1…N; at -tn 1 also CATEGORY, a copy of CLASS-1: outputs), or JSON from the service |
a PDF, recognised by its content: every page, or a pages selection, rendered at 300 dpi |
optionally all eleven scores (--raw, or topn=11) and each model's own vote (--best --save-intermediates, or per_model=true) |
| optionally, an ATRIUM document record to add to | the record with its page_categories block and each page's category and category_confidence; the run's paradata |
The tool reads the image only, so the language of the page does not matter. The command line and the service read the same inputs the same way; what differs, and why, is in docs/usage.md.
The eleven categories and their definitions: docs/reference/categories.md. The record follows the hub's document contract; page-classification is its first stage, so it creates the record as often as it adds to one.
What you need: Docker, or Python 3.11 and a checkout of this repository. Network access to huggingface.co on the
first start, to download the models (about 2.44 GB for the five of the ensemble); the tool calls no other host. Memory
for the models. A GPU is optional: a CPU works, more slowly.
The service, from the published image, on a sample page of this repository:
docker run --rm -p 8000:8000 -v hf-cache:/cache/huggingface \
ghcr.io/ufal/atrium-page-classification-api:<version> # the release without its v, e.g. 1.14.2-beta
curl -s localhost:8000/ready # fails until the five models are loaded, then 200
curl --fail-with-body -sS -X POST localhost:8000/predict_image \
-F "file=@small_data_samples/TEXT_T/pages_online_2.png" -F version=v4.4 -F topn=3Expected result (abridged):
{"type": "image",
"predictions": [{"label": "TEXT_T", "score": 0.9}, {"label": "TEXT", "score": 0.099}, {"label": "TEXT_P", "score": 0.001}],
"models_used": ["v4.4"], "limits_applied": [], "document_json": null,
"paradata": {"@type": "CreateAction", "…": "…"}}The scores are those v4.4 gave this page in the stored run
result/tables/20261022-1218_v44_TOP-3.csv, rounded. Without version
the service uses the ensemble (all), and models_used lists the five revisions that answered. The image holds no
weights. On its first start it downloads the five models from the Hugging Face Hub into the hf-cache volume.
/health answers at once; /ready returns 200 once the models are loaded. Every endpoint, limit and error:
service/README.md.
The same page on the command line, from a checkout with its setup done:
python3 run.py --hf -rev v4.4 -f small_data_samples/TEXT_T/pages_online_2.pngAfter the argument list and the download messages it prints:
File small_data_samples/TEXT_T/pages_online_2.png predicted:
TEXT_T: 90.0%
TEXT: 9.9%
TEXT_P: 0.1%
Results for TOP-3 predictions are recorded into ./result/tables/<date-time>_model_v44_TOP-3.csv
The classifiers are fine-tuned Vision Transformer, RegNetY and EfficientNetV2 models, published as revisions of
ufal/vit-historical-page. They were trained on annotated pages of
archaeological reports from Czech archives, 1920–2020, published as a dataset on LINDAT. One revision can run alone,
or the five vX.4 revisions run as an ensemble that averages their scores; the ensemble is the default of the HTTP
service.
Each page image is resized to the model's square input (224 or 384 pixels) and scored for all eleven categories;
the scores become probabilities, and the best ones are kept, best first. With --best (or version=all) five
models score every page and their probabilities are averaged, so a page on which they disagree gets a lower top
score. The command line writes CSV tables and a paradata log; the service answers in JSON. More, with the diagrams:
docs/how-it-works.md.
page-classification is the first stage of the route for scanned documents. Its categories inform a routing
decision, taken by a person or a script reading the results: which pages go to OCR, to handwriting recognition, to
table extraction or to image extraction. The next stage, ocr-postprocess, works from the OCR output and does not
read the categories. On the born-digital route, digital-convert's /describe asks /predict_document about the
pages that have no usable text layer. The whole chain: the
hub's workflow page.
- Accuracy is measured for single models, not for the ensemble. Each
vX.4model scored 94.64–95.87 % top-1 and 99.75–99.90 % top-3 on about 4,800 held-out pages of the fold it was trained on. The split is by page, so about a quarter of those pages come from documents with other pages in training; on pages of unseen documents top-1 is 93.87–95.60 %. The ensemble has no held-out figure, because its members were trained on different folds (docs/evaluation/results.md). - Some pages were not measured. Born-digital pages, pages unlike the archaeological reports of the training data, and the optional YOLO backend have no held-out figure, so treat their labels as unchecked. The 2025 sweep's 98–99 % were measured mostly on training pages and are not held-out accuracy (the sweep). The test suite checks that the code does what it says; it does not measure accuracy.
- Neighbouring categories are the hard cases.
TEXT_Tis most often taken forTEXT(recall 87–92 %), andLINE_HWforLINE_T, and one category is given per page. A low top score with a close second marks a page worth a look (reading the output). - Name a revision. The Hub's
mainrevision still resolves tov4.3, the generation beforevX.4, so pass-rev v4.4(or--best) rather than relying on it.setup/config.txthas[HF] revision = main, and the batch image's default command runs-rev v4.3(models). - PDFs are rendered with pypdfium2 at 300 dpi, by the service and by the command line, while the training pages were made with pdftoppm (the service's known limits).
- One service request is limited in size. It classifies at most
MAX_PDF_PAGESpages (50 by default; a deployment can change it) and takes at most 10 MB (MAX_UPLOAD_MB). Send a longer PDF in parts withpages. The command line has no such limit (Limits). - The ensemble can answer with fewer models when one fails to load, so check that
models_usedlists five (Partial results). - The service answers nothing while it loads its models. The first start downloads about 2.44 GB. Until then both
/healthand/readyrefuse the connection, so wait for/readyto return 200 (Known limits).
| For | To | Read |
|---|---|---|
| using it | see one result at once | the Quick start above |
| learn what the eleven labels mean | docs/reference/categories.md | |
| classify your own pages, a folder at a time, and read the result tables | docs/usage.md | |
| look up models, flags, outputs, paradata and licences | models, flags, outputs, paradata, licences | |
set every option of setup/config.txt |
docs/configuration.md | |
| running the service | deploy and operate it, and read its limits and errors | service/README.md |
| see the API contract | service/openapi.json | |
| judging the results | decide how far to rely on them | Limits and maturity above |
| see what was measured, and on which data | results, data, the 2025 sweep | |
| changing the code | understand how a page is classified and how the ensemble runs | docs/how-it-works.md |
| set up, test, release; find the history | docs/development.md, CONTRIBUTING.md (its Release History is the changelog) | |
| read why it is built this way | docs/decisions/history.md | |
| turn PDFs into an annotated training set | docs/data-preparation.md | |
| train, retrain on the published folds, evaluate | docs/training.md | |
run the decision-model experiment decide_pages.py; not in the service |
docs/reference/cli.md |
Terms used across the tools are explained in the ATRIUM glossary. The documentation follows the ATRIUM documentation style.
The code is MIT (LICENSE). The fine-tuned models, the LINDAT training dataset they were trained on and the sample pages in small_data_samples are CC BY-NC 4.0, the optional YOLO backend (Ultralytics) is AGPL-3.0, and the service renders PDFs with pypdfium2 (Apache-2.0 or BSD-3-Clause). Each component, with its licence and where that is stated: docs/reference/licences.md. The paradata of a run lists the components it used; it does not set the rights in what the run produces, which depend first on the source document (Licences and paradata).
Cite the method with the preprints Page image classifier fine-tuned on century-spanning archives of scanned documents for further content-specific processing (arXiv 2606.07558, 2026) and Page image classification for content-specific data processing (arXiv 2507.21114, 2025), the models with their Hugging Face repository and revision, the data with its LINDAT record, and the software with CITATION.cff, which lists the authors with their ORCIDs. The tool is listed in the SSH Open Marketplace as Historical documents page classifier - ATRIUM.
Developed by ÚFAL, Charles University, with AMČR (the Archaeological Map of the Czech Republic) as the pipeline partner. Funded by ATRIUM (GA 101132163), with the support of the LINDAT/CLARIAH-CZ research infrastructure and the AIS CR infrastructure (LM2023031). Contact: lutsai@ufal.mff.cuni.cz, or the issue tracker.
© 2026 ÚFAL & ATRIUM