Skip to content

Releases

v1.10.0 — Improved print recognition, optional page orientation and custom domain packs

Choose a tag to compare

@FilipJy FilipJy released this 22 Oct 06:41

What changed

A new print recognizer, optional page orientation and contrast normalisation, and support for custom domain packs.

Printed pages

  • New default recognizer, rec-E-v7. Better recognition of large type, dark or coloured paper, faint typewriting, carbon copies, dot-matrix print and one-line formulas. Adds Slovak letters and common Greek letters and symbols.
  • Page orientation (--orientation auto). Reads sideways and upside-down scans, keeping output coordinates in the original image.
  • Contrast normalisation (--contrast auto). Helps with pale carbon copies and faint typewriting. Orientation and contrast are both off by default.

Custom domain packs

  • Install a .tpack containing your own recognizer, vocabulary and settings, with an optional layout model. Select it by name through the CLI or API, or make it the default.
  • New CLI flags: --version, --list-domains, --check-domains. New API endpoint: GET /api/v1/version.
  • ALTO and PAGE XML record the pack name and version.

Compatibility

  • Printed recognition changes by default. Set TUZKAOCR_OCR_MODEL=rec-E-v5.onnx to keep the previous recognizer.
  • A pack marked as default takes over domain=default; use domain=print to select built-in print.
  • Unknown API domains now return 422 instead of 400.
  • No changes to the bundled handwritten, Kurrent or Kramarky models.

v1.9.0 — Column-first reading order, newspaper masthead pass, single-pass print layout + Docker image

Choose a tag to compare

@FilipJy FilipJy released this 14 Oct 15:01

What changed

Printed pages get a faster single-pass layout, column-first reading order, and a new
pass that reads newspaper mastheads. The CPU Docker image is now published with every
release. Handwritten, Kurrent and Kramarky output is unchanged.

Printed pages

  • Single-pass layout. Each page is laid out once, at a scale matched to its text size,
    replacing the old multi-pass escalation. Accuracy is the same or better, and newspapers process about 20% faster.
  • Column-first reading order (TUZKAOCR_READING_ORDER=columns, default). Columns are
    read top to bottom.
  • Mastheads (TUZKAOCR_LARGE_TEXT=true, default). Newspaper titles and other very
    large type are read as whole lines.

Docker image

docker run -d -p 8000:8000 ghcr.io/moravianlibrary/tuzkaocr:1.9.0

Published to ghcr.io/moravianlibrary/tuzkaocr for amd64 and arm64 (1.9.0, 1.9,
latest). See Docker.

Compatibility

  • Printed output changes in line segmentation and block order. For the previous block
    order without the masthead pass, set TUZKAOCR_READING_ORDER=basic and
    TUZKAOCR_LARGE_TEXT=false.
  • New settings: TUZKAOCR_LAYOUT_LINE_HEIGHT, TUZKAOCR_READING_ORDER,
    TUZKAOCR_LARGE_TEXT; CLI --reading-order, --no-large-text.
  • No API, model or dependency changes.

v1.8.0 — NDK ALTO profile, PAGE XML output, rec-I-v2 page-style handwritten recognizer, single-pass layout

Choose a tag to compare

@FilipJy FilipJy released this 09 Oct 08:42

What changed

ALTO output now targets the Czech NDK ingest profile by default. It adds Processing
provenance, page margins, styles with estimated font sizes, detected LANG, word and
character confidence, and hyphenation. The previous output shape is still available as
alto_profile=basic. A new PAGE XML output format (fmt=page) keeps line baselines and
polygons for correction tools such as Transkribus and eScriptorium. The handwritten and
Kurrent domains get a new writer-adaptive recognizer, rec-I-v2, which reads every line
with the style of its page. The handwritten, Kurrent and Kramarky domains now always use
single-pass layout.

ALTO: NDK profile (new default)

  • New ndk profile, selected with TUZKAOCR_ALTO_PROFILE (server-wide), the
    alto_profile form field (API) or --alto-profile (CLI). basic reproduces the
    previous output shape.
  • Provenance is written as Processing elements (IdPreOperation when the page was
    downsampled for layout, IdLayout, IdRecognition). Each one carries the package
    version and the model name in applicationDescription. processingAgency is written
    when TUZKAOCR_ALTO_AGENCY / --alto-agency is set.
  • sourceImageInformation records the source filename and, optionally, a
    fileIdentifier (source_identifier / --source-identifier).
  • PHYSICAL_IMG_NR comes from physical_img_nr / --physical-img-nr, then from a
    trailing page number in the filename (pr_0007.jp2 → 7), then defaults to 1.
  • PrintSpace is the bounding box of the body text, and the four margins are always
    present. With role classification on, header and footer/page-number blocks move into
    TopMargin / BottomMargin.
  • TextBlocks carry the detected region outline as Shape/Polygon.
  • Confidence: WC and CC on every String, and ACCURACY and PC on Page. All four
    are derived from the recognizer's per-character probabilities.
  • Hyphenation: a word broken across a line end is written as HypPart1 / HypPart2
    fragments with SUBS_CONTENT and a HYP element. Plain-text output is unchanged.
  • Styles: ParagraphStyle/@ALIGN is measured per block. FONTSIZE is estimated from line
    geometry and the scan resolution. The resolution comes from TUZKAOCR_ALTO_DPI, then
    the image metadata (below 100 dpi is ignored), then TUZKAOCR_ALTO_PAGE_WIDTH_MM.
    FONTFAMILY is written only when TUZKAOCR_ALTO_FONTFAMILY is set.
  • Language: a new bundled detector, lang-A-v1.npz (45 KB, NumPy only), sets LANG for
    Czech, German, English, Slovak, Polish and Latin. Other languages and pages
    with very little text get no LANG. TUZKAOCR_ALTO_LANG / --alto-lang forces a
    value.
  • Identifiers are page-sequential (P1_TB0001, P1_TL0001, P1_ST0001, P1_SP0001),
    and SP elements carry coordinates.
  • --ndk-warn (CLI) reports a missing agency and pages whose PHYSICAL_IMG_NR fell back
    to 1.
  • Known gaps are listed in the docs under NDK profile: remaining limitations. They are
    ComposedBlock/Illustration, FONTSTYLE, ALTERNATIVE, ROTATION/POSITION/
    QUALITY, StructureTag/@TYPE, and marginalia in side margins.

PAGE XML output (new)

  • New output format page: fmt=page (API) or --format page (CLI). It writes PAGE XML
    in the 2019-07-15 schema, which Transkribus, eScriptorium, Kraken and OCR-D read, and
    the output validates against the official pagecontent.xsd.
  • Unlike ALTO, PAGE keeps the detected line geometry. Each TextLine carries its
    Baseline and the oriented quadrilateral it was read from, so skewed and curved lines
    survive a round trip into a correction tool. Regions carry the detected outline, words
    carry rectangles, and every point is clipped to the page.
  • TextEquiv/@conf on words and lines uses the same recognizer confidence as ALTO WC.
    An explicit ReadingOrder lists the regions, and Metadata records the package
    version and the layout and recognition models.
  • primaryLanguage comes from the same language detector as ALTO LANG, written as a
    PAGE language name (Czech, German, …). TUZKAOCR_ALTO_LANG overrides it as well.
  • With role classification on, a region's type is the dominant role of its lines
    (heading, header, footer, page-number, otherwise paragraph). Non-body lines
    also get a Transkribus-style custom="… structure {type:heading;}".
  • PAGE has no per-character confidence and no hyphenation markup, so a word broken across
    a line end stays as two words, as in plain text. The --alto-* options and
    alto_profile don't affect it, and multi still produces ALTO + text only.
  • API results are stored as <job_id>.page.xml and served by the usual result endpoint.

Recognition (handwritten + Kurrent)

  • rec-I-v2 is the new default for domain=handwritten and domain=kurrent, replacing
    rec-H-v6. A small style model reads all lines of the page first, and the recognizer
    then reads each line with that page-level style. A single-line page is read without
    page context.
  • It ships as two files, rec-I-v2.onnx (12.6 MB) and rec-I-v2.style.onnx (0.3 MB).
    They must sit in the same directory. The style file is found by name, and startup fails
    without it. --ocr-model pointing at a page-style recognizer picks up its .style
    sibling automatically.
  • Vocabulary widened 158 → 203 characters, mostly Western European accented letters
    (è à ê ç ñ œ æ ø å …) plus & $ £ { } # ½ ¼ ¾ ¶ ẞ ” ’. New entries are appended, so
    the print, Kramarky and rec-H-v6 recognizers decode identically.
  • rec-H-v6.onnx stays bundled for pinning. GET /api/v1/models lists rec-I-v2.onnx
    but not the style file.

Layout: single pass for handwritten, Kurrent and Kramarky

  • Adaptive downsampling now applies to the printed domain only. handwritten, kurrent
    and kramarky always use a single pass at the standard downsample, whatever
    TUZKAOCR_ADAPTIVE_DOWNSAMPLE says.
  • On handwriting, the finer passes split faint pencil and grid-paper lines into fragments.
    A single pass is more accurate and about 3× faster in layout.
  • On Kramarky, a single pass is about 2× faster, at roughly +0.3 pp CER on the benchmark
    set.

Model files

  • Removed from the package: dec-A-v4.onnx, dec-A-v3k5.onnx, rec-H-v4.int8.onnx,
    rec-H-v3h-kurrent.int8.onnx and role-G-v1.npz.

Other

  • Empty numeric environment variables (for example TUZKAOCR_ALTO_DPI=) now fall back to
    their default instead of failing to parse.

Compatibility

  • ALTO output changes by default. OCRProcessing becomes Processing, identifiers
    change from block_/line_/word_ to P1_TB…/P1_TL…/P1_ST…, PrintSpace no longer
    covers the whole page, and several new elements and attributes are added. Consumers
    written against the old shape should set TUZKAOCR_ALTO_PROFILE=basic or send
    alto_profile=basic.
  • New output format page for the API fmt field and the CLI --format flag. Existing
    formats are unchanged.
  • New optional API form fields: alto_profile, source_identifier and
    physical_img_nr. An unknown alto_profile or a physical_img_nr below 1 returns
    400. Existing requests are unaffected.
  • New CLI flags: --alto-profile, --alto-agency, --alto-lang, --alto-dpi,
    --alto-page-width-mm, --source-identifier, --physical-img-nr and --ndk-warn.
    --source-identifier and --physical-img-nr are rejected together with --batch.
  • New env vars: TUZKAOCR_ALTO_PROFILE, TUZKAOCR_ALTO_AGENCY, TUZKAOCR_ALTO_LANG,
    TUZKAOCR_ALTO_FONTFAMILY, TUZKAOCR_ALTO_DPI, TUZKAOCR_ALTO_PAGE_WIDTH_MM and
    TUZKAOCR_LANG_MODEL. For NDK ingest, set at least TUZKAOCR_ALTO_AGENCY.
  • Handwritten and Kurrent output changes because of the new recognizer and single-pass
    layout. To keep the previous recognizer, pin both
    TUZKAOCR_HANDWRITTEN_OCR_MODEL=rec-H-v6.onnx and
    TUZKAOCR_KURRENT_OCR_MODEL=rec-H-v6.onnx.
  • Kramarky output changes on pages that previously escalated to a finer layout pass.
    Printed (default) recognition is unchanged.
  • Pins to removed models now fail at startup. This includes
    rec-H-v3h-kurrent.int8.onnx, and rec-H-v4.int8.onnx. Move those pins to rec-H-v6.onnx or
    rec-I-v2.onnx.
  • A custom TUZKAOCR_VOCAB must match the model in use. rec-I-v2 needs all 203
    characters and fails to load with a shorter vocabulary.
  • No new runtime dependencies.

v1.7.1 — Multi-user API key auth fix + documentation site

Choose a tag to compare

@FilipJy FilipJy released this 01 Sep 08:29

What changed

Multi-user API key authentication was broken: a missing
import made _KeyStore.load() raise NameError on the first authenticated request,
so a server configured with TUZKAOCR_API_KEYS_FILE rejected every call with a 500.
Requests failed closed, so no unauthorized access was possible.
This release fixes it and adds a full documentation site at https://moravianlibrary.github.io/TuzkaOCR/.

Multi-user API key authentication (bug fix)

  • api/routes.py called time.monotonic() twice in _KeyStore.load() without
    importing time. On the first call the TTL check short-circuits (no path cached
    yet), so the NameError surfaced at the timestamp assignment — meaning the very
    first authenticated request failed, not a later one.
  • The exception was raised inside the _require_key dependency, so the request never
    reached its handler and every authenticated endpoint returned 500.
  • The failure was masked at startup: _validate_auth reads, validates and counts the
    key file correctly, logging [auth] multi-user, N key(s) loaded, so a server with a
    valid key file looked healthy right up until the first request.

Documentation

  • New documentation site published from docs/, built with Material for MkDocs:
    installation, CLI, HTTP API, Docker, configuration, models and domains, output
    formats, and troubleshooting, plus a guided setup page for users new to the command
    line.
  • Documents behaviour that was previously undocumented, including the full status-code
    table, the legacy endpoints' differing field names and response shapes, job retention
    and what survives a restart, and per-domain overrides of TUZKAOCR_CROP_ENDPOINT_EXT
    and TUZKAOCR_COLUMN_SPLIT.
  • TUZKAOCR_SPOOL_DIR and the per-domain model override variables are now documented in
    README.md and listed in tuzkaocr.env.

Compatibility

  • No public API surface change — routes, CLI flags, API form fields, env vars and the
    ALTO output schema are unchanged.
  • No model changes. Output is byte-identical to v1.7.0 across all four domains.
  • No new runtime dependencies. docs/requirements.txt (mkdocs, mkdocs-material) builds
    the documentation only and is not installed by pip install ..

v1.7.0 — Unified handwritten/Kurrent recognizer + disk-spooled upload pipeline

Choose a tag to compare

@FilipJy FilipJy released this 31 Aug 12:14

What changed

The handwritten and Kurrent domains now share one recognizer (rec-H-v6, replacing
rec-H-v4 and the Kurrent-only rec-H-v3h-kurrent) on a widened 158-character
vocabulary. The API upload path was reworked: every upload is streamed to a
service-owned spool file and decoded once inside its worker, instead of being decoded in
the request handler and held in RAM for the whole time the job sits queued. ALTO output
gained ID sanitization and page-bounds clipping, the job store gained single-shot
terminal states and typed input errors, and the container images were fixed up.

Recognition (handwritten + Kurrent)

  • rec-H-v6.int8.onnx is the new default for both domain=handwritten and
    domain=kurrent; a single general handwritten recognizer supersedes the earlier
    Kurrent-only specialist.
  • Shared vocabulary widened 134 → 158 characters: uppercase Czech diacritics
    (Ů Ň Ť Ď Ó), German ß Ä Ö Ü, Polish ą ć ę ł ń ś ż ź Ł Ś Ż, a combining macron,
    plus @ and _. New entries are appended, so indices for existing characters are
    unchanged and the print/Kramarky recognizers decode identically.
  • The superseded model files stay bundled, so existing HANDWRITTEN_OCR_MODEL /
    KURRENT_OCR_MODEL pins still resolve.

Upload handling and memory

  • Each accepted upload streams to a uniquely named spool file under
    TUZKAOCR_SPOOL_DIR (system temp if unset); its worker decodes it once at processing
    time. Previously the request handler decoded the image inline and the queued job
    carried a decoded BGR array.
  • Queue depth no longer costs memory per queued job — the sizing guide loses its
    MAX_QUEUE × ~8 MiB term, leaving TUZKAOCR_PAGE_WORKERS as the memory lever.
  • Spool files are removed after processing, on a rejected submission, on executor-submit
    failure, and for jobs that expire while still queued. An explicitly configured spool
    directory is swept at startup to clear crash leftovers; the shared system temp
    directory is never swept, and a configured directory must not be shared between
    bare-metal instances.
  • Startup fails fast with a clear message if TUZKAOCR_SPOOL_DIR is not writable.
  • Image decoding no longer runs on the event loop, so a huge or malformed upload can't
    stall request handling.

Job store correctness

  • Terminal transitions are single-shot: a job can no longer be overwritten after
    done/failed, and results written for an already-finalized job are unlinked instead
    of orphaned on disk.
  • Workers bail out if the job is no longer queued, guarding against double dispatch.
  • Failed jobs carry an HTTP status. Undecodable or over-pixel-limit images surface as
    422 from the result endpoint instead of a blanket 500.
  • BaseException in a worker (cancellation, interpreter shutdown) marks the job failed
    and re-raises, instead of leaving it stuck in running forever.
  • The hourly cleanup timer reschedules itself even when a sweep raises, so one error no
    longer stops cleanup for the process lifetime; it stops cleanly on shutdown.
  • Age expiry fails over-age queued jobs explicitly rather than dropping them silently;
    running jobs are left to finish.

ALTO output

  • Page IDs are sanitized to valid XML names — page_id comes from the uploaded
    filename, so spaces, diacritics or a leading digit no longer produce invalid ALTO.
  • All TextBlock / TextLine / String coordinates are clipped to the page box with
    WIDTH/HEIGHT guaranteed ≥ 1, and block extents are derived from the clipped line
    rects — no geometry can extend past the declared page WIDTH/HEIGHT.
  • build_alto rejects non-positive page dimensions, and word content is coerced to a
    string before serialization.

CLI and pipeline

  • File decoding goes through np.fromfile + cv2.imdecode, so non-ASCII paths work on
    Windows and unreadable files give a clear Cannot read image error.
  • The role classifier now honours the resolved device: with TUZKAOCR_DEVICE=auto it
    runs on CUDA instead of silently falling back to CPU, and it respects
    TUZKAOCR_CPU_MEM_ARENA.
  • TUZKAOCR_MAX_IMAGE_PIXELS is API-only; command-line processing has no pixel limit.

Containers and Compose

  • Added .dockerignore — .git, Python caches, results/, spool/ and
    api_keys.yaml stay out of the build context and the image.
  • Both images create /app/spool, declare it as a volume, and set
    TUZKAOCR_SPOOL_DIR=/app/spool.
  • Compose replaces the shared ./spool bind mount with separate named volumes
    (cpu_spool, gpu_spool), so the CPU and GPU services cannot sweep each other's
    spool files.
  • Dockerfile.gpu installs Python 3.11 from deadsnakes into a dedicated venv, replacing
    the broken python3.11-pip / pip symlink setup on the CUDA base image.

Compatibility

  • No public API surface change — routes, CLI flags, API form fields, env vars and the
    ALTO output schema are unchanged.
  • Error timing changed. A corrupt or over-MAX_IMAGE_PIXELS image previously
    returned a synchronous 422 from POST /api/v1/process. It now returns 200 with a
    job_id, and the failure appears as 422 on GET /api/v1/result/{job_id}. Clients
    that only inspected the POST status must read job status/result.
  • Failed-job result responses return 422 for input errors instead of 500; other failures
    stay 500.
  • ALTO is not byte-identical for pages whose line/word geometry ran past the page box, or
    whose filename was not a valid XML name. Well-behaved pages are unchanged.
  • Handwritten and Kurrent output changes — new recognizer. Print (default) and
    Kramarky accuracy are unchanged. To keep the old Kurrent behaviour, pin
    TUZKAOCR_KURRENT_OCR_MODEL=rec-H-v3h-kurrent.int8.onnx.
  • tuzkaocr.pipeline.ensure_bgr was removed; decoding always yields 3-channel BGR. Use
    tuzkaocr.images.decode_image_path.
  • Compose users: the ./spool bind mount is gone, so the host ./spool directory can be
    removed after upgrade.
  • No new dependencies.

v1.6.0 — New Kramarky layout model

Choose a tag to compare

@FilipJy FilipJy released this 13 Jul 13:58

What changed

The Kramarky domain gets a new layout model: dec-B-v1k.onnx replaces dec-A-v3k5.onnx
as the default for domain=kramarky. The old U-Net layout under-detected and fragmented
lines on small (~600 px) and faded/whitish broadsheet scans.

Compatibility

  • No API or CLI changes; domain=kramarky selects the new model automatically.
  • Env override unchanged: TUZKAOCR_KRAMARKY_LAYOUT_MODEL (new default dec-B-v1k.onnx).
  • Output changes for all Kramarky pages: line segmentation, counts, and geometry differ. ALTO provenance now reports dec-B-v1k as the layout softwareName.
  • Print, handwritten, and kurrent domains are untouched.

v1.5.0 — Handwritten & Kurrent domains, rec-H-v4 general handwriting model

Choose a tag to compare

@FilipJy FilipJy released this 10 Jul 09:44

What changed

The pipeline now covers four domains instead of two: print (default), kramarky,
handwritten, and kurrent. Each domain ships its own layout + recognizer pair and is
selectable per request (domain form field), per CLI run (--domain), or via env
overrides.

New domains & models

  • domain=handwritten — Czech handwriting, old and modern. Layout dec-B-v2h.onnx + recognizer rec-H-v4.int8.onnx, a general handwriting model.
  • domain=kurrent — German Kurrent/Sütterlin script. Same dec-B-v2h layout + rec-H-v3h-kurrent.int8.onnx specialist.

Pipeline improvements (handwritten/kurrent only)

  • Line crops are extended by 0.3× line-height at both baseline endpoints — handwriting layout endpoints are tight, and the first/last glyph of a line was frequently clipped before recognition.
  • In-region N-column reading order: table/register pages with several columns in one region are now read column-by-column instead of interleaving rows across columns. Conservative activation (clustered narrow lines only); prose pages and marginalia keep the original order.
  • max_width raised 1600 → 3400: very long lines (common in Kurrent registers) were silently truncated mid-line before recognition.

Compatibility

  • No breaking changes. Clients that omit domain or send kramarky get byte-identical behavior; print/kramarky layout, recognition, and postprocessing are untouched.
  • New accepted domain values: handwritten and kurrent ("print" also resolves to the default models).
  • CLI: --domain gains handwritten and kurrent choices; existing flags unchanged.
  • New env overrides: TUZKAOCR_HANDWRITTEN_LAYOUT_MODEL / _OCR_MODEL, TUZKAOCR_KURRENT_LAYOUT_MODEL / _OCR_MODEL, TUZKAOCR_CROP_ENDPOINT_EXT, TUZKAOCR_COLUMN_SPLIT — all default to per-domain values above.
  • Config-default change: max_width 1600 → 3400 (affects only lines longer than 1600 px, which were previously truncated; output for those lines improves).
  • EXIF-tagged images now OCR in their upright orientation — output for such images changes; images without EXIF rotation are unaffected.
  • Three model files added (~2×12 MB recognizers + 11 MB layout); no dependency changes.

v1.4.2 — Accept default domain (fixes 400 on advertised value)

Choose a tag to compare

@FilipJy FilipJy released this 01 Jul 20:02

What changed

The default domain value is now accepted by the API instead of being rejected with a 400. /api/v1/models advertised default in selectable_via_domain, but submitting domain=default failed validation — it now resolves to the general models, consistent with what the API advertises.

default domain accepted (bug fix)

  • /api/v1/models lists default under selectable_via_domain, but _validate_domain only mapped None/empty to the general models and rejected domain=default with 400 Unknown domain 'default'.
  • default (alongside None/empty) now normalizes to the general models, so it behaves identically to omitting the field.

Compatibility

  • No breaking changes. Clients that omit domain or send kramarky are unaffected; requests with domain=default that previously returned 400 now succeed.
  • No public API surface change — CLI flags, API form fields, and env vars are unchanged.
  • Layout, recognition, and model selection are unchanged — default selects the same general models as omitting the field. Output is identical to v1.4.1.
  • No model, config-default, or dependency changes.

v1.4.1 — Faster dense pages, plus per-page confidence in the API.

Choose a tag to compare

@FilipJy FilipJy released this 19 Jun 09:11

What changed

Adaptive downsampling no longer recognizes resolution levels it discards, making dense pages faster with byte-identical output. Separately, the job-status response now exposes per-page OCR confidence (mean_conf) and line count (n_lines).

Faster adaptive downsampling (affects throughput, not output)

  • The pipeline picks a working resolution by escalating ds3 → ds2 → ds1 on "starved" pages. Line-height / pitch / width starvation is a layout-only signal, so escalation is now decided from the layout pass alone — recognition runs only at the resolution that is kept.
  • ~18% faster on dense periodical pages (where escalation fires); negligible change on simple pages that don't escalate.
  • Output is byte-identical to v1.4.0 — verified across mixed and dense pages.

Per-page confidence in job status (additive)

  • Job status now reports mean_conf (mean OCR confidence over the page's recognized lines, 0–1) and n_lines (recognized line count).
  • Available on both GET /api/v1/status/{job_id} and the legacy GET /status/{job_id}.

Compatibility

  • No breaking changes. The new mean_conf / n_lines fields are additive — existing clients that don't read them are unaffected.
  • No public API surface change — CLI flags, API form fields, and env vars are unchanged. process() returns the same content as before (the new metadata is opt-in via with_meta, used internally by the job runner).
  • Layout, recognition, and adaptive decisions are unchanged — the same resolution is chosen per page as in v1.4.0.
  • No model, config-default, or dependency changes.

v1.4.0 — New line-role classifier (role-H5) + ALTO 4.4 validity fix

Choose a tag to compare

@FilipJy FilipJy released this 17 Jun 13:22

What changed

The experimental line-role classifier was replaced (role-G → role-H5) with a small visual+text+geometry model. Separately, the ALTO output was made schema-valid against ALTO 4.4 — fixing defects that were present in all output.

Role classification (optional, off by default)

  • Per-line, 5 classes: body / heading / header / footer / page_number (replaces role-G's body / prominent / pagenum; title and sub-headings are merged into heading).
  • Per-class F1: page_number 0.97, header 0.89, body 0.96, heading ~0.80, footer ~0.75 — a clear upgrade over the geometry-only role-G.

ALTO 4.4 validity (affects all output)

  • Layout and recognition provenance are now two separate <OCRProcessing> elements (IdLayout / IdRecognition). Previously a single <OCRProcessing> held two <ocrProcessingStep> elements, which the schema forbids — so prior ALTO was invalid even with roles off.
  • Line roles are now emitted as <Tags>/<StructureTag> referenced via TextLine TAGREFS, replacing the invalid TYPE attribute.
  • Verified against alto-4-4.xsd (xmllint) across varied pages, role tagging on and off.

Compatibility

  • ALTO structure changes (now schema-valid): provenance is two <OCRProcessing> elements; role tags moved from TYPE to <Tags>/TAGREFS. ALTO consumers that read provenance or role tags must adjust; the change is what makes the output conformant.
  • No public API change — CLI flags, API form fields, and env vars are unchanged. Role tagging remains off by default (--role-classifier / TUZKAOCR_ROLE_CLASSIFIER).
  • Default role-model filename changes (role-G-v1.npz → role-H5.onnx); config default updated, so no action needed unless you pin ROLE_MODEL.
  • Layout and recognition models/accuracy unchanged.
  • No new dependencies.