page-classification — Reference¶
Look something up and stop reading. Every value here is taken from the code; the files it was read from are listed under Sources. For what the tool is and how a prediction is made, start at the Overview.
CLI — run.py¶
Defaults come from setup/config.txt, read before the parser is built. Flags written
--x / --no-x are argparse.BooleanOptionalAction: passing --no-x overrides a true
in the config file, which a bare store_true could not do.
Input and output¶
| Flag | Default | What it does |
|---|---|---|
-f, --file |
— | A single page image |
-d, --directory |
— | A folder of unprocessed pages |
--dir |
off | Process the folder named in [INPUT] FOLDER_INPUT instead of passing -d |
-ff, --file_format |
png ([SETUP] files_format) |
Extension to collect from a directory |
--inner / --no-inner |
True ([SETUP] inner) |
Recurse into nested folders |
--chunk / --no-chunk |
False ([INPUT] chunking) |
Write predictions in chunks of [INPUT] chunk_size (100), appending to the output file as the run proceeds |
-tn, --topn |
3 ([SETUP] top_N) |
How many categories to report. Validated at start-up: must be 1–11 |
--raw / --no-raw |
False ([SETUP] raw) |
Also emit per-class scores for all 11 categories |
Model selection¶
| Flag | Default | What it does |
|---|---|---|
-m, --model |
./model/model_<revision-without-dots> |
Folder holding the model sub-folders |
-b, --base |
timm/regnety_160.swag_ft_in1k ([SETUP] base_model) |
Base-model repository |
-rev, --revision |
— | Hugging Face revision: main, vN.0 or vN.M |
--hf / --no-hf |
False ([HF] use_hf) |
Take model and processor from the Hugging Face hub |
--yolo / --no-yolo |
False ([YOLO] use_yolo) |
Use a YOLO-cls model instead of ViT/CNN — overrides --base and --revision |
--yolo_base |
yolov8s-cls.pt ([YOLO] yolo_base) |
Short tag (yv8s), an Ultralytics id, or a local .pt path |
Ensemble¶
| Flag | Default | What it does |
|---|---|---|
--best |
off | Run all five models of the canonical ensemble; the result is averaged into one Top-N CSV automatically |
--parallel |
off | Memory-aware grouped parallel execution for --best. Requires CUDA |
--no-average-best |
off | Skip the automatic averaging |
--save-intermediates |
off | Also keep each model's individual Top-N CSV |
Training and evaluation¶
| Flag | Default | What it does |
|---|---|---|
--train / --no-train |
False ([TRAIN] Training) |
Train |
--eval / --no-eval |
False ([TRAIN] Testing) |
Evaluate |
--folds |
0 ([TRAIN] cross_runs) |
Number of cross-validation folds, 80/10/10 split |
--folds_csv |
[TRAIN] folds_csv |
Explicit folds CSV (PNG column + foldN columns holding train/dev/test). Takes precedence over --folds; pages absent from the CSV are excluded |
--fold_column |
auto | Which fold column to read. Resolved per revision from REVISION_BEST_FOLDS when omitted |
--average |
off | Average existing fold weights |
-ap, --average_pattern |
— | Which weights to average, e.g. model_v4 |
Document-record integration¶
| Flag | Default | What it does |
|---|---|---|
--document-json |
[DOCUMENT] document_json |
Baseline record, for a single-file run |
--document-json-out |
[DOCUMENT] document_json_out |
Where to write the updated record |
--document-json-dir |
[DOCUMENT] document_json_dir |
Baseline directory, for a batch run |
--document-json-out-dir |
[DOCUMENT] document_json_out_dir |
Output directory, one <doc_id>.document.json per document |
--strict-document-json / --no-… |
False ([DOCUMENT] strict) |
Turn ownership and schema warnings into errors |
The adapter is opt-in and a no-op unless an output path or directory is set, on the CLI or in the config.
Configuration — setup/config.txt¶
| Section | Keys as shipped |
|---|---|
[OUTPUT] |
FOLDER_RESULTS=./result · FOLDER_CPOINTS=./checkpoint · FOLDER_MODELS=./model |
[EVAL] |
FOLDER_PAGES=./small_data_samples |
[TRAIN] |
FOLDER_PAGES=./small_data_samples · cross_runs=0 · folds_csv= · test_size=0.1 · log_step=100 · epochs=3 · lr=5e-5 · max_categ=14000 · Training=False · Testing=False |
[SETUP] |
files_format=png · seed=420 · base_model=timm/regnety_160.swag_ft_in1k · batch=16 · top_N=3 · raw=False · inner=True |
[YOLO] |
use_yolo=False · yolo_base=yolov8s-cls.pt · yolo_imgsz=224 · yolo_epochs=30 · yolo_patience=20 · yolo_lr0=0 · yolo_dropout=0.1 · yolo_cache=True |
[INPUT] |
FOLDER_INPUT=./small_data_samples · chunk_size=100 · chunking=False |
[HF] |
repo_name=ufal/vit-historical-page · token= (read from $HF_TOKEN) · use_hf=False · revision=main · latest=v4.4 |
[DOCUMENT] |
all four paths blank · strict=False |
[HF] token is deliberately blank in version control; the token is read from $HF_TOKEN
at run time. [HF] latest names the newest published single-model revision; --best
does not read it, because the ensemble is fixed in code (see
The canonical ensemble). [YOLO] configures the optional YOLO-cls
path (yolo_imgsz, the Ultralytics training schedule); [DOCUMENT] holds the defaults for
the five document-record flags above.
HTTP service¶
Started by python -m service.api. Environment: HOST (0.0.0.0), PORT (8000),
RELOAD (false), GRACEFUL_SHUTDOWN_S (20), LOG_LEVEL (INFO), ALLOWED_ORIGINS,
MAX_UPLOAD_MB (10; the older MAX_UPLOAD_BYTES is still honoured as a fallback),
HF_HOME, HF_TOKEN. These are the ecosystem's shared service variables; the service
defines none of its own.
At start-up the service loads and warms the five models of the canonical ensemble
(v1.4–v5.4), off the event loop, before /ready reports ready. Any other revision
passed as version is resolved and downloaded on its first request.
| Method | Path | Returns |
|---|---|---|
GET |
/ |
a short JSON welcome message pointing at /info |
GET |
/info |
service, version, endpoints, limits (max_upload_mb: 10, max_pdf_pages: 50), categories (all 11), available_models (v1.4…v5.4 plus all) |
GET |
/health |
{"status":"ok"}, always 200 — liveness, including while draining |
GET |
/health?deep=true |
503 with status, detail, in_flight, draining when degraded or draining |
GET |
/ready |
503 starting until warm-up completes → 200 ready → 503 draining after SIGTERM |
POST |
/predict_image |
ImageResponse |
POST |
/predict_document |
per-page predictions for a PDF |
GET |
/docs, /openapi.json |
FastAPI built-ins |
A static browser interface is served at /frontend: upload an image or a PDF, pick a
model or the ensemble, and read the Top-N back.
Request fields¶
Both prediction endpoints take the same multipart form:
| Field | Type | Default | Notes |
|---|---|---|---|
file |
upload | required | /predict_image: content_type must start image/. /predict_document: must be exactly application/pdf |
version |
form | "all" |
One of v1.4…v5.4, or all for the ensemble |
topn |
form | 3 |
|
document_json |
upload | — | Baseline ATRIUM record to accrete onto |
document_json_out |
form bool | False |
Ask for the updated record in the response |
Response shapes¶
POST /predict_image:
{ "type": "image",
"predictions": [{"label": "TEXT_P", "score": 0.981}],
"document_json": null,
"document_json_schema_error": null }
POST /predict_document — one entry per PDF page, numbered from 1; document_json is
added only when a record was asked for:
From the command line:
curl -F file=@page.png -F version=v4.4 -F topn=3 http://localhost:8000/predict_image
curl -F file=@report.pdf -F version=all http://localhost:8000/predict_document
curl -F file=@page.png -F document_json=@record.json -F document_json_out=true \
http://localhost:8000/predict_image # accrete onto a baseline record
service/api_client.py wraps the same calls: python3 service/api_client.py -f page.png -v v4.4 --top 3.
document_json_schema_error is non-null only when an uploaded baseline failed to
validate and the record was emitted with a warning rather than refused — a field an
automated caller can test instead of grepping the service log.
Errors¶
| Code | When |
|---|---|
400 |
Wrong content type for the endpoint |
413 |
Over MAX_UPLOAD_MB, or a PDF with more than 50 pages |
422 |
The uploaded document_json baseline is unparseable, or written by a newer schema |
500 |
Inference failure, or the service's own output failed schema validation |
503 |
Draining after SIGTERM |
The canonical ensemble¶
--best, parallel_best.run_best_models and the service's version=all all read one
dict, REVISION_BEST_MODELS. Since v1.8.0-beta it names the v*.4 generation.
| Revision | Base model | Fold | Resolution | Params (bytes) |
|---|---|---|---|---|
v1.4 |
timm/tf_efficientnetv2_m.in21k_ft_in1k |
fold1 |
384 | 211,489,788 |
v2.4 |
google/vit-base-patch16-224 |
fold5 |
224 | 343,228,460 |
v3.4 |
google/vit-base-patch16-384 |
fold2 |
384 | 344,395,820 |
v4.4 |
timm/regnety_160.swag_ft_in1k |
fold1 |
384 | 322,393,660 |
v5.4 |
google/vit-large-patch16-384 |
fold2 |
384 | 1,214,808,108 |
The fold rule is splitN ↔ foldN column ↔ seed = 420 + (N−1). Resolution and parameter
size come from MODEL_STATIC, which --parallel reads to group models by memory before
running them.
tests/test_best_ensemble_distinct.py guards the ensemble: its static half asserts that
the five revisions name five distinct base models, and its -m slow half reads each
revision's config.json from the Hub and asserts the published architectures are
distinct too. The changelog records why that test exists.
Two version namespaces, and where they collide¶
REVISION_TO_BASE_MODEL holds both, and reading it as one is the mistake:
- Sweep numbering — one number per candidate base model, as recorded in
model_accuracies_new.csv:v4.= efficientnetv2_L,v6.= regnety_120,v7.= regnety_160,v8.= regnety_640,v9./v10./v11.= the DiT family,v12.= efficientnetv2_M. - Published numbering — the five sweep winners, re-published under compact names:
v1.3= efficientnetv2_m (sweepv12.3.1),v2.3= vit-base-224,v3.3= vit-base-384,v4.3= regnety_160 (sweepv7.3.1),v5.3= vit-large-384.
They collide on v1.3 and v4.3. The published meaning wins, by exact-key-first
resolution rather than by ordering luck. model_accuracies_new.csv records the sweep
namespace; the Hugging Face model card and this site use the published one.
Outputs and their columns¶
Written under [OUTPUT] FOLDER_RESULTS (default ./result).
| File | Columns |
|---|---|
{stamp}_{rev}_TOP-{N}.csv |
FILE, PAGE, CLASS-1…N, SCORE-1…N. At top_N == 1 a CATEGORY alias is added; CLASS-1 and SCORE-1 are kept, because the confidence is needed for the document record |
{stamp}_{rev}_RAW.csv |
FILE, PAGE followed by all 11 category columns, in canonical order |
{stamp}_{n}_{rev}_TOP-{N}_EVAL.csv |
as Top-N, plus TRUE (the gold label) |
{stamp}_{n}_{rev}_EVAL_RAW.csv |
raw per-class probabilities, plus TRUE |
{stamp}_BEST_{n}_models_TOP-1.csv |
FILE, PAGE, CLASS-1-v1.4 … CLASS-1-v5.4 — one Top-1 column per model |
{stamp}_BEST_{n}_models_AVG_TOP-{N}.csv |
FILE, PAGE, V1.4 … V5.4, CLASS-1, SCORE-1, … — probability-averaged. Zero scores are emitted blank, and the paired CLASS-K is blanked too |
{stamp}_{n}_{rev}_conf_mat_TOP-{N}.png |
confusion matrix, 300 dpi |
{stamp}_{rev}_FOLD_{n}_DATASETS.txt |
the train/dev/test split actually used |
model/gpu_profile.json |
measured VRAM profile used by --parallel to group models |
{stamp}_page-classification.json |
paradata — tool version, run id, resolved licence, config, statistics, skipped files |
<doc_id>.document.json |
the ATRIUM record, carrying page_categories, pages[].category, pages[].category_confidence |
ro-crate-metadata.json |
RO-Crate 1.1 JSON-LD, from atrium_rocrate.py's own CLI |
RO-Crate export
atrium_rocrate.py, vendored from the hub, is a deterministic exporter — @graph
sorted by @id, datePublished derived from the record's stamps rather than the
clock — with --document, --run, --paradata, --out-dir and --selftest. It
runs as a separate step over finished records. See
RO-Crate export.
Licence resolution¶
setup/para_config.txt declares the components; the paradata records what a given run
actually resolved to.
| Component | Licence | When it counts |
|---|---|---|
vit_models |
MIT | always |
lindat_dataset |
CC BY-NC 4.0 | conditional — only when training |
ultralytics |
AGPL-3.0 | conditional — only with --yolo |
So an inference or evaluation run is MIT; --train pulls in the LINDAT dataset and
the run resolves to CC BY-NC 4.0 — non-commercial, not share-alike. A --yolo run adds
Ultralytics: an inference run with it resolves to AGPL-3.0, and with --train the
resolver ranks CC BY-NC 4.0 above it. The rule — the most restrictive component wins — is
the shared para_licenses.py, the same in every ATRIUM tool.
Sources¶
Read from ufal/atrium-page-classification at branch vit, commit adee922
(2026-09-23). This table records provenance, not a build instruction.
| Source | What was taken from it |
|---|---|
run.py (build_parser) |
the complete flag table and every default |
setup/config.txt |
the configuration table |
model_registry.py |
ensemble, folds, MODEL_STATIC, the two namespaces |
service/api.py, service/inference.py |
limits, warm-up, response models, endpoints, errors |
service/atrium_service.py |
/health, /ready and the upload-limit resolution |
service/api_client.py |
the client invocation |
utils.py, parallel_best.py |
the CSV column construction and the ensemble file names |
setup/para_config.txt |
licence components |