diff --git a/.codacy.yml b/.codacy.yml index 626bab79e8..783221dcea 100644 --- a/.codacy.yml +++ b/.codacy.yml @@ -1,7 +1,7 @@ --- # Codacy configuration file # https://docs.codacy.com/repositories-configure/codacy-configuration-file/ -# Analyze only doctr/ and references/ — everything else is excluded. +# Analyze only doctr/ and references/ - everything else is excluded. exclude_paths: - ".github/**" diff --git a/.gitignore b/.gitignore index b2169a9bc9..5ed675fbf6 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,8 @@ instance/ # Sphinx documentation docs/_build/ +docs/build/ +docs/source/models/_generated/ # PyBuilder target/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1b702b4795..907ec7eb72 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: no-commit-to-branch args: ['--branch', 'main'] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.17.1 + rev: v0.17.3 hooks: - id: ruff args: [ --fix ] diff --git a/README.md b/README.md index 561e637437..ee52fdb638 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ What you can expect from this repository: ### Getting your pretrained model End-to-End OCR is achieved in docTR using a two-stage approach: text detection (localizing words), then text recognition (identify all characters in the word). -As such, you can select the architecture used for [text detection](https://mindee.github.io/doctr/latest/modules/models.html#doctr-models-detection), and the one for [text recognition](https://mindee.github.io/doctr/latest//modules/models.html#doctr-models-recognition) from the list of available implementations. +As such, you can select the architecture used for [text detection](https://mindee.github.io/doctr/latest/models/detection.html), and the one for [text recognition](https://mindee.github.io/doctr/latest/models/recognition.html) from the list of available implementations. ```python from doctr.models import ocr_predictor @@ -119,7 +119,7 @@ plt.show() ![Synthesis sample](https://github.com/mindee/doctr/raw/main/docs/images/synthesized_sample.png) The `ocr_predictor` returns a `Document` object with a nested structure (with `Page`, `Block`, `Line`, `Word`, `Artefact`). -To get a better understanding of our document model, check our [documentation](https://mindee.github.io/doctr/modules/io.html#document-structure): +To get a better understanding of our document model, check our [documentation](https://mindee.github.io/doctr/latest/getting_started/concepts.html#the-document-tree): You can also export them as a nested dict, more appropriate for JSON format: diff --git a/docs/README.md b/docs/README.md index 0aabfe1048..3ac55a93f2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,13 +1,85 @@ -# Contribute to Documentation +# docTR documentation -Please have a look at our [contribution guide](../CONTRIBUTING.md) to see how to install -the development environment and how to generate the documentation. +The source of [mindee.github.io/doctr](https://mindee.github.io/doctr), built with [Sphinx](https://www.sphinx-doc.org), +the [PyData theme](https://pydata-sphinx-theme.readthedocs.io), [MyST Markdown](https://myst-parser.readthedocs.io) +and [sphinx-design](https://sphinx-design.readthedocs.io). -To install only the `docs` environment, you can do: +## Build it locally ```bash -# Make sure you are at the root of the repository before executing these commands -python -m pip install --upgrade pip -pip install -e .[viz,html] -pip install -e .[docs] +# From the root of the repository +pip install -e ".[viz,html,docs]" +make docs-single-version ``` + +Then open `docs/_build/index.html` in your browser. Sphinx only rebuilds the pages that changed: delete +`docs/_build` to force a full rebuild. For a live preview while you write, `pip install sphinx-autobuild` and run +`sphinx-autobuild docs/source docs/_build`. + +`make docs` builds all the deployed versions (see `build.sh`), as the CI does. + +## Structure + +| Folder | Section | Content | +| --- | --- | --- | +| `source/index.md` | Landing page | Hero, features and entry points | +| `source/getting_started/` | Get started | Installation, quickstart, core concepts | +| `source/user_guide/` | User guide | One page per feature, task-oriented | +| `source/models/` | Model zoo | Architectures, benchmarks and vocabularies | +| `source/training/` | Training | Datasets, training scripts, custom and shared models | +| `source/deployment/` | Deployment | Performance, ONNX, Docker, web API, configuration | +| `source/api/` | API reference | Generated from the docstrings with `autodoc` | +| `source/community/` | Community | Help, tools, resources, contributing and changelog | +| `source/_static/` | | CSS (`css/doctr.css`), JS, fonts, images and the version switcher (`switcher.json`) | +| `source/_templates/` | | Footer components | +| `source/_includes/` | | Snippets included in several pages (pipeline diagram) | + +Each section has an `index.md` with an overview and the `toctree` of its pages. The top navigation bar lists the +sections of the `toctree` of `source/index.md`. + +## Writing pages + +Pages are written in [MyST Markdown](https://myst-parser.readthedocs.io/en/latest/syntax/typography.html), a superset +of CommonMark that supports all Sphinx features: + +````markdown +# Page title + +A first paragraph that says what the page is about: it is displayed as a lead. + +## Section + +Link to another page: [](../user_guide/exporting.md), or to a section: [](exporting.md#figures). +Link to the API: {func}`~doctr.models.ocr_predictor`, {class}`~doctr.io.Document`. + +```python +from doctr.models import ocr_predictor +``` + +:::{tip} +Admonitions: note, tip, important, warning, seealso, dropdown... +::: + +::::{tab-set} +:::{tab-item} pip +... +::: +:::: +```` + +Guidelines: + +- **Show, then explain**: start sections with a short runnable snippet, then describe the options. +- **Keep snippets runnable**: complete imports, realistic file names, and real outputs. The outputs shown in the + guides come from `source/_static/images/examples/invoice.pdf`. +- **One topic per page**, and link to the related pages instead of repeating them. +- **Cards** (`grid-item-card`) are for navigation, **tabs** for alternatives (e.g. CPU / GPU), **dropdowns** for + details most readers can skip. +- Document new public functions and classes in the docstrings (Google style), and add them to the matching page of + `source/api/`. + +## Releasing a new version + +1. Add the release to `source/changelog.md`. +2. Add it to `source/_static/switcher.json` and move the `(stable)` label and `"preferred": true` to it. +3. In `build.sh`, build the previous stable release into its own folder and the new one at the root. diff --git a/docs/build.sh b/docs/build.sh index 29e74cc8a5..c8234350f3 100644 --- a/docs/build.sh +++ b/docs/build.sh @@ -1,40 +1,56 @@ +# Build the multi-version documentation deployed on https://mindee.github.io/doctr +# +# build/ -> latest stable release +# build/latest/ -> main branch +# build/vX.Y.Z/ -> previous releases +# +# Every version is rendered with the configuration, theme and static files of the main branch, so that they all share +# the same look and version switcher (`source/_static/switcher.json`, to update at each release). +# +# deploy_doc +# commit: commit to build ("" for the current one) +# folder: output folder in build/ ("latest", "vX.Y.Z", or "" for the stable version at the root) +# version: version shown as selected in the version switcher (must match an entry of switcher.json) function deploy_doc(){ - if [ ! -z "$1" ] - then - git checkout $1 + local commit=$1 folder=$2 version=$3 + if [ -n "$commit" ]; then + git checkout "$commit" fi - COMMIT=$(git rev-parse --short HEAD) - echo "Creating doc at commit" $COMMIT "and pushing to folder $2" + echo "Creating doc at commit $(git rev-parse --short HEAD) (version: $version)" pip install -U .. - if [ ! -z "$2" ] - then - if [ "$2" == "latest" ]; then - echo "Pushing main" - sphinx-build source _build -a && mkdir build && mkdir build/$2 && cp -a _build/* build/$2/ - elif [ -d build/$2 ]; then - echo "Directory" $2 "already exists" - else - echo "Pushing version" $2 - cp -r _static source/ && cp _conf.py source/conf.py - sphinx-build source _build -a - mkdir build/$2 && cp -a _build/* build/$2/ && git checkout source/ && git clean -f source/ - fi + + # Older commits are rendered with the current configuration and assets + local legacy=0 + if [ -n "$commit" ]; then + legacy=$([ -f source/index.md ] && echo 0 || echo 1) + rm -rf source/_static source/_templates + cp -r _static _templates source/ && cp _conf.py source/conf.py + fi + + local out=build + if [ -n "$folder" ]; then + out=build/$folder + fi + if [ -d "$out" ] && [ -n "$folder" ]; then + echo "Directory $out already exists" else - echo "Pushing stable" - cp -r _static source/ && cp _conf.py source/conf.py - sphinx-build source build -a && git checkout source/ && git clean -f source/ + DOCTR_DOCS_VERSION=$version DOCTR_DOCS_LEGACY=$legacy sphinx-build source _build -a + mkdir -p "$out" && cp -a _build/. "$out/" && rm -rf _build + fi + + if [ -n "$commit" ]; then + git checkout source/ && git clean -fd source/ fi } # You can find the commit for each tag on https://github.com/mindee/doctr/tags if [ -d build ]; then rm -Rf build; fi -cp -r source/_static . -cp source/conf.py _conf.py +cp -r source/_static source/_templates . && cp source/conf.py _conf.py git fetch --all --tags --unshallow -deploy_doc "" latest -deploy_doc "1c9ce92" v0.11.0 -deploy_doc "97d4006" v0.12.0 -deploy_doc "7dabbe1" v1.0.0 -deploy_doc "6541a6e" v1.0.1 -deploy_doc "2d8e244" # v1.1.0 Latest stable release -rm -rf _build _static _conf.py +deploy_doc "" latest latest +deploy_doc "1c9ce92" v0.11.0 v0.11.0 +deploy_doc "97d4006" v0.12.0 v0.12.0 +deploy_doc "7dabbe1" v1.0.0 v1.0.0 +deploy_doc "6541a6e" v1.0.1 v1.0.1 +deploy_doc "2d8e244" "" v1.1.0 # Latest stable release +rm -rf _build _static _templates _conf.py diff --git a/docs/source/_includes/pipeline.html b/docs/source/_includes/pipeline.html new file mode 100644 index 0000000000..65c0a06a5b --- /dev/null +++ b/docs/source/_includes/pipeline.html @@ -0,0 +1,67 @@ + diff --git a/docs/source/_static/css/doctr.css b/docs/source/_static/css/doctr.css new file mode 100644 index 0000000000..6a8e0906b7 --- /dev/null +++ b/docs/source/_static/css/doctr.css @@ -0,0 +1,1022 @@ +/* ========================================================================== + docTR documentation theme (on top of pydata-sphinx-theme) + ========================================================================== */ + +/* -- Self-hosted fonts (SIL Open Font License, see ../fonts) ---------------- */ + +@font-face { + font-family: "Inter"; + font-style: normal; + font-display: swap; + font-weight: 100 900; + src: url("../fonts/inter-latin-wght-normal.woff2") format("woff2-variations"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, + U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-display: swap; + font-weight: 100 900; + src: url("../fonts/inter-latin-ext-wght-normal.woff2") format("woff2-variations"); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, + U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +@font-face { + font-family: "JetBrains Mono"; + font-style: normal; + font-display: swap; + font-weight: 100 800; + src: url("../fonts/jetbrains-mono-latin-wght-normal.woff2") format("woff2-variations"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, + U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +@font-face { + font-family: "JetBrains Mono"; + font-style: normal; + font-display: swap; + font-weight: 100 800; + src: url("../fonts/jetbrains-mono-latin-ext-wght-normal.woff2") format("woff2-variations"); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, + U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* -- Design tokens ---------------------------------------------------------- */ + +:root { + --doctr-navy: #001e3f; + --doctr-navy-2: #0a2f5c; + --doctr-red: #ff003e; + --doctr-red-soft: #fd3246; + --doctr-radius: 12px; + --doctr-radius-sm: 8px; + + --pst-font-family-base: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", + Arial, sans-serif; + --pst-font-family-heading: var(--pst-font-family-base); + --pst-font-family-monospace: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + --pst-font-size-base: 1rem; + --pst-header-height: 4rem; + --pst-sidebar-font-size: 0.92rem; +} + +html[data-theme="light"] { + --pst-color-primary: #0b57d0; + --pst-color-primary-highlight: #0842a0; + --pst-color-primary-bg: #e7efff; + --pst-color-secondary: #e0003a; + --pst-color-secondary-highlight: #b8002f; + --pst-color-secondary-bg: #ffe4ea; + --pst-color-accent: #e0003a; + --pst-color-link: var(--pst-color-primary); + --pst-color-link-hover: var(--pst-color-primary-highlight); + --pst-color-text-base: #1b2433; + --pst-color-text-muted: #556274; + --pst-color-heading: #0b1b33; + --pst-color-background: #ffffff; + --pst-color-on-background: #ffffff; + --pst-color-surface: #f5f7fa; + --pst-color-border: #e1e6ed; + --pst-color-border-muted: #eceff4; + --pst-color-inline-code: #1f2a3d; + --pst-color-target: #fff3c4; + --pst-color-table-row-hover-bg: #eef3ff; + --sd-color-primary: var(--pst-color-primary); + --sd-color-primary-highlight: var(--pst-color-primary-highlight); + --sd-color-primary-bg: var(--pst-color-primary-bg); + --sd-color-secondary: var(--pst-color-secondary); + --sd-color-secondary-highlight: var(--pst-color-secondary-highlight); + --sd-color-secondary-bg: var(--pst-color-secondary-bg); + --sd-color-card-border: var(--pst-color-border); + + --doctr-code-bg: #f6f8fa; + --doctr-card-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 1px 3px rgba(16, 24, 40, 0.06); + --doctr-card-shadow-hover: 0 10px 24px -8px rgba(11, 87, 208, 0.25); +} + +html[data-theme="dark"] { + --pst-color-primary: #7cacf8; + --pst-color-primary-highlight: #a8c7fa; + --pst-color-primary-bg: #0d2a55; + --pst-color-secondary: #ff5c7a; + --pst-color-secondary-highlight: #ff8aa0; + --pst-color-secondary-bg: #4a0f1f; + --pst-color-accent: #ff5c7a; + --pst-color-link: var(--pst-color-primary); + --pst-color-link-hover: var(--pst-color-primary-highlight); + --pst-color-text-base: #d5dce6; + --pst-color-text-muted: #98a3b3; + --pst-color-heading: #f1f5fa; + --pst-color-background: #0b111c; + --pst-color-on-background: #111a28; + --pst-color-surface: #152033; + --pst-color-border: #243247; + --pst-color-border-muted: #1b2639; + --pst-color-inline-code: #e3e9f2; + --pst-color-target: #4a3f05; + --pst-color-table-row-hover-bg: #17284a; + --sd-color-primary: var(--pst-color-primary); + --sd-color-primary-highlight: var(--pst-color-primary-highlight); + --sd-color-primary-bg: var(--pst-color-primary-bg); + --sd-color-primary-text: #0b111c; + --sd-color-secondary: var(--pst-color-secondary); + --sd-color-secondary-highlight: var(--pst-color-secondary-highlight); + --sd-color-secondary-bg: var(--pst-color-secondary-bg); + --sd-color-secondary-text: #0b111c; + --sd-color-card-border: var(--pst-color-border); + --sd-color-card-background: var(--pst-color-on-background); + + --doctr-code-bg: #0f1726; + --doctr-card-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + --doctr-card-shadow-hover: 0 12px 28px -10px rgba(124, 172, 248, 0.35); +} + +/* -- Typography ------------------------------------------------------------- */ + +body { + font-feature-settings: "cv11", "ss01"; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.bd-article h1, +.bd-article h2, +.bd-article h3, +.bd-article h4 { + font-weight: 700; + letter-spacing: -0.018em; +} + +.bd-article h1 { + font-size: 2.3rem; + letter-spacing: -0.03em; + margin-bottom: 0.6rem; +} + +.bd-article h2 { + font-size: 1.6rem; + margin-top: 2.6rem; + padding-top: 0.2rem; +} + +.bd-article h3 { + font-size: 1.22rem; + margin-top: 2rem; +} + +/* Lead paragraph: the first paragraph right under the page title */ +.bd-article section > h1 + p { + font-size: 1.12rem; + line-height: 1.65; + color: var(--pst-color-text-muted); +} + +.bd-article p, +.bd-article li { + line-height: 1.7; +} + +a { + text-underline-offset: 0.18em; +} + +/* -- Header ------------------------------------------------------------------ */ + +.bd-header { + box-shadow: none; + border-bottom: 1px solid var(--pst-color-border); + backdrop-filter: saturate(160%) blur(10px); + -webkit-backdrop-filter: saturate(160%) blur(10px); +} + +html[data-theme="light"] .bd-header { + background-color: rgba(255, 255, 255, 0.86) !important; +} + +html[data-theme="dark"] .bd-header { + background-color: rgba(11, 17, 28, 0.86) !important; +} + +.navbar-brand img { + height: 30px; +} + +.bd-header .navbar-header-items__start .navbar-item { + gap: 0.75rem; +} + +.bd-header ul.navbar-nav > li.nav-item > .nav-link { + font-weight: 550; + font-size: 0.95rem; +} + +.bd-header ul.navbar-nav > li.nav-item.current > .nav-link { + color: var(--pst-color-heading); +} + +.bd-header ul.navbar-nav > li.nav-item.current > .nav-link::before { + border-bottom: 3px solid var(--pst-color-secondary); +} + +/* Version switcher button */ +.version-switcher__button { + font-size: 0.82rem; + font-weight: 600; + border-radius: 999px; + padding: 0.15rem 0.7rem; + border-color: var(--pst-color-border); +} + +/* -- Sidebars ---------------------------------------------------------------- */ + +.bd-sidebar-primary { + border-right: 1px solid var(--pst-color-border-muted); +} + +.bd-sidebar-primary .sidebar-primary-items__start .bd-links__title { + font-size: 0.78rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--pst-color-text-muted); +} + +.bd-sidebar-primary .toctree-l1 > a, +.bd-sidebar-primary .toctree-l2 > a { + border-radius: var(--doctr-radius-sm); +} + +nav.bd-links li > a:hover { + background-color: var(--pst-color-surface); + text-decoration: none; +} + +nav.bd-links .current > a { + box-shadow: inset 3px 0 0 var(--pst-color-secondary); + background-color: var(--pst-color-surface); + font-weight: 600; +} + +.bd-sidebar-secondary { + font-size: 0.88rem; +} + +/* -- Code -------------------------------------------------------------------- */ + +div.highlight, +div.highlight pre { + background-color: var(--doctr-code-bg) !important; +} + +div.highlight { + border: 1px solid var(--pst-color-border); + border-radius: var(--doctr-radius-sm); +} + +/* Code must read exactly as typed: no ligatures (e.g. "==" or "->") */ +pre, +code, +kbd, +.doctr-tree { + font-variant-ligatures: none; + font-feature-settings: "liga" 0, "calt" 0; +} + +div.highlight pre { + border: none; + border-radius: var(--doctr-radius-sm); + font-size: 0.86rem; + line-height: 1.6; + padding: 1rem 1.1rem; + box-shadow: none; +} + +.bd-content div.literal-block-wrapper, +div[class*="highlight-"] { + border-radius: var(--doctr-radius-sm); +} + +code.literal { + font-size: 0.86em; + padding: 0.12em 0.36em; + border-radius: 6px; + border: 1px solid var(--pst-color-border-muted); + background-color: var(--pst-color-surface); +} + +/* Code-block captions (file names) */ +div.code-block-caption { + font-family: var(--pst-font-family-monospace); + font-size: 0.8rem; + padding: 0.45rem 0.9rem; + border: 1px solid var(--pst-color-border); + border-bottom: none; + border-radius: var(--doctr-radius-sm) var(--doctr-radius-sm) 0 0; + background: var(--pst-color-surface); +} + +div.code-block-caption + div.highlight-default, +div.code-block-caption + div[class*="highlight-"] div.highlight { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +/* Keep the page TOC and the navigation light */ +.bd-toc code.literal, +nav.bd-links code.literal, +.bd-breadcrumbs code.literal { + border: none; + background: none; + padding: 0; + font-size: 0.92em; +} + +/* Inline code inside links keeps the link color */ +a code.literal, +a:visited code.literal { + color: var(--pst-color-link); +} + +/* Figures */ +.bd-article img.align-center { + border: 1px solid var(--pst-color-border); + border-radius: var(--doctr-radius); + box-shadow: var(--doctr-card-shadow); + margin: 1.25rem auto 1.75rem; +} + +button.copybtn { + border-radius: 6px; +} + +/* -- Admonitions ------------------------------------------------------------ */ + +.admonition, +div.admonition { + border-radius: var(--doctr-radius-sm); + box-shadow: none; + border: 1px solid var(--pst-color-border); + border-left-width: 4px; +} + +/* -- Tables ------------------------------------------------------------------ */ + +.bd-article table.table { + font-size: 0.9rem; + border-radius: var(--doctr-radius-sm); +} + +.bd-article table.table th { + font-weight: 600; + white-space: nowrap; +} + +.bd-article table.table td code.literal { + white-space: nowrap; +} + +/* -- sphinx-design ------------------------------------------------------------ */ + +.sd-card { + border-radius: var(--doctr-radius); + box-shadow: var(--doctr-card-shadow); + transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease; +} + +.sd-card.sd-card-hover:hover { + transform: translateY(-2px); + box-shadow: var(--doctr-card-shadow-hover); + border-color: var(--pst-color-primary); +} + +.sd-card .sd-card-title { + font-weight: 650; + font-size: 1.02rem; + margin-bottom: 0.35rem; +} + +.sd-card .sd-card-body p { + color: var(--pst-color-text-muted); + font-size: 0.93rem; + line-height: 1.55; +} + +.sd-card .sd-card-title .sd-octicon, +.sd-card .sd-card-title svg { + color: var(--pst-color-secondary); + margin-right: 0.35rem; +} + +.sd-tab-set > label { + font-weight: 600; + font-size: 0.9rem; +} + +.sd-tab-set > input:checked + label { + border-color: var(--pst-color-secondary); + color: var(--pst-color-heading); +} + +.sd-badge { + font-weight: 600; + letter-spacing: 0.01em; +} + +/* Step-by-step lists (".steps" class on ordered lists) */ +ol.steps { + counter-reset: step; + list-style: none; + padding-left: 0; +} + +ol.steps > li { + counter-increment: step; + position: relative; + padding-left: 2.6rem; + margin-bottom: 1rem; +} + +ol.steps > li::before { + content: counter(step); + position: absolute; + left: 0; + top: 0.05rem; + width: 1.75rem; + height: 1.75rem; + border-radius: 999px; + display: grid; + place-items: center; + font-size: 0.85rem; + font-weight: 700; + color: #fff; + background: var(--doctr-navy); +} + +html[data-theme="dark"] ol.steps > li::before { + background: var(--pst-color-primary-bg); + color: var(--pst-color-primary); +} + +/* -- API reference ------------------------------------------------------------ */ + +dl.py > dt { + font-family: var(--pst-font-family-monospace); + font-size: 0.88rem; + background: var(--pst-color-surface); + border: 1px solid var(--pst-color-border); + border-left: 3px solid var(--pst-color-primary); + border-radius: 6px; + padding: 0.5rem 0.75rem; +} + +dl.py.method > dt, +dl.py.attribute > dt, +dl.py.property > dt { + border-left-color: var(--pst-color-border); + background: transparent; +} + +dl.py > dd { + margin-left: 1.25rem; +} + +dl.field-list > dt { + font-weight: 600; +} + +/* -- Pipeline diagram ---------------------------------------------------------- */ + +.doctr-diagram { + margin: 1.5rem 0 2rem; + padding: 1.25rem; + border: 1px solid var(--pst-color-border); + border-radius: var(--doctr-radius); + background: var(--pst-color-surface); + overflow-x: auto; +} + +.doctr-diagram svg { + display: block; + width: 100%; + min-width: 640px; + height: auto; +} + +.doctr-diagram .box { + fill: var(--pst-color-background); + stroke: var(--pst-color-border); + stroke-width: 1.5; +} + +.doctr-diagram .box.core { + stroke: var(--pst-color-primary); +} + +.doctr-diagram .box.opt { + stroke-dasharray: 5 4; +} + +.doctr-diagram .box.out { + fill: var(--doctr-navy); + stroke: var(--doctr-navy); +} + +.doctr-diagram .title { + font: 600 14px var(--pst-font-family-base); + fill: var(--pst-color-heading); +} + +.doctr-diagram .sub { + font: 400 11.5px var(--pst-font-family-base); + fill: var(--pst-color-text-muted); +} + +.doctr-diagram .mono { + font: 500 11px var(--pst-font-family-monospace); + fill: var(--pst-color-primary); +} + +.doctr-diagram .out .title, +.doctr-diagram .title.on-dark { + fill: #ffffff; +} + +.doctr-diagram .sub.on-dark { + fill: #b8c7dc; +} + +.doctr-diagram .arrow { + stroke: var(--pst-color-text-muted); + stroke-width: 1.5; + fill: none; +} + +.doctr-diagram .arrow-head { + fill: var(--pst-color-text-muted); +} + +.doctr-diagram .legend { + font: 400 11px var(--pst-font-family-base); + fill: var(--pst-color-text-muted); +} + +/* -- Document tree (concepts page) ------------------------------------------------ */ + +.doctr-tree { + font-family: var(--pst-font-family-monospace); + font-size: 0.86rem; + line-height: 1.75; + padding: 1rem 1.25rem; + border: 1px solid var(--pst-color-border); + border-radius: var(--doctr-radius-sm); + background: var(--doctr-code-bg); + white-space: pre; + overflow-x: auto; + margin-bottom: 1.5rem; +} + +.doctr-tree .k { + color: var(--pst-color-primary); + font-weight: 600; +} + +.doctr-tree .c { + color: var(--pst-color-text-muted); +} + +/* ========================================================================== + Landing page + ========================================================================== */ + +/* Full-width landing page: hide the article chrome */ +body:has(.doctr-hero) .bd-main .bd-content .bd-article-container { + max-width: 100%; +} + +body:has(.doctr-hero) .bd-header-article, +body:has(.doctr-hero) .prev-next-area, +body:has(.doctr-hero) .bd-sidebar-secondary { + display: none !important; +} + +body:has(.doctr-hero) .bd-main .bd-content .bd-article-container .bd-article { + padding-top: 0; +} + +body:has(.doctr-hero) .bd-container__inner { + max-width: 1240px; +} + +body:has(.doctr-hero) .bd-article > section > h1:first-child { + /* The page title is rendered by the hero: keep it for screen readers only */ + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.doctr-hero { + position: relative; + overflow: hidden; + margin: 1.25rem 0 3rem; + padding: 3.5rem 3rem 3.25rem; + border-radius: 20px; + color: #e8eef7; + background: + radial-gradient(900px 380px at 100% 0%, rgba(255, 0, 62, 0.28), transparent 60%), + radial-gradient(700px 400px at 0% 100%, rgba(11, 87, 208, 0.35), transparent 60%), + linear-gradient(135deg, #001e3f 0%, #04254d 55%, #0a2f5c 100%); +} + +/* Pixel grid echoing the docTR mark */ +.doctr-hero::before { + content: ""; + position: absolute; + inset: 0; + background-image: + linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px); + background-size: 28px 28px; + mask-image: linear-gradient(120deg, transparent 20%, #000 70%); + -webkit-mask-image: linear-gradient(120deg, transparent 20%, #000 70%); + pointer-events: none; +} + +.doctr-hero__grid { + position: relative; + display: grid; + grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); + gap: 2.5rem; + align-items: center; +} + +.doctr-hero__eyebrow { + display: inline-flex; + align-items: center; + gap: 0.5rem; + font-size: 0.8rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: #ffd2dc; + background: rgba(255, 0, 62, 0.16); + border: 1px solid rgba(255, 92, 122, 0.35); + padding: 0.3rem 0.75rem; + border-radius: 999px; + margin-bottom: 1.1rem; +} + +.doctr-hero p.doctr-hero__title { + font-size: clamp(2.2rem, 4.2vw, 3.3rem); + line-height: 1.08; + font-weight: 800; + letter-spacing: -0.035em; + color: #ffffff; + margin: 0 0 1rem; +} + +.doctr-hero__title span { + background: linear-gradient(90deg, #ff4d6d, #ff8fa3); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +.doctr-hero p.doctr-hero__lead { + font-size: 1.12rem; + line-height: 1.65; + color: #c4d1e3; + max-width: 34rem; + margin-bottom: 1.6rem; +} + +.doctr-hero__actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-bottom: 1.5rem; +} + +.doctr-btn { + display: inline-flex; + align-items: center; + gap: 0.5rem; + font-weight: 650; + font-size: 0.97rem; + padding: 0.7rem 1.2rem; + border-radius: 10px; + text-decoration: none !important; + transition: transform 0.15s ease, background-color 0.15s ease, border-color 0.15s ease; +} + +.doctr-btn:hover { + transform: translateY(-1px); +} + +.doctr-btn--primary { + color: #ffffff !important; + background: var(--doctr-red); + box-shadow: 0 8px 22px -8px rgba(255, 0, 62, 0.65); +} + +.doctr-btn--primary:hover { + background: #e60038; +} + +.doctr-btn--ghost { + color: #ffffff !important; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.22); +} + +.doctr-btn--ghost:hover { + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.4); +} + +.doctr-hero__install { + display: inline-flex; + align-items: center; + gap: 0.75rem; + font-family: var(--pst-font-family-monospace); + font-size: 0.9rem; + color: #e8eef7; + background: rgba(0, 0, 0, 0.28); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 10px; + padding: 0.55rem 0.6rem 0.55rem 0.9rem; +} + +.doctr-hero__install .prompt { + color: #ff8fa3; + user-select: none; +} + +.doctr-hero__install button { + font: inherit; + font-size: 0.78rem; + font-family: var(--pst-font-family-base); + font-weight: 600; + color: #c4d1e3; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.14); + border-radius: 6px; + padding: 0.15rem 0.55rem; + cursor: pointer; +} + +.doctr-hero__install button:hover { + color: #ffffff; + background: rgba(255, 255, 255, 0.16); +} + +/* Code window in the hero */ +.doctr-window { + border-radius: 14px; + background: #0b1424; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.6); + overflow: hidden; +} + +.doctr-window__bar { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.7rem 0.9rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.07); +} + +.doctr-window__bar i { + width: 11px; + height: 11px; + border-radius: 50%; + background: #33415a; +} + +.doctr-window__bar i:first-child { + background: #ff5f57; +} + +.doctr-window__bar i:nth-child(2) { + background: #febc2e; +} + +.doctr-window__bar i:nth-child(3) { + background: #28c840; +} + +.doctr-window__bar span { + margin-left: 0.6rem; + font-family: var(--pst-font-family-monospace); + font-size: 0.76rem; + color: #7f8ea6; +} + +.doctr-window pre { + margin: 0; + padding: 1.1rem 1.25rem 1.3rem; + font-family: var(--pst-font-family-monospace); + font-size: 0.86rem; + line-height: 1.75; + color: #d6deeb; + background: transparent; + border: none; + white-space: pre; + overflow-x: auto; +} + +.doctr-window .tk-k { + color: #ff7a93; +} + +.doctr-window .tk-f { + color: #82aaff; +} + +.doctr-window .tk-s { + color: #a5e075; +} + +.doctr-window .tk-c { + color: #637392; + font-style: italic; +} + +.doctr-window .tk-n { + color: #f9c859; +} + +.doctr-window .tk-o { + color: #9fb0c8; +} + +.doctr-window__output { + border-top: 1px dashed rgba(255, 255, 255, 0.1); + padding: 0.85rem 1.25rem 1rem; + font-family: var(--pst-font-family-monospace); + font-size: 0.8rem; + line-height: 1.6; + color: #8fa3bf; + white-space: pre; + overflow-x: auto; +} + +.doctr-window__output b { + color: #e8eef7; + font-weight: 500; +} + +/* Stats strip */ +.doctr-stats { + position: relative; + display: flex; + flex-wrap: wrap; + gap: 2.25rem; + margin-top: 2.5rem; + padding-top: 1.6rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.doctr-stats div { + display: flex; + flex-direction: column; +} + +.doctr-stats strong { + font-size: 1.5rem; + font-weight: 750; + color: #ffffff; + letter-spacing: -0.02em; +} + +.doctr-stats span { + font-size: 0.85rem; + color: #9fb0c8; +} + +/* Landing sections */ +.doctr-section-title { + text-align: center; + margin: 3.5rem auto 0.4rem; + font-size: 1.9rem !important; + font-weight: 750; + letter-spacing: -0.03em; +} + +.doctr-section-lead { + text-align: center; + max-width: 42rem; + margin: 0 auto 2rem; + color: var(--pst-color-text-muted); + font-size: 1.05rem; +} + +body:has(.doctr-hero) .sd-card .sd-card-title { + font-size: 1.05rem; +} + +.doctr-cta { + margin: 3.5rem 0 1rem; + padding: 2rem 2.25rem; + border-radius: 16px; + border: 1px solid var(--pst-color-border); + background: + radial-gradient(500px 200px at 100% 0%, rgba(255, 0, 62, 0.08), transparent 70%), + var(--pst-color-surface); + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + align-items: center; + justify-content: space-between; +} + +.doctr-cta h3 { + margin: 0 0 0.35rem !important; + font-size: 1.25rem !important; +} + +.doctr-cta > div { + flex: 1 1 28rem; +} + +.doctr-cta p { + margin: 0; + color: var(--pst-color-text-muted); +} + +.doctr-cta .doctr-btn--primary { + white-space: nowrap; +} + +@media (max-width: 960px) { + .doctr-hero { + padding: 2.25rem 1.5rem; + } + + .doctr-hero__grid { + grid-template-columns: minmax(0, 1fr); + } + + .doctr-stats { + gap: 1.5rem; + } +} + +/* -- Footer ------------------------------------------------------------------ */ + +.bd-footer { + border-top: 1px solid var(--pst-color-border); + font-size: 0.86rem; +} + +.bd-footer .footer-items__center { + text-align: center; +} + +.bd-footer p { + margin: 0; + color: var(--pst-color-text-muted); +} + +/* -- Vocabulary tables ------------------------------------------------------- */ + +.vocab-table td:nth-child(1), +.vocab-table td:nth-child(2) { + white-space: nowrap; + vertical-align: top; +} + +.vocab-table td:nth-child(3) { + width: 100%; +} + +.vocab-table summary { + cursor: pointer; + font-family: var(--pst-font-family-monospace); + font-size: 0.85rem; +} + +.vocab-table .vocab-base { + font-family: var(--pst-font-family-base); + color: var(--pst-color-text-muted); +} + +.vocab-table .vocab-chars { + margin-top: 0.5rem; + font-family: var(--pst-font-family-monospace); + font-size: 0.85rem; + word-break: break-all; + max-height: 14rem; + overflow-y: auto; + padding: 0.5rem 0.65rem; + border-radius: 6px; + background: var(--pst-color-surface); +} diff --git a/docs/source/_static/css/mindee.css b/docs/source/_static/css/mindee.css deleted file mode 100644 index 94954e57c5..0000000000 --- a/docs/source/_static/css/mindee.css +++ /dev/null @@ -1,52 +0,0 @@ -/* Version control */ - -.version-button { - color: white; - border: none; - padding: 5px; - font-size: 15px; - cursor: pointer; -} - -.version-button:hover, .version-button:focus { - background-color: #5eb2e6; -} - -.version-dropdown { - display: none; - min-width: 160px; - overflow: auto; - font-size: 15px; -} - -.version-dropdown a { - color: white; - padding: 3px 4px; - text-decoration: none; - display: block; -} - -.version-dropdown a:hover { - background-color: #5eb2e6; -} - -.version-show { - display: block; -} - -h1 { - font-family: "Helvetica Neue", Arial, sans-serif; - /* style fix for headline that it fits into one line */ - font-size: 240%; -} - -h1, h2, h3, h4, h5, .caption-text { - font-family: "Helvetica Neue", Arial, sans-serif; -} - -/* Github button */ - -.github-repo { - display: flex; - justify-content: center; -} diff --git a/docs/source/_static/fonts/LICENSE-Inter.txt b/docs/source/_static/fonts/LICENSE-Inter.txt new file mode 100644 index 0000000000..40589daa9d --- /dev/null +++ b/docs/source/_static/fonts/LICENSE-Inter.txt @@ -0,0 +1,93 @@ +Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter) Inter-Italic[opsz,wght].ttf: Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/source/_static/fonts/LICENSE-JetBrainsMono.txt b/docs/source/_static/fonts/LICENSE-JetBrainsMono.txt new file mode 100644 index 0000000000..8f7ed67113 --- /dev/null +++ b/docs/source/_static/fonts/LICENSE-JetBrainsMono.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) JetBrainsMono-Italic[wght].ttf: Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/source/_static/fonts/inter-latin-ext-wght-normal.woff2 b/docs/source/_static/fonts/inter-latin-ext-wght-normal.woff2 new file mode 100644 index 0000000000..479d010d29 Binary files /dev/null and b/docs/source/_static/fonts/inter-latin-ext-wght-normal.woff2 differ diff --git a/docs/source/_static/fonts/inter-latin-wght-normal.woff2 b/docs/source/_static/fonts/inter-latin-wght-normal.woff2 new file mode 100644 index 0000000000..d15208de03 Binary files /dev/null and b/docs/source/_static/fonts/inter-latin-wght-normal.woff2 differ diff --git a/docs/source/_static/fonts/jetbrains-mono-latin-ext-wght-normal.woff2 b/docs/source/_static/fonts/jetbrains-mono-latin-ext-wght-normal.woff2 new file mode 100644 index 0000000000..01769d9e63 Binary files /dev/null and b/docs/source/_static/fonts/jetbrains-mono-latin-ext-wght-normal.woff2 differ diff --git a/docs/source/_static/fonts/jetbrains-mono-latin-wght-normal.woff2 b/docs/source/_static/fonts/jetbrains-mono-latin-wght-normal.woff2 new file mode 100644 index 0000000000..cd5102a44c Binary files /dev/null and b/docs/source/_static/fonts/jetbrains-mono-latin-wght-normal.woff2 differ diff --git a/docs/source/_static/images/Logo-docTR-white.png b/docs/source/_static/images/Logo-docTR-white.png deleted file mode 100644 index 988eb2cf7f..0000000000 Binary files a/docs/source/_static/images/Logo-docTR-white.png and /dev/null differ diff --git a/docs/source/_static/images/demo.png b/docs/source/_static/images/demo.png new file mode 100644 index 0000000000..9095e3727a Binary files /dev/null and b/docs/source/_static/images/demo.png differ diff --git a/docs/source/_static/images/examples/invoice.pdf b/docs/source/_static/images/examples/invoice.pdf new file mode 100644 index 0000000000..6474045493 Binary files /dev/null and b/docs/source/_static/images/examples/invoice.pdf differ diff --git a/docs/source/_static/images/examples/invoice_input.png b/docs/source/_static/images/examples/invoice_input.png new file mode 100644 index 0000000000..521d3ae10e Binary files /dev/null and b/docs/source/_static/images/examples/invoice_input.png differ diff --git a/docs/source/_static/images/examples/invoice_layout.png b/docs/source/_static/images/examples/invoice_layout.png new file mode 100644 index 0000000000..2fc98c436b Binary files /dev/null and b/docs/source/_static/images/examples/invoice_layout.png differ diff --git a/docs/source/_static/images/examples/invoice_synthesized.png b/docs/source/_static/images/examples/invoice_synthesized.png new file mode 100644 index 0000000000..b7e29742ba Binary files /dev/null and b/docs/source/_static/images/examples/invoice_synthesized.png differ diff --git a/docs/source/_static/images/examples/invoice_words.png b/docs/source/_static/images/examples/invoice_words.png new file mode 100644 index 0000000000..6ff552a653 Binary files /dev/null and b/docs/source/_static/images/examples/invoice_words.png differ diff --git a/docs/source/_static/images/logo-dark.png b/docs/source/_static/images/logo-dark.png new file mode 100644 index 0000000000..cea6b16ee7 Binary files /dev/null and b/docs/source/_static/images/logo-dark.png differ diff --git a/docs/source/_static/images/logo-light.png b/docs/source/_static/images/logo-light.png new file mode 100644 index 0000000000..44489fc3c7 Binary files /dev/null and b/docs/source/_static/images/logo-light.png differ diff --git a/docs/source/_static/js/custom.js b/docs/source/_static/js/custom.js deleted file mode 100644 index c8df1c354f..0000000000 --- a/docs/source/_static/js/custom.js +++ /dev/null @@ -1,104 +0,0 @@ -// Based on https://github.com/huggingface/transformers/blob/master/docs/source/_static/js/custom.js - - -// These two things need to be updated at each release for the version selector. -// Last stable version -const stableVersion = "v1.1.0" -// Dictionary doc folder to label. The last stable version should have an empty key. -const versionMapping = { - "latest": "latest", - "": "v1.1.1 (stable)", - "v1.1.0": "v1.1.0", - "v1.0.0": "v1.0.0", - "v0.12.0": "v0.12.0", - "v0.11.0": "v0.11.0", -} - -function addGithubButton() { - const div = ` - - `; - document.querySelector(".sidebar-brand").insertAdjacentHTML('afterend', div); -} - -function addVersionControl() { - // To grab the version currently in view, we parse the url - const parts = location.toString().split('#')[0].split('/'); - let versionIndex = parts.length - 2; - // Index page may not have a last part with filename.html so we need to go up - if (parts[parts.length - 1] != "" && ! parts[parts.length - 1].match(/\.html$|^search.html?/)) { - versionIndex = parts.length - 1; - } - const version = parts[versionIndex]; - - // Menu with all the links, - const versionMenu = document.createElement("div"); - - const htmlLines = []; - for (const [key, value] of Object.entries(versionMapping)) { - let baseUrlIndex = (version == "doctr") ? versionIndex + 1: versionIndex; - var urlParts = parts.slice(0, baseUrlIndex); - if (key != "") { - urlParts = urlParts.concat([key]); - } - urlParts = urlParts.concat(parts.slice(versionIndex+1)); - htmlLines.push(`${value}`); - } - - versionMenu.classList.add("version-dropdown"); - versionMenu.innerHTML = htmlLines.join('\n'); - - // Button for version selection - const versionButton = document.createElement("div"); - versionButton.classList.add("version-button"); - let label = (version == "doctr") ? stableVersion : version - versionButton.innerText = label.concat(" ▼"); - - // Toggle the menu when we click on the button - versionButton.addEventListener("click", () => { - versionMenu.classList.toggle("version-show"); - }); - - // Hide the menu when we click elsewhere - window.addEventListener("click", (event) => { - if (event.target != versionButton){ - versionMenu.classList.remove('version-show'); - } - }); - - // Container - const div = document.createElement("div"); - div.appendChild(versionButton); - div.appendChild(versionMenu); - div.style.paddingTop = '5px'; - div.style.paddingBottom = '5px'; - div.style.display = 'block'; - div.style.textAlign = 'center'; - - const scrollDiv = document.querySelector(".sidebar-brand"); - scrollDiv.insertBefore(div, scrollDiv.children[1]); -} - -/*! - * github-buttons v2.2.10 - * (c) 2019 なつき - * @license BSD-2-Clause - */ -/** - * modified to run programmatically - */ -function parseGithubButtons (){"use strict";var e=window.document,t=e.location,o=window.encodeURIComponent,r=window.decodeURIComponent,n=window.Math,a=window.HTMLElement,i=window.XMLHttpRequest,l="https://unpkg.com/github-buttons@2.2.10/dist/buttons.html",c=i&&i.prototype&&"withCredentials"in i.prototype,d=c&&a&&a.prototype.attachShadow&&!a.prototype.attachShadow.prototype,s=function(e,t,o){e.addEventListener?e.addEventListener(t,o):e.attachEvent("on"+t,o)},u=function(e,t,o){e.removeEventListener?e.removeEventListener(t,o):e.detachEvent("on"+t,o)},h=function(e,t,o){var r=function(n){return u(e,t,r),o(n)};s(e,t,r)},f=function(e,t,o){var r=function(n){if(t.test(e.readyState))return u(e,"readystatechange",r),o(n)};s(e,"readystatechange",r)},p=function(e){return function(t,o,r){var n=e.createElement(t);if(o)for(var a in o){var i=o[a];null!=i&&(null!=n[a]?n[a]=i:n.setAttribute(a,i))}if(r)for(var l=0,c=r.length;l'},eye:{width:16,height:16,path:''},star:{width:14,height:16,path:''},"repo-forked":{width:10,height:16,path:''},"issue-opened":{width:14,height:16,path:''},"cloud-download":{width:16,height:16,path:''}},w={},x=function(e,t,o){var r=p(e.ownerDocument),n=e.appendChild(r("style",{type:"text/css"}));n.styleSheet?n.styleSheet.cssText=m:n.appendChild(e.ownerDocument.createTextNode(m));var a,l,d=r("a",{className:"btn",href:t.href,target:"_blank",innerHTML:(a=t["data-icon"],l=/^large$/i.test(t["data-size"])?16:14,a=(""+a).toLowerCase().replace(/^octicon-/,""),{}.hasOwnProperty.call(v,a)||(a="mark-github"),'"),"aria-label":t["aria-label"]||void 0},[" ",r("span",{},[t["data-text"]||""])]);/\.github\.com$/.test("."+d.hostname)?/^https?:\/\/((gist\.)?github\.com\/[^\/?#]+\/[^\/?#]+\/archive\/|github\.com\/[^\/?#]+\/[^\/?#]+\/releases\/download\/|codeload\.github\.com\/)/.test(d.href)&&(d.target="_top"):(d.href="#",d.target="_self");var u,h,g,x,y=e.appendChild(r("div",{className:"widget"+(/^large$/i.test(t["data-size"])?" lg":"")},[d]));/^(true|1)$/i.test(t["data-show-count"])&&"github.com"===d.hostname&&(u=d.pathname.replace(/^(?!\/)/,"/").match(/^\/([^\/?#]+)(?:\/([^\/?#]+)(?:\/(?:(subscription)|(fork)|(issues)|([^\/?#]+)))?)?(?:[\/?#]|$)/))&&!u[6]?(u[2]?(h="/repos/"+u[1]+"/"+u[2],u[3]?(x="subscribers_count",g="watchers"):u[4]?(x="forks_count",g="network"):u[5]?(x="open_issues_count",g="issues"):(x="stargazers_count",g="stargazers")):(h="/users/"+u[1],g=x="followers"),function(e,t){var o=w[e]||(w[e]=[]);if(!(o.push(t)>1)){var r=b(function(){for(delete w[e];t=o.shift();)t.apply(null,arguments)});if(c){var n=new i;s(n,"abort",r),s(n,"error",r),s(n,"load",function(){var e;try{e=JSON.parse(n.responseText)}catch(e){return void r(e)}r(200!==n.status,e)}),n.open("GET",e),n.send()}else{var a=this||window;a._=function(e){a._=null,r(200!==e.meta.status,e.data)};var l=p(a.document)("script",{async:!0,src:e+(/\?/.test(e)?"&":"?")+"callback=_"}),d=function(){a._&&a._({meta:{}})};s(l,"load",d),s(l,"error",d),l.readyState&&f(l,/de|m/,d),a.document.getElementsByTagName("head")[0].appendChild(l)}}}.call(this,"https://api.github.com"+h,function(e,t){if(!e){var n=t[x];y.appendChild(r("a",{className:"social-count",href:t.html_url+"/"+g,target:"_blank","aria-label":n+" "+x.replace(/_count$/,"").replace("_"," ").slice(0,n<2?-1:void 0)+" on GitHub"},[r("b"),r("i"),r("span",{},[(""+n).replace(/\B(?=(\d{3})+(?!\d))/g,",")])]))}o&&o(y)})):o&&o(y)},y=window.devicePixelRatio||1,C=function(e){return(y>1?n.ceil(n.round(e*y)/y*2)/2:n.ceil(e))||0},F=function(e,t){e.style.width=t[0]+"px",e.style.height=t[1]+"px"},k=function(t,r){if(null!=t&&null!=r)if(t.getAttribute&&(t=function(e){for(var t={href:e.href,title:e.title,"aria-label":e.getAttribute("aria-label")},o=["icon","text","size","show-count"],r=0,n=o.length;r { + document.querySelectorAll("[data-copy]").forEach((button) => { + button.addEventListener("click", async () => { + try { + await navigator.clipboard.writeText(button.dataset.copy); + const label = button.textContent; + button.textContent = "Copied!"; + setTimeout(() => (button.textContent = label), 1500); + } catch (err) { + console.warn("[docTR] unable to copy to the clipboard", err); + } + }); + }); +}); diff --git a/docs/source/_static/switcher.json b/docs/source/_static/switcher.json new file mode 100644 index 0000000000..4efae7b979 --- /dev/null +++ b/docs/source/_static/switcher.json @@ -0,0 +1,33 @@ +[ + { + "name": "latest (main)", + "version": "latest", + "url": "https://mindee.github.io/doctr/latest/" + }, + { + "name": "v1.1.0 (stable)", + "version": "v1.1.0", + "url": "https://mindee.github.io/doctr/", + "preferred": true + }, + { + "name": "v1.0.1", + "version": "v1.0.1", + "url": "https://mindee.github.io/doctr/v1.0.1/" + }, + { + "name": "v1.0.0", + "version": "v1.0.0", + "url": "https://mindee.github.io/doctr/v1.0.0/" + }, + { + "name": "v0.12.0", + "version": "v0.12.0", + "url": "https://mindee.github.io/doctr/v0.12.0/" + }, + { + "name": "v0.11.0", + "version": "v0.11.0", + "url": "https://mindee.github.io/doctr/v0.11.0/" + } +] diff --git a/docs/source/_templates/license.html b/docs/source/_templates/license.html new file mode 100644 index 0000000000..2dcc3c4f6f --- /dev/null +++ b/docs/source/_templates/license.html @@ -0,0 +1,3 @@ +

+ Released under the Apache 2.0 License +

diff --git a/docs/source/_templates/maintainers.html b/docs/source/_templates/maintainers.html new file mode 100644 index 0000000000..a829b8d03b --- /dev/null +++ b/docs/source/_templates/maintainers.html @@ -0,0 +1,4 @@ +

+ Created by Mindee · developed and maintained by + t2k GmbH and the docTR community +

diff --git a/docs/source/api/contrib.md b/docs/source/api/contrib.md new file mode 100644 index 0000000000..3b7d6009b7 --- /dev/null +++ b/docs/source/api/contrib.md @@ -0,0 +1,13 @@ +# doctr.contrib + +```{eval-rst} +.. module:: doctr.contrib +``` + +Complementary modules for document analysis pipelines, running on ONNX Runtime +(`pip install "python-doctr[contrib]"`). See [](../user_guide/artefacts.md). + +```{eval-rst} +.. autoclass:: ArtefactDetector + :members: show +``` diff --git a/docs/source/api/datasets.md b/docs/source/api/datasets.md new file mode 100644 index 0000000000..f7b0c9e827 --- /dev/null +++ b/docs/source/api/datasets.md @@ -0,0 +1,93 @@ +# doctr.datasets + +```{eval-rst} +.. module:: doctr.datasets +``` + +Public datasets, synthetic data generators and loaders for your own annotations. See [](../training/datasets.md) +for a guide. + +## Public datasets + +```{eval-rst} +.. autoclass:: FUNSD + +.. autoclass:: SROIE + +.. autoclass:: CORD + +.. autoclass:: IIIT5K + +.. autoclass:: SVT + +.. autoclass:: SVHN + +.. autoclass:: SynthText + +.. autoclass:: IC03 + +.. autoclass:: IC13 + +.. autoclass:: IMGUR5K + +.. autoclass:: MJSynth + +.. autoclass:: IIITHWS + +.. autoclass:: WILDRECEIPT + +.. autoclass:: COCOTEXT + +.. autoclass:: DocArtefacts +``` + +## Synthetic data generators + +```{eval-rst} +.. autoclass:: CharacterGenerator + +.. autoclass:: WordGenerator +``` + +## Custom datasets + +```{eval-rst} +.. autoclass:: DetectionDataset + +.. autoclass:: RecognitionDataset + +.. autoclass:: OCRDataset + +.. autoclass:: LayoutDataset + +.. autoclass:: TableStructureDataset + +.. autoclass:: OrientationDataset +``` + +## Vocabularies + +```{eval-rst} +.. data:: VOCABS + :type: dict[str, str] + + The supported vocabularies, by name. See :doc:`../models/vocabs` for the complete list. +``` + +## Utilities + +```{eval-rst} +.. autofunction:: translate + +.. autofunction:: encode_string + +.. autofunction:: decode_sequence + +.. autofunction:: encode_sequences + +.. autofunction:: pre_transform_multiclass + +.. autofunction:: crop_bboxes_from_image + +.. autofunction:: convert_target_to_relative +``` diff --git a/docs/source/api/index.md b/docs/source/api/index.md new file mode 100644 index 0000000000..393cc85a39 --- /dev/null +++ b/docs/source/api/index.md @@ -0,0 +1,61 @@ +# API reference + +The complete reference of the public API, generated from the docstrings of the source code. For a guided tour, +start with the [user guide](../user_guide/index.md). + +::::{grid} 1 2 3 3 +:gutter: 3 + +:::{grid-item-card} {octicon}`file;1.1em` doctr.io +:link: io +:link-type: doc + +Reading files, the `Document` elements, and the exporters. +::: + +:::{grid-item-card} {octicon}`cpu;1.1em` doctr.models +:link: models +:link-type: doc + +Predictors, architectures, the Hugging Face Hub integration and model utilities. +::: + +:::{grid-item-card} {octicon}`database;1.1em` doctr.datasets +:link: datasets +:link-type: doc + +Public datasets, synthetic generators, custom loaders and vocabularies. +::: + +:::{grid-item-card} {octicon}`paintbrush;1.1em` doctr.transforms +:link: transforms +:link-type: doc + +Data augmentation and pre-processing transformations. +::: + +:::{grid-item-card} {octicon}`tools;1.1em` doctr.utils +:link: utils +:link-type: doc + +Visualization, page reconstruction and evaluation metrics. +::: + +:::{grid-item-card} {octicon}`plus-circle;1.1em` doctr.contrib +:link: contrib +:link-type: doc + +Complementary modules, such as the artefact detector. +::: +:::: + +```{toctree} +:hidden: + +io +models +datasets +transforms +utils +contrib +``` diff --git a/docs/source/api/io.md b/docs/source/api/io.md new file mode 100644 index 0000000000..5957cf70c9 --- /dev/null +++ b/docs/source/api/io.md @@ -0,0 +1,159 @@ +# doctr.io + +```{eval-rst} +.. module:: doctr.io +``` + +Read documents into pages, represent the predictions as a tree of elements, and export them. + +## Reading files + +```{eval-rst} +.. autoclass:: DocumentFile + + .. automethod:: from_pdf + .. automethod:: from_images + .. automethod:: from_url + +.. autofunction:: read_pdf + +.. autofunction:: read_img_as_numpy + +.. autofunction:: read_img_as_tensor + +.. autofunction:: decode_img_as_tensor + +.. autofunction:: read_html +``` + +## Document structure + +The elements returned by the predictors, from the largest to the smallest. See +[](../getting_started/concepts.md#the-document-tree) for an overview. + +### Document + +```{eval-rst} +.. autoclass:: Document + + .. automethod:: render + .. automethod:: export + .. automethod:: export_as + .. automethod:: export_as_markdown + .. automethod:: export_as_asciidoc + .. automethod:: export_as_html + .. automethod:: export_as_xml + .. automethod:: show + .. automethod:: synthesize + .. automethod:: from_dict +``` + +### Page + +```{eval-rst} +.. autoclass:: Page + + .. automethod:: render + .. automethod:: export + .. automethod:: export_as + .. automethod:: export_as_markdown + .. automethod:: export_as_asciidoc + .. automethod:: export_as_html + .. automethod:: export_as_xml + .. automethod:: items_in_reading_order + .. automethod:: show + .. automethod:: synthesize +``` + +### Block, Line and Word + +```{eval-rst} +.. autoclass:: Block + +.. autoclass:: Line + +.. autoclass:: Word + +.. autoclass:: Artefact +``` + +### Layout and tables + +```{eval-rst} +.. autoclass:: LayoutElement + +.. autoclass:: Table + + .. automethod:: to_grid + .. automethod:: render + +.. autoclass:: TableCell + :members: row_span, col_span +``` + +### Key information extraction + +Returned by {func}`~doctr.models.kie_predictor`: predictions are grouped by class instead of by spatial layout. + +```{eval-rst} +.. autoclass:: KIEDocument + + .. automethod:: show + +.. autoclass:: KIEPage + + .. automethod:: render + .. automethod:: export + .. automethod:: export_as + .. automethod:: export_as_markdown + .. automethod:: export_as_asciidoc + .. automethod:: export_as_html + .. automethod:: export_as_xml + .. automethod:: show + +.. autoclass:: Prediction +``` + +## Exporters + +The `export_as_*` methods of the elements delegate to these exporters, which share the same reading-order +linearization. See [](../user_guide/exporting.md). + +```{eval-rst} +.. autoclass:: TextExporter + :members: export_page, export_kie_page, export_document + +.. autoclass:: MarkdownExporter + :members: export_page, export_kie_page, export_document + +.. autoclass:: AsciiDocExporter + :members: export_page, export_kie_page, export_document + +.. autoclass:: HTMLExporter + :members: export_page, export_kie_page, export_document + +.. autoclass:: XMLExporter + :members: export_page, export_kie_page, export_document + +.. autofunction:: page_reading_order +``` + +## Figures + +Rendering of the figures found by the layout model in the Markdown, AsciiDoc and HTML exports. See +[](../user_guide/exporting.md#figures). + +```{eval-rst} +.. autoclass:: FigureEncoder + :members: resolve, source, enabled, materializes + +.. autofunction:: crop_layout_region + +.. autofunction:: encode_crop + +.. autofunction:: picture_regions + +.. autofunction:: is_picture_region + +.. autofunction:: is_picture_label +``` diff --git a/docs/source/modules/models.rst b/docs/source/api/models.md similarity index 65% rename from docs/source/modules/models.rst rename to docs/source/api/models.md index 8083f9922c..00bbdf32b4 100644 --- a/docs/source/modules/models.rst +++ b/docs/source/api/models.md @@ -1,172 +1,186 @@ -doctr.models -============ +# doctr.models -.. currentmodule:: doctr.models +```{eval-rst} +.. module:: doctr.models +``` +Predictors and model architectures. All the factory functions below are also importable from `doctr.models` +directly. See the [model zoo](../models/index.md) for benchmarks. -doctr.models.classification ---------------------------- +## End-to-end predictors -.. autofunction:: doctr.models.classification.vgg16_bn_r - -.. autofunction:: doctr.models.classification.resnet18 +```{eval-rst} +.. autofunction:: doctr.models.ocr_predictor -.. autofunction:: doctr.models.classification.resnet34 +.. autofunction:: doctr.models.kie_predictor -.. autofunction:: doctr.models.classification.resnet50 +.. autoclass:: doctr.models.predictor.OCRPredictor + :members: add_hook -.. autofunction:: doctr.models.classification.resnet31 +.. autoclass:: doctr.models.kie_predictor.KIEPredictor + :members: add_hook -.. autofunction:: doctr.models.classification.mobilenet_v3_small +.. autoclass:: doctr.models.builder.DocumentBuilder -.. autofunction:: doctr.models.classification.mobilenet_v3_large +.. autoclass:: doctr.models.preprocessor.PreProcessor +``` -.. autofunction:: doctr.models.classification.mobilenet_v3_small_r +## Text detection -.. autofunction:: doctr.models.classification.mobilenet_v3_large_r +```{eval-rst} +.. autofunction:: doctr.models.detection.detection_predictor -.. autofunction:: doctr.models.classification.mobilenet_v3_small_crop_orientation +.. autofunction:: doctr.models.detection.db_resnet34 -.. autofunction:: doctr.models.classification.mobilenet_v3_small_page_orientation +.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.classification.magc_resnet31 +.. autofunction:: doctr.models.detection.db_mobilenet_v3_large -.. autofunction:: doctr.models.classification.vit_s +.. autofunction:: doctr.models.detection.linknet_resnet18 -.. autofunction:: doctr.models.classification.vit_b +.. autofunction:: doctr.models.detection.linknet_resnet34 -.. autofunction:: doctr.models.classification.textnet_tiny +.. autofunction:: doctr.models.detection.linknet_resnet50 -.. autofunction:: doctr.models.classification.textnet_small +.. autofunction:: doctr.models.detection.fast_tiny -.. autofunction:: doctr.models.classification.textnet_base +.. autofunction:: doctr.models.detection.fast_small -.. autofunction:: doctr.models.classification.vip_tiny +.. autofunction:: doctr.models.detection.fast_base +``` -.. autofunction:: doctr.models.classification.vip_base +## Text recognition -.. autofunction:: doctr.models.classification.vit_det_s +```{eval-rst} +.. autofunction:: doctr.models.recognition.recognition_predictor -.. autofunction:: doctr.models.classification.vit_det_m +.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.classification.starnet_s3 +.. autofunction:: doctr.models.recognition.crnn_mobilenet_v3_small -.. autofunction:: doctr.models.classification.crop_orientation_predictor +.. autofunction:: doctr.models.recognition.crnn_mobilenet_v3_large -.. autofunction:: doctr.models.classification.page_orientation_predictor +.. autofunction:: doctr.models.recognition.sar_resnet31 +.. autofunction:: doctr.models.recognition.master -doctr.models.detection ----------------------- +.. autofunction:: doctr.models.recognition.vitstr_small -.. autofunction:: doctr.models.detection.linknet_resnet18 +.. autofunction:: doctr.models.recognition.vitstr_base -.. autofunction:: doctr.models.detection.linknet_resnet34 +.. autofunction:: doctr.models.recognition.parseq -.. autofunction:: doctr.models.detection.linknet_resnet50 +.. autofunction:: doctr.models.recognition.viptr_tiny +``` -.. autofunction:: doctr.models.detection.db_resnet50 +## Layout analysis -.. autofunction:: doctr.models.detection.db_mobilenet_v3_large +```{eval-rst} +.. autofunction:: doctr.models.layout.layout_predictor -.. autofunction:: doctr.models.detection.fast_tiny +.. autofunction:: doctr.models.layout.lw_detr_s -.. autofunction:: doctr.models.detection.fast_small +.. autofunction:: doctr.models.layout.lw_detr_m +``` -.. autofunction:: doctr.models.detection.fast_base +## Table structure recognition -.. autofunction:: doctr.models.detection.detection_predictor +```{eval-rst} +.. autofunction:: doctr.models.table_structure.table_predictor +.. autofunction:: doctr.models.table_structure.tablecenternet +``` -doctr.models.layout -------------------- +## Classification -.. autofunction:: doctr.models.layout.lw_detr_s +Orientation classifiers, and the backbones used by the detection and recognition models. -.. autofunction:: doctr.models.layout.lw_detr_m +```{eval-rst} +.. autofunction:: doctr.models.classification.crop_orientation_predictor -.. autofunction:: doctr.models.layout.layout_predictor +.. autofunction:: doctr.models.classification.page_orientation_predictor +.. autofunction:: doctr.models.classification.mobilenet_v3_small_crop_orientation -doctr.models.table_structure ----------------------------- +.. autofunction:: doctr.models.classification.mobilenet_v3_small_page_orientation -.. autofunction:: doctr.models.table_structure.tablecenternet +.. autofunction:: doctr.models.classification.vgg16_bn_r -.. autofunction:: doctr.models.table_structure.table_predictor +.. autofunction:: doctr.models.classification.resnet18 +.. autofunction:: doctr.models.classification.resnet31 -doctr.models.recognition ------------------------- +.. autofunction:: doctr.models.classification.resnet34 -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn +.. autofunction:: doctr.models.classification.resnet50 -.. autofunction:: doctr.models.recognition.crnn_mobilenet_v3_small +.. autofunction:: doctr.models.classification.magc_resnet31 -.. autofunction:: doctr.models.recognition.crnn_mobilenet_v3_large +.. autofunction:: doctr.models.classification.mobilenet_v3_small -.. autofunction:: doctr.models.recognition.sar_resnet31 +.. autofunction:: doctr.models.classification.mobilenet_v3_large -.. autofunction:: doctr.models.recognition.master +.. autofunction:: doctr.models.classification.mobilenet_v3_small_r -.. autofunction:: doctr.models.recognition.vitstr_small +.. autofunction:: doctr.models.classification.mobilenet_v3_large_r -.. autofunction:: doctr.models.recognition.vitstr_base +.. autofunction:: doctr.models.classification.vit_s -.. autofunction:: doctr.models.recognition.parseq +.. autofunction:: doctr.models.classification.vit_b -.. autofunction:: doctr.models.recognition.viptr_tiny +.. autofunction:: doctr.models.classification.textnet_tiny -.. autofunction:: doctr.models.recognition.recognition_predictor +.. autofunction:: doctr.models.classification.textnet_small +.. autofunction:: doctr.models.classification.textnet_base -doctr.models.zoo ----------------- +.. autofunction:: doctr.models.classification.vip_tiny -.. autofunction:: doctr.models.ocr_predictor +.. autofunction:: doctr.models.classification.vip_base -.. autofunction:: doctr.models.kie_predictor +.. autofunction:: doctr.models.classification.vit_det_s +.. autofunction:: doctr.models.classification.vit_det_m -doctr.models.factory --------------------- +.. autofunction:: doctr.models.classification.starnet_s3 +``` -.. autofunction:: doctr.models.factory.login_to_hub +## Hugging Face Hub +```{eval-rst} .. autofunction:: doctr.models.factory.from_hub .. autofunction:: doctr.models.factory.push_to_hf_hub +.. autofunction:: doctr.models.factory.login_to_hub +``` -doctr.models.utils ------------------- - -.. currentmodule:: doctr.models.utils - -.. autofunction:: export_model_to_onnx +## Model utilities -.. autofunction:: add_whitelist +```{eval-rst} +.. autofunction:: doctr.models.utils.add_whitelist +.. autofunction:: doctr.models.utils.export_model_to_onnx +``` -doctr.models.reading_order --------------------------- +## Reading order -Language-independent estimation of the reading order of the elements of a page, combining a column-following -topological sort of their spatial relations with optional layout semantics (headers, footers, captions). The -reading-order-aware export of a :class:`~doctr.io.elements.Page` / :class:`~doctr.io.elements.Document` to -Markdown or AsciiDoc is exposed through the ``export_as_markdown`` / ``export_as_asciidoc`` methods of -:mod:`doctr.io.elements`. +Language-independent estimation of the reading order of the elements of a page. It combines a column-following +topological sort of their spatial relations with optional layout semantics (headers, footers, captions), and is +used by all the [exports](../user_guide/exporting.md#reading-order). -.. currentmodule:: doctr.models.reading_order +```{eval-rst} +.. module:: doctr.models.reading_order -.. autofunction:: detect_text_direction +.. autoclass:: doctr.models.reading_order.ReadingOrderPredictor -.. autofunction:: sort_reading_order +.. autofunction:: doctr.models.reading_order.sort_reading_order -.. autofunction:: resolve_reading_segments +.. autofunction:: doctr.models.reading_order.detect_text_direction -.. autofunction:: assign_layout_labels +.. autofunction:: doctr.models.reading_order.resolve_reading_segments -.. autofunction:: deskew_reading_geometries +.. autofunction:: doctr.models.reading_order.assign_layout_labels -.. autoclass:: ReadingOrderPredictor +.. autofunction:: doctr.models.reading_order.deskew_reading_geometries +``` diff --git a/docs/source/api/transforms.md b/docs/source/api/transforms.md new file mode 100644 index 0000000000..67d5aa1db8 --- /dev/null +++ b/docs/source/api/transforms.md @@ -0,0 +1,70 @@ +# doctr.transforms + +```{eval-rst} +.. module:: doctr.transforms +``` + +Data transformations for training and inference. Inspired by +[torchvision](https://github.com/pytorch/vision), they are expressed as composable modules. Image transforms apply to +the image only; sample transforms (wrapped in {class}`SampleCompose`) also update the target, e.g. the boxes of a +rotated image. + +```python +from torchvision.transforms.v2 import Compose + +from doctr import transforms as T + +img_transforms = Compose([ + T.Resize((32, 128), preserve_aspect_ratio=True), + T.RandomApply(T.ColorInversion(), 0.1), + T.RandomApply(T.GaussianNoise(mean=0.1, std=0.1), 0.1), +]) +``` + +## Image transformations + +```{eval-rst} +.. autoclass:: Resize + +.. autoclass:: RandomResize + +.. autoclass:: GaussianNoise + +.. autoclass:: GaussianBlur + +.. autoclass:: ChannelShuffle + +.. autoclass:: ColorInversion + +.. autoclass:: RandomShadow + +.. autoclass:: RandomGlare + +.. autoclass:: RandomLighting +``` + +## Geometric transformations + +```{eval-rst} +.. autoclass:: RandomHorizontalFlip + +.. autoclass:: RandomRotate + +.. autoclass:: RandomCrop + +.. autoclass:: RandomPerspective +``` + +## Composition + +```{eval-rst} +.. autoclass:: SampleCompose + +.. autoclass:: ImageTransform + +.. autoclass:: ImageTorchvisionTransform + +.. autoclass:: OneOf + +.. autoclass:: RandomApply +``` diff --git a/docs/source/api/utils.md b/docs/source/api/utils.md new file mode 100644 index 0000000000..6f43421576 --- /dev/null +++ b/docs/source/api/utils.md @@ -0,0 +1,53 @@ +# doctr.utils + +Complementary tools: visualization, page reconstruction and evaluation metrics. + +## Visualization + +```{eval-rst} +.. currentmodule:: doctr.utils.visualization + +.. autofunction:: visualize_page + +.. autofunction:: visualize_kie_page + +.. autofunction:: draw_boxes +``` + +## Reconstitution + +```{eval-rst} +.. currentmodule:: doctr.utils.reconstitution + +.. autofunction:: synthesize_page + +.. autofunction:: synthesize_kie_page +``` + +(task-evaluation)= +## Task evaluation + +Metrics to evaluate models on each task, used by the evaluation scripts of the references and in the +[model zoo](../models/index.md). + +```{eval-rst} +.. currentmodule:: doctr.utils.metrics + +.. autoclass:: TextMatch + :members: update, summary + +.. autoclass:: LocalizationConfusion + :members: update, summary + +.. autoclass:: OCRMetric + :members: update, summary + +.. autoclass:: DetectionMetric + :members: update, summary + +.. autoclass:: ObjectDetectionMetric + :members: update, summary + +.. autoclass:: TableCellMetric + :members: update, summary +``` diff --git a/docs/source/changelog.md b/docs/source/changelog.md new file mode 100644 index 0000000000..33177ee3ca --- /dev/null +++ b/docs/source/changelog.md @@ -0,0 +1,27 @@ +# Changelog + +Every release of docTR, with a link to its detailed release notes on GitHub. All releases are also listed on the [GitHub releases page](https://github.com/mindee/doctr/releases). + +| Version | Release date | Release notes | +| --- | --- | --- | +| **v1.1.0** | 2026-08-21 | [v1.1.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v1.1.0) | +| **v1.0.1** | 2026-02-04 | [v1.0.1 on GitHub](https://github.com/mindee/doctr/releases/tag/v1.0.1) | +| **v1.0.0** | 2025-07-09 | [v1.0.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v1.0.0) | +| **v0.12.0** | 2025-06-20 | [v0.12.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.12.0) | +| **v0.11.0** | 2025-01-30 | [v0.11.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.11.0) | +| **v0.10.0** | 2024-10-21 | [v0.10.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.10.0) | +| **v0.9.0** | 2024-08-08 | [v0.9.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.9.0) | +| **v0.8.1** | 2024-03-04 | [v0.8.1 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.8.1) | +| **v0.8.0** | 2024-02-28 | [v0.8.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.8.0) | +| **v0.7.0** | 2023-09-09 | [v0.7.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.7.0) | +| **v0.6.0** | 2022-09-29 | [v0.6.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.6.0) | +| **v0.5.1** | 2022-03-22 | [v0.5.1 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.5.1) | +| **v0.5.0** | 2021-12-31 | [v0.5.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.5.0) | +| **v0.4.1** | 2021-11-22 | [v0.4.1 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.4.1) | +| **v0.4.0** | 2021-10-01 | [v0.4.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.4.0) | +| **v0.3.1** | 2021-08-27 | [v0.3.1 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.3.1) | +| **v0.3.0** | 2021-07-02 | [v0.3.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.3.0) | +| **v0.2.1** | 2021-05-28 | [v0.2.1 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.2.1) | +| **v0.2.0** | 2021-05-11 | [v0.2.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.2.0) | +| **v0.1.1** | 2021-03-18 | [v0.1.1 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.1.1) | +| **v0.1.0** | 2021-03-05 | [v0.1.0 on GitHub](https://github.com/mindee/doctr/releases/tag/v0.1.0) | diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst deleted file mode 100644 index f862b71195..0000000000 --- a/docs/source/changelog.rst +++ /dev/null @@ -1,86 +0,0 @@ -Changelog -========= - -v1.1.0 (2026-08-21) -------------------- -Release note: `v1.1.0 `_ - -v1.0.1 (2026-02-04) -------------------- -Release note: `v1.0.1 `_ - -v1.0.0 (2025-07-09) -------------------- -Release note: `v1.0.0 `_ - -v0.12.0 (2025-06-20) --------------------- -Release note: `v0.12.0 `_ - -v0.11.0 (2025-01-30) --------------------- -Release note: `v0.11.0 `_ - -v0.10.0 (2024-10-21) --------------------- -Release note: `v0.10.0 `_ - -v0.9.0 (2024-08-08) -------------------- -Release note: `v0.9.0 `_ - -v0.8.1 (2024-03-04) -------------------- -Release note: `v0.8.1 `_ - -v0.8.0 (2024-02-28) -------------------- -Release note: `v0.8.0 `_ - -v0.7.0 (2023-09-09) -------------------- -Release note: `v0.7.0 `_ - -v0.6.0 (2022-09-29) -------------------- -Release note: `v0.6.0 `_ - -v0.5.1 (2022-03-22) -------------------- -Release note: `v0.5.1 `_ - -v0.5.0 (2021-12-31) -------------------- -Release note: `v0.5.0 `_ - -v0.4.1 (2021-11-22) -------------------- -Release note: `v0.4.1 `_ - -v0.4.0 (2021-10-01) -------------------- -Release note: `v0.4.0 `_ - -v0.3.1 (2021-08-27) -------------------- -Release note: `v0.3.1 `_ - -v0.3.0 (2021-07-02) -------------------- -Release note: `v0.3.0 `_ - -v0.2.1 (2021-05-28) -------------------- -Release note: `v0.2.1 `_ - -v0.2.0 (2021-05-11) -------------------- -Release note: `v0.2.0 `_ - -v0.1.1 (2021-03-18) -------------------- -Release note: `v0.1.1 `_ - -v0.1.0 (2021-03-05) -------------------- -Release note: `v0.1.0 `_ diff --git a/docs/source/community/index.md b/docs/source/community/index.md new file mode 100644 index 0000000000..b9ca213419 --- /dev/null +++ b/docs/source/community/index.md @@ -0,0 +1,114 @@ +# Community + +docTR is an open-source project developed in the open on [GitHub](https://github.com/mindee/doctr). Whether you need +help, want to share what you built, or would like to contribute, you are welcome here. + +## Get help + +::::{grid} 1 2 3 3 +:gutter: 3 + +:::{grid-item-card} {octicon}`comment-discussion;1.1em` Discussions +:link: https://github.com/mindee/doctr/discussions + +Questions, ideas and show-and-tell. The best place to ask "how do I...?". +::: + +:::{grid-item-card} {octicon}`issue-opened;1.1em` Issues +:link: https://github.com/mindee/doctr/issues + +Bug reports and feature requests. Please include the output of `scripts/collect_env.py`. +::: + +:::{grid-item-card} {octicon}`hubot;1.1em` Ask the docTR Guru +:link: https://gurubase.io/g/doctr + +An AI assistant trained on the docTR documentation and code. +::: +:::: + +Before asking, have a look at [](../user_guide/faq.md). + +## Learn and explore + +::::{grid} 1 2 3 3 +:gutter: 3 + +:::{grid-item-card} {octicon}`book;1.1em` Notebooks +:link: notebooks +:link-type: doc + +Tutorials you can run in Google Colab. +::: + +:::{grid-item-card} {octicon}`tools;1.1em` Community tools +:link: tools +:link-type: doc + +Labeling tool, ONNX runtime, Docling plugin and more. +::: + +:::{grid-item-card} {octicon}`megaphone;1.1em` Articles & videos +:link: resources +:link-type: doc + +What the community wrote and recorded about docTR. +::: +:::: + +## Contribute + +Fixing a typo, adding a vocabulary, implementing a paper: every contribution counts. The +{doc}`contribution guide <../contributing/contributing>` explains how to set up a development environment, run the +tests and build this documentation. Please also read the {doc}`code of conduct <../contributing/code_of_conduct>`. + +## Professional support + +docTR was originally created by [Mindee](https://mindee.com) and is now developed and maintained by +[t2k GmbH](https://www.text2knowledge.de). t2k offers professional services around docTR: + +- **Custom OCR development**: detection, recognition and pipeline work tailored to your documents +- **Project implementation**: turning your ideas into production-ready docTR integrations +- **Technical consulting**: architecture, deployment and integration guidance +- **Ongoing development**: feature extensions and implementation support +- **Workshops & advisory**: hands-on sessions and recommendations for working with docTR + +Contact [info@text2knowledge.de](mailto:info@text2knowledge.de) or visit [text2knowledge.de](https://www.text2knowledge.de) +to discuss your project. + +## Cite docTR + +If docTR helps your research, please cite it: + +```bibtex +@misc{doctr2021, + title={docTR: Document Text Recognition}, + author={Mindee}, + year={2021}, + publisher={GitHub}, + howpublished={\url{https://github.com/mindee/doctr}} +} +``` + +## License + +docTR is distributed under the [Apache 2.0 License](https://github.com/mindee/doctr/blob/main/LICENSE): you can use +it in commercial products. + +```{toctree} +:hidden: +:caption: Learn + +notebooks +tools +resources +``` + +```{toctree} +:hidden: +:caption: Contribute + +../contributing/contributing +../contributing/code_of_conduct +../changelog +``` diff --git a/docs/source/community/notebooks.rst b/docs/source/community/notebooks.rst new file mode 120000 index 0000000000..ff8927cf00 --- /dev/null +++ b/docs/source/community/notebooks.rst @@ -0,0 +1 @@ +../../../notebooks/README.rst \ No newline at end of file diff --git a/docs/source/community/resources.md b/docs/source/community/resources.md new file mode 100644 index 0000000000..5baedafbd1 --- /dev/null +++ b/docs/source/community/resources.md @@ -0,0 +1,43 @@ +# Articles & videos + +Resources created by the docTR community. Wrote or recorded something about docTR? Open a pull request to add it. + +::::{grid} 1 1 2 2 +:gutter: 3 + +:::{grid-item-card} {octicon}`book;1.1em` Fine-tuning OCR works really well +:link: https://www.christianmoscardi.com/blog/2024/10/03/digitizing-us-statistical-abstracts.html + +Digitizing the Statistical Abstracts of the United States, by [Christian Moscardi](https://github.com/cmoscardi). +::: + +:::{grid-item-card} {octicon}`video;1.1em` Mindee docTR — probably the best open-source OCR +:link: https://youtu.be/3nYPIDCToes?si=P3xKIHIiv335x0Ct + +A video by [Andrej Baranovskij](https://github.com/abaranovskis-redsamurai). +::: + +:::{grid-item-card} {octicon}`book;1.1em` Extract text from images in Python with docTR +:link: https://medium.com/@netrajpatil12mati/extract-text-from-images-in-python-with-doctr-455970878507 + +An article by Netraj Patil. +::: + +:::{grid-item-card} {octicon}`book;1.1em` How to detect text in images with OCR +:link: https://blog.roboflow.com/ocr-api/ + +The Roboflow integration, by James Gallagher. +::: + +:::{grid-item-card} {octicon}`book;1.1em` Our search for the best OCR tool in 2023 +:link: https://source.opennews.org/articles/our-search-best-ocr-tool-2023/ + +What we found, by Sanjin Ibrahimovic. +::: + +:::{grid-item-card} {octicon}`book;1.1em` Real-time information extraction from documents with docTR +:link: https://analyticsindiamag.com/ai-mysteries/real-time-information-extraction-from-documents-with-doctr/ + +An article by Yugesh Verma. +::: +:::: diff --git a/docs/source/community/resources.rst b/docs/source/community/resources.rst deleted file mode 100644 index 3332e765c4..0000000000 --- a/docs/source/community/resources.rst +++ /dev/null @@ -1,23 +0,0 @@ -Community Resources -=================== - -This section contains some cool resources created by the docTR community. - - -* |:book:| Fine-tuning OCR works really well: the Statistical Abstracts of the United States: - `Article `_ created by: `Christian Moscardi `_. - -* |:video_camera:| Mindee docTR - Probably the Best Open-Source OCR: - `Video `_ created by: `Andrej Baranovskij `_. - -* |:book:| Extract Text from images in Python with docTR: - `Article `_ created by: `Netraj Patil`. - -* |:book:| How to Detect Text in Images with OCR (Roboflow integration): - `Article `_ created by: `James Gallagher`. - -* |:book:| Our search for the best OCR tool in 2023, and what we found: - `Article `_ created by: `Sanjin Ibrahimovic`. - -* |:book:| Real-time information extraction from documents with docTR: - `Article `_ created by: `Yugesh Verma`. diff --git a/docs/source/community/tools.md b/docs/source/community/tools.md new file mode 100644 index 0000000000..689ea71b0b --- /dev/null +++ b/docs/source/community/tools.md @@ -0,0 +1,41 @@ +# Community tools + +Projects built by the community on top of docTR. Built something useful? Open a pull request to add it to this page, +with a short description and what makes it unique. + +## docTR Labeler + +[github.com/text2knowledge/docTR-Labeler](https://github.com/text2knowledge/docTR-Labeler) + +An annotation tool to create and correct OCR datasets for training and fine-tuning docTR models. + +- **Polygon editing**: draw and edit polygons around text regions, with automatic shape correction +- **Auto-annotation**: suggestions and polygon refinement powered by OnnxTR +- **Fast workflow**: keyboard shortcuts for selection, zoom, undo/redo and saving +- **Flexible**: launch it with the `doctr-labeler` command or use its Python API +- **Private**: everything runs locally, no account needed + +## OnnxTR + +[github.com/felixdittrich92/OnnxTR](https://github.com/felixdittrich92/OnnxTR) + +An [ONNX Runtime](https://onnxruntime.ai) backend for docTR models: the same interface, without PyTorch. See +[](../deployment/onnx.md). + +- **Minimal dependencies**: no PyTorch or TensorFlow +- **Fast inference**: CPU, GPU and accelerators such as OpenVINO +- **Quantization**: 8-bit models for a smaller memory footprint +- **Familiar API**: `onnxtr.models.ocr_predictor`, just like docTR +- **Production ready**: Docker images and Hugging Face Hub integration + +## docling-OCR-OnnxTR + +[github.com/felixdittrich92/docling-OCR-OnnxTR](https://github.com/felixdittrich92/docling-OCR-OnnxTR) + +A plugin that brings the OnnxTR OCR engine to the [Docling](https://github.com/docling-project/docling) document +parsing framework. + +- **Native integration**: use it in Docling pipelines through `OnnxtrOcrOptions` +- **Drop-in replacement** for the other OCR engines of Docling +- **Configurable**: model choice, languages, confidence thresholds, batch sizes, orientation correction and 8-bit + models diff --git a/docs/source/community/tools.rst b/docs/source/community/tools.rst deleted file mode 100644 index 14eb26af89..0000000000 --- a/docs/source/community/tools.rst +++ /dev/null @@ -1,90 +0,0 @@ -Community Tools -=============== - -This section highlights notable tools developed by the docTR community. - - -docTR-Labeler -------------- - -:Link: https://github.com/text2knowledge/docTR-Labeler - -**Overview** - -``docTR-Labeler`` is a dedicated annotation tool tailored for creating and editing OCR datasets to train and fine-tune docTR models. It offers a user-friendly graphical interface, featuring polygon-based text labeling, automatic annotation suggestions via OnnxTR, and convenient label export capabilities. - -**Key Features** - -* Interactive Polygon Editing: Draw and edit polygons around text regions with precision -* AI-Powered Auto-Annotation: Automatic annotation suggestions and polygon refinement powered by OnnxTR -* Auto-Correction: Automatic correction of polygon shapes to ensure accurate text region representation -* Efficient Workflow: Keyboard shortcuts for selection, zooming, undo/redraw, and saving operations -* Flexible Access: CLI launch with ``doctr-labeler`` command and full programmatic Python API integration -* Privacy-First: No authentication required - everything runs locally on your machine -* Real-Time Rendering: Live image rendering with helpful visual feedback - - -OnnxTR ------- - -:Link: https://github.com/felixdittrich92/OnnxTR - -**Overview** - -``OnnxTR`` provides an ONNX-based backend for docTR models, enabling fast, cross-platform inference using ONNX Runtime. It's a core refactored library that enhances the performance and flexibility of OCR tasks without relying on heavy frameworks like PyTorch or TensorFlow. - -**Key Features** - -* Minimal Dependencies: No PyTorch or TensorFlow requirements -* Fast Inference: Optimized with ONNX Runtime for production environments -* Quantization Support: Reduced memory usage and faster inference through model quantization -* Batch Processing: Efficient batch inference capabilities -* Multi-Platform: CPU, GPU, and specialized accelerator runtimes like OpenVINO -* Flexible Installation: Separate install options for different runtime requirements -* Familiar API: One-line inference via ``onnxtr.models.ocr_predictor`` (similar to docTR) -* Docker Ready: Production-ready Docker images available -* Hugging Face Integration: Seamless model sharing and loading -* Server Optimized: OpenCV headless installation options for server environments - - -docling-OCR-OnnxTR ------------------- - -:Link: https://github.com/felixdittrich92/docling-OCR-OnnxTR - -**Overview** - -``docling-OCR-OnnxTR`` is a high-performance plugin that integrates the OnnxTR OCR engine into the Docling document parsing framework. By leveraging ONNX Runtime, it delivers superior accuracy and efficiency compared to traditional OCR engines across various hardware configurations. - -**Key Features** - -* Native Docling Support: Direct integration with Docling pipelines using ``OnnxtrOcrOptions`` -* Drop-in Replacement: Easy migration from existing OCR engines -* Model Selection: Control over detection and recognition model choices -* Multi-Language Support: Configurable language settings -* Quality Control: Adjustable confidence thresholds -* Performance Tuning: Batch size optimization -* Enhanced Processing: Orientation correction and 8-bit model loading options - - -Contribute Your Tool --------------------- - -**Share Your Innovation** - -Have you built something amazing on top of docTR ? - -We'd love to showcase your work! Whether it's a useful plugin, dataset preparation tool, or any other docTR-based project, the community would benefit from learning about it. - -**How to Contribute** - -To contribute your tool to the docTR community, please follow these steps: - -1. **GitHub**: Open a pull request with your tool information -2. **Format**: Follow the structure above with clear descriptions and key features - - -.. tip:: - Include a clear tool description and highlight what makes your tool unique or particularly useful to the docTR community. - - This helps others quickly understand its value and how to use it effectively. diff --git a/docs/source/conf.py b/docs/source/conf.py index c57746536d..3c3064cbbb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -3,18 +3,11 @@ # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# +"""Sphinx configuration of the docTR documentation. + +Build it locally from the repository root with ``make docs-single-version`` (output in ``docs/_build``). +""" + import os import sys from datetime import datetime @@ -34,112 +27,329 @@ version = doctr.__version__ release = doctr.__version__ + "-git" +# Version selected in the version switcher. `build.sh` sets it for every deployed version +# ("latest" for the main branch, "v1.1.0", ... for the releases). +DOCS_VERSION = os.environ.get("DOCTR_DOCS_VERSION", "latest") + +# `build.sh` also renders the previous releases with this configuration: their sources still rely on +# a few extensions that the current pages do not use anymore. +LEGACY_BUILD = os.environ.get("DOCTR_DOCS_LEGACY", "0") == "1" + # -- General configuration --------------------------------------------------- -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.intersphinx", "sphinx.ext.viewcode", - "sphinx.ext.coverage", "sphinx.ext.mathjax", - "sphinx.ext.autosectionlabel", - "sphinxemoji.sphinxemoji", # cf. https://sphinxemojicodes.readthedocs.io/en/stable/ + "myst_parser", + "sphinx_design", "sphinx_copybutton", - "recommonmark", - "sphinx_markdown_tables", - "sphinx_tabs.tabs", ] +if LEGACY_BUILD: + extensions += ["sphinx.ext.autosectionlabel", "sphinxemoji.sphinxemoji", "sphinx_tabs.tabs"] +else: + extensions += ["sphinx_reredirects"] + +source_suffix = {".rst": "restructuredtext", ".md": "markdown"} + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "notebooks/*.rst", "**/_generated", "_includes"] + intersphinx_mapping = { "python": ("https://docs.python.org/3", None), + "numpy": ("https://numpy.org/doc/stable/", None), + "torch": ("https://docs.pytorch.org/docs/stable/", None), "pypdfium2": ("https://pypdfium2.readthedocs.io/en/stable/", None), } +# -- MyST (Markdown) --------------------------------------------------------- + +myst_enable_extensions = [ + "attrs_block", + "attrs_inline", + "colon_fence", + "deflist", + "fieldlist", + "html_image", + "substitution", + "tasklist", +] +myst_heading_anchors = 3 +myst_substitutions = { + "version": version, +} + +# -- API documentation ------------------------------------------------------- + +autodoc_member_order = "bysource" +autodoc_typehints = "description" +autodoc_typehints_description_target = "documented_params" +autodoc_preserve_defaults = True +add_module_names = False +python_maximum_signature_line_length = 88 +napoleon_google_docstring = True +napoleon_numpy_docstring = False napoleon_use_ivar = True +napoleon_use_rtype = False -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] +# -- Copy button ------------------------------------------------------------- -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "notebooks/*.rst"] +# Strip the prompts of doctest / shell snippets when copying them +copybutton_prompt_text = r">>> |\.\.\. |\$ " +copybutton_prompt_is_regexp = True +copybutton_exclude = ".linenos, .gp, .go" +# -- Redirects (pages moved by the documentation refactor) ------------------- -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "friendly" -pygments_dark_style = "monokai" -highlight_language = "python3" +redirects = { + "getting_started/installing": "installation.html", + "notebooks": "community/notebooks.html", + "using_doctr/using_models": "../user_guide/ocr_predictor.html", + "using_doctr/using_datasets": "../training/datasets.html", + "using_doctr/using_contrib_modules": "../user_guide/artefacts.html", + "using_doctr/sharing_models": "../training/sharing_models.html", + "using_doctr/using_model_export": "../deployment/performance.html", + "using_doctr/custom_models_training": "../training/train.html", + "using_doctr/running_on_aws": "../deployment/configuration.html", + "using_doctr/using_cli": "../user_guide/cli.html", + "modules/contrib": "../api/contrib.html", + "modules/datasets": "../api/datasets.html", + "modules/io": "../api/io.html", + "modules/models": "../api/models.html", + "modules/transforms": "../api/transforms.html", + "modules/utils": "../api/utils.html", +} # -- Options for HTML output ------------------------------------------------- -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "furo" +html_theme = "pydata_sphinx_theme" +html_title = "docTR documentation" +html_short_title = "docTR" +html_favicon = "_static/images/favicon.ico" +html_static_path = ["_static"] +html_css_files = ["css/doctr.css"] +html_js_files = ["js/doctr.js"] +html_show_sourcelink = False +html_copy_source = False + +html_context = { + "github_user": "mindee", + "github_repo": "doctr", + "github_version": "main", + "doc_path": "docs/source", + "default_mode": "auto", +} -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = { - "sidebar_hide_name": True, - "navigation_with_keys": True, - "light_css_variables": { - "color-sidebar-background": "#082747", - "color-sidebar-background-border": "#082747", - "color-sidebar-caption-text": "white", - "color-sidebar-link-text--top-level": "white", - "color-sidebar-link-text": "white", - "sidebar-caption-font-size": "normal", - "color-sidebar-item-background--hover": " #5dade2", + "logo": { + "image_light": "_static/images/logo-light.png", + "image_dark": "_static/images/logo-dark.png", + "alt_text": "docTR - Home", + }, + "navbar_align": "left", + "navbar_start": ["navbar-logo", "version-switcher"], + "navbar_end": ["theme-switcher", "navbar-icon-links"], + "header_links_before_dropdown": 4 if LEGACY_BUILD else 7, + "switcher": { + "json_url": "_static/switcher.json", + "version_match": DOCS_VERSION, }, - "dark_css_variables": { - "color-sidebar-background": "#1a1c1e", - "color-sidebar-background-border": "#1a1c1e", - "color-sidebar-caption-text": "white", - "color-sidebar-link-text--top-level": "white", + "show_version_warning_banner": not LEGACY_BUILD, + "icon_links": [ + { + "name": "GitHub", + "url": "https://github.com/mindee/doctr", + "icon": "fa-brands fa-github", + }, + { + "name": "PyPI", + "url": "https://pypi.org/project/python-doctr/", + "icon": "fa-brands fa-python", + }, + { + "name": "Live demo on Hugging Face", + "url": "https://huggingface.co/spaces/mindee/doctr", + "icon": "fa-solid fa-wand-magic-sparkles", + }, + ], + "use_edit_page_button": not LEGACY_BUILD, + "secondary_sidebar_items": { + "**": ["page-toc", "edit-this-page"], + "index": [], }, + "show_toc_level": 2, + "show_nav_level": 1, + "navigation_depth": 3, + "show_prev_next": True, + "back_to_top_button": True, + "search_as_you_type": True, + "footer_start": ["copyright"], + "footer_center": ["maintainers"], + "footer_end": ["license"], + "pygments_light_style": "github-light", + "pygments_dark_style": "github-dark", + "analytics": {"google_analytics_id": "G-40DVRMX8T4"}, } -html_logo = "_static/images/Logo-docTR-white.png" -html_favicon = "_static/images/favicon.ico" -html_title = "docTR documentation" - - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] +# The landing page is a full-width page +html_sidebars = {"index": []} # A list of files that should not be packed into the epub file. epub_exclude_files = ["search.html"] -def add_ga_javascript(app, pagename, templatename, context, doctree): - # Add googleanalytics id - # ref: https://github.com/orenhecht/googleanalytics/blob/master/sphinxcontrib/googleanalytics.py +# -- Generated content --------------------------------------------------------- + +# Vocabularies listed on `models/vocabs.md`, grouped by script. Any vocabulary missing from these groups is listed +# under "Other scripts", so that the page always reflects `doctr.datasets.VOCABS`. +VOCAB_GROUPS = { + "General purpose": ["latin", "english", "multilingual", "latex"], + "Latin script": [ + "albanian", + "afrikaans", + "azerbaijani", + "basque", + "bosnian", + "catalan", + "croatian", + "czech", + "danish", + "dutch", + "estonian", + "esperanto", + "french", + "finnish", + "frisian", + "galician", + "german", + "hausa", + "hungarian", + "icelandic", + "indonesian", + "irish", + "italian", + "latvian", + "lithuanian", + "luxembourgish", + "malagasy", + "malay", + "maltese", + "maori", + "montenegrin", + "norwegian", + "polish", + "portuguese", + "quechua", + "romanian", + "scottish_gaelic", + "serbian_latin", + "slovak", + "slovene", + "somali", + "spanish", + "swahili", + "swedish", + "tagalog", + "turkish", + "uzbek_latin", + "vietnamese", + "welsh", + "yoruba", + "zulu", + ], # fmt: skip + "Cyrillic script": [ + "russian", + "belarusian", + "tatar", + "tajik", + "kazakh", + "kyrgyz", + "bulgarian", + "macedonian", + "mongolian", + "yakut", + "serbian_cyrillic", + "uzbek_cyrillic", + "ukrainian", + ], # fmt: skip + "Greek script": ["greek", "greek_extended"], + "Hebrew & Arabic scripts": ["hebrew", "arabic", "persian", "urdu", "pashto", "kurdish", "uyghur", "sindhi"], + "Indic scripts": [ + "devanagari", + "hindi", + "sanskrit", + "marathi", + "nepali", + "gujarati", + "bengali", + "tamil", + "telugu", + "kannada", + "sinhala", + "malayalam", + "punjabi", + "odia", + ], # fmt: skip + "Southeast Asian & other scripts": [ + "thai", + "lao", + "khmer", + "burmese", + "javanese", + "sudanese", + "armenian", + "georgian", + "ethiopic", + ], # fmt: skip + "East Asian scripts": ["japanese", "korean", "simplified_chinese"], +} + + +def _generate_vocab_tables(app): + """Write the tables of the supported vocabularies, included by `models/vocabs.md`""" + from html import escape + + from doctr.datasets import VOCABS + + english = set(VOCABS["english"]) + listed = {name for names in VOCAB_GROUPS.values() for name in names} + first_lang = list(VOCABS).index("latin") # the entries before are building blocks (digits, punctuation, ...) + others = [name for name in list(VOCABS)[first_lang:] if name not in listed] + groups = {**VOCAB_GROUPS, **({"Other scripts": others} if others else {})} + + parts = [] + for title, names in groups.items(): + rows = [] + for name in names: + if name not in VOCABS: + continue + chars = VOCABS[name] + extra = "".join(c for c in chars if c not in english) + if title == "Latin script" and len(extra) < len(chars) / 2: + summary = f'english + {escape(extra)}' if extra else "english" + else: + summary = escape(chars[:60]) + ("…" if len(chars) > 60 else "") + rows.append( + f"{name}{len(chars)}" + f'
{summary}
{escape(chars)}
' + "
" + ) + parts.append( + f"### {title}\n\n```{{raw}} html\n" + '
' + "" + f"{''.join(rows)}
NameSizeCharacters
\n```\n" + ) - metatags = context.get("metatags", "") - metatags += """ - - - - """.format(app.config.googleanalytics_id) - context["metatags"] = metatags + out_dir = os.path.join(app.srcdir, "models", "_generated") + os.makedirs(out_dir, exist_ok=True) + with open(os.path.join(out_dir, "vocabs.md"), "w", encoding="utf-8") as f: + f.write("\n".join(parts)) def setup(app): - app.add_config_value("googleanalytics_id", "G-40DVRMX8T4", "html") - app.add_css_file("css/mindee.css") - app.add_js_file("js/custom.js") - app.connect("html-page-context", add_ga_javascript) + if not LEGACY_BUILD: + app.connect("builder-inited", _generate_vocab_tables) diff --git a/docs/source/deployment/configuration.md b/docs/source/deployment/configuration.md new file mode 100644 index 0000000000..7e93aa2a6c --- /dev/null +++ b/docs/source/deployment/configuration.md @@ -0,0 +1,64 @@ +# Configuration + +docTR has no configuration file: predictors are configured in code, and a few environment variables control how +the library interacts with the system. + +## Environment variables + +| Variable | Default | Effect | +| --- | --- | --- | +| `DOCTR_CACHE_DIR` | `~/.cache/doctr` | Where downloaded model weights (`models/`) and datasets (`datasets/`) are stored | +| `DOCTR_MULTIPROCESSING_DISABLE` | unset | Set to `TRUE` to disable the thread pools used for data processing, e.g. where `/dev/shm` is not writable | + +## Model cache + +Pretrained weights are downloaded the first time a model is used, then loaded from the cache. Their integrity is +checked with the hash in the file name. + +```bash +export DOCTR_CACHE_DIR=/opt/models/doctr +``` + +### Offline use + +To run on a machine without internet access, populate the cache on a connected machine, then copy it: + +```python +# On a connected machine: instantiating the predictors downloads their weights +from doctr.models import ocr_predictor + +ocr_predictor(pretrained=True) # detection + recognition +ocr_predictor(pretrained=True, assume_straight_pages=False) # + orientation classifiers +ocr_predictor(pretrained=True, detect_tables=True) # + layout and table models +``` + +Copy the cache directory (`~/.cache/doctr` or `$DOCTR_CACHE_DIR`) to the same location on the offline machine. +Alternatively, load the weight files from any path with `model.from_pretrained("path/to/weights.pt")` (see +[](../training/custom_models.md)). + +## AWS Lambda + +The [AWS Lambda](https://aws.amazon.com/lambda/) runtime only allows writing to `/tmp`, and does not provide the +shared memory used by multiprocessing. Configure the function with: + +```bash +DOCTR_MULTIPROCESSING_DISABLE=TRUE +DOCTR_CACHE_DIR=/tmp/doctr +``` + +:::{tip} +Downloading the weights at every cold start is slow. Package them in your container image or a Lambda layer, and +point `DOCTR_CACHE_DIR` to their location if it is writable, or load them with `from_pretrained`. +::: + +The same settings apply to other restricted environments (read-only containers, some serverless platforms). + +## Logging + +docTR logs through the standard {mod}`logging` module, under the `doctr` logger: + +```python +import logging + +logging.getLogger("doctr").setLevel(logging.WARNING) +``` diff --git a/docs/source/deployment/docker.md b/docs/source/deployment/docker.md new file mode 100644 index 0000000000..2abcaa688a --- /dev/null +++ b/docs/source/deployment/docker.md @@ -0,0 +1,59 @@ +# Docker + +Prebuilt images with docTR installed are published on the +[GitHub Container Registry](https://github.com/mindee/doctr/pkgs/container/doctr). They are based on CUDA 12.2 and +ready for GPU inference. + +## Run an image + +```bash +docker run -it --gpus all ghcr.io/mindee/doctr: bash +``` + +Drop `--gpus all` to run on the CPU. For GPU support, your host needs: + +- an NVIDIA driver supporting **CUDA 12.2 or newer**, otherwise PyTorch cannot initialize the GPU; +- the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) + so that Docker can access the GPU. + +## Tags + +Tags follow the pattern `-py-`: + +`` +: `torch` for the core package, or `torch-viz-html-contrib` with all the optional extras. + +`` +: The Python version of the image, e.g. `3.11.15`, `3.12.9` or `3.13.14`. + +`` +: A release tag (e.g. `v1.1.0`), or a `YYYY-MM` date for the images rebuilt from the `main` branch every three + months. + +For instance, `torch-viz-html-contrib-py3.11.15-v1.1.0` is release 1.1.0 with all the extras on Python 3.11.15. +Browse the [registry](https://github.com/mindee/doctr/pkgs/container/doctr) for the available tags. + +## Build your own image + +The [`Dockerfile`](https://github.com/mindee/doctr/blob/main/Dockerfile) at the root of the repository accepts +build arguments to choose the Python version, the extras and the docTR version: + +```bash +docker build -t doctr \ + --build-arg FRAMEWORK="torch,viz,html,contrib" \ + --build-arg PYTHON_VERSION=3.11.15 \ + --build-arg DOCTR_VERSION=v1.1.0 \ + . +``` + +| Argument | Default | Description | +| ---------------- | ---------- | ----------------------------------------------------------- | +| `FRAMEWORK` | `torch` | Extras to install, comma-separated (e.g. `torch,viz,html`) | +| `PYTHON_VERSION` | `3.11.15` | Python version, compiled from source | +| `DOCTR_VERSION` | `main` | Git branch, tag or commit of docTR to install | + +:::{tip} +Pretrained weights are downloaded at runtime. To avoid it, warm the cache when building the image, e.g. with +`RUN python -c "from doctr.models import ocr_predictor; ocr_predictor(pretrained=True)"`, or mount a volume on +the cache directory (see [](configuration.md)). +::: diff --git a/docs/source/deployment/index.md b/docs/source/deployment/index.md new file mode 100644 index 0000000000..6d182a1201 --- /dev/null +++ b/docs/source/deployment/index.md @@ -0,0 +1,52 @@ +# Deployment + +Taking docTR to production means making it fast, packaging it, and serving it. This section covers each step. + +::::{grid} 1 2 2 2 +:gutter: 3 + +:::{grid-item-card} {octicon}`rocket;1.1em` Performance +:link: performance +:link-type: doc + +GPU and Apple Silicon, half precision, `torch.compile` and other speed-ups. +::: + +:::{grid-item-card} {octicon}`cpu;1.1em` ONNX & OnnxTR +:link: onnx +:link-type: doc + +Export models to ONNX and run them without PyTorch. +::: + +:::{grid-item-card} {octicon}`container;1.1em` Docker +:link: docker +:link-type: doc + +Prebuilt GPU-ready images, tags, and building your own. +::: + +:::{grid-item-card} {octicon}`globe;1.1em` Web API & demo app +:link: web_api +:link-type: doc + +A FastAPI template to serve docTR, and a Streamlit demo. +::: + +:::{grid-item-card} {octicon}`gear;1.1em` Configuration +:link: configuration +:link-type: doc + +Environment variables, model cache, offline use and AWS Lambda. +::: +:::: + +```{toctree} +:hidden: + +performance +onnx +docker +web_api +configuration +``` diff --git a/docs/source/deployment/onnx.md b/docs/source/deployment/onnx.md new file mode 100644 index 0000000000..0c675d32af --- /dev/null +++ b/docs/source/deployment/onnx.md @@ -0,0 +1,74 @@ +# ONNX & OnnxTR + +[ONNX](https://onnx.ai) is an open format for machine learning models, supported by many runtimes and hardware +vendors. Exporting docTR models to ONNX lets you run them **without PyTorch**, with a smaller footprint and +often a faster CPU inference. + +## Export a model + +{func}`~doctr.models.utils.export_model_to_onnx` traces a model with a dummy input. Build the model with +`exportable=True`, which makes it return raw outputs instead of post-processed predictions: + +```python +import torch + +from doctr.models import vitstr_small +from doctr.models.utils import export_model_to_onnx + +model = vitstr_small(pretrained=True, exportable=True) # or your own fine-tuned model + +batch_size = 1 +input_shape = (3, 32, 128) # (C, H, W), the input shape of the model +dummy_input = torch.rand((batch_size, *input_shape), dtype=torch.float32) + +model_path = export_model_to_onnx(model, model_name="vitstr_small", dummy_input=dummy_input) +``` + +The batch dimension of the exported model is dynamic. Find the input shape of any model in `model.cfg["input_shape"]`. + +## Run it with OnnxTR + +[OnnxTR](https://github.com/felixdittrich92/OnnxTR) is a lightweight package built on +[ONNX Runtime](https://onnxruntime.ai), with the same interface as docTR. It ships ONNX versions of the pretrained +models and loads your exported ones: + +::::{tab-set} + +:::{tab-item} CPU +```bash +pip install "onnxtr[cpu]" +``` +::: + +:::{tab-item} NVIDIA GPU +```bash +pip install "onnxtr[gpu]" +``` +::: + +:::{tab-item} Intel (OpenVINO) +```bash +pip install "onnxtr[openvino]" +``` +::: +:::: + +```python +from onnxtr.io import DocumentFile +from onnxtr.models import linknet_resnet18, ocr_predictor, parseq + +pages = DocumentFile.from_images("path/to/page.jpg") + +# Pretrained models, exactly like in docTR +predictor = ocr_predictor(det_arch="linknet_resnet18", reco_arch="parseq") + +# Or your exported models +det_model = linknet_resnet18("path/to/detection.onnx") +reco_model = parseq("path/to/recognition.onnx", vocab="ABC") # the vocabulary used for training +predictor = ocr_predictor(det_arch=det_model, reco_arch=reco_model) + +result = predictor(pages) +``` + +See the OnnxTR documentation for [installation options](https://github.com/felixdittrich92/OnnxTR#installation) +and [loading custom models](https://github.com/felixdittrich92/OnnxTR#loading-custom-exported-models). diff --git a/docs/source/deployment/performance.md b/docs/source/deployment/performance.md new file mode 100644 index 0000000000..826a56cac8 --- /dev/null +++ b/docs/source/deployment/performance.md @@ -0,0 +1,136 @@ +# Performance + +docTR runs on the CPU out of the box. This page lists the levers to make it faster, from the most effective to the +most specialized. + +## Run on a GPU + +Predictors are PyTorch modules: move them to a device like any model. Inputs are moved automatically. + +::::{tab-set} + +:::{tab-item} NVIDIA (CUDA) +```python +import torch + +from doctr.models import ocr_predictor + +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +predictor = ocr_predictor(pretrained=True).to(device) +# or simply: ocr_predictor(pretrained=True).cuda() +``` +::: + +:::{tab-item} Apple Silicon (MPS) +```python +import torch + +from doctr.models import ocr_predictor + +device = torch.device("mps" if torch.backends.mps.is_available() else "cpu") +predictor = ocr_predictor(pretrained=True).to(device) +``` +::: +:::: + +The same applies to every predictor: `detection_predictor`, `recognition_predictor`, `layout_predictor`, +`table_predictor`, `crop_orientation_predictor` and `page_orientation_predictor`. + +## Half precision + +On a GPU, half-precision inference uses 16-bit floats instead of 32-bit: it is faster and uses less memory, with +the same results in practice. + +```python +from doctr.models import ocr_predictor + +predictor = ocr_predictor(pretrained=True).cuda().bfloat16() # or .half() for float16 +result = predictor(pages) +``` + +| Format | Method | Notes | +| --- | --- | --- | +| **bfloat16** {bdg-success}`recommended` | `.bfloat16()` | Same range as float32, so no overflow. Requires an Ampere GPU or newer (compute capability 8.0+) | +| float16 | `.half()` | More precision but a much narrower range. Use it on older GPUs | + +:::{note} +Half precision is only supported on GPU devices. The [CLI](../user_guide/cli.md) enables bfloat16 automatically on +compatible GPUs. +::: + +## Batch sizes + +The predictor processes pages and word crops in batches: `det_bs` pages at a time for detection (default `2`) and +`reco_bs` crops at a time for recognition (default `128`). On a GPU with free memory, larger batches are faster; +lower them if you run out of memory: + +```python +predictor = ocr_predictor(pretrained=True, det_bs=8, reco_bs=512) +``` + +Pass all the pages of a document (or several documents) in **one call** so that batches are full. + +## Skip what you don't need + +- Keep `assume_straight_pages=True` (default) for upright documents: it avoids the orientation classifiers and + the more expensive rotated-box post-processing. +- With rotated documents, `disable_page_orientation=True` and `disable_crop_orientation=True` skip the orientation + classifiers when you know the text is horizontal (see [](../user_guide/rotated_documents.md)). +- Only enable `detect_layout`, `detect_tables`, `detect_orientation` and `detect_language` when you use them. +- Use lighter architectures, see the [model zoo](../models/index.md). + +## Compile the models + +[`torch.compile`](https://pytorch.org/tutorials/intermediate/torch_compile_tutorial.html) converts the models into +optimized graphs. Compile the models first, then build the predictor with them: + +```python +import torch + +from doctr.models import ( + crop_orientation_predictor, + fast_base, + mobilenet_v3_small_crop_orientation, + mobilenet_v3_small_page_orientation, + ocr_predictor, + page_orientation_predictor, + vitstr_small, +) + +detection_model = torch.compile(fast_base(pretrained=True).eval()) +recognition_model = torch.compile(vitstr_small(pretrained=True).eval()) + +predictor = ocr_predictor(detection_model, recognition_model, assume_straight_pages=False) + +# Only needed when the orientation classifiers are used (rotated documents) +predictor.crop_orientation_predictor = crop_orientation_predictor( + torch.compile(mobilenet_v3_small_crop_orientation(pretrained=True).eval()) +) +predictor.page_orientation_predictor = page_orientation_predictor( + torch.compile(mobilenet_v3_small_page_orientation(pretrained=True).eval()) +) + +result = predictor(pages) +``` + +:::{important} +- The first calls are slow while the graphs are compiled: warm the predictor up before measuring. +- The `master` recognition architecture does not support compilation yet. +- Only the default `inductor` backend is officially supported. +::: + +## CPU inference + +- PyTorch uses all the cores by default. When several workers share a machine, limit each of them with + `torch.set_num_threads(n)`. +- For the smallest footprint, export the models to ONNX and run them with [OnnxTR](onnx.md), optionally with + 8-bit quantized models. + +## Measure + +The `latency.py` scripts of the [references](https://github.com/mindee/doctr/tree/main/references) benchmark any +architecture on your hardware: + +```bash +python references/detection/latency.py fast_base --it 100 +``` diff --git a/docs/source/deployment/web_api.md b/docs/source/deployment/web_api.md new file mode 100644 index 0000000000..b1babbeb67 --- /dev/null +++ b/docs/source/deployment/web_api.md @@ -0,0 +1,80 @@ +# Web API & demo app + +The repository contains two ready-to-run applications: a REST API template to integrate docTR into your services, +and an interactive demo to try the models. + +## REST API + +The [`api/`](https://github.com/mindee/doctr/tree/main/api) folder is a [FastAPI](https://fastapi.tiangolo.com) +application exposing docTR over HTTP, packaged with Docker. + +### Start the server + +You need [Docker](https://docs.docker.com/get-docker/) and Python (to resolve the dependencies with +[uv](https://docs.astral.sh/uv/)): + +```bash +git clone https://github.com/mindee/doctr.git +cd doctr/api +make run +``` + +The server listens on port **8080**. Its interactive documentation (Swagger UI) is available at +[http://localhost:8080/docs](http://localhost:8080/docs). + +### Routes + +| Route | Description | +| --- | --- | +| `POST /detection` | Text detection: the boxes of the words | +| `POST /recognition` | Text recognition of word crops | +| `POST /ocr` | End-to-end OCR | +| `POST /kie` | Key information extraction | + +Each route accepts images and PDFs (`application/pdf`, `image/jpeg`, `image/png`) as multipart files, and the +architectures to use as query parameters: + +```python +import requests + +params = {"det_arch": "fast_base", "reco_arch": "crnn_vgg16_bn"} + +with open("path/to/invoice.pdf", "rb") as f: + files = [("files", ("invoice.pdf", f.read(), "application/pdf"))] + +response = requests.post("http://localhost:8080/ocr", params=params, files=files) +print(response.json()) +``` + +The template is a starting point: adapt the routes, the response schemas and the models to your needs. The +[API README](https://github.com/mindee/doctr/tree/main/api#readme) documents every route with examples. + +## Demo app + +A [Streamlit](https://streamlit.io) app lets you upload a document, pick the models, and inspect the +segmentation maps, the detected words and the reconstructed page. + +```{image} ../_static/images/demo.png +:alt: The docTR demo app +:align: center +``` + +Try it online on [Hugging Face Spaces](https://huggingface.co/spaces/mindee/doctr), or run it locally from the +repository: + +```bash +pip install -r demo/pt-requirements.txt +streamlit run demo/app.py +``` + +## Example script + +[`scripts/analyze.py`](https://github.com/mindee/doctr/blob/main/scripts/analyze.py) analyzes a single file and +displays the results, a handy way to try options from the terminal: + +```bash +python scripts/analyze.py path/to/your/doc.pdf +python scripts/analyze.py --help +``` + +For batch processing from the terminal, use the [CLI](../user_guide/cli.md). diff --git a/docs/source/getting_started/concepts.md b/docs/source/getting_started/concepts.md new file mode 100644 index 0000000000..1aa2ce6d56 --- /dev/null +++ b/docs/source/getting_started/concepts.md @@ -0,0 +1,176 @@ +# Core concepts + +A few ideas come back on every page of this documentation: **predictors**, the **`Document`** tree they return, +and the way docTR expresses **positions**. Ten minutes here will make everything else easier to follow. + +## The OCR pipeline + +OCR is split into two consecutive tasks, each solved by a dedicated deep learning model: + +1. **Text detection** localizes every word on the page (*where is text?*). +2. **Text recognition** transcribes each detected word from its crop (*what does it say?*). + +The predictions are then assembled into a structured document. Optional stages plug into the same pipeline when +you need them: orientation handling for rotated pages, layout analysis, and table structure recognition. + +```{raw} html +:file: ../_includes/pipeline.html +``` + +## Predictors and models + +You rarely call a model directly. Instead you use a **predictor**: a callable that takes care of everything +around the neural network so you can feed it raw pages. + +| Layer | Responsibility | Example | +| ----------------- | ---------------------------------------------------------------------------------- | -------------------------------- | +| **Pre-processor** | Resizes, pads, normalizes and batches the input images | {class}`~doctr.models.preprocessor.PreProcessor` | +| **Model** | A regular PyTorch `nn.Module` that runs the network | `fast_base`, `crnn_vgg16_bn` | +| **Post-processor**| Turns raw network outputs into boxes, strings and scores | decoding of the segmentation map | + +docTR exposes a factory function for every task. Each one accepts an architecture **name** (or a model +**instance**, e.g. one you trained yourself) and returns a ready-to-use predictor: + +| Task | Factory | Returns | +| --------------------------- | ----------------------------------------------------------- | ----------------------------------------- | +| End-to-end OCR | {func}`~doctr.models.ocr_predictor` | a {class}`~doctr.io.Document` | +| Key information extraction | {func}`~doctr.models.kie_predictor` | a {class}`~doctr.io.KIEDocument` | +| Text detection only | {func}`~doctr.models.detection.detection_predictor` | boxes per page | +| Text recognition only | {func}`~doctr.models.recognition.recognition_predictor` | `(text, confidence)` per crop | +| Layout analysis | {func}`~doctr.models.layout.layout_predictor` | labelled regions per page | +| Table structure recognition | {func}`~doctr.models.table_structure.table_predictor` | cells with row/column spans per table | + +The end-to-end {func}`~doctr.models.ocr_predictor` composes the task predictors, which stay reachable as attributes: + +```python +from doctr.models import ocr_predictor + +predictor = ocr_predictor(pretrained=True) + +predictor.det_predictor # text detection (pre-processor + model + post-processor) +predictor.reco_predictor # text recognition +predictor.doc_builder # assembles words into lines, blocks and pages +``` + +:::{important} +Always pass `pretrained=True` unless you load your own weights: without it, the models are randomly initialized +and return meaningless predictions. +::: + +## The `Document` tree + +The OCR predictor returns a {class}`~doctr.io.Document`: a tree of elements that mirrors the structure of what was +read. + +```{raw} html +
Document +└── pages: list[Page] # one per input page + ├── blocks: list[Block] # groups of lines (paragraphs, addresses...) + │ ├── lines: list[Line] # words read together + │ │ └── words: list[Word] # value, confidence, geometry + │ └── artefacts: list[Artefact] # non-textual elements + ├── layout: list[LayoutElement] # with detect_layout=True + └── tables: list[Table] # with detect_tables=True + └── cells: list[TableCell]
+``` + +| Element | What it represents | +| ---------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| {class}`~doctr.io.Word` | An uninterrupted sequence of characters, with its text `value`, recognition `confidence` and `geometry` | +| {class}`~doctr.io.Line` | Words aligned and meant to be read together. On a two-column page, a horizontal row holds two lines | +| {class}`~doctr.io.Block` | A group of lines (e.g. an address written on several lines) and artefacts | +| {class}`~doctr.io.Artefact` | A non-textual element: a picture, a logo, a QR code, a signature... | +| {class}`~doctr.io.LayoutElement` | A region found by the layout model: `Title`, `Text`, `Table`, `Picture`, `Page-header`... | +| {class}`~doctr.io.Table` / {class}`~doctr.io.TableCell` | A recognized table, as cells spanning rows and columns | +| {class}`~doctr.io.Page` | Everything found on one page, plus its `dimensions`, `orientation` and `language` | + +Every element can be **rendered** to text with `.render()` and **exported** to a JSON-serializable dict with +`.export()`. See [](../user_guide/results.md) for a hands-on tour. + +## Coordinates + +All geometries are expressed in **relative coordinates**, between `0` and `1`, as a fraction of the page width (x) +and height (y), with the origin at the **top-left** corner. This makes them independent of the resolution the page +was rendered at. + +::::{grid} 1 1 2 2 +:gutter: 3 + +:::{grid-item-card} Straight boxes (default) +Two points: the top-left and the bottom-right corners. + +```python +((xmin, ymin), (xmax, ymax)) +# e.g. ((0.12, 0.05), (0.31, 0.08)) +``` +::: + +:::{grid-item-card} Rotated boxes +With `assume_straight_pages=False`: a polygon of four `(x, y)` points, as a NumPy array of shape `(4, 2)`. + +```python +array([[x1, y1], [x2, y2], [x3, y3], [x4, y4]]) +``` +::: +:::: + +To get pixel coordinates, multiply by the page size. Note that `page.dimensions` is `(height, width)`: + +```python +height, width = page.dimensions +(xmin, ymin), (xmax, ymax) = word.geometry +box_in_pixels = (int(xmin * width), int(ymin * height), int(xmax * width), int(ymax * height)) +``` + +## Scores + +docTR attaches several scores to its predictions. They answer different questions: + +`Word.confidence` +: How sure the **recognition** model is about the text, between 0 and 1. By default it is the lowest + probability among the characters of the word (see [confidence aggregation](../user_guide/ocr_predictor.md#confidence-scores)). + +`Word.objectness_score` +: How sure the **detection** model is that the box contains text. + +`Word.crop_orientation` +: The predicted rotation of the word crop, `{"value": angle, "confidence": score}`, when crop orientation is + classified (rotated documents). + +`Page.orientation` / `Page.language` +: The estimated page rotation and language, filled with `detect_orientation=True` / `detect_language=True`. + +## Vocabularies + +A recognition model can only output characters from the **vocabulary** it was trained on. The pretrained recognition +models use the `french` vocabulary, which covers Latin characters with the most common accents, digits and +punctuation. docTR ships [100+ vocabularies](../models/vocabs.md) for training models on other languages and +alphabets, and lets you [restrict a model to a subset](../user_guide/ocr_predictor.md#restrict-the-output-characters) +of its vocabulary. + +```python +from doctr.models import recognition_predictor + +predictor = recognition_predictor("crnn_vgg16_bn", pretrained=True) +print(predictor.model.cfg["vocab"]) +``` + +## Next steps + +::::{grid} 1 2 2 2 +:gutter: 3 + +:::{grid-item-card} {octicon}`book;1.1em` User guide +:link: ../user_guide/index +:link-type: doc + +Every option of the OCR predictor, explained with examples. +::: + +:::{grid-item-card} {octicon}`stack;1.1em` Model zoo +:link: ../models/index +:link-type: doc + +Compare the architectures and pick the right one for your documents. +::: +:::: diff --git a/docs/source/getting_started/index.md b/docs/source/getting_started/index.md new file mode 100644 index 0000000000..0641a37844 --- /dev/null +++ b/docs/source/getting_started/index.md @@ -0,0 +1,43 @@ +# Get started + +New to docTR? These three pages take you from `pip install` to structured OCR results, and explain the +handful of concepts that the rest of the documentation builds on. + +::::{grid} 1 1 3 3 +:gutter: 3 + +:::{grid-item-card} {octicon}`download;1.1em` Installation +:link: installation +:link-type: doc + +Install the package, the optional extras, or use the Docker images. +::: + +:::{grid-item-card} {octicon}`play;1.1em` Quickstart +:link: quickstart +:link-type: doc + +Run OCR on your first document and export the result in five minutes. +::: + +:::{grid-item-card} {octicon}`light-bulb;1.1em` Core concepts +:link: concepts +:link-type: doc + +How the pipeline works, what a `Document` is made of, and how coordinates are expressed. +::: +:::: + +:::{tip} +Prefer to explore in a notebook? The [Quicktour notebook](https://colab.research.google.com/github/mindee/notebooks/blob/main/doctr/quicktour.ipynb) +runs in Google Colab without installing anything, and the [live demo](https://huggingface.co/spaces/mindee/doctr) +lets you try the models on your own files in the browser. +::: + +```{toctree} +:hidden: + +installation +quickstart +concepts +``` diff --git a/docs/source/getting_started/installation.md b/docs/source/getting_started/installation.md new file mode 100644 index 0000000000..8bdbf25721 --- /dev/null +++ b/docs/source/getting_started/installation.md @@ -0,0 +1,134 @@ +# Installation + +docTR is distributed on PyPI as `python-doctr`. It requires **Python 3.11 or newer** and installs +[PyTorch](https://pytorch.org) as its deep learning backend. + +## Install the package + +::::{tab-set} + +:::{tab-item} pip +```bash +pip install python-doctr +``` +::: + +:::{tab-item} uv +```bash +uv add python-doctr +# or, outside of a uv project +uv pip install python-doctr +``` +::: + +:::{tab-item} From source +Install the latest development version, e.g. to try a feature that is not released yet: + +```bash +pip install "python-doctr @ git+https://github.com/mindee/doctr.git" +``` +::: +:::: + +To check that everything works, print the installed version: + +```bash +python -c "import doctr; print(doctr.__version__)" +``` + +:::{note} +The pretrained weights are **not** part of the package: each model is downloaded the first time you use it and +cached in `~/.cache/doctr` (see [](../deployment/configuration.md) to change this location or to run offline). +::: + +## Optional extras + +The core package is kept light. A few features need extra dependencies, which you can pull in with *extras*: + +| Extra | Installs | Needed for | +| --------- | ----------------------------- | ------------------------------------------------------------------------------------------------ | +| `viz` | `matplotlib`, `mplcursors` | Displaying predictions with {meth}`Document.show() ` | +| `html` | `weasyprint` | Reading web pages with {meth}`DocumentFile.from_url() ` | +| `contrib` | `onnxruntime` | The [artefact detector](../user_guide/artefacts.md) of the `contrib` module | + +```bash +# One extra +pip install "python-doctr[viz]" +# Several extras +pip install "python-doctr[viz,html,contrib]" +``` + +:::{admonition} WeasyPrint system libraries +:class: dropdown + +The `html` extra relies on [WeasyPrint](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#installation), +which needs Pango on the system. On Debian/Ubuntu: `sudo apt install libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0`. +On macOS: `brew install pango`. +::: + +## GPU support + +docTR runs on whatever device PyTorch supports. The default `torch` wheel from PyPI already supports NVIDIA GPUs on +Linux; for another CUDA version, ROCm or a CPU-only build, install PyTorch first by following the +[official selector](https://pytorch.org/get-started/locally/), then install docTR: + +```bash +# Example: CPU-only build of PyTorch (smaller download) +pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu +pip install python-doctr +``` + +Check which accelerator PyTorch sees: + +```python +import torch + +print("CUDA:", torch.cuda.is_available()) +print("Apple MPS:", torch.backends.mps.is_available()) +``` + +Models run on the CPU until you move them: see [](../deployment/performance.md) to run them on a GPU or in half precision. + +## Docker images + +Ready-to-use images with docTR preinstalled are published on the +[GitHub Container Registry](https://github.com/mindee/doctr/pkgs/container/doctr). They are GPU-ready (CUDA 12.2): + +```bash +docker run -it --gpus all ghcr.io/mindee/doctr: bash +``` + +Pick a `` from the registry; [](../deployment/docker.md) explains the tag naming scheme and how to build your own image. + +## Development installation + +To contribute to docTR, clone the repository and install it in editable mode with all the development tools: + +```bash +git clone https://github.com/mindee/doctr.git +cd doctr +pip install -e ".[dev]" +pre-commit install +``` + +See the {doc}`contribution guide <../contributing/contributing>` for the tests, the code style and how to build this documentation. + +## Next steps + +::::{grid} 1 2 2 2 +:gutter: 3 + +:::{grid-item-card} {octicon}`play;1.1em` Quickstart +:link: quickstart +:link-type: doc + +Extract the text of your first document. +::: + +:::{grid-item-card} {octicon}`light-bulb;1.1em` Core concepts +:link: concepts +:link-type: doc + +Understand predictors, documents and coordinates. +::: +:::: diff --git a/docs/source/getting_started/installing.rst b/docs/source/getting_started/installing.rst deleted file mode 100644 index d8c99d8f50..0000000000 --- a/docs/source/getting_started/installing.rst +++ /dev/null @@ -1,52 +0,0 @@ - -************ -Installation -************ - -This library requires `Python `_ 3.11 or higher. - - -Via Python Package -================== - -Install the last stable release of the package using `pip `_: - -.. code:: bash - - pip install python-doctr - -We strive towards reducing framework-specific dependencies to a minimum, but some necessary features are developed by third-parties for specific frameworks. To avoid missing some dependencies for a specific framework, you can install specific builds as follows: - -.. code:: bash - - pip install python-doctr - # or with preinstalled packages for visualization & html & contrib module support - pip install "python-doctr[viz,html,contrib]" - -Available optional extras: - -* ``viz``: installs `matplotlib` and `mplcursors` for result visualisation (e.g. ``Page.show()``) -* ``html``: installs `weasyprint` for reading HTML documents via :func:`~doctr.io.read_html` -* ``contrib``: installs `onnxruntime` for the :class:`~doctr.contrib.ArtefactDetector` contrib module - - -Via Git -======= - -Install the library in developer mode: - - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. - - -Via Docker -========== - -Official Docker images are available on the `GitHub Container Registry `_. - -.. code:: bash - - docker run -it ghcr.io/mindee/doctr:latest bash diff --git a/docs/source/getting_started/quickstart.md b/docs/source/getting_started/quickstart.md new file mode 100644 index 0000000000..2392d023fa --- /dev/null +++ b/docs/source/getting_started/quickstart.md @@ -0,0 +1,249 @@ +# Quickstart + +This page takes you from a PDF to structured text in a few lines of code. You need docTR installed +(`pip install python-doctr`, see [](installation.md)) and a document to read. + +:::{tip} +No document at hand? Download the {download}`sample invoice <../_static/images/examples/invoice.pdf>` used on this +page and save it as `invoice.pdf` next to your script: you will get exactly the outputs shown below. +::: + +```{image} ../_static/images/examples/invoice_input.png +:alt: The sample invoice used on this page +:width: 560px +:align: center +``` + +## 1. Load the document + +{class}`~doctr.io.DocumentFile` turns PDFs, images and web pages into a list of pages: + +```python +from doctr.io import DocumentFile + +pages = DocumentFile.from_pdf("invoice.pdf") +# or: DocumentFile.from_images(["page1.jpg", "page2.jpg"]) +``` + +## 2. Run OCR + +{func}`~doctr.models.ocr_predictor` builds a complete pipeline: a text detection model followed by a text +recognition model. With `pretrained=True`, the weights are downloaded on first use and cached. + +```python +from doctr.models import ocr_predictor + +predictor = ocr_predictor(pretrained=True) +result = predictor(pages) +``` + +That's it: `result` is a {class}`~doctr.io.Document` holding every word, its position and its confidence. + +## 3. Get the text + +{meth}`~doctr.io.Document.render` returns the text of the whole document, in reading order: + +```python +print(result.render()) +``` + +```text +ACME Office Supplies + +INVOICE + +42 Harbour Road, Hamburg + +No. 2026-0412 +Date: 26/10/2026 + +Bill to: +Jane Doe +12 Market Street, Berlin +... +``` + +## 4. Explore the structure + +The result mirrors the document: pages contain blocks, blocks contain lines, lines contain words. +Printing it shows the tree: + +```python +print(result) +``` + +```text +Document( + (pages): [Page( + dimensions=(1684, 1191) + (blocks): [Block( + (lines): [ + Line( + (words): [ + Word(value='ACME', confidence=1.0), + Word(value='Office', confidence=1.0), + Word(value='Supplies', confidence=1.0), + ] + ), + Line( + (words): [Word(value='INVOICE', confidence=1.0)] + ), + ... +``` + +Every element is a regular Python object, so you can walk the tree to get the words with their positions: + +```python +for page in result.pages: + for block in page.blocks: + for line in block.lines: + for word in line.words: + (xmin, ymin), (xmax, ymax) = word.geometry + print( + f"{word.value:<10} conf={word.confidence:.2f} box=({xmin:.3f}, {ymin:.3f}, {xmax:.3f}, {ymax:.3f})" + ) +``` + +```text +ACME conf=1.00 box=(0.071, 0.054, 0.160, 0.071) +Office conf=1.00 box=(0.169, 0.054, 0.258, 0.071) +Supplies conf=1.00 box=(0.269, 0.055, 0.399, 0.073) +INVOICE conf=1.00 box=(0.697, 0.057, 0.855, 0.074) +... +``` + +Geometries are **relative** to the page size: `((xmin, ymin), (xmax, ymax))` with values between 0 and 1. +[](concepts.md#coordinates) shows how to convert them to pixels. + +## 5. Visualize the predictions + +With the `viz` extra installed (`pip install "python-doctr[viz]"`), {meth}`~doctr.io.Document.show` overlays the +predictions on the pages in an interactive window: + +```python +result.show() +``` + +```{image} ../_static/images/examples/invoice_words.png +:alt: Detected words overlaid on the invoice +:width: 560px +:align: center +``` + +## 6. Export the results + +Turn the result into the format your application needs: + +::::{tab-set} + +:::{tab-item} JSON +```python +import json + +with open("invoice.json", "w") as f: + json.dump(result.export(), f) +``` + +The export is a nested dictionary with the same structure as the `Document` (pages → blocks → lines → words). +::: + +:::{tab-item} Markdown +```python +markdown = result.export_as_markdown() +``` +::: + +:::{tab-item} HTML +```python +html = result.export_as_html() +``` +::: + +:::{tab-item} hOCR (XML) +```python +# One (bytes, ElementTree) pair per page +xml_pages = result.export_as_xml() +``` +::: +:::: + +[](../user_guide/exporting.md) covers every format and its options. + +## 7. Go further: layout & tables + +Add `detect_tables=True` and docTR also finds the layout of the page and reconstructs its tables. Here is the +Markdown export of the same invoice: + +```python +predictor = ocr_predictor(pretrained=True, detect_tables=True) +result = predictor(pages) +print(result.export_as_markdown()) +``` + +```markdown +## ACME Office Supplies + +42 Harbour Road, Hamburg + +## Bill to: + +Jane Doe + +12 Market Street, Berlin + +INVOICE + +No. 2026-0412 +Date: 26/10/2026 + +| Description | Qty | Unit price | Amount | +| --- | --- | --- | --- | +| Recycled paper A4 | 10 | 8.50 | 85.00 | +| Ballpoint pens (box) | 5 | 12.00 | 60.00 | +| Desk lamp | 2 | 45.00 | 90.00 | +| Office chair | 1 | 1,015.00 | 1,015.00 | + +Total due + +1,250.00 EUR +... +``` + +:::{admonition} Prefer the command line? +:class: tip + +The `doctr-cli` command runs the same pipeline without writing any code: + +```bash +doctr-cli --input_path invoice.pdf --detect_tables --output invoice.md +``` + +See [](../user_guide/cli.md). +::: + +## Next steps + +::::{grid} 1 2 3 3 +:gutter: 3 + +:::{grid-item-card} {octicon}`light-bulb;1.1em` Core concepts +:link: concepts +:link-type: doc + +Predictors, the `Document` tree, coordinates and scores. +::: + +:::{grid-item-card} {octicon}`gear;1.1em` Configure the predictor +:link: ../user_guide/ocr_predictor +:link-type: doc + +Choose the models and tune the pipeline to your documents. +::: + +:::{grid-item-card} {octicon}`sync;1.1em` Rotated documents +:link: ../user_guide/rotated_documents +:link-type: doc + +Read skewed scans, photos and upside-down pages. +::: +:::: diff --git a/docs/source/getting_started/quickstart.rst b/docs/source/getting_started/quickstart.rst deleted file mode 100644 index 94818a2415..0000000000 --- a/docs/source/getting_started/quickstart.rst +++ /dev/null @@ -1,110 +0,0 @@ - -********** -Quickstart -********** - -This page shows you how to get OCR results from a document in just a few lines of code. -For more details see :ref:`using_models`. - - -Load a document -=============== - -docTR can read PDFs, images, and web pages: - -.. code:: python3 - - from doctr.io import DocumentFile - - # From a PDF - doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - # From one or more images - doc = DocumentFile.from_images("path/to/your/img.jpg") - doc = DocumentFile.from_images(["path/to/page1.jpg", "path/to/page2.jpg"]) - # From a URL (requires the ``html`` extra: pip install "python-doctr[html]") - doc = DocumentFile.from_url("https://www.example.com") - - -Run OCR -======= - -.. code:: python3 - - from doctr.io import DocumentFile - from doctr.models import ocr_predictor - - doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - model = ocr_predictor(pretrained=True) - result = model(doc) - -The predictor uses ``db_resnet50`` for text detection and ``crnn_vgg16_bn`` for text recognition by default. -You can choose any combination of :ref:`supported architectures `. - - -Inspect the output -================== - -The result is a :class:`~doctr.io.Document` object. - -Render as plain text:: - - print(result.render()) - -Export as a nested dictionary (JSON-serialisable):: - - import json - print(json.dumps(result.export(), indent=2)) - -Visualize on screen (requires the ``viz`` extra: ``pip install "python-doctr[viz]"``):: - - result.pages[0].show() - - -Multi-page PDF end-to-end example -================================== - -The following snippet processes every page of a PDF and collects the plain-text output: - -.. code:: python3 - - import json - from doctr.io import DocumentFile - from doctr.models import ocr_predictor - - model = ocr_predictor(pretrained=True) - doc = DocumentFile.from_pdf("path/to/multi_page.pdf") - result = model(doc) - - # Plain-text — one string per page - for page_idx, page in enumerate(result.pages): - print(f"--- Page {page_idx + 1} ---") - print(page.render()) - - # Structured output — JSON-serialisable dict - output = result.export() - with open("ocr_output.json", "w") as f: - json.dump(output, f, indent=2) - - -Common pitfalls -=============== - -.. note:: - - * **Visualization** requires the ``viz`` extra (installs ``matplotlib`` and ``mplcursors``): - ``pip install "python-doctr[viz]"``. Calls to ``result.show()`` or - ``result.pages[0].show()`` raise a ``ModuleNotFoundError`` without it. - * **HTML input** requires the ``html`` extra: ``pip install "python-doctr[html]"``. - * **Image format**: pass file paths or NumPy ``uint8`` arrays shaped ``(H, W, C)`` in - RGB order. Grayscale arrays must be converted to 3-channel before use. - * **Pretrained weights** are downloaded on first use and cached locally. Subsequent calls are instantaneous. - * **PDF pages are returned as images**: ``DocumentFile.from_pdf`` returns one - NumPy array per page, so ``result.pages[i]`` corresponds to the *i*-th PDF page. - - -Next steps -========== - -* :doc:`../using_doctr/using_models` - full predictor guide, architecture benchmarks, GPU usage. -* :doc:`../using_doctr/custom_models_training` - train and load your own models. -* :doc:`../using_doctr/sharing_models` - share your trained models on Hugging Face Hub. diff --git a/docs/source/index.md b/docs/source/index.md new file mode 100644 index 0000000000..cc61345889 --- /dev/null +++ b/docs/source/index.md @@ -0,0 +1,194 @@ +--- +myst: + html_meta: + description: "docTR: state-of-the-art OCR for PDFs and images in a few lines of Python. Text detection, text recognition, layout analysis and table structure recognition, powered by PyTorch." +--- + +# docTR: Document Text Recognition + +```{raw} html +
+
+
+
Open-source OCR · Powered by PyTorch
+

OCR made seamless & accessible to anyone

+

+ docTR reads PDFs and images and gives you back every word with its position, organized into + lines, blocks, layout regions and tables. Run state-of-the-art models in three lines of Python, + fine-tune them on your own documents and ship them anywhere. +

+ +
+ $ pip install python-doctr + +
+
+
+
ocr.py
+
from doctr.io import DocumentFile
+from doctr.models import ocr_predictor
+
+model = ocr_predictor(pretrained=True)
+doc = DocumentFile.from_pdf("invoice.pdf")
+result = model(doc)
+
+# or export to JSON, Markdown, HTML...
+print(result.render())
+
ACME Office Supplies + +INVOICE + +42 Harbour Road, Hamburg + +No. 2026-0412 +Date: 26/10/2026 +…
+
+
+
+
4 tasksdetection · recognition · layout · tables
+
20+pretrained architectures
+
100+built-in vocabularies
+
6export formats
+
Apache 2.0free for commercial use
+
+
+``` + +```{raw} html +

Everything you need to read documents

+

From a scanned receipt to a hundred-page report: docTR covers the whole pipeline, and every stage can be swapped, tuned or retrained.

+``` + +::::{grid} 1 2 3 3 +:gutter: 3 + +:::{grid-item-card} {octicon}`zap;1.1em` Two-stage OCR +:link: user_guide/ocr_predictor +:link-type: doc + +A text detection model finds the words, a recognition model reads them. Mix and match 9 detection and 9 recognition architectures. +::: + +:::{grid-item-card} {octicon}`table;1.1em` Layout & tables +:link: user_guide/layout_and_tables +:link-type: doc + +Detect titles, paragraphs, figures and tables, and turn tables into a row/column grid that loads straight into pandas. +::: + +:::{grid-item-card} {octicon}`sync;1.1em` Rotated documents +:link: user_guide/rotated_documents +:link-type: doc + +Handle skewed scans, upside-down pages and vertical text with built-in page and word orientation classifiers. +::: + +:::{grid-item-card} {octicon}`file-code;1.1em` Export anywhere +:link: user_guide/exporting +:link-type: doc + +Get plain text, JSON, Markdown, AsciiDoc, HTML or hOCR, in a reading order that follows columns, headers and footers. +::: + +:::{grid-item-card} {octicon}`mortar-board;1.1em` Train & share +:link: training/index +:link-type: doc + +Fine-tune any model on your own data with the reference training scripts, then share it on the Hugging Face Hub. +::: + +:::{grid-item-card} {octicon}`rocket;1.1em` Production ready +:link: deployment/index +:link-type: doc + +GPU & half precision, `torch.compile`, ONNX export, Docker images, a CLI and a REST API template. +::: +:::: + +```{raw} html +

How it works

+

An OCR predictor chains specialized models. Each stage is a regular PyTorch module you can replace with your own.

+``` + +```{raw} html +:file: _includes/pipeline.html +``` + +```{raw} html +

Find your way around

+

Whether you need text out of a PDF in five minutes or want to train a model for a new alphabet, start here.

+``` + +::::{grid} 1 2 3 3 +:gutter: 3 + +:::{grid-item-card} {octicon}`play;1.1em` Quickstart +:link: getting_started/quickstart +:link-type: doc + +Install docTR and extract the text of your first document in five minutes. +::: + +:::{grid-item-card} {octicon}`book;1.1em` User guide +:link: user_guide/index +:link-type: doc + +Load documents, configure the OCR predictor, navigate and export the results. +::: + +:::{grid-item-card} {octicon}`stack;1.1em` Model zoo +:link: models/index +:link-type: doc + +All architectures with their benchmarks, to pick the right speed/accuracy trade-off. +::: + +:::{grid-item-card} {octicon}`beaker;1.1em` Training +:link: training/index +:link-type: doc + +Datasets, data formats, training scripts and how to load your own checkpoints. +::: + +:::{grid-item-card} {octicon}`server;1.1em` Deployment +:link: deployment/index +:link-type: doc + +Speed up inference, export to ONNX, and serve docTR with Docker or a REST API. +::: + +:::{grid-item-card} {octicon}`code-square;1.1em` API reference +:link: api/index +:link-type: doc + +Every public class and function, generated from the source code. +::: +:::: + +```{raw} html +
+
+

Need help with a complex use case?

+

docTR was created by Mindee and is developed and maintained by t2k GmbH, + who offer custom development, integration and consulting around docTR.

+
+ Get support → +
+``` + +```{toctree} +:hidden: + +getting_started/index +user_guide/index +models/index +training/index +deployment/index +api/index +community/index +``` diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 1cfdb298b6..0000000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,127 +0,0 @@ -******************************** -docTR: Document Text Recognition -******************************** - -State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by PyTorch - -.. image:: https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png - :align: center - - -DocTR provides an easy and powerful way to extract valuable information from your documents: - -* |:receipt:| **for automation**: seamlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) from your documents. -* |:woman_scientist:| **for research**: quickly compare your own architectures speed & performances with state-of-art models on public datasets. - - -Main Features -------------- - -* |:robot:| Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters -* |:page_facing_up:| Layout analysis predictor for detecting document regions (tables, figures, headers, …) -* |:zap:| User-friendly, 3 lines of code to load a document and extract text with a predictor -* |:rocket:| State-of-the-art performance on public document datasets, comparable with GoogleVision/AWS Textract -* |:zap:| Optimized for inference speed on both CPU & GPU -* |:bird:| Light package, minimal dependencies -* |:tools:| Actively maintained by Mindee -* |:factory:| Easy integration (available templates for browser demo & API deployment) - - -.. toctree:: - :maxdepth: 2 - :caption: Getting started - :hidden: - - getting_started/installing - getting_started/quickstart - notebooks - - -Model zoo -^^^^^^^^^ - -Text detection models -""""""""""""""""""""" -* DBNet from `"Real-time Scene Text Detection with Differentiable Binarization" `_ -* LinkNet from `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" `_ -* FAST from `"FAST: Faster Arbitrarily-Shaped Text Detector with Minimalist Kernel Representation" `_ - -Text recognition models -""""""""""""""""""""""" -* SAR from `"Show, Attend and Read: A Simple and Strong Baseline for Irregular Text Recognition" `_ -* CRNN from `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" `_ -* MASTER from `"MASTER: Multi-Aspect Non-local Network for Scene Text Recognition" `_ -* ViTSTR from `"Vision Transformer for Fast and Efficient Scene Text Recognition" `_ -* PARSeq from `"Scene Text Recognition with Permuted Autoregressive Sequence Models" `_ -* VIPTR from `"A Vision Permutable Extractor for Fast and Efficient Scene Text Recognition" `_ - - -Supported datasets -^^^^^^^^^^^^^^^^^^ -* FUNSD from `"FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents" `_. -* CORD from `"CORD: A Consolidated Receipt Dataset forPost-OCR Parsing" `_. -* SROIE from `ICDAR 2019 `_. -* IIIT-5k from `CVIT `_. -* Street View Text from `"End-to-End Scene Text Recognition" `_. -* SynthText from `Visual Geometry Group `_. -* SVHN from `"Reading Digits in Natural Images with Unsupervised Feature Learning" `_. -* IC03 from `ICDAR 2003 `_. -* IC13 from `ICDAR 2013 `_. -* IMGUR5K from `"TextStyleBrush: Transfer of Text Aesthetics from a Single Example" `_. -* MJSynth from `"Synthetic Data and Artificial Neural Networks for Natural Scene Text Recognition" `_. -* IIITHWS from `"Generating Synthetic Data for Text Recognition" `_. -* WILDRECEIPT from `"Spatial Dual-Modality Graph Reasoning for Key Information Extraction" `_. -* COCO-Text dataset from `"COCO-Text: Dataset and Benchmark for Text Detection and Recognition in Natural Images" `_. - -.. toctree:: - :maxdepth: 2 - :caption: Using docTR - :hidden: - - using_doctr/using_models - using_doctr/using_datasets - using_doctr/using_contrib_modules - using_doctr/sharing_models - using_doctr/using_model_export - using_doctr/custom_models_training - using_doctr/running_on_aws - using_doctr/using_cli - - -.. toctree:: - :maxdepth: 2 - :caption: Community - :hidden: - - community/resources - community/tools - - -.. toctree:: - :maxdepth: 2 - :caption: Package Reference - :hidden: - - modules/contrib - modules/datasets - modules/io - modules/models - modules/transforms - modules/utils - - -.. toctree:: - :maxdepth: 2 - :caption: Contributing - :hidden: - - contributing/code_of_conduct - contributing/contributing - - -.. toctree:: - :maxdepth: 2 - :caption: Notes - :hidden: - - changelog diff --git a/docs/source/models/detection.md b/docs/source/models/detection.md new file mode 100644 index 0000000000..10e1de1054 --- /dev/null +++ b/docs/source/models/detection.md @@ -0,0 +1,49 @@ +# Text detection + +Text detection models localize the words of a page. In docTR, a *word* is an uninterrupted sequence of characters. +All models predict a segmentation map that is turned into boxes, and support both straight and rotated boxes +(`assume_straight_pages=False`). + +## Architectures + +| Family | Paper | Variants | +| --- | --- | --- | +| **DBNet** | [Real-time Scene Text Detection with Differentiable Binarization](https://arxiv.org/pdf/1911.08947.pdf) | {func}`~doctr.models.detection.db_resnet34`, {func}`~doctr.models.detection.db_resnet50`, {func}`~doctr.models.detection.db_mobilenet_v3_large` | +| **LinkNet** | [LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation](https://arxiv.org/pdf/1707.03718.pdf) | {func}`~doctr.models.detection.linknet_resnet18`, {func}`~doctr.models.detection.linknet_resnet34`, {func}`~doctr.models.detection.linknet_resnet50` | +| **FAST** | [FAST: Faster Arbitrarily-Shaped Text Detector with Minimalist Kernel Representation](https://arxiv.org/pdf/2111.02394.pdf) | {func}`~doctr.models.detection.fast_tiny`, {func}`~doctr.models.detection.fast_small`, {func}`~doctr.models.detection.fast_base` {bdg-secondary}`default` | + +## Benchmark + +Recall and precision on FUNSD and CORD, and latency with a batch size of 1: + +| Architecture | Input shape | # params | FUNSD recall | FUNSD precision | CORD recall | CORD precision | sec/it | +| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | +| `db_resnet34` | (1024, 1024, 3) | 22.4 M | 82.76 | 76.75 | 89.20 | 71.74 | 0.8 | +| `db_resnet50` | (1024, 1024, 3) | 25.4 M | 83.56 | 86.68 | 92.61 | 86.39 | 1.1 | +| `db_mobilenet_v3_large` | (1024, 1024, 3) | 4.2 M | 82.69 | 84.63 | **94.51** | 70.28 | 0.5 | +| `linknet_resnet18` | (1024, 1024, 3) | 11.5 M | 81.64 | 85.52 | 88.92 | 82.74 | 0.6 | +| `linknet_resnet34` | (1024, 1024, 3) | 21.6 M | 81.62 | 82.95 | 86.26 | 81.06 | 0.7 | +| `linknet_resnet50` | (1024, 1024, 3) | 28.8 M | 81.78 | 82.47 | 87.29 | 85.54 | 1.0 | +| `fast_tiny` | (1024, 1024, 3) | 13.5 M (8.5 M) | 84.90 | 85.04 | 93.73 | 76.26 | 0.7 (0.4) | +| `fast_small` | (1024, 1024, 3) | 14.7 M (9.7 M) | **85.36** | 86.68 | 94.09 | 78.53 | 0.7 (0.5) | +| `fast_base` | (1024, 1024, 3) | 16.3 M (10.6 M) | 84.95 | **86.73** | 94.39 | 85.36 | 0.8 (0.5) | + +:::{note} +FAST models are **reparameterized** by `detection_predictor` and `ocr_predictor`: their branches are fused into +single convolutions for inference. Values in parentheses are measured after reparameterization. +::: + +## Usage + +```python +from doctr.models import ocr_predictor, detection_predictor + +# In the OCR pipeline +predictor = ocr_predictor(det_arch="db_resnet50", pretrained=True) + +# On its own +det_predictor = detection_predictor("db_resnet50", pretrained=True) +``` + +Tune the binarization and box thresholds of the post-processor to detect more (or fewer) words, see +[](../user_guide/ocr_predictor.md#tune-the-text-detection). diff --git a/docs/source/models/end_to_end.md b/docs/source/models/end_to_end.md new file mode 100644 index 0000000000..57c6423ac4 --- /dev/null +++ b/docs/source/models/end_to_end.md @@ -0,0 +1,31 @@ +# End-to-end OCR + +An OCR predictor combines a detection model with a recognition model: any pair of the architectures listed in this +section works. A word counts as correct when it is both well localized and correctly transcribed. + +## Benchmark + +| Architecture | FUNSD recall | FUNSD precision | CORD recall | CORD precision | +| --- | ---: | ---: | ---: | ---: | +| `db_resnet50` + `crnn_vgg16_bn` | 73.37 | 76.11 | 84.80 | 79.09 | +| `db_resnet50` + `crnn_mobilenet_v3_small` | 73.06 | 75.79 | 84.64 | 78.94 | +| `db_resnet50` + `crnn_mobilenet_v3_large` | 73.17 | 75.90 | 84.96 | 79.25 | +| `db_resnet50` + `master` | **73.90** | **76.66** | 85.84 | 80.07 | +| `db_resnet50` + `sar_resnet31` | 73.58 | 76.33 | 85.64 | 79.88 | +| `db_resnet50` + `vitstr_small` | 73.06 | 75.79 | **85.95** | **80.17** | +| `db_resnet50` + `vitstr_base` | 73.70 | 76.46 | 85.76 | 79.99 | +| `db_resnet50` + `parseq` | 73.52 | 76.27 | 85.91 | 80.13 | +| *Google Vision — text detection* | 59.50 | 62.50 | 75.30 | 59.03 | +| *Google Vision — document text detection* | 64.00 | 53.30 | 68.90 | 61.10 | +| *AWS Textract* | 78.10 | 83.00 | 87.50 | 66.00 | +| *Azure Form Recognizer (v3.2)* | 79.42 | 85.89 | 89.62 | 88.93 | + +Commercial services are shown in italics, for reference. + +## Usage + +```python +from doctr.models import ocr_predictor + +predictor = ocr_predictor(det_arch="db_resnet50", reco_arch="master", pretrained=True) +``` diff --git a/docs/source/models/index.md b/docs/source/models/index.md new file mode 100644 index 0000000000..8599be215e --- /dev/null +++ b/docs/source/models/index.md @@ -0,0 +1,71 @@ +# Model zoo + +docTR implements a range of published architectures for each task of the pipeline, all with pretrained weights. +This section lists them with their benchmarks, so you can pick the right trade-off between speed and accuracy. + +## At a glance + +| Task | Architectures | Default | Use it with | +| --- | --- | --- | --- | +| [Text detection](detection.md) | DBNet, LinkNet, FAST | `fast_base` | `ocr_predictor(det_arch=...)` | +| [Text recognition](recognition.md) | CRNN, SAR, MASTER, ViTSTR, PARSeq, VIPTR | `crnn_vgg16_bn` | `ocr_predictor(reco_arch=...)` | +| [Layout analysis](layout.md) | LW-DETR | `lw_detr_s` | `ocr_predictor(detect_layout=True, layout_arch=...)` | +| [Table structure](table_structure.md) | TableCenterNet | `tablecenternet` | `ocr_predictor(detect_tables=True)` | +| [Orientation](#orientation-classifiers) | MobileNetV3 | `mobilenet_v3_small_*_orientation` | `ocr_predictor(assume_straight_pages=False)` | + +[End-to-end benchmarks](end_to_end.md) compare detection + recognition pairs, including commercial OCR services. + +## How to choose + +::::{grid} 1 1 3 3 +:gutter: 3 + +:::{grid-item-card} {octicon}`check-circle;1.1em` Balanced +The defaults, `fast_base` + `crnn_vgg16_bn`, are accurate on most documents and run fine on a CPU. +::: + +:::{grid-item-card} {octicon}`zap;1.1em` Fast +`fast_tiny` or `db_mobilenet_v3_large` with `crnn_mobilenet_v3_small` or `viptr_tiny` for CPUs, edge devices and +high-volume pipelines. +::: + +:::{grid-item-card} {octicon}`goal;1.1em` Accurate +`db_resnet50` or `fast_base` with `parseq`, `vitstr_base` or `master`, ideally on a GPU. +::: +:::: + +Benchmarks on public datasets are a starting point: the best way to choose is to compare a few combinations on a +sample of **your** documents. If none is good enough, [fine-tune a model](../training/index.md) on your data. + +## Orientation classifiers + +Two lightweight MobileNetV3 classifiers handle [rotated documents](../user_guide/rotated_documents.md). They are +loaded automatically when needed. + +| Architecture | Predicts | Factory | +| ---------------------------------------- | ------------------------------------------------ | ---------------------------------------------------------- | +| `mobilenet_v3_small_page_orientation` | The orientation of a page: 0°, 90°, 180°, 270° | {func}`~doctr.models.classification.page_orientation_predictor` | +| `mobilenet_v3_small_crop_orientation` | The orientation of a word crop: 0°, 90°, 180°, 270° | {func}`~doctr.models.classification.crop_orientation_predictor` | + +## About the benchmarks + +- **Datasets**: unless stated otherwise, models are evaluated on both the training and test sets of + [FUNSD](https://guillaumejaume.github.io/FUNSD/) and [CORD](https://github.com/clovaai/cord) (see + [](../training/datasets.md)). FUNSD has 199 pages (30,595 word crops), which might not be representative of every + use case. +- **Metrics** are described in [](../api/utils.md#task-evaluation). +- **Speed** (`sec/it`) is measured on an *11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz* CPU, after a warm-up of + 100 tensors, as the average over 1,000 samples. The batch size is given in each table. +- Reproduce or extend them with the `evaluate.py` and `latency.py` scripts of the + [references](https://github.com/mindee/doctr/tree/main/references). + +```{toctree} +:hidden: + +detection +recognition +layout +table_structure +end_to_end +vocabs +``` diff --git a/docs/source/models/layout.md b/docs/source/models/layout.md new file mode 100644 index 0000000000..24e3d5325b --- /dev/null +++ b/docs/source/models/layout.md @@ -0,0 +1,36 @@ +# Layout analysis + +Layout models localize and classify the regions of a page (titles, paragraphs, tables, figures, headers...). Like +the text detection models, they work on rotated and skewed documents. + +## Architectures + +| Architecture | Paper | Pretrained weights | +| --- | --- | --- | +| {func}`~doctr.models.layout.lw_detr_s` {bdg-secondary}`default` | [LW-DETR: A Transformer Replacement to YOLO for Real-Time Detection](https://arxiv.org/pdf/2406.03459v1) | {bdg-success}`available` | +| {func}`~doctr.models.layout.lw_detr_m` | Same, larger backbone | {bdg-warning}`coming soon` | + +The pretrained model predicts 11 classes: `Caption`, `Footnote`, `Formula`, `List-item`, `Page-footer`, +`Page-header`, `Picture`, `Section-header`, `Table`, `Text` and `Title`. + +## Benchmark + +| Architecture | Input shape | # params | mAP@[.5:.95] | AP@.5 | AP@.75 | sec/it (batch 1) | +| --- | --- | ---: | ---: | ---: | ---: | ---: | +| `lw_detr_s` | (1024, 1024, 3) | 15.1 M | 66.89 | 82.83 | 73.75 | 0.5 | +| `lw_detr_m` | (1024, 1024, 3) | 29.5 M | soon | soon | soon | 0.7 | + +## Usage + +```python +from doctr.models import layout_predictor, ocr_predictor + +# In the OCR pipeline +predictor = ocr_predictor(pretrained=True, detect_layout=True, layout_arch="lw_detr_s") + +# On its own +layout = layout_predictor("lw_detr_s", pretrained=True) +``` + +See [](../user_guide/layout_and_tables.md) for how the regions are used, and +[](../training/custom_models.md#layout-models) to plug a model trained on your own classes. diff --git a/docs/source/models/recognition.md b/docs/source/models/recognition.md new file mode 100644 index 0000000000..6d705fe593 --- /dev/null +++ b/docs/source/models/recognition.md @@ -0,0 +1,55 @@ +# Text recognition + +Text recognition models transcribe the characters of a word crop. They output a string and a confidence score, and +can only predict the characters of the [vocabulary](vocabs.md) they were trained on. + +## Architectures + +| Architecture | Paper | Decoding | +| --- | --- | --- | +| {func}`~doctr.models.recognition.crnn_vgg16_bn` {bdg-secondary}`default`, {func}`~doctr.models.recognition.crnn_mobilenet_v3_small`, {func}`~doctr.models.recognition.crnn_mobilenet_v3_large` | [An End-to-End Trainable Neural Network for Image-based Sequence Recognition](https://arxiv.org/pdf/1507.05717.pdf) | CTC | +| {func}`~doctr.models.recognition.sar_resnet31` | [Show, Attend and Read: A Simple and Strong Baseline for Irregular Text Recognition](https://arxiv.org/pdf/1811.00751.pdf) | Attention | +| {func}`~doctr.models.recognition.master` | [MASTER: Multi-Aspect Non-local Network for Scene Text Recognition](https://arxiv.org/pdf/1910.02562.pdf) | Transformer | +| {func}`~doctr.models.recognition.vitstr_small`, {func}`~doctr.models.recognition.vitstr_base` | [Vision Transformer for Fast and Efficient Scene Text Recognition](https://arxiv.org/pdf/2105.08582.pdf) | Parallel | +| {func}`~doctr.models.recognition.parseq` | [Scene Text Recognition with Permuted Autoregressive Sequence Models](https://arxiv.org/pdf/2207.06966) | Permuted autoregressive | +| {func}`~doctr.models.recognition.viptr_tiny` | [A Vision Permutable Extractor for Fast and Efficient Scene Text Recognition](https://arxiv.org/abs/2401.10110) | CTC | + +## Benchmark + +Word accuracy on FUNSD and CORD word crops (exact and partial match, see {class}`~doctr.utils.metrics.TextMatch`), and latency with a batch size of 64: + +| Architecture | Input shape | # params | FUNSD exact | FUNSD partial | CORD exact | CORD partial | sec/it | +| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | +| `crnn_vgg16_bn` | (32, 128, 3) | 15.8 M | 88.21 | 88.95 | 95.47 | 95.91 | 0.6 | +| `crnn_mobilenet_v3_small` | (32, 128, 3) | 2.1 M | 87.25 | 87.99 | 93.91 | 94.34 | **0.05** | +| `crnn_mobilenet_v3_large` | (32, 128, 3) | 4.5 M | 87.38 | 88.09 | 94.46 | 94.92 | 0.08 | +| `master` | (32, 128, 3) | 58.7 M | **88.57** | **89.39** | **95.73** | **96.21** | 17.6 | +| `sar_resnet31` | (32, 128, 3) | 55.4 M | 88.10 | 88.88 | 94.83 | 95.29 | 4.9 | +| `vitstr_small` | (32, 128, 3) | 21.4 M | 88.00 | 88.82 | 95.40 | 95.78 | 1.5 | +| `vitstr_base` | (32, 128, 3) | 85.2 M | 88.33 | 89.09 | 95.32 | 95.71 | 4.1 | +| `parseq` | (32, 128, 3) | 23.8 M | 88.53 | 89.24 | 95.56 | 95.91 | 2.2 | +| `viptr_tiny` | (32, 128, 3) | 3.2 M | 86.03 | 86.71 | 93.08 | 93.47 | 0.08 | + +## Usage + +```python +from doctr.models import ocr_predictor, recognition_predictor + +# In the OCR pipeline +predictor = ocr_predictor(reco_arch="parseq", pretrained=True) + +# On its own, on word crops +reco_predictor = recognition_predictor("parseq", pretrained=True) + +# The characters the model can predict +print(reco_predictor.model.cfg["vocab"]) +``` + +## Languages + +The pretrained recognition models are trained on the `french` vocabulary: Latin characters with the accents of +the main Western European languages, digits, punctuation and common currency symbols. For other languages: + +- restrict the output to the characters you expect with a [whitelist](../user_guide/ocr_predictor.md#restrict-the-output-characters); +- look for a [community model](../training/sharing_models.md#community-models), such as the multilingual PARSeq; +- [train your own](../training/train.md) on one of the [100+ vocabularies](vocabs.md). diff --git a/docs/source/models/table_structure.md b/docs/source/models/table_structure.md new file mode 100644 index 0000000000..fe95454479 --- /dev/null +++ b/docs/source/models/table_structure.md @@ -0,0 +1,34 @@ +# Table structure recognition + +Table structure models parse the image of a table into a machine-understandable grid: they localize every cell +(spatial structure) and recover the rows and columns it spans (logical structure). + +## Architectures + +| Architecture | Paper | +| --- | --- | +| {func}`~doctr.models.table_structure.tablecenternet` {bdg-secondary}`default` | [TableCenterNet: A one-stage network for table structure recognition](https://arxiv.org/abs/2504.17522) | + +## Benchmark + +| Architecture | Input shape | # params | Recall | Precision | F1 | Structure acc. | sec/it (batch 1) | +| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | +| `tablecenternet` | (1024, 1024, 3) | 7.1 M | 82.31 | 96.01 | 88.64 | 77.53 | 0.5 | + +The metrics are produced by `references/table/evaluate.py` with +{class}`~doctr.utils.metrics.TableCellMetric`: cell detection **recall**, **precision** and **F1** (cells matched +above an IoU of 0.5), and **structure accuracy**, the share of matched cells whose row and column spans are correct. + +## Usage + +```python +from doctr.models import ocr_predictor, table_predictor + +# In the OCR pipeline: tables are located by the layout model, then parsed +predictor = ocr_predictor(pretrained=True, detect_tables=True) + +# On its own, on images of tables +tables = table_predictor("tablecenternet", pretrained=True) +``` + +See [](../user_guide/layout_and_tables.md#table-structure-recognition) to work with the recognized tables. diff --git a/docs/source/models/vocabs.md b/docs/source/models/vocabs.md new file mode 100644 index 0000000000..13d300ff31 --- /dev/null +++ b/docs/source/models/vocabs.md @@ -0,0 +1,34 @@ +# Vocabularies + +A vocabulary is the set of characters a recognition model can predict. docTR ships more than 100 of them, covering +the main writing systems, in {data}`doctr.datasets.VOCABS`: + +```python +from doctr.datasets import VOCABS + +print(len(VOCABS["german"])) # 108 +print(VOCABS["german"][-8:]) # äöüßÄÖÜẞ +``` + +They are used to: + +- **train** a recognition model on a language: `crnn_vgg16_bn(vocab=VOCABS["german"])`, or + `--vocab german` with the [training script](../training/train.md#text-recognition); +- **restrict** a trained model to a subset of its characters with a + [whitelist](../user_guide/ocr_predictor.md#restrict-the-output-characters). + +:::{tip} +Vocabularies are plain strings, so you can combine them: `VOCABS["german"] + VOCABS["polish"]`. Remove the +duplicates while keeping the order with `"".join(dict.fromkeys(...))`. +::: + +## Available vocabularies + +Click a row to see all its characters. For Latin-script languages, the summary lists the characters added to the +`english` vocabulary. + +```{include} _generated/vocabs.md +``` + +`VOCABS` also contains the building blocks these vocabularies are made of, such as `digits`, `punctuation`, +`currency` or the consonants and vowels of each Indic script. diff --git a/docs/source/modules/contrib.rst b/docs/source/modules/contrib.rst deleted file mode 100644 index 1a2659e2e3..0000000000 --- a/docs/source/modules/contrib.rst +++ /dev/null @@ -1,13 +0,0 @@ -doctr.contrib -============= - -.. currentmodule:: doctr.contrib - -This module contains all the available contribution modules for docTR. - - -Supported contribution modules ------------------------------- -Here are all the available contribution modules: - -.. autoclass:: ArtefactDetector diff --git a/docs/source/modules/datasets.rst b/docs/source/modules/datasets.rst deleted file mode 100644 index 234e280903..0000000000 --- a/docs/source/modules/datasets.rst +++ /dev/null @@ -1,404 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -.. _datasets: - -doctr.datasets --------------- - -.. autoclass:: FUNSD - -.. autoclass:: SROIE - -.. autoclass:: CORD - -.. autoclass:: IIIT5K - -.. autoclass:: SVT - -.. autoclass:: SVHN - -.. autoclass:: SynthText - -.. autoclass:: IC03 - -.. autoclass:: IC13 - -.. autoclass:: IMGUR5K - -.. autoclass:: MJSynth - -.. autoclass:: IIITHWS - -.. autoclass:: DocArtefacts - -.. autoclass:: WILDRECEIPT - -.. autoclass:: COCOTEXT - -Synthetic dataset generator ---------------------------- - -.. autoclass:: CharacterGenerator - -.. autoclass:: WordGenerator - -Custom dataset loader ---------------------- - -.. autoclass:: DetectionDataset - -.. autoclass:: LayoutDataset - -.. autoclass:: RecognitionDataset - -.. autoclass:: TableStructureDataset - -.. autoclass:: OCRDataset - -Dataset utils -------------- - -.. autofunction:: translate - -.. autofunction:: encode_string - -.. autofunction:: decode_sequence - -.. autofunction:: encode_sequences - -.. autofunction:: pre_transform_multiclass - -.. autofunction:: crop_bboxes_from_image - -.. autofunction:: convert_target_to_relative - -.. _vocabs: - -Supported Vocabs ----------------- - -Since textual content has to be encoded properly for models to interpret them efficiently, docTR supports multiple sets -of vocabs. - -.. list-table:: docTR Vocabs - :widths: 20 5 50 - :header-rows: 1 - - * - Name - - size - - characters - * - latex - - 81 - - ()+,-./0123456789<=>ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_abcdefghijklmnopqrstuvwxyz{|} - * - latin - - 94 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ - * - english - - 100 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ - * - albanian - - 104 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿çëÇË - * - afrikaans - - 128 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿èëïîôûêÈËÏÎÔÛÊáéíóúäöÁÉÍÓÚÄÖ - * - azerbaijani - - 113 - - 0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿çəğıöşüÇƏĞİÖŞÜ₼ - * - basque - - 104 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ñçÑÇ - * - bosnian - - 102 - - 0123456789abcdefghijklmnoprstuvzABCDEFGHIJKLMNOPRSTUVZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿čćđšžČĆĐŠŽ - * - catalan - - 123 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿àèéíïòóúüçÀÈÉÍÏÒÓÚÜÇ·ŀĿ - * - croatian - - 110 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ČčĆćĐ𩹮ž - * - czech - - 130 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ - * - danish - - 106 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿æøåÆØÅ - * - dutch - - 126 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿áéíóúüñÁÉÍÓÚÜÑäëïöèêÄËÏÖÈÊ - * - estonian - - 112 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿šžõäöüŠŽÕÄÖÜ - * - esperanto - - 105 - - 0123456789abcdefghijklmnoprstuvzABCDEFGHIJKLMNOPRSTUVZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ĉĝĥĵŝŭĈĜĤĴŜŬ₷ - * - french - - 126 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿àâéèêëîïôùûüçÀÂÉÈÊËÎÏÔÙÛÜÇ - * - finnish - - 104 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿äöÄÖ - * - frisian - - 108 - - 0123456789abcdefghijklmnoprstuvwyzABCDEFGHIJKLMNOPRSTUVWYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿âêôûúÂÊÔÛÚƒƑ - * - galician - - 110 - - 0123456789abcdefghilmnopqrstuvxyzABCDEFGHILMNOPQRSTUVXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ñÑçÇáéíóúüÁÉÍÓÚÜ - * - german - - 108 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿äöüßÄÖÜẞ - * - hausa - - 101 - - 0123456789abcdefghijklmnorstuwyzABCDEFGHIJKLMNORSTUWYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ɓɗƙƴƁƊƘƳ₦ - * - hungarian - - 118 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿áéíóöúüÁÉÍÓÖÚÜőűŐŰ - * - icelandic - - 114 - - 0123456789abdefghijklmnoprstuvxyzABDEFGHIJKLMNOPRSTUVXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ðáéíóúýþæöÐÁÉÍÓÚÝÞÆÖ - * - indonesian - - 100 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ - * - irish - - 110 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿áéíóúÁÉÍÓÚ - * - italian - - 120 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿àèéìíîòóùúÀÈÉÌÍÎÒÓÙÚ - * - latvian - - 116 - - 0123456789abcdefghijklmnoprstuvyzABCDEFGHIJKLMNOPRSTUVYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿āčēģīķļņšūžĀČĒĢĪĶĻŅŠŪŽ - * - lithuanian - - 112 - - 0123456789abcdefghijklmnoprstuvyzABCDEFGHIJKLMNOPRSTUVYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ąčęėįšųūžĄČĘĖĮŠŲŪŽ - * - luxembourgish - - 110 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿äöüéëÄÖÜÉË - * - malagasy - - 94 - - 0123456789abdefghijklmnoprstvyzABDEFGHIJKLMNOPRSTVYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ôñÔÑ - * - malay - - 100 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ - * - maltese - - 114 - - 0123456789abdefghijklmnopqrstuvwxzABDEFGHIJKLMNOPQRSTUVWXZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ċġħżĊĠĦŻàèìòùÀÈÌÒÙ - * - maori - - 86 - - 0123456789aeghikmnoprtuwAEGHIKMNOPRTUW!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿āēīōūĀĒĪŌŪ - * - montenegrin - - 104 - - 0123456789abcdefghijklmnoprstuvzABCDEFGHIJKLMNOPRSTUVZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿čćšśžźČĆŠŚŽŹ - * - norwegian - - 106 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿æøåÆØÅ - * - polish - - 118 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ąćęłńóśźżĄĆĘŁŃÓŚŹŻ - * - portuguese - - 128 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿áàâãéêíïóôõúüçÁÀÂÃÉÊÍÏÓÔÕÚÜÇ - * - quechua - - 90 - - 0123456789acehiklmnopqrstuwyACEHIKLMNOPQRSTUWY!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ñÑĉĈçÇ - * - romanian - - 110 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ăâîșțĂÂÎȘȚ - * - scottish_gaelic - - 94 - - 0123456789abcdefghilmnoprstuABCDEFGHILMNOPRSTU!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿àèìòùÀÈÌÒÙ - * - serbian_latin - - 110 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿čćđžšČĆĐŽŠ - * - slovak - - 134 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ôäčďľňšťžáéíĺóŕúýÔÄČĎĽŇŠŤŽÁÉÍĹÓŔÚÝ - * - slovene - - 102 - - 0123456789abcdefghijklmnoprstuvzABCDEFGHIJKLMNOPRSTUVZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿čćđšžČĆĐŠŽ - * - somali - - 94 - - 0123456789abcdefghijklmnoqrstuwxyABCDEFGHIJKLMNOQRSTUWXY!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ - * - spanish - - 116 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿áéíóúüñÁÉÍÓÚÜÑ¡¿ - * - swahili - - 96 - - 0123456789abcdefghijklmnoprstuvwyzABCDEFGHIJKLMNOPRSTUVWYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ - * - swedish - - 106 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿åäöÅÄÖ - * - tagalog - - 103 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ñÑ₱ - * - turkish - - 113 - - 0123456789abcdefghijklmnoprstuvyzABCDEFGHIJKLMNOPRSTUVYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿çğıöşüâîûÇĞİÖŞÜÂÎÛ₺ - * - uzbek_latin - - 102 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ʻʼ - * - vietnamese - - 235 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿áàảạãăắằẳẵặâấầẩẫậđéèẻẽẹêếềểễệóòỏõọôốồổộỗơớờởợỡúùủũụưứừửữựíìỉĩịýỳỷỹỵÁÀẢẠÃĂẮẰẲẴẶÂẤẦẨẪẬĐÉÈẺẼẸÊẾỀỂỄỆÓÒỎÕỌÔỐỒỔỘỖƠỚỜỞỢỠÚÙỦŨỤƯỨỪỬỮỰÍÌỈĨỊÝỲỶỸỴ₫ - * - welsh - - 102 - - 0123456789abcdefghijlmnoprstuwyABCDEFGHIJLMNOPRSTUWY!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿âêîôŵŷÂÊÎÔŴŶ - * - yoruba - - 121 - - 0123456789abdefghijklmnoprstuwyABDEFGHIJKLMNOPRSTUWY!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ẹọṣẸỌṢàáèéìíòóùúÀÁÈÉÌÍÒÓÙÚńǹŃǸ₦ - * - zulu - - 100 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿ - * - russian - - 114 - - абвгдежзийклмнопрстуфхцчшщьюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЮЯёыэЁЫЭъЪ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿₽ - * - belarusian - - 116 - - абвгдежзийклмнопрстуфхцчшщьюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЮЯёыэЁЫЭ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿ўіЎІ₽ - * - ukrainian - - 114 - - абвгдежзийклмнопрстуфхцчшщьюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЮЯ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿ґіїєҐІЇЄ₴ - * - tatar - - 126 - - абвгдежзийклмнопрстуфхцчшщьюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЮЯёыэЁЫЭъЪ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿₽ӘәҖҗҢңӨөҮүҺһ - * - tajik - - 125 - - абвгдежзийклмнопрстуфхцчшщьюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЮЯёыэЁЫЭъЪ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿ҒғҚқҲҳҶҷӢӣӮӯ - * - kazakh - - 132 - - абвгдежзийклмнопрстуфхцчшщьюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЮЯёыэЁЫЭъЪ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿ӘәҒғҚқҢңӨөҰұҮүҺһІі₸ - * - kyrgyz - - 119 - - абвгдежзийклмнопрстуфхцчшщьюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЮЯёыэЁЫЭъЪ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿ҢңӨөҮү - * - bulgarian - - 107 - - абвгдежзийклмнопрстуфхцчшщьюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЮЯъЪ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿ - * - macedonian - - 119 - - абвгдежзийклмнопрстуфхцчшщьюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЮЯ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿ЃѓЅѕЈјЉљЊњЌќЏџ - * - mongolian - - 128 - - абвгдежзийклмнопрстуфхцчшщьюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЮЯёыэЁЫЭъЪ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿ӨөҮү᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙₮ - * - yakut - - 124 - - абвгдежзийклмнопрстуфхцчшщьюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЮЯёыэЁЫЭъЪ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿ҔҕҤҥӨөҺһҮү₽ - * - serbian_cyrillic - - 107 - - абвгдежзиклмнопрстуфхцчшАБВГДЕЖЗИКЛМНОПРСТУФХЦЧШјЈЂђЉљЊњЋћЏџ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿ - * - uzbek_cyrillic - - 121 - - абвгдежзийклмнопрстуфхцчшщьюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЮЯёыэЁЫЭъЪ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿ЎўҚқҒғҲҳ - * - greek - - 117 - - 0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~αβγδεζηθικλμνξοπρστςυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ£€¥¢฿άέήίϊΐόύϋΰώΆΈΉΊΪΌΎΫΏ· - * - greek_extended - - 312 - - 0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~αβγδεζηθικλμνξοπρστςυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ£€¥¢฿άέήίϊΐόύϋΰώΆΈΉΊΪΌΎΫΏ·ͶͷϜϝἀἁἂἃἄἅἆἇἈἉἊἋἌἍἎἏἐἑἒἓἔἕἘἙἚἛἜἝἠἡἢἣἤἥἦἧἨἩἪἫἬἭἮἯἰἱἲἳἴἵἶἷἸἹἺἻἼἽἾἿὀὁὂὃὄὅὈὉὊὋὌὍὐὑὒὓὔὕὖὗὙὛὝὟὠὡὢὣὤὥὦὧὨὩὪὫὬὭὮὯὰὲὴὶὸὺὼᾀᾁᾂᾃᾄᾅᾆᾇᾈᾉᾊᾋᾌᾍᾎᾏᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾞᾟᾠᾡᾢᾣᾤᾥᾦᾧᾨᾩᾪᾫᾬᾭᾮᾯᾲᾳᾴᾶᾷᾺᾼῂῃῄῆῇῈῊῌῒΐῖῗῚῢΰῤῥῦῧῪῬῲῳῴῶῷῸῺῼ - * - hebrew - - 176 - - 0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~אבגדהוזחטיךכלםמןנסעףפץצקרשתְֱֲֳִֵֶַָׇֹֺֻֽ־ֿ׀ׁׂ׃ׅׄ׆׳״֑֖֛֢֣֤֥֦֧֪֚֭֮֒֓֔֕֗֘֙֜֝֞֟֠֡֨֩֫֬֯ׯװױײיִﬞײַﬠﬡﬢﬣﬤﬥﬦﬧﬨ﬩שׁשׂשּׁשּׂאַאָאּבּגּדּהּוּזּטּיּךּכּלּמּנּסּףּפּצּקּרּשּתּוֹבֿכֿפֿﭏ₪ - * - arabic - - 116 - - 0123456789٠١٢٣٤٥٦٧٨٩ءآأؤإئابةتثجحخدذرزسشصضطظعغـفقكلمنهوىيٱپچژڢڤگکیًٌٍَُِّْٕٓٔٚ؟؛«»—،!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ - * - persian - - 126 - - 0123456789٠١٢٣٤٥٦٧٨٩ءآأؤإئابةتثجحخدذرزسشصضطظعغـفقكلمنهوىيٱپچژڢڤگکیًٌٍَُِّْٕٓٔٚ؟؛«»—،!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~۰۱۲۳۴۵۶۷۸۹ - * - urdu - - 134 - - 0123456789٠١٢٣٤٥٦٧٨٩ءآأؤإئابةتثجحخدذرزسشصضطظعغـفقكلمنهوىيٱپچژڢڤگکیًٌٍَُِّْٕٓٔٚ؟؛«»—،!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~۰۱۲۳۴۵۶۷۸۹ٹڈڑںھےہۃ - * - pashto - - 137 - - 0123456789٠١٢٣٤٥٦٧٨٩ءآأؤإئابةتثجحخدذرزسشصضطظعغـفقكلمنهوىيٱپچژڢڤگکیًٌٍَُِّْٕٓٔٚ؟؛«»—،!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~۰۱۲۳۴۵۶۷۸۹ټډړږښځڅڼېۍګ - * - kurdish - - 131 - - 0123456789٠١٢٣٤٥٦٧٨٩ءآأؤإئابةتثجحخدذرزسشصضطظعغـفقكلمنهوىيٱپچژڢڤگکیًٌٍَُِّْٕٓٔٚ؟؛«»—،!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~۰۱۲۳۴۵۶۷۸۹ڵڕۆێە - * - uyghur - - 134 - - 0123456789٠١٢٣٤٥٦٧٨٩ءآأؤإئابةتثجحخدذرزسشصضطظعغـفقكلمنهوىيٱپچژڢڤگکیًٌٍَُِّْٕٓٔٚ؟؛«»—،!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~۰۱۲۳۴۵۶۷۸۹ەېۆۇۈڭھۋ - * - sindhi - - 147 - - 0123456789٠١٢٣٤٥٦٧٨٩ءآأؤإئابةتثجحخدذرزسشصضطظعغـفقكلمنهوىيٱپچژڢڤگکیًٌٍَُِّْٕٓٔٚ؟؛«»—،!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~۰۱۲۳۴۵۶۷۸۹ڀٿٺٽڦڄڃڇڏڌڊڍڙڳڱڻھڪٻ۽۾ - * - devanagari - - 161 - - 0123456789कखगघङचछजझञटठडढणतथदधनपफबभमयरलवशषसहऴऩळक़ख़ग़ज़ड़ढ़फ़य़ऱॺॻॼॽॾअआइईउऊऋऌऍऎएऐऑऒओऔॠॡॲऄॵॶॳॴॷॸॹ०१२३४५६७८९़ंँः॒॑ािीुूृॄॅॆेैॉॊोौॢॣॏॎ्।॥॰ऽꣲ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~₹ - * - hindi - - 161 - - 0123456789कखगघङचछजझञटठडढणतथदधनपफबभमयरलवशषसहऴऩळक़ख़ग़ज़ड़ढ़फ़य़ऱॺॻॼॽॾअआइईउऊऋऌऍऎएऐऑऒओऔॠॡॲऄॵॶॳॴॷॸॹ०१२३४५६७८९़ंँः॒॑ािीुूृॄॅॆेैॉॊोौॢॣॏॎ्।॥॰ऽꣲ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~₹ - * - sanskrit - - 161 - - 0123456789कखगघङचछजझञटठडढणतथदधनपफबभमयरलवशषसहऴऩळक़ख़ग़ज़ड़ढ़फ़य़ऱॺॻॼॽॾअआइईउऊऋऌऍऎएऐऑऒओऔॠॡॲऄॵॶॳॴॷॸॹ०१२३४५६७८९़ंँः॒॑ािीुूृॄॅॆेैॉॊोौॢॣॏॎ्।॥॰ऽꣲ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~₹ - * - marathi - - 161 - - 0123456789कखगघङचछजझञटठडढणतथदधनपफबभमयरलवशषसहऴऩळक़ख़ग़ज़ड़ढ़फ़य़ऱॺॻॼॽॾअआइईउऊऋऌऍऎएऐऑऒओऔॠॡॲऄॵॶॳॴॷॸॹ०१२३४५६७८९़ंँः॒॑ािीुूृॄॅॆेैॉॊोौॢॣॏॎ्।॥॰ऽꣲ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~₹ - * - nepali - - 161 - - 0123456789कखगघङचछजझञटठडढणतथदधनपफबभमयरलवशषसहऴऩळक़ख़ग़ज़ड़ढ़फ़य़ऱॺॻॼॽॾअआइईउऊऋऌऍऎएऐऑऒओऔॠॡॲऄॵॶॳॴॷॸॹ०१२३४५६७८९़ंँः॒॑ािीुूृॄॅॆेैॉॊोौॢॣॏॎ्।॥॰ऽꣲ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~₹ - * - gujarati - - 132 - - 0123456789કખગઘઙચછજઝઞટઠડઢણતથદધનપફબભમયરલળવશષસહઅઆઇઈઉઊઋઌઍએઐઑઓઔ૦૧૨૩૪૫૬૭૮૯ઁંઃ઼ાિીુૂૃૄૅેૈૉોૌૢૣૺૻૼ૽૾૿્ઽ॥!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ૐ૰૱₹ - * - bengali - - 127 - - 0123456789কখগঘঙচছজঝঞটঠডঢণতথদধনপফবভমযরলশষসহড়ঢ়য়ৰৱৼঅআইঈউঊঋঌএঐওঔৠৡ০১২৩৪৫৬৭৮৯ািীুূৃেৈোৌৗ্ঽৎ৽৺৻!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ঁংঃ়৳₹ - * - tamil - - 113 - - 0123456789ஜஶஷஸஹகஙசஞடணதநபமயரலவழளறனஅஆஇஈஉஊஎஏஐஒஓஔ௦௧௨௩௪௫௬௭௮௯ாிீுூெேைொோௌ்௰௱௲!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~௳௴௵௶௷௸௹௺ஃௐ₹ - * - telugu - - 129 - - 0123456789కఖగఘఙచఛజఝఞటఠడఢణతథదధనపఫబభమయరఱలళవశషసహఴఅఆఇఈఉఊఋఌఎఏఐఒఓఔౠౡ౦౧౨౩౪౫౬౭౮౯౸౹౺౻ాిీుూృౄెేైొోౌౢౣ్ఽ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ఁంః₹ - * - kannada - - 124 - - 0123456789ಕಖಗಘಙಚಛಜಝಞಟಠಡಢಣತಥದಧನಪಫಬಭಮಯರಲವಶಷಸಹಳಅಆಇಈಉಊಋೠಌೡಎಏಐಒಓಔ೦೧೨೩೪೫೬೭೮೯ಾಿೀುೂೃೄೆೇೈೊೋೌ್।॥ೱೲ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ಂಃಁ₹ - * - sinhala - - 129 - - 0123456789කඛගඝඞඟචඡජඣඤඥඦටඨඩඪණඬතථදධනඳපඵබභමඹයරලවශෂසහළෆඅආඇඈඉඊඋඌඍඎඏඐඑඒඓඔඕඖ෦෧෨෩෪෫෬෭෮෯ාැෑිීුූෙේෛොෝෞ්෴!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ංඃ₹ - * - malayalam - - 132 - - 0123456789ൺൻർൽൾൿകഖഗഘങചഛജഝഞടഠഡഢണതഥദധനപഫബഭമയരറലളഴവശഷസഹഅആഇഈഉഊഋൠഌൡഎഏഐഒഓഔ൦൧൨൩൪൫൬൭൮൯ാിീുൂൃൄൢൣെേൈൊോൌ്!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ഃ൹ഽ൏ം₹ - * - punjabi - - 122 - - 0123456789ਕਖਗਘਙਚਛਜਝਞਟਠਡਢਣਤਥਦਧਨਪਫਬਭਮਯਰਲਵਸ਼ਸਹਖ਼ਗ਼ਜ਼ਫ਼ੜਲ਼ਅਆਇਈਉਊਏਐਓਔੲੳ੦੧੨੩੪੫੬੭੮੯ਂ਼ਾਿੀੁੂੇੈੋੌੑੰੱੵ੍।॥!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ੴ₹ - * - odia - - 131 - - 0123456789କଖଗଘଙଚଛଜଝଞଟଠଡଢଣତଥଦଧନପଫବଭମଯରଲଳଵଶଷସହୟୱଡ଼ଢ଼ଅଆଇଈଉଊଋଌଏଐଓଔୡୠ୦୧୨୩୪୫୬୭୮୯୲୳୴୵୶୷ାିୀୁୂୃୄେୈୋୌୢୣ୍ଽ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ଂଃଁ଼୰₹ - * - khmer - - 144 - - 0123456789កខគឃងចឆជឈញដឋឌឍណតថទធនបផពភមយរលវឝឞសហឡអឣឤឥឦឧឨឩឪឫឬឭឮឯឰឱឲឳ០១២៣៤៥៦៧៨៩ាិីឹឺុូួើឿៀេែៃោៅ្ំះៈ៉៊់៌៍៎៏័៑៓៝។៕៖៘៙៚ៗៜ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~៛ - * - armenian - - 131 - - ԱԲԳԴԵԶԷԸԹԺԻԼԽԾԿՀՁՂՃՄՅՆՇՈՉՊՋՌՍՎՏՐՑՒՓՔՕՖՙՠաբգդեզէըթժիլխծկհձղճմյնշոչպջռսվտրցւփքօֆևֈ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~՚՛՜՝՞՟։֊֏ - * - sudanese - - 106 - - 0123456789᮰᮱᮲᮳᮴᮵᮶᮷᮸᮹ᮊᮋᮌᮍᮎᮏᮐᮑᮒᮓᮔᮕᮖᮗᮘᮙᮚᮛᮜᮝᮞᮟᮠᮮᮯᮺᮻᮼᮽᮾᮿᮃᮄᮅᮆᮇᮈᮉᮀᮁᮂᮡᮢᮣᮤᮥᮦᮧᮨᮩ᮪᮫ᮬᮭ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ - * - thai - - 129 - - 0123456789๐๑๒๓๔๕๖๗๘๙!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~๏๚๛ๆฯกขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮะาำเแโใไๅัิีึืฺุู็่้๊๋์ํ๎฿ - * - lao - - 124 - - 0123456789໐໑໒໓໔໕໖໗໘໙!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ໆໞໟຯກຂຄຆງຈຉຊຌຍຎຏຐຑຒຓດຕຖທຘນບປຜຝພຟຠມຢຣລວຨຩສຫຬອຮະາຳຽເແໂໃໄໜໝັິີຶື຺ຸູົຼ່້໊໋໌ໍ - * - burmese - - 162 - - 0123456789၀၁၂၃၄၅၆၇၈၉႐႑႒႓႔႕႖႗႘႙ကခဂဃငစဆဇဈဉညဋဌဍဎဏတထဒဓနပဖဗဘမယရလဝသဟဠအၐၑၒၓၔၕၚၛၜၝၡၥၦၮၯၰၵၶၷၸၹၺၻၼၽၾၿႀႁႎဣဤဥဦဧဩဪဿ့းံါာိီုူေဲဳဴဵျြွှ္်၊။၌၍၎၏ၤၗ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ - * - javanese - - 124 - - 0123456789꧐꧑꧒꧓꧔꧕꧖꧗꧘꧙ꦏꦐꦑꦒꦓꦔꦕꦖꦗꦘꦙꦚꦛꦜꦝꦞꦟꦠꦡꦢꦣꦤꦥꦦꦧꦨꦩꦪꦫꦬꦭꦮꦯꦰꦱꦲꦄꦅꦆꦇꦈꦉꦊꦋꦌꦍꦎꦴꦵꦶꦷꦸꦹꦺꦻꦼꦀꦁꦂꦃ꦳ꦽꦾꦿ꧀꧈꧉꧊꧋꧌꧍ꧏ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ - * - georgian - - 177 - - 0123456789ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀჁჂჃჄჅჇჍაბგდევზთიკლმნოპჟრსტუფქღყშჩცძწჭხჯჰჱჲჳჴჵჶჷჸჹჺჼჽჾჿᲐᲑᲒᲓᲔᲕᲖᲗᲘᲙᲚᲛᲜᲝᲞᲟᲠᲡᲢᲣᲤᲥᲦᲧᲨᲩᲪᲫᲬᲭᲮᲯᲰᲱᲲᲳᲴᲵᲶᲷᲸᲹᲺᲽᲾᲿ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~჻₾ - * - ethiopic - - 418 - - ሀሁሂሃሄህሆሇለሉሊላሌልሎሏሐሑሒሓሔሕሖሗመሙሚማሜምሞሟሠሡሢሣሤሥሦሧረሩሪራሬርሮሯሰሱሲሳሴስሶሷሸሹሺሻሼሽሾሿቀቁቂቃቄቅቆቇቈቊቋቌቍቐቑቒቓቔቕቖቘቚቛቜቝበቡቢባቤብቦቧቨቩቪቫቬቭቮቯተቱቲታቴትቶቷቸቹቺቻቼችቾቿኀኁኂኃኄኅኆኇኈኊኋኌኍነኑኒናኔንኖኗኘኙኚኛኜኝኞኟአኡኢኣኤእኦኧከኩኪካኬክኮኯኰኲኳኴኵኸኹኺኻኼኽኾዀዂዃዄዅወዉዊዋዌውዎዏዐዑዒዓዔዕዖዘዙዚዛዜዝዞዟዠዡዢዣዤዥዦዧየዩዪያዬይዮዯደዱዲዳዴድዶዷዸዹዺዻዼዽዾዿጀጁጂጃጄጅጆጇገጉጊጋጌግጎጏጐጒጓጔጕጘጙጚጛጜጝጞጟጠጡጢጣጤጥጦጧጨጩጪጫጬጭጮጯጰጱጲጳጴጵጶጷጸጹጺጻጼጽጾጿፀፁፂፃፄፅፆፇፈፉፊፋፌፍፎፏፐፑፒፓፔፕፖፗፘፙፚᎀᎁᎂᎃᎄᎅᎆᎇᎈᎉᎊᎋᎌᎍᎎᎏ፩፪፫፬፭፮፯፰፱፲፳፴፵፶፷፸፹፺፻፼0123456789፠፡።፣፤፥፦፧፨!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~£€¥¢฿ - * - japanese - - 2383 - - 0123456789ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔゕゖゝゞゟァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺーヽヾヿ亜哀挨愛曖悪握圧扱宛嵐安案暗以衣位囲医依委威為畏胃尉異移萎偉椅彙意違維慰遺緯域育一壱逸茨芋引印因咽姻員院淫陰飲隠韻右宇羽雨唄鬱畝浦運雲永泳英映栄営詠影鋭衛易疫益液駅悦越謁閲円延沿炎怨宴媛援園煙猿遠鉛塩演縁艶汚王凹央応往押旺欧殴桜翁奥横岡屋億憶臆虞乙俺卸音恩温穏下化火加可仮何花佳価果河苛科架夏家荷華菓貨渦過嫁暇禍靴寡歌箇稼課蚊牙瓦我画芽賀雅餓介回灰会快戒改怪拐悔海界皆械絵開階塊楷解潰壊懐諧貝外劾害崖涯街慨蓋該概骸垣柿各角拡革格核殻郭覚較隔閣確獲嚇穫学岳楽額顎掛潟括活喝渇割葛滑褐轄且株釜鎌刈干刊甘汗缶完肝官冠巻看陥乾勘患貫寒喚堪換敢棺款間閑勧寛幹感漢慣管関歓監緩憾還館環簡観韓艦鑑丸含岸岩玩眼頑顔願企伎危机気岐希忌汽奇祈季紀軌既記起飢鬼帰基寄規亀喜幾揮期棋貴棄毀旗器畿輝機騎技宜偽欺義疑儀戯擬犠議菊吉喫詰却客脚逆虐九久及弓丘旧休吸朽臼求究泣急級糾宮救球給嗅窮牛去巨居拒拠挙虚許距魚御漁凶共叫狂京享供協況峡挟狭恐恭胸脅強教郷境橋矯鏡競響驚仰暁業凝曲局極玉巾斤均近金菌勤琴筋僅禁緊錦謹襟吟銀区句苦駆具惧愚空偶遇隅串屈掘窟熊繰君訓勲薫軍郡群兄刑形系径茎係型契計恵啓掲渓経蛍敬景軽傾携継詣慶憬稽憩警鶏芸迎鯨隙劇撃激桁欠穴血決結傑潔月犬件見券肩建研県倹兼剣拳軒健険圏堅検嫌献絹遣権憲賢謙鍵繭顕験懸元幻玄言弦限原現舷減源厳己戸古呼固股虎孤弧故枯個庫湖雇誇鼓錮顧五互午呉後娯悟碁語誤護口工公勾孔功巧広甲交光向后好江考行坑孝抗攻更効幸拘肯侯厚恒洪皇紅荒郊香候校耕航貢降高康控梗黄喉慌港硬絞項溝鉱構綱酵稿興衡鋼講購乞号合拷剛傲豪克告谷刻国黒穀酷獄骨駒込頃今困昆恨根婚混痕紺魂墾懇左佐沙査砂唆差詐鎖座挫才再災妻采砕宰栽彩採済祭斎細菜最裁債催塞歳載際埼在材剤財罪崎作削昨柵索策酢搾錯咲冊札刷刹拶殺察撮擦雑皿三山参桟蚕惨産傘散算酸賛残斬暫士子支止氏仕史司四市矢旨死糸至伺志私使刺始姉枝祉肢姿思指施師恣紙脂視紫詞歯嗣試詩資飼誌雌摯賜諮示字寺次耳自似児事侍治持時滋慈辞磁餌璽鹿式識軸七叱失室疾執湿嫉漆質実芝写社車舎者射捨赦斜煮遮謝邪蛇尺借酌釈爵若弱寂手主守朱取狩首殊珠酒腫種趣寿受呪授需儒樹収囚州舟秀周宗拾秋臭修袖終羞習週就衆集愁酬醜蹴襲十汁充住柔重従渋銃獣縦叔祝宿淑粛縮塾熟出述術俊春瞬旬巡盾准殉純循順準潤遵処初所書庶暑署緒諸女如助序叙徐除小升少召匠床抄肖尚招承昇松沼昭宵将消症祥称笑唱商渉章紹訟勝掌晶焼焦硝粧詔証象傷奨照詳彰障憧衝賞償礁鐘上丈冗条状乗城浄剰常情場畳蒸縄壌嬢錠譲醸色拭食植殖飾触嘱織職辱尻心申伸臣芯身辛侵信津神唇娠振浸真針深紳進森診寝慎新審震薪親人刃仁尽迅甚陣尋腎須図水吹垂炊帥粋衰推酔遂睡穂随髄枢崇数据杉裾寸瀬是井世正生成西声制姓征性青斉政星牲省凄逝清盛婿晴勢聖誠精製誓静請整醒税夕斥石赤昔析席脊隻惜戚責跡積績籍切折拙窃接設雪摂節説舌絶千川仙占先宣専泉浅洗染扇栓旋船戦煎羨腺詮践箋銭潜線遷選薦繊鮮全前善然禅漸膳繕狙阻祖租素措粗組疎訴塑遡礎双壮早争走奏相荘草送倉捜挿桑巣掃曹曽爽窓創喪痩葬装僧想層総遭槽踪操燥霜騒藻造像増憎蔵贈臓即束足促則息捉速側測俗族属賊続卒率存村孫尊損遜他多汰打妥唾堕惰駄太対体耐待怠胎退帯泰堆袋逮替貸隊滞態戴大代台第題滝宅択沢卓拓託濯諾濁但達脱奪棚誰丹旦担単炭胆探淡短嘆端綻誕鍛団男段断弾暖談壇地池知値恥致遅痴稚置緻竹畜逐蓄築秩窒茶着嫡中仲虫沖宙忠抽注昼柱衷酎鋳駐著貯丁弔庁兆町長挑帳張彫眺釣頂鳥朝貼超腸跳徴嘲潮澄調聴懲直勅捗沈珍朕陳賃鎮追椎墜通痛塚漬坪爪鶴低呈廷弟定底抵邸亭貞帝訂庭逓停偵堤提程艇締諦泥的笛摘滴適敵溺迭哲鉄徹撤天典店点展添転塡田伝殿電斗吐妬徒途都渡塗賭土奴努度怒刀冬灯当投豆東到逃倒凍唐島桃討透党悼盗陶塔搭棟湯痘登答等筒統稲踏糖頭謄藤闘騰同洞胴動堂童道働銅導瞳峠匿特得督徳篤毒独読栃凸突届屯豚頓貪鈍曇丼那奈内梨謎鍋南軟難二尼弐匂肉虹日入乳尿任妊忍認寧熱年念捻粘燃悩納能脳農濃把波派破覇馬婆罵拝杯背肺俳配排敗廃輩売倍梅培陪媒買賠白伯拍泊迫剝舶博薄麦漠縛爆箱箸畑肌八鉢発髪伐抜罰閥反半氾犯帆汎伴判坂阪板版班畔般販斑飯搬煩頒範繁藩晩番蛮盤比皮妃否批彼披肥非卑飛疲秘被悲扉費碑罷避尾眉美備微鼻膝肘匹必泌筆姫百氷表俵票評漂標苗秒病描猫品浜貧賓頻敏瓶不夫父付布扶府怖阜附訃負赴浮婦符富普腐敷膚賦譜侮武部舞封風伏服副幅復福腹複覆払沸仏物粉紛雰噴墳憤奮分文聞丙平兵併並柄陛閉塀幣弊蔽餅米壁璧癖別蔑片辺返変偏遍編弁便勉歩保哺捕補舗母募墓慕暮簿方包芳邦奉宝抱放法泡胞俸倣峰砲崩訪報蜂豊飽褒縫亡乏忙坊妨忘防房肪某冒剖紡望傍帽棒貿貌暴膨謀頰北木朴牧睦僕墨撲没勃堀本奔翻凡盆麻摩磨魔毎妹枚昧埋幕膜枕又末抹万満慢漫未味魅岬密蜜脈妙民眠矛務無夢霧娘名命明迷冥盟銘鳴滅免面綿麺茂模毛妄盲耗猛網目黙門紋問冶夜野弥厄役約訳薬躍闇由油喩愉諭輸癒唯友有勇幽悠郵湧猶裕遊雄誘憂融優与予余誉預幼用羊妖洋要容庸揚揺葉陽溶腰様瘍踊窯養擁謡曜抑沃浴欲翌翼拉裸羅来雷頼絡落酪辣乱卵覧濫藍欄吏利里理痢裏履璃離陸立律慄略柳流留竜粒隆硫侶旅虜慮了両良料涼猟陵量僚領寮療瞭糧力緑林厘倫輪隣臨瑠涙累塁類令礼冷励戻例鈴零霊隷齢麗暦歴列劣烈裂恋連廉練錬呂炉賂路露老労弄郎朗浪廊楼漏籠六録麓論和話賄脇惑枠湾腕!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~。・〜°—、「」『』【】゛》《〉〈£€¥¢฿ - * - korean - - 11237 - - 0123456789가각갂갃간갅갆갇갈갉갊갋갌갍갎갏감갑값갓갔강갖갗갘같갚갛개객갞갟갠갡갢갣갤갥갦갧갨갩갪갫갬갭갮갯갰갱갲갳갴갵갶갷갸갹갺갻갼갽갾갿걀걁걂걃걄걅걆걇걈걉걊걋걌걍걎걏걐걑걒걓걔걕걖걗걘걙걚걛걜걝걞걟걠걡걢걣걤걥걦걧걨걩걪걫걬걭걮걯거걱걲걳건걵걶걷걸걹걺걻걼걽걾걿검겁겂것겄겅겆겇겈겉겊겋게겍겎겏겐겑겒겓겔겕겖겗겘겙겚겛겜겝겞겟겠겡겢겣겤겥겦겧겨격겪겫견겭겮겯결겱겲겳겴겵겶겷겸겹겺겻겼경겾겿곀곁곂곃계곅곆곇곈곉곊곋곌곍곎곏곐곑곒곓곔곕곖곗곘곙곚곛곜곝곞곟고곡곢곣곤곥곦곧골곩곪곫곬곭곮곯곰곱곲곳곴공곶곷곸곹곺곻과곽곾곿관괁괂괃괄괅괆괇괈괉괊괋괌괍괎괏괐광괒괓괔괕괖괗괘괙괚괛괜괝괞괟괠괡괢괣괤괥괦괧괨괩괪괫괬괭괮괯괰괱괲괳괴괵괶괷괸괹괺괻괼괽괾괿굀굁굂굃굄굅굆굇굈굉굊굋굌굍굎굏교굑굒굓굔굕굖굗굘굙굚굛굜굝굞굟굠굡굢굣굤굥굦굧굨굩굪굫구국굮굯군굱굲굳굴굵굶굷굸굹굺굻굼굽굾굿궀궁궂궃궄궅궆궇궈궉궊궋권궍궎궏궐궑궒궓궔궕궖궗궘궙궚궛궜궝궞궟궠궡궢궣궤궥궦궧궨궩궪궫궬궭궮궯궰궱궲궳궴궵궶궷궸궹궺궻궼궽궾궿귀귁귂귃귄귅귆귇귈귉귊귋귌귍귎귏귐귑귒귓귔귕귖귗귘귙귚귛규귝귞귟균귡귢귣귤귥귦귧귨귩귪귫귬귭귮귯귰귱귲귳귴귵귶귷그극귺귻근귽귾귿글긁긂긃긄긅긆긇금급긊긋긌긍긎긏긐긑긒긓긔긕긖긗긘긙긚긛긜긝긞긟긠긡긢긣긤긥긦긧긨긩긪긫긬긭긮긯기긱긲긳긴긵긶긷길긹긺긻긼긽긾긿김깁깂깃깄깅깆깇깈깉깊깋까깍깎깏깐깑깒깓깔깕깖깗깘깙깚깛깜깝깞깟깠깡깢깣깤깥깦깧깨깩깪깫깬깭깮깯깰깱깲깳깴깵깶깷깸깹깺깻깼깽깾깿꺀꺁꺂꺃꺄꺅꺆꺇꺈꺉꺊꺋꺌꺍꺎꺏꺐꺑꺒꺓꺔꺕꺖꺗꺘꺙꺚꺛꺜꺝꺞꺟꺠꺡꺢꺣꺤꺥꺦꺧꺨꺩꺪꺫꺬꺭꺮꺯꺰꺱꺲꺳꺴꺵꺶꺷꺸꺹꺺꺻꺼꺽꺾꺿껀껁껂껃껄껅껆껇껈껉껊껋껌껍껎껏껐껑껒껓껔껕껖껗께껙껚껛껜껝껞껟껠껡껢껣껤껥껦껧껨껩껪껫껬껭껮껯껰껱껲껳껴껵껶껷껸껹껺껻껼껽껾껿꼀꼁꼂꼃꼄꼅꼆꼇꼈꼉꼊꼋꼌꼍꼎꼏꼐꼑꼒꼓꼔꼕꼖꼗꼘꼙꼚꼛꼜꼝꼞꼟꼠꼡꼢꼣꼤꼥꼦꼧꼨꼩꼪꼫꼬꼭꼮꼯꼰꼱꼲꼳꼴꼵꼶꼷꼸꼹꼺꼻꼼꼽꼾꼿꽀꽁꽂꽃꽄꽅꽆꽇꽈꽉꽊꽋꽌꽍꽎꽏꽐꽑꽒꽓꽔꽕꽖꽗꽘꽙꽚꽛꽜꽝꽞꽟꽠꽡꽢꽣꽤꽥꽦꽧꽨꽩꽪꽫꽬꽭꽮꽯꽰꽱꽲꽳꽴꽵꽶꽷꽸꽹꽺꽻꽼꽽꽾꽿꾀꾁꾂꾃꾄꾅꾆꾇꾈꾉꾊꾋꾌꾍꾎꾏꾐꾑꾒꾓꾔꾕꾖꾗꾘꾙꾚꾛꾜꾝꾞꾟꾠꾡꾢꾣꾤꾥꾦꾧꾨꾩꾪꾫꾬꾭꾮꾯꾰꾱꾲꾳꾴꾵꾶꾷꾸꾹꾺꾻꾼꾽꾾꾿꿀꿁꿂꿃꿄꿅꿆꿇꿈꿉꿊꿋꿌꿍꿎꿏꿐꿑꿒꿓꿔꿕꿖꿗꿘꿙꿚꿛꿜꿝꿞꿟꿠꿡꿢꿣꿤꿥꿦꿧꿨꿩꿪꿫꿬꿭꿮꿯꿰꿱꿲꿳꿴꿵꿶꿷꿸꿹꿺꿻꿼꿽꿾꿿뀀뀁뀂뀃뀄뀅뀆뀇뀈뀉뀊뀋뀌뀍뀎뀏뀐뀑뀒뀓뀔뀕뀖뀗뀘뀙뀚뀛뀜뀝뀞뀟뀠뀡뀢뀣뀤뀥뀦뀧뀨뀩뀪뀫뀬뀭뀮뀯뀰뀱뀲뀳뀴뀵뀶뀷뀸뀹뀺뀻뀼뀽뀾뀿끀끁끂끃끄끅끆끇끈끉끊끋끌끍끎끏끐끑끒끓끔끕끖끗끘끙끚끛끜끝끞끟끠끡끢끣끤끥끦끧끨끩끪끫끬끭끮끯끰끱끲끳끴끵끶끷끸끹끺끻끼끽끾끿낀낁낂낃낄낅낆낇낈낉낊낋낌낍낎낏낐낑낒낓낔낕낖낗나낙낚낛난낝낞낟날낡낢낣낤낥낦낧남납낪낫났낭낮낯낰낱낲낳내낵낶낷낸낹낺낻낼낽낾낿냀냁냂냃냄냅냆냇냈냉냊냋냌냍냎냏냐냑냒냓냔냕냖냗냘냙냚냛냜냝냞냟냠냡냢냣냤냥냦냧냨냩냪냫냬냭냮냯냰냱냲냳냴냵냶냷냸냹냺냻냼냽냾냿넀넁넂넃넄넅넆넇너넉넊넋넌넍넎넏널넑넒넓넔넕넖넗넘넙넚넛넜넝넞넟넠넡넢넣네넥넦넧넨넩넪넫넬넭넮넯넰넱넲넳넴넵넶넷넸넹넺넻넼넽넾넿녀녁녂녃년녅녆녇녈녉녊녋녌녍녎녏념녑녒녓녔녕녖녗녘녙녚녛녜녝녞녟녠녡녢녣녤녥녦녧녨녩녪녫녬녭녮녯녰녱녲녳녴녵녶녷노녹녺녻논녽녾녿놀놁놂놃놄놅놆놇놈놉놊놋놌농놎놏놐놑높놓놔놕놖놗놘놙놚놛놜놝놞놟놠놡놢놣놤놥놦놧놨놩놪놫놬놭놮놯놰놱놲놳놴놵놶놷놸놹놺놻놼놽놾놿뇀뇁뇂뇃뇄뇅뇆뇇뇈뇉뇊뇋뇌뇍뇎뇏뇐뇑뇒뇓뇔뇕뇖뇗뇘뇙뇚뇛뇜뇝뇞뇟뇠뇡뇢뇣뇤뇥뇦뇧뇨뇩뇪뇫뇬뇭뇮뇯뇰뇱뇲뇳뇴뇵뇶뇷뇸뇹뇺뇻뇼뇽뇾뇿눀눁눂눃누눅눆눇눈눉눊눋눌눍눎눏눐눑눒눓눔눕눖눗눘눙눚눛눜눝눞눟눠눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀뉁뉂뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌뉍뉎뉏뉐뉑뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜뉝뉞뉟뉠뉡뉢뉣뉤뉥뉦뉧뉨뉩뉪뉫뉬뉭뉮뉯뉰뉱뉲뉳뉴뉵뉶뉷뉸뉹뉺뉻뉼뉽뉾뉿늀늁늂늃늄늅늆늇늈늉늊늋늌늍늎늏느늑늒늓는늕늖늗늘늙늚늛늜늝늞늟늠늡늢늣늤능늦늧늨늩늪늫늬늭늮늯늰늱늲늳늴늵늶늷늸늹늺늻늼늽늾늿닀닁닂닃닄닅닆닇니닉닊닋닌닍닎닏닐닑닒닓닔닕닖닗님닙닚닛닜닝닞닟닠닡닢닣다닥닦닧단닩닪닫달닭닮닯닰닱닲닳담답닶닷닸당닺닻닼닽닾닿대댁댂댃댄댅댆댇댈댉댊댋댌댍댎댏댐댑댒댓댔댕댖댗댘댙댚댛댜댝댞댟댠댡댢댣댤댥댦댧댨댩댪댫댬댭댮댯댰댱댲댳댴댵댶댷댸댹댺댻댼댽댾댿덀덁덂덃덄덅덆덇덈덉덊덋덌덍덎덏덐덑덒덓더덕덖덗던덙덚덛덜덝덞덟덠덡덢덣덤덥덦덧덨덩덪덫덬덭덮덯데덱덲덳덴덵덶덷델덹덺덻덼덽덾덿뎀뎁뎂뎃뎄뎅뎆뎇뎈뎉뎊뎋뎌뎍뎎뎏뎐뎑뎒뎓뎔뎕뎖뎗뎘뎙뎚뎛뎜뎝뎞뎟뎠뎡뎢뎣뎤뎥뎦뎧뎨뎩뎪뎫뎬뎭뎮뎯뎰뎱뎲뎳뎴뎵뎶뎷뎸뎹뎺뎻뎼뎽뎾뎿돀돁돂돃도독돆돇돈돉돊돋돌돍돎돏돐돑돒돓돔돕돖돗돘동돚돛돜돝돞돟돠돡돢돣돤돥돦돧돨돩돪돫돬돭돮돯돰돱돲돳돴돵돶돷돸돹돺돻돼돽돾돿됀됁됂됃됄됅됆됇됈됉됊됋됌됍됎됏됐됑됒됓됔됕됖됗되됙됚됛된됝됞됟될됡됢됣됤됥됦됧됨됩됪됫됬됭됮됯됰됱됲됳됴됵됶됷됸됹됺됻됼됽됾됿둀둁둂둃둄둅둆둇둈둉둊둋둌둍둎둏두둑둒둓둔둕둖둗둘둙둚둛둜둝둞둟둠둡둢둣둤둥둦둧둨둩둪둫둬둭둮둯둰둱둲둳둴둵둶둷둸둹둺둻둼둽둾둿뒀뒁뒂뒃뒄뒅뒆뒇뒈뒉뒊뒋뒌뒍뒎뒏뒐뒑뒒뒓뒔뒕뒖뒗뒘뒙뒚뒛뒜뒝뒞뒟뒠뒡뒢뒣뒤뒥뒦뒧뒨뒩뒪뒫뒬뒭뒮뒯뒰뒱뒲뒳뒴뒵뒶뒷뒸뒹뒺뒻뒼뒽뒾뒿듀듁듂듃듄듅듆듇듈듉듊듋듌듍듎듏듐듑듒듓듔듕듖듗듘듙듚듛드득듞듟든듡듢듣들듥듦듧듨듩듪듫듬듭듮듯듰등듲듳듴듵듶듷듸듹듺듻듼듽듾듿딀딁딂딃딄딅딆딇딈딉딊딋딌딍딎딏딐딑딒딓디딕딖딗딘딙딚딛딜딝딞딟딠딡딢딣딤딥딦딧딨딩딪딫딬딭딮딯따딱딲딳딴딵딶딷딸딹딺딻딼딽딾딿땀땁땂땃땄땅땆땇땈땉땊땋때땍땎땏땐땑땒땓땔땕땖땗땘땙땚땛땜땝땞땟땠땡땢땣땤땥땦땧땨땩땪땫땬땭땮땯땰땱땲땳땴땵땶땷땸땹땺땻땼땽땾땿떀떁떂떃떄떅떆떇떈떉떊떋떌떍떎떏떐떑떒떓떔떕떖떗떘떙떚떛떜떝떞떟떠떡떢떣떤떥떦떧떨떩떪떫떬떭떮떯떰떱떲떳떴떵떶떷떸떹떺떻떼떽떾떿뗀뗁뗂뗃뗄뗅뗆뗇뗈뗉뗊뗋뗌뗍뗎뗏뗐뗑뗒뗓뗔뗕뗖뗗뗘뗙뗚뗛뗜뗝뗞뗟뗠뗡뗢뗣뗤뗥뗦뗧뗨뗩뗪뗫뗬뗭뗮뗯뗰뗱뗲뗳뗴뗵뗶뗷뗸뗹뗺뗻뗼뗽뗾뗿똀똁똂똃똄똅똆똇똈똉똊똋똌똍똎똏또똑똒똓똔똕똖똗똘똙똚똛똜똝똞똟똠똡똢똣똤똥똦똧똨똩똪똫똬똭똮똯똰똱똲똳똴똵똶똷똸똹똺똻똼똽똾똿뙀뙁뙂뙃뙄뙅뙆뙇뙈뙉뙊뙋뙌뙍뙎뙏뙐뙑뙒뙓뙔뙕뙖뙗뙘뙙뙚뙛뙜뙝뙞뙟뙠뙡뙢뙣뙤뙥뙦뙧뙨뙩뙪뙫뙬뙭뙮뙯뙰뙱뙲뙳뙴뙵뙶뙷뙸뙹뙺뙻뙼뙽뙾뙿뚀뚁뚂뚃뚄뚅뚆뚇뚈뚉뚊뚋뚌뚍뚎뚏뚐뚑뚒뚓뚔뚕뚖뚗뚘뚙뚚뚛뚜뚝뚞뚟뚠뚡뚢뚣뚤뚥뚦뚧뚨뚩뚪뚫뚬뚭뚮뚯뚰뚱뚲뚳뚴뚵뚶뚷뚸뚹뚺뚻뚼뚽뚾뚿뛀뛁뛂뛃뛄뛅뛆뛇뛈뛉뛊뛋뛌뛍뛎뛏뛐뛑뛒뛓뛔뛕뛖뛗뛘뛙뛚뛛뛜뛝뛞뛟뛠뛡뛢뛣뛤뛥뛦뛧뛨뛩뛪뛫뛬뛭뛮뛯뛰뛱뛲뛳뛴뛵뛶뛷뛸뛹뛺뛻뛼뛽뛾뛿뜀뜁뜂뜃뜄뜅뜆뜇뜈뜉뜊뜋뜌뜍뜎뜏뜐뜑뜒뜓뜔뜕뜖뜗뜘뜙뜚뜛뜜뜝뜞뜟뜠뜡뜢뜣뜤뜥뜦뜧뜨뜩뜪뜫뜬뜭뜮뜯뜰뜱뜲뜳뜴뜵뜶뜷뜸뜹뜺뜻뜼뜽뜾뜿띀띁띂띃띄띅띆띇띈띉띊띋띌띍띎띏띐띑띒띓띔띕띖띗띘띙띚띛띜띝띞띟띠띡띢띣띤띥띦띧띨띩띪띫띬띭띮띯띰띱띲띳띴띵띶띷띸띹띺띻라락띾띿란랁랂랃랄랅랆랇랈랉랊랋람랍랎랏랐랑랒랓랔랕랖랗래랙랚랛랜랝랞랟랠랡랢랣랤랥랦랧램랩랪랫랬랭랮랯랰랱랲랳랴략랶랷랸랹랺랻랼랽랾랿럀럁럂럃럄럅럆럇럈량럊럋럌럍럎럏럐럑럒럓럔럕럖럗럘럙럚럛럜럝럞럟럠럡럢럣럤럥럦럧럨럩럪럫러럭럮럯런럱럲럳럴럵럶럷럸럹럺럻럼럽럾럿렀렁렂렃렄렅렆렇레렉렊렋렌렍렎렏렐렑렒렓렔렕렖렗렘렙렚렛렜렝렞렟렠렡렢렣려력렦렧련렩렪렫렬렭렮렯렰렱렲렳렴렵렶렷렸령렺렻렼렽렾렿례롁롂롃롄롅롆롇롈롉롊롋롌롍롎롏롐롑롒롓롔롕롖롗롘롙롚롛로록롞롟론롡롢롣롤롥롦롧롨롩롪롫롬롭롮롯롰롱롲롳롴롵롶롷롸롹롺롻롼롽롾롿뢀뢁뢂뢃뢄뢅뢆뢇뢈뢉뢊뢋뢌뢍뢎뢏뢐뢑뢒뢓뢔뢕뢖뢗뢘뢙뢚뢛뢜뢝뢞뢟뢠뢡뢢뢣뢤뢥뢦뢧뢨뢩뢪뢫뢬뢭뢮뢯뢰뢱뢲뢳뢴뢵뢶뢷뢸뢹뢺뢻뢼뢽뢾뢿룀룁룂룃룄룅룆룇룈룉룊룋료룍룎룏룐룑룒룓룔룕룖룗룘룙룚룛룜룝룞룟룠룡룢룣룤룥룦룧루룩룪룫룬룭룮룯룰룱룲룳룴룵룶룷룸룹룺룻룼룽룾룿뤀뤁뤂뤃뤄뤅뤆뤇뤈뤉뤊뤋뤌뤍뤎뤏뤐뤑뤒뤓뤔뤕뤖뤗뤘뤙뤚뤛뤜뤝뤞뤟뤠뤡뤢뤣뤤뤥뤦뤧뤨뤩뤪뤫뤬뤭뤮뤯뤰뤱뤲뤳뤴뤵뤶뤷뤸뤹뤺뤻뤼뤽뤾뤿륀륁륂륃륄륅륆륇륈륉륊륋륌륍륎륏륐륑륒륓륔륕륖륗류륙륚륛륜륝륞륟률륡륢륣륤륥륦륧륨륩륪륫륬륭륮륯륰륱륲륳르륵륶륷른륹륺륻를륽륾륿릀릁릂릃름릅릆릇릈릉릊릋릌릍릎릏릐릑릒릓릔릕릖릗릘릙릚릛릜릝릞릟릠릡릢릣릤릥릦릧릨릩릪릫리릭릮릯린릱릲릳릴릵릶릷릸릹릺릻림립릾릿맀링맂맃맄맅맆맇마막맊맋만맍많맏말맑맒맓맔맕맖맗맘맙맚맛맜망맞맟맠맡맢맣매맥맦맧맨맩맪맫맬맭맮맯맰맱맲맳맴맵맶맷맸맹맺맻맼맽맾맿먀먁먂먃먄먅먆먇먈먉먊먋먌먍먎먏먐먑먒먓먔먕먖먗먘먙먚먛먜먝먞먟먠먡먢먣먤먥먦먧먨먩먪먫먬먭먮먯먰먱먲먳먴먵먶먷머먹먺먻먼먽먾먿멀멁멂멃멄멅멆멇멈멉멊멋멌멍멎멏멐멑멒멓메멕멖멗멘멙멚멛멜멝멞멟멠멡멢멣멤멥멦멧멨멩멪멫멬멭멮멯며멱멲멳면멵멶멷멸멹멺멻멼멽멾멿몀몁몂몃몄명몆몇몈몉몊몋몌몍몎몏몐몑몒몓몔몕몖몗몘몙몚몛몜몝몞몟몠몡몢몣몤몥몦몧모목몪몫몬몭몮몯몰몱몲몳몴몵몶몷몸몹몺못몼몽몾몿뫀뫁뫂뫃뫄뫅뫆뫇뫈뫉뫊뫋뫌뫍뫎뫏뫐뫑뫒뫓뫔뫕뫖뫗뫘뫙뫚뫛뫜뫝뫞뫟뫠뫡뫢뫣뫤뫥뫦뫧뫨뫩뫪뫫뫬뫭뫮뫯뫰뫱뫲뫳뫴뫵뫶뫷뫸뫹뫺뫻뫼뫽뫾뫿묀묁묂묃묄묅묆묇묈묉묊묋묌묍묎묏묐묑묒묓묔묕묖묗묘묙묚묛묜묝묞묟묠묡묢묣묤묥묦묧묨묩묪묫묬묭묮묯묰묱묲묳무묵묶묷문묹묺묻물묽묾묿뭀뭁뭂뭃뭄뭅뭆뭇뭈뭉뭊뭋뭌뭍뭎뭏뭐뭑뭒뭓뭔뭕뭖뭗뭘뭙뭚뭛뭜뭝뭞뭟뭠뭡뭢뭣뭤뭥뭦뭧뭨뭩뭪뭫뭬뭭뭮뭯뭰뭱뭲뭳뭴뭵뭶뭷뭸뭹뭺뭻뭼뭽뭾뭿뮀뮁뮂뮃뮄뮅뮆뮇뮈뮉뮊뮋뮌뮍뮎뮏뮐뮑뮒뮓뮔뮕뮖뮗뮘뮙뮚뮛뮜뮝뮞뮟뮠뮡뮢뮣뮤뮥뮦뮧뮨뮩뮪뮫뮬뮭뮮뮯뮰뮱뮲뮳뮴뮵뮶뮷뮸뮹뮺뮻뮼뮽뮾뮿므믁믂믃믄믅믆믇믈믉믊믋믌믍믎믏믐믑믒믓믔믕믖믗믘믙믚믛믜믝믞믟믠믡믢믣믤믥믦믧믨믩믪믫믬믭믮믯믰믱믲믳믴믵믶믷미믹믺믻민믽믾믿밀밁밂밃밄밅밆밇밈밉밊밋밌밍밎및밐밑밒밓바박밖밗반밙밚받발밝밞밟밠밡밢밣밤밥밦밧밨방밪밫밬밭밮밯배백밲밳밴밵밶밷밸밹밺밻밼밽밾밿뱀뱁뱂뱃뱄뱅뱆뱇뱈뱉뱊뱋뱌뱍뱎뱏뱐뱑뱒뱓뱔뱕뱖뱗뱘뱙뱚뱛뱜뱝뱞뱟뱠뱡뱢뱣뱤뱥뱦뱧뱨뱩뱪뱫뱬뱭뱮뱯뱰뱱뱲뱳뱴뱵뱶뱷뱸뱹뱺뱻뱼뱽뱾뱿벀벁벂벃버벅벆벇번벉벊벋벌벍벎벏벐벑벒벓범법벖벗벘벙벚벛벜벝벞벟베벡벢벣벤벥벦벧벨벩벪벫벬벭벮벯벰벱벲벳벴벵벶벷벸벹벺벻벼벽벾벿변볁볂볃별볅볆볇볈볉볊볋볌볍볎볏볐병볒볓볔볕볖볗볘볙볚볛볜볝볞볟볠볡볢볣볤볥볦볧볨볩볪볫볬볭볮볯볰볱볲볳보복볶볷본볹볺볻볼볽볾볿봀봁봂봃봄봅봆봇봈봉봊봋봌봍봎봏봐봑봒봓봔봕봖봗봘봙봚봛봜봝봞봟봠봡봢봣봤봥봦봧봨봩봪봫봬봭봮봯봰봱봲봳봴봵봶봷봸봹봺봻봼봽봾봿뵀뵁뵂뵃뵄뵅뵆뵇뵈뵉뵊뵋뵌뵍뵎뵏뵐뵑뵒뵓뵔뵕뵖뵗뵘뵙뵚뵛뵜뵝뵞뵟뵠뵡뵢뵣뵤뵥뵦뵧뵨뵩뵪뵫뵬뵭뵮뵯뵰뵱뵲뵳뵴뵵뵶뵷뵸뵹뵺뵻뵼뵽뵾뵿부북붂붃분붅붆붇불붉붊붋붌붍붎붏붐붑붒붓붔붕붖붗붘붙붚붛붜붝붞붟붠붡붢붣붤붥붦붧붨붩붪붫붬붭붮붯붰붱붲붳붴붵붶붷붸붹붺붻붼붽붾붿뷀뷁뷂뷃뷄뷅뷆뷇뷈뷉뷊뷋뷌뷍뷎뷏뷐뷑뷒뷓뷔뷕뷖뷗뷘뷙뷚뷛뷜뷝뷞뷟뷠뷡뷢뷣뷤뷥뷦뷧뷨뷩뷪뷫뷬뷭뷮뷯뷰뷱뷲뷳뷴뷵뷶뷷뷸뷹뷺뷻뷼뷽뷾뷿븀븁븂븃븄븅븆븇븈븉븊븋브븍븎븏븐븑븒븓블븕븖븗븘븙븚븛븜븝븞븟븠븡븢븣븤븥븦븧븨븩븪븫븬븭븮븯븰븱븲븳븴븵븶븷븸븹븺븻븼븽븾븿빀빁빂빃비빅빆빇빈빉빊빋빌빍빎빏빐빑빒빓빔빕빖빗빘빙빚빛빜빝빞빟빠빡빢빣빤빥빦빧빨빩빪빫빬빭빮빯빰빱빲빳빴빵빶빷빸빹빺빻빼빽빾빿뺀뺁뺂뺃뺄뺅뺆뺇뺈뺉뺊뺋뺌뺍뺎뺏뺐뺑뺒뺓뺔뺕뺖뺗뺘뺙뺚뺛뺜뺝뺞뺟뺠뺡뺢뺣뺤뺥뺦뺧뺨뺩뺪뺫뺬뺭뺮뺯뺰뺱뺲뺳뺴뺵뺶뺷뺸뺹뺺뺻뺼뺽뺾뺿뻀뻁뻂뻃뻄뻅뻆뻇뻈뻉뻊뻋뻌뻍뻎뻏뻐뻑뻒뻓뻔뻕뻖뻗뻘뻙뻚뻛뻜뻝뻞뻟뻠뻡뻢뻣뻤뻥뻦뻧뻨뻩뻪뻫뻬뻭뻮뻯뻰뻱뻲뻳뻴뻵뻶뻷뻸뻹뻺뻻뻼뻽뻾뻿뼀뼁뼂뼃뼄뼅뼆뼇뼈뼉뼊뼋뼌뼍뼎뼏뼐뼑뼒뼓뼔뼕뼖뼗뼘뼙뼚뼛뼜뼝뼞뼟뼠뼡뼢뼣뼤뼥뼦뼧뼨뼩뼪뼫뼬뼭뼮뼯뼰뼱뼲뼳뼴뼵뼶뼷뼸뼹뼺뼻뼼뼽뼾뼿뽀뽁뽂뽃뽄뽅뽆뽇뽈뽉뽊뽋뽌뽍뽎뽏뽐뽑뽒뽓뽔뽕뽖뽗뽘뽙뽚뽛뽜뽝뽞뽟뽠뽡뽢뽣뽤뽥뽦뽧뽨뽩뽪뽫뽬뽭뽮뽯뽰뽱뽲뽳뽴뽵뽶뽷뽸뽹뽺뽻뽼뽽뽾뽿뾀뾁뾂뾃뾄뾅뾆뾇뾈뾉뾊뾋뾌뾍뾎뾏뾐뾑뾒뾓뾔뾕뾖뾗뾘뾙뾚뾛뾜뾝뾞뾟뾠뾡뾢뾣뾤뾥뾦뾧뾨뾩뾪뾫뾬뾭뾮뾯뾰뾱뾲뾳뾴뾵뾶뾷뾸뾹뾺뾻뾼뾽뾾뾿뿀뿁뿂뿃뿄뿅뿆뿇뿈뿉뿊뿋뿌뿍뿎뿏뿐뿑뿒뿓뿔뿕뿖뿗뿘뿙뿚뿛뿜뿝뿞뿟뿠뿡뿢뿣뿤뿥뿦뿧뿨뿩뿪뿫뿬뿭뿮뿯뿰뿱뿲뿳뿴뿵뿶뿷뿸뿹뿺뿻뿼뿽뿾뿿쀀쀁쀂쀃쀄쀅쀆쀇쀈쀉쀊쀋쀌쀍쀎쀏쀐쀑쀒쀓쀔쀕쀖쀗쀘쀙쀚쀛쀜쀝쀞쀟쀠쀡쀢쀣쀤쀥쀦쀧쀨쀩쀪쀫쀬쀭쀮쀯쀰쀱쀲쀳쀴쀵쀶쀷쀸쀹쀺쀻쀼쀽쀾쀿쁀쁁쁂쁃쁄쁅쁆쁇쁈쁉쁊쁋쁌쁍쁎쁏쁐쁑쁒쁓쁔쁕쁖쁗쁘쁙쁚쁛쁜쁝쁞쁟쁠쁡쁢쁣쁤쁥쁦쁧쁨쁩쁪쁫쁬쁭쁮쁯쁰쁱쁲쁳쁴쁵쁶쁷쁸쁹쁺쁻쁼쁽쁾쁿삀삁삂삃삄삅삆삇삈삉삊삋삌삍삎삏삐삑삒삓삔삕삖삗삘삙삚삛삜삝삞삟삠삡삢삣삤삥삦삧삨삩삪삫사삭삮삯산삱삲삳살삵삶삷삸삹삺삻삼삽삾삿샀상샂샃샄샅샆샇새색샊샋샌샍샎샏샐샑샒샓샔샕샖샗샘샙샚샛샜생샞샟샠샡샢샣샤샥샦샧샨샩샪샫샬샭샮샯샰샱샲샳샴샵샶샷샸샹샺샻샼샽샾샿섀섁섂섃섄섅섆섇섈섉섊섋섌섍섎섏섐섑섒섓섔섕섖섗섘섙섚섛서석섞섟선섡섢섣설섥섦섧섨섩섪섫섬섭섮섯섰성섲섳섴섵섶섷세섹섺섻센섽섾섿셀셁셂셃셄셅셆셇셈셉셊셋셌셍셎셏셐셑셒셓셔셕셖셗션셙셚셛셜셝셞셟셠셡셢셣셤셥셦셧셨셩셪셫셬셭셮셯셰셱셲셳셴셵셶셷셸셹셺셻셼셽셾셿솀솁솂솃솄솅솆솇솈솉솊솋소속솎솏손솑솒솓솔솕솖솗솘솙솚솛솜솝솞솟솠송솢솣솤솥솦솧솨솩솪솫솬솭솮솯솰솱솲솳솴솵솶솷솸솹솺솻솼솽솾솿쇀쇁쇂쇃쇄쇅쇆쇇쇈쇉쇊쇋쇌쇍쇎쇏쇐쇑쇒쇓쇔쇕쇖쇗쇘쇙쇚쇛쇜쇝쇞쇟쇠쇡쇢쇣쇤쇥쇦쇧쇨쇩쇪쇫쇬쇭쇮쇯쇰쇱쇲쇳쇴쇵쇶쇷쇸쇹쇺쇻쇼쇽쇾쇿숀숁숂숃숄숅숆숇숈숉숊숋숌숍숎숏숐숑숒숓숔숕숖숗수숙숚숛순숝숞숟술숡숢숣숤숥숦숧숨숩숪숫숬숭숮숯숰숱숲숳숴숵숶숷숸숹숺숻숼숽숾숿쉀쉁쉂쉃쉄쉅쉆쉇쉈쉉쉊쉋쉌쉍쉎쉏쉐쉑쉒쉓쉔쉕쉖쉗쉘쉙쉚쉛쉜쉝쉞쉟쉠쉡쉢쉣쉤쉥쉦쉧쉨쉩쉪쉫쉬쉭쉮쉯쉰쉱쉲쉳쉴쉵쉶쉷쉸쉹쉺쉻쉼쉽쉾쉿슀슁슂슃슄슅슆슇슈슉슊슋슌슍슎슏슐슑슒슓슔슕슖슗슘슙슚슛슜슝슞슟슠슡슢슣스슥슦슧슨슩슪슫슬슭슮슯슰슱슲슳슴습슶슷슸승슺슻슼슽슾슿싀싁싂싃싄싅싆싇싈싉싊싋싌싍싎싏싐싑싒싓싔싕싖싗싘싙싚싛시식싞싟신싡싢싣실싥싦싧싨싩싪싫심십싮싯싰싱싲싳싴싵싶싷싸싹싺싻싼싽싾싿쌀쌁쌂쌃쌄쌅쌆쌇쌈쌉쌊쌋쌌쌍쌎쌏쌐쌑쌒쌓쌔쌕쌖쌗쌘쌙쌚쌛쌜쌝쌞쌟쌠쌡쌢쌣쌤쌥쌦쌧쌨쌩쌪쌫쌬쌭쌮쌯쌰쌱쌲쌳쌴쌵쌶쌷쌸쌹쌺쌻쌼쌽쌾쌿썀썁썂썃썄썅썆썇썈썉썊썋썌썍썎썏썐썑썒썓썔썕썖썗썘썙썚썛썜썝썞썟썠썡썢썣썤썥썦썧써썩썪썫썬썭썮썯썰썱썲썳썴썵썶썷썸썹썺썻썼썽썾썿쎀쎁쎂쎃쎄쎅쎆쎇쎈쎉쎊쎋쎌쎍쎎쎏쎐쎑쎒쎓쎔쎕쎖쎗쎘쎙쎚쎛쎜쎝쎞쎟쎠쎡쎢쎣쎤쎥쎦쎧쎨쎩쎪쎫쎬쎭쎮쎯쎰쎱쎲쎳쎴쎵쎶쎷쎸쎹쎺쎻쎼쎽쎾쎿쏀쏁쏂쏃쏄쏅쏆쏇쏈쏉쏊쏋쏌쏍쏎쏏쏐쏑쏒쏓쏔쏕쏖쏗쏘쏙쏚쏛쏜쏝쏞쏟쏠쏡쏢쏣쏤쏥쏦쏧쏨쏩쏪쏫쏬쏭쏮쏯쏰쏱쏲쏳쏴쏵쏶쏷쏸쏹쏺쏻쏼쏽쏾쏿쐀쐁쐂쐃쐄쐅쐆쐇쐈쐉쐊쐋쐌쐍쐎쐏쐐쐑쐒쐓쐔쐕쐖쐗쐘쐙쐚쐛쐜쐝쐞쐟쐠쐡쐢쐣쐤쐥쐦쐧쐨쐩쐪쐫쐬쐭쐮쐯쐰쐱쐲쐳쐴쐵쐶쐷쐸쐹쐺쐻쐼쐽쐾쐿쑀쑁쑂쑃쑄쑅쑆쑇쑈쑉쑊쑋쑌쑍쑎쑏쑐쑑쑒쑓쑔쑕쑖쑗쑘쑙쑚쑛쑜쑝쑞쑟쑠쑡쑢쑣쑤쑥쑦쑧쑨쑩쑪쑫쑬쑭쑮쑯쑰쑱쑲쑳쑴쑵쑶쑷쑸쑹쑺쑻쑼쑽쑾쑿쒀쒁쒂쒃쒄쒅쒆쒇쒈쒉쒊쒋쒌쒍쒎쒏쒐쒑쒒쒓쒔쒕쒖쒗쒘쒙쒚쒛쒜쒝쒞쒟쒠쒡쒢쒣쒤쒥쒦쒧쒨쒩쒪쒫쒬쒭쒮쒯쒰쒱쒲쒳쒴쒵쒶쒷쒸쒹쒺쒻쒼쒽쒾쒿쓀쓁쓂쓃쓄쓅쓆쓇쓈쓉쓊쓋쓌쓍쓎쓏쓐쓑쓒쓓쓔쓕쓖쓗쓘쓙쓚쓛쓜쓝쓞쓟쓠쓡쓢쓣쓤쓥쓦쓧쓨쓩쓪쓫쓬쓭쓮쓯쓰쓱쓲쓳쓴쓵쓶쓷쓸쓹쓺쓻쓼쓽쓾쓿씀씁씂씃씄씅씆씇씈씉씊씋씌씍씎씏씐씑씒씓씔씕씖씗씘씙씚씛씜씝씞씟씠씡씢씣씤씥씦씧씨씩씪씫씬씭씮씯씰씱씲씳씴씵씶씷씸씹씺씻씼씽씾씿앀앁앂앃아악앆앇안앉않앋알앍앎앏앐앑앒앓암압앖앗았앙앚앛앜앝앞앟애액앢앣앤앥앦앧앨앩앪앫앬앭앮앯앰앱앲앳앴앵앶앷앸앹앺앻야약앾앿얀얁얂얃얄얅얆얇얈얉얊얋얌얍얎얏얐양얒얓얔얕얖얗얘얙얚얛얜얝얞얟얠얡얢얣얤얥얦얧얨얩얪얫얬얭얮얯얰얱얲얳어억얶얷언얹얺얻얼얽얾얿엀엁엂엃엄업없엇었엉엊엋엌엍엎엏에엑엒엓엔엕엖엗엘엙엚엛엜엝엞엟엠엡엢엣엤엥엦엧엨엩엪엫여역엮엯연엱엲엳열엵엶엷엸엹엺엻염엽엾엿였영옂옃옄옅옆옇예옉옊옋옌옍옎옏옐옑옒옓옔옕옖옗옘옙옚옛옜옝옞옟옠옡옢옣오옥옦옧온옩옪옫올옭옮옯옰옱옲옳옴옵옶옷옸옹옺옻옼옽옾옿와왁왂왃완왅왆왇왈왉왊왋왌왍왎왏왐왑왒왓왔왕왖왗왘왙왚왛왜왝왞왟왠왡왢왣왤왥왦왧왨왩왪왫왬왭왮왯왰왱왲왳왴왵왶왷외왹왺왻왼왽왾왿욀욁욂욃욄욅욆욇욈욉욊욋욌욍욎욏욐욑욒욓요욕욖욗욘욙욚욛욜욝욞욟욠욡욢욣욤욥욦욧욨용욪욫욬욭욮욯우욱욲욳운욵욶욷울욹욺욻욼욽욾욿움웁웂웃웄웅웆웇웈웉웊웋워웍웎웏원웑웒웓월웕웖웗웘웙웚웛웜웝웞웟웠웡웢웣웤웥웦웧웨웩웪웫웬웭웮웯웰웱웲웳웴웵웶웷웸웹웺웻웼웽웾웿윀윁윂윃위윅윆윇윈윉윊윋윌윍윎윏윐윑윒윓윔윕윖윗윘윙윚윛윜윝윞윟유육윢윣윤윥윦윧율윩윪윫윬윭윮윯윰윱윲윳윴융윶윷윸윹윺윻으윽윾윿은읁읂읃을읅읆읇읈읉읊읋음읍읎읏읐응읒읓읔읕읖읗의읙읚읛읜읝읞읟읠읡읢읣읤읥읦읧읨읩읪읫읬읭읮읯읰읱읲읳이익읶읷인읹읺읻일읽읾읿잀잁잂잃임입잆잇있잉잊잋잌잍잎잏자작잒잓잔잕잖잗잘잙잚잛잜잝잞잟잠잡잢잣잤장잦잧잨잩잪잫재잭잮잯잰잱잲잳잴잵잶잷잸잹잺잻잼잽잾잿쟀쟁쟂쟃쟄쟅쟆쟇쟈쟉쟊쟋쟌쟍쟎쟏쟐쟑쟒쟓쟔쟕쟖쟗쟘쟙쟚쟛쟜쟝쟞쟟쟠쟡쟢쟣쟤쟥쟦쟧쟨쟩쟪쟫쟬쟭쟮쟯쟰쟱쟲쟳쟴쟵쟶쟷쟸쟹쟺쟻쟼쟽쟾쟿저적젂젃전젅젆젇절젉젊젋젌젍젎젏점접젒젓젔정젖젗젘젙젚젛제젝젞젟젠젡젢젣젤젥젦젧젨젩젪젫젬젭젮젯젰젱젲젳젴젵젶젷져젹젺젻젼젽젾젿졀졁졂졃졄졅졆졇졈졉졊졋졌졍졎졏졐졑졒졓졔졕졖졗졘졙졚졛졜졝졞졟졠졡졢졣졤졥졦졧졨졩졪졫졬졭졮졯조족졲졳존졵졶졷졸졹졺졻졼졽졾졿좀좁좂좃좄종좆좇좈좉좊좋좌좍좎좏좐좑좒좓좔좕좖좗좘좙좚좛좜좝좞좟좠좡좢좣좤좥좦좧좨좩좪좫좬좭좮좯좰좱좲좳좴좵좶좷좸좹좺좻좼좽좾좿죀죁죂죃죄죅죆죇죈죉죊죋죌죍죎죏죐죑죒죓죔죕죖죗죘죙죚죛죜죝죞죟죠죡죢죣죤죥죦죧죨죩죪죫죬죭죮죯죰죱죲죳죴죵죶죷죸죹죺죻주죽죾죿준줁줂줃줄줅줆줇줈줉줊줋줌줍줎줏줐중줒줓줔줕줖줗줘줙줚줛줜줝줞줟줠줡줢줣줤줥줦줧줨줩줪줫줬줭줮줯줰줱줲줳줴줵줶줷줸줹줺줻줼줽줾줿쥀쥁쥂쥃쥄쥅쥆쥇쥈쥉쥊쥋쥌쥍쥎쥏쥐쥑쥒쥓쥔쥕쥖쥗쥘쥙쥚쥛쥜쥝쥞쥟쥠쥡쥢쥣쥤쥥쥦쥧쥨쥩쥪쥫쥬쥭쥮쥯쥰쥱쥲쥳쥴쥵쥶쥷쥸쥹쥺쥻쥼쥽쥾쥿즀즁즂즃즄즅즆즇즈즉즊즋즌즍즎즏즐즑즒즓즔즕즖즗즘즙즚즛즜증즞즟즠즡즢즣즤즥즦즧즨즩즪즫즬즭즮즯즰즱즲즳즴즵즶즷즸즹즺즻즼즽즾즿지직짂짃진짅짆짇질짉짊짋짌짍짎짏짐집짒짓짔징짖짗짘짙짚짛짜짝짞짟짠짡짢짣짤짥짦짧짨짩짪짫짬짭짮짯짰짱짲짳짴짵짶짷째짹짺짻짼짽짾짿쨀쨁쨂쨃쨄쨅쨆쨇쨈쨉쨊쨋쨌쨍쨎쨏쨐쨑쨒쨓쨔쨕쨖쨗쨘쨙쨚쨛쨜쨝쨞쨟쨠쨡쨢쨣쨤쨥쨦쨧쨨쨩쨪쨫쨬쨭쨮쨯쨰쨱쨲쨳쨴쨵쨶쨷쨸쨹쨺쨻쨼쨽쨾쨿쩀쩁쩂쩃쩄쩅쩆쩇쩈쩉쩊쩋쩌쩍쩎쩏쩐쩑쩒쩓쩔쩕쩖쩗쩘쩙쩚쩛쩜쩝쩞쩟쩠쩡쩢쩣쩤쩥쩦쩧쩨쩩쩪쩫쩬쩭쩮쩯쩰쩱쩲쩳쩴쩵쩶쩷쩸쩹쩺쩻쩼쩽쩾쩿쪀쪁쪂쪃쪄쪅쪆쪇쪈쪉쪊쪋쪌쪍쪎쪏쪐쪑쪒쪓쪔쪕쪖쪗쪘쪙쪚쪛쪜쪝쪞쪟쪠쪡쪢쪣쪤쪥쪦쪧쪨쪩쪪쪫쪬쪭쪮쪯쪰쪱쪲쪳쪴쪵쪶쪷쪸쪹쪺쪻쪼쪽쪾쪿쫀쫁쫂쫃쫄쫅쫆쫇쫈쫉쫊쫋쫌쫍쫎쫏쫐쫑쫒쫓쫔쫕쫖쫗쫘쫙쫚쫛쫜쫝쫞쫟쫠쫡쫢쫣쫤쫥쫦쫧쫨쫩쫪쫫쫬쫭쫮쫯쫰쫱쫲쫳쫴쫵쫶쫷쫸쫹쫺쫻쫼쫽쫾쫿쬀쬁쬂쬃쬄쬅쬆쬇쬈쬉쬊쬋쬌쬍쬎쬏쬐쬑쬒쬓쬔쬕쬖쬗쬘쬙쬚쬛쬜쬝쬞쬟쬠쬡쬢쬣쬤쬥쬦쬧쬨쬩쬪쬫쬬쬭쬮쬯쬰쬱쬲쬳쬴쬵쬶쬷쬸쬹쬺쬻쬼쬽쬾쬿쭀쭁쭂쭃쭄쭅쭆쭇쭈쭉쭊쭋쭌쭍쭎쭏쭐쭑쭒쭓쭔쭕쭖쭗쭘쭙쭚쭛쭜쭝쭞쭟쭠쭡쭢쭣쭤쭥쭦쭧쭨쭩쭪쭫쭬쭭쭮쭯쭰쭱쭲쭳쭴쭵쭶쭷쭸쭹쭺쭻쭼쭽쭾쭿쮀쮁쮂쮃쮄쮅쮆쮇쮈쮉쮊쮋쮌쮍쮎쮏쮐쮑쮒쮓쮔쮕쮖쮗쮘쮙쮚쮛쮜쮝쮞쮟쮠쮡쮢쮣쮤쮥쮦쮧쮨쮩쮪쮫쮬쮭쮮쮯쮰쮱쮲쮳쮴쮵쮶쮷쮸쮹쮺쮻쮼쮽쮾쮿쯀쯁쯂쯃쯄쯅쯆쯇쯈쯉쯊쯋쯌쯍쯎쯏쯐쯑쯒쯓쯔쯕쯖쯗쯘쯙쯚쯛쯜쯝쯞쯟쯠쯡쯢쯣쯤쯥쯦쯧쯨쯩쯪쯫쯬쯭쯮쯯쯰쯱쯲쯳쯴쯵쯶쯷쯸쯹쯺쯻쯼쯽쯾쯿찀찁찂찃찄찅찆찇찈찉찊찋찌찍찎찏찐찑찒찓찔찕찖찗찘찙찚찛찜찝찞찟찠찡찢찣찤찥찦찧차착찪찫찬찭찮찯찰찱찲찳찴찵찶찷참찹찺찻찼창찾찿챀챁챂챃채책챆챇챈챉챊챋챌챍챎챏챐챑챒챓챔챕챖챗챘챙챚챛챜챝챞챟챠챡챢챣챤챥챦챧챨챩챪챫챬챭챮챯챰챱챲챳챴챵챶챷챸챹챺챻챼챽챾챿첀첁첂첃첄첅첆첇첈첉첊첋첌첍첎첏첐첑첒첓첔첕첖첗처척첚첛천첝첞첟철첡첢첣첤첥첦첧첨첩첪첫첬청첮첯첰첱첲첳체첵첶첷첸첹첺첻첼첽첾첿쳀쳁쳂쳃쳄쳅쳆쳇쳈쳉쳊쳋쳌쳍쳎쳏쳐쳑쳒쳓쳔쳕쳖쳗쳘쳙쳚쳛쳜쳝쳞쳟쳠쳡쳢쳣쳤쳥쳦쳧쳨쳩쳪쳫쳬쳭쳮쳯쳰쳱쳲쳳쳴쳵쳶쳷쳸쳹쳺쳻쳼쳽쳾쳿촀촁촂촃촄촅촆촇초촉촊촋촌촍촎촏촐촑촒촓촔촕촖촗촘촙촚촛촜총촞촟촠촡촢촣촤촥촦촧촨촩촪촫촬촭촮촯촰촱촲촳촴촵촶촷촸촹촺촻촼촽촾촿쵀쵁쵂쵃쵄쵅쵆쵇쵈쵉쵊쵋쵌쵍쵎쵏쵐쵑쵒쵓쵔쵕쵖쵗쵘쵙쵚쵛최쵝쵞쵟쵠쵡쵢쵣쵤쵥쵦쵧쵨쵩쵪쵫쵬쵭쵮쵯쵰쵱쵲쵳쵴쵵쵶쵷쵸쵹쵺쵻쵼쵽쵾쵿춀춁춂춃춄춅춆춇춈춉춊춋춌춍춎춏춐춑춒춓추축춖춗춘춙춚춛출춝춞춟춠춡춢춣춤춥춦춧춨충춪춫춬춭춮춯춰춱춲춳춴춵춶춷춸춹춺춻춼춽춾춿췀췁췂췃췄췅췆췇췈췉췊췋췌췍췎췏췐췑췒췓췔췕췖췗췘췙췚췛췜췝췞췟췠췡췢췣췤췥췦췧취췩췪췫췬췭췮췯췰췱췲췳췴췵췶췷췸췹췺췻췼췽췾췿츀츁츂츃츄츅츆츇츈츉츊츋츌츍츎츏츐츑츒츓츔츕츖츗츘츙츚츛츜츝츞츟츠측츢츣츤츥츦츧츨츩츪츫츬츭츮츯츰츱츲츳츴층츶츷츸츹츺츻츼츽츾츿칀칁칂칃칄칅칆칇칈칉칊칋칌칍칎칏칐칑칒칓칔칕칖칗치칙칚칛친칝칞칟칠칡칢칣칤칥칦칧침칩칪칫칬칭칮칯칰칱칲칳카칵칶칷칸칹칺칻칼칽칾칿캀캁캂캃캄캅캆캇캈캉캊캋캌캍캎캏캐캑캒캓캔캕캖캗캘캙캚캛캜캝캞캟캠캡캢캣캤캥캦캧캨캩캪캫캬캭캮캯캰캱캲캳캴캵캶캷캸캹캺캻캼캽캾캿컀컁컂컃컄컅컆컇컈컉컊컋컌컍컎컏컐컑컒컓컔컕컖컗컘컙컚컛컜컝컞컟컠컡컢컣커컥컦컧컨컩컪컫컬컭컮컯컰컱컲컳컴컵컶컷컸컹컺컻컼컽컾컿케켁켂켃켄켅켆켇켈켉켊켋켌켍켎켏켐켑켒켓켔켕켖켗켘켙켚켛켜켝켞켟켠켡켢켣켤켥켦켧켨켩켪켫켬켭켮켯켰켱켲켳켴켵켶켷켸켹켺켻켼켽켾켿콀콁콂콃콄콅콆콇콈콉콊콋콌콍콎콏콐콑콒콓코콕콖콗콘콙콚콛콜콝콞콟콠콡콢콣콤콥콦콧콨콩콪콫콬콭콮콯콰콱콲콳콴콵콶콷콸콹콺콻콼콽콾콿쾀쾁쾂쾃쾄쾅쾆쾇쾈쾉쾊쾋쾌쾍쾎쾏쾐쾑쾒쾓쾔쾕쾖쾗쾘쾙쾚쾛쾜쾝쾞쾟쾠쾡쾢쾣쾤쾥쾦쾧쾨쾩쾪쾫쾬쾭쾮쾯쾰쾱쾲쾳쾴쾵쾶쾷쾸쾹쾺쾻쾼쾽쾾쾿쿀쿁쿂쿃쿄쿅쿆쿇쿈쿉쿊쿋쿌쿍쿎쿏쿐쿑쿒쿓쿔쿕쿖쿗쿘쿙쿚쿛쿜쿝쿞쿟쿠쿡쿢쿣쿤쿥쿦쿧쿨쿩쿪쿫쿬쿭쿮쿯쿰쿱쿲쿳쿴쿵쿶쿷쿸쿹쿺쿻쿼쿽쿾쿿퀀퀁퀂퀃퀄퀅퀆퀇퀈퀉퀊퀋퀌퀍퀎퀏퀐퀑퀒퀓퀔퀕퀖퀗퀘퀙퀚퀛퀜퀝퀞퀟퀠퀡퀢퀣퀤퀥퀦퀧퀨퀩퀪퀫퀬퀭퀮퀯퀰퀱퀲퀳퀴퀵퀶퀷퀸퀹퀺퀻퀼퀽퀾퀿큀큁큂큃큄큅큆큇큈큉큊큋큌큍큎큏큐큑큒큓큔큕큖큗큘큙큚큛큜큝큞큟큠큡큢큣큤큥큦큧큨큩큪큫크큭큮큯큰큱큲큳클큵큶큷큸큹큺큻큼큽큾큿킀킁킂킃킄킅킆킇킈킉킊킋킌킍킎킏킐킑킒킓킔킕킖킗킘킙킚킛킜킝킞킟킠킡킢킣키킥킦킧킨킩킪킫킬킭킮킯킰킱킲킳킴킵킶킷킸킹킺킻킼킽킾킿타탁탂탃탄탅탆탇탈탉탊탋탌탍탎탏탐탑탒탓탔탕탖탗탘탙탚탛태택탞탟탠탡탢탣탤탥탦탧탨탩탪탫탬탭탮탯탰탱탲탳탴탵탶탷탸탹탺탻탼탽탾탿턀턁턂턃턄턅턆턇턈턉턊턋턌턍턎턏턐턑턒턓턔턕턖턗턘턙턚턛턜턝턞턟턠턡턢턣턤턥턦턧턨턩턪턫턬턭턮턯터턱턲턳턴턵턶턷털턹턺턻턼턽턾턿텀텁텂텃텄텅텆텇텈텉텊텋테텍텎텏텐텑텒텓텔텕텖텗텘텙텚텛템텝텞텟텠텡텢텣텤텥텦텧텨텩텪텫텬텭텮텯텰텱텲텳텴텵텶텷텸텹텺텻텼텽텾텿톀톁톂톃톄톅톆톇톈톉톊톋톌톍톎톏톐톑톒톓톔톕톖톗톘톙톚톛톜톝톞톟토톡톢톣톤톥톦톧톨톩톪톫톬톭톮톯톰톱톲톳톴통톶톷톸톹톺톻톼톽톾톿퇀퇁퇂퇃퇄퇅퇆퇇퇈퇉퇊퇋퇌퇍퇎퇏퇐퇑퇒퇓퇔퇕퇖퇗퇘퇙퇚퇛퇜퇝퇞퇟퇠퇡퇢퇣퇤퇥퇦퇧퇨퇩퇪퇫퇬퇭퇮퇯퇰퇱퇲퇳퇴퇵퇶퇷퇸퇹퇺퇻퇼퇽퇾퇿툀툁툂툃툄툅툆툇툈툉툊툋툌툍툎툏툐툑툒툓툔툕툖툗툘툙툚툛툜툝툞툟툠툡툢툣툤툥툦툧툨툩툪툫투툭툮툯툰툱툲툳툴툵툶툷툸툹툺툻툼툽툾툿퉀퉁퉂퉃퉄퉅퉆퉇퉈퉉퉊퉋퉌퉍퉎퉏퉐퉑퉒퉓퉔퉕퉖퉗퉘퉙퉚퉛퉜퉝퉞퉟퉠퉡퉢퉣퉤퉥퉦퉧퉨퉩퉪퉫퉬퉭퉮퉯퉰퉱퉲퉳퉴퉵퉶퉷퉸퉹퉺퉻퉼퉽퉾퉿튀튁튂튃튄튅튆튇튈튉튊튋튌튍튎튏튐튑튒튓튔튕튖튗튘튙튚튛튜튝튞튟튠튡튢튣튤튥튦튧튨튩튪튫튬튭튮튯튰튱튲튳튴튵튶튷트특튺튻튼튽튾튿틀틁틂틃틄틅틆틇틈틉틊틋틌틍틎틏틐틑틒틓틔틕틖틗틘틙틚틛틜틝틞틟틠틡틢틣틤틥틦틧틨틩틪틫틬틭틮틯티틱틲틳틴틵틶틷틸틹틺틻틼틽틾틿팀팁팂팃팄팅팆팇팈팉팊팋파팍팎팏판팑팒팓팔팕팖팗팘팙팚팛팜팝팞팟팠팡팢팣팤팥팦팧패팩팪팫팬팭팮팯팰팱팲팳팴팵팶팷팸팹팺팻팼팽팾팿퍀퍁퍂퍃퍄퍅퍆퍇퍈퍉퍊퍋퍌퍍퍎퍏퍐퍑퍒퍓퍔퍕퍖퍗퍘퍙퍚퍛퍜퍝퍞퍟퍠퍡퍢퍣퍤퍥퍦퍧퍨퍩퍪퍫퍬퍭퍮퍯퍰퍱퍲퍳퍴퍵퍶퍷퍸퍹퍺퍻퍼퍽퍾퍿펀펁펂펃펄펅펆펇펈펉펊펋펌펍펎펏펐펑펒펓펔펕펖펗페펙펚펛펜펝펞펟펠펡펢펣펤펥펦펧펨펩펪펫펬펭펮펯펰펱펲펳펴펵펶펷편펹펺펻펼펽펾펿폀폁폂폃폄폅폆폇폈평폊폋폌폍폎폏폐폑폒폓폔폕폖폗폘폙폚폛폜폝폞폟폠폡폢폣폤폥폦폧폨폩폪폫포폭폮폯폰폱폲폳폴폵폶폷폸폹폺폻폼폽폾폿퐀퐁퐂퐃퐄퐅퐆퐇퐈퐉퐊퐋퐌퐍퐎퐏퐐퐑퐒퐓퐔퐕퐖퐗퐘퐙퐚퐛퐜퐝퐞퐟퐠퐡퐢퐣퐤퐥퐦퐧퐨퐩퐪퐫퐬퐭퐮퐯퐰퐱퐲퐳퐴퐵퐶퐷퐸퐹퐺퐻퐼퐽퐾퐿푀푁푂푃푄푅푆푇푈푉푊푋푌푍푎푏푐푑푒푓푔푕푖푗푘푙푚푛표푝푞푟푠푡푢푣푤푥푦푧푨푩푪푫푬푭푮푯푰푱푲푳푴푵푶푷푸푹푺푻푼푽푾푿풀풁풂풃풄풅풆풇품풉풊풋풌풍풎풏풐풑풒풓풔풕풖풗풘풙풚풛풜풝풞풟풠풡풢풣풤풥풦풧풨풩풪풫풬풭풮풯풰풱풲풳풴풵풶풷풸풹풺풻풼풽풾풿퓀퓁퓂퓃퓄퓅퓆퓇퓈퓉퓊퓋퓌퓍퓎퓏퓐퓑퓒퓓퓔퓕퓖퓗퓘퓙퓚퓛퓜퓝퓞퓟퓠퓡퓢퓣퓤퓥퓦퓧퓨퓩퓪퓫퓬퓭퓮퓯퓰퓱퓲퓳퓴퓵퓶퓷퓸퓹퓺퓻퓼퓽퓾퓿픀픁픂픃프픅픆픇픈픉픊픋플픍픎픏픐픑픒픓픔픕픖픗픘픙픚픛픜픝픞픟픠픡픢픣픤픥픦픧픨픩픪픫픬픭픮픯픰픱픲픳픴픵픶픷픸픹픺픻피픽픾픿핀핁핂핃필핅핆핇핈핉핊핋핌핍핎핏핐핑핒핓핔핕핖핗하학핚핛한핝핞핟할핡핢핣핤핥핦핧함합핪핫핬항핮핯핰핱핲핳해핵핶핷핸핹핺핻핼핽핾핿햀햁햂햃햄햅햆햇했행햊햋햌햍햎햏햐햑햒햓햔햕햖햗햘햙햚햛햜햝햞햟햠햡햢햣햤향햦햧햨햩햪햫햬햭햮햯햰햱햲햳햴햵햶햷햸햹햺햻햼햽햾햿헀헁헂헃헄헅헆헇허헉헊헋헌헍헎헏헐헑헒헓헔헕헖헗험헙헚헛헜헝헞헟헠헡헢헣헤헥헦헧헨헩헪헫헬헭헮헯헰헱헲헳헴헵헶헷헸헹헺헻헼헽헾헿혀혁혂혃현혅혆혇혈혉혊혋혌혍혎혏혐협혒혓혔형혖혗혘혙혚혛혜혝혞혟혠혡혢혣혤혥혦혧혨혩혪혫혬혭혮혯혰혱혲혳혴혵혶혷호혹혺혻혼혽혾혿홀홁홂홃홄홅홆홇홈홉홊홋홌홍홎홏홐홑홒홓화확홖홗환홙홚홛활홝홞홟홠홡홢홣홤홥홦홧홨황홪홫홬홭홮홯홰홱홲홳홴홵홶홷홸홹홺홻홼홽홾홿횀횁횂횃횄횅횆횇횈횉횊횋회획횎횏횐횑횒횓횔횕횖횗횘횙횚횛횜횝횞횟횠횡횢횣횤횥횦횧효횩횪횫횬횭횮횯횰횱횲횳횴횵횶횷횸횹횺횻횼횽횾횿훀훁훂훃후훅훆훇훈훉훊훋훌훍훎훏훐훑훒훓훔훕훖훗훘훙훚훛훜훝훞훟훠훡훢훣훤훥훦훧훨훩훪훫훬훭훮훯훰훱훲훳훴훵훶훷훸훹훺훻훼훽훾훿휀휁휂휃휄휅휆휇휈휉휊휋휌휍휎휏휐휑휒휓휔휕휖휗휘휙휚휛휜휝휞휟휠휡휢휣휤휥휦휧휨휩휪휫휬휭휮휯휰휱휲휳휴휵휶휷휸휹휺휻휼휽휾휿흀흁흂흃흄흅흆흇흈흉흊흋흌흍흎흏흐흑흒흓흔흕흖흗흘흙흚흛흜흝흞흟흠흡흢흣흤흥흦흧흨흩흪흫희흭흮흯흰흱흲흳흴흵흶흷흸흹흺흻흼흽흾흿힀힁힂힃힄힅힆힇히힉힊힋힌힍힎힏힐힑힒힓힔힕힖힗힘힙힚힛힜힝힞힟힠힡힢힣!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~。・〜°—、「」『』【】゛》《〉〈£€¥¢฿₩ - * - simplified_chinese - - 6839 - - 0123456789啊阿埃挨哎唉哀皑癌蔼矮艾碍爱隘鞍氨安俺按暗岸胺案肮昂盎凹敖熬翱袄傲奥懊澳芭捌扒叭吧笆八疤巴拔跋靶把耙坝霸罢爸白柏百摆佰败拜稗斑班搬扳般颁板版扮拌伴瓣半办绊邦帮梆榜膀绑棒磅蚌镑傍谤苞胞包褒剥薄雹保堡饱宝抱报暴豹鲍爆杯碑悲卑北辈背贝钡倍狈备惫焙被奔苯本笨崩绷甭泵蹦迸逼鼻比鄙笔彼碧蓖蔽毕毙毖币庇痹闭敝弊必辟壁臂避陛鞭边编贬扁便变卞辨辩辫遍标彪膘表鳖憋别瘪彬斌濒滨宾摈兵冰柄丙秉饼炳病并玻菠播拨钵波博勃搏铂箔伯帛舶脖膊渤泊驳捕卜哺补埠不布步簿部怖擦猜裁材才财睬踩采彩菜蔡餐参蚕残惭惨灿苍舱仓沧藏操糙槽曹草厕策侧册测层蹭插叉茬茶查碴搽察岔差诧拆柴豺搀掺蝉馋谗缠铲产阐颤昌猖场尝常长偿肠厂敞畅唱倡超抄钞朝嘲潮巢吵炒车扯撤掣彻澈郴臣辰尘晨忱沉陈趁衬撑称城橙成呈乘程惩澄诚承逞骋秤吃痴持匙池迟弛驰耻齿侈尺赤翅斥炽充冲虫崇宠抽酬畴踌稠愁筹仇绸瞅丑臭初出橱厨躇锄雏滁除楚础储矗搐触处揣川穿椽传船喘串疮窗幢床闯创吹炊捶锤垂春椿醇唇淳纯蠢戳绰疵茨磁雌辞慈瓷词此刺赐次聪葱囱匆从丛凑粗醋簇促蹿篡窜摧崔催脆瘁粹淬翠村存寸磋撮搓措挫错搭达答瘩打大呆歹傣戴带殆代贷袋待逮怠耽担丹单郸掸胆旦氮但惮淡诞弹蛋当挡党荡档刀捣蹈倒岛祷导到稻悼道盗德得的蹬灯登等瞪凳邓堤低滴迪敌笛狄涤翟嫡抵底地蒂第帝弟递缔颠掂滇碘点典靛垫电佃甸店惦奠淀殿碉叼雕凋刁掉吊钓调跌爹碟蝶迭谍叠丁盯叮钉顶鼎锭定订丢东冬董懂动栋侗恫冻洞兜抖斗陡豆逗痘都督毒犊独读堵睹赌杜镀肚度渡妒端短锻段断缎堆兑队对墩吨蹲敦顿囤钝盾遁掇哆多夺垛躲朵跺舵剁惰堕蛾峨鹅俄额讹娥恶厄扼遏鄂饿恩而儿耳尔饵洱二贰发罚筏伐乏阀法珐藩帆番翻樊矾钒繁凡烦反返范贩犯饭泛坊芳方肪房防妨仿访纺放菲非啡飞肥匪诽吠肺废沸费芬酚吩氛分纷坟焚汾粉奋份忿愤粪丰封枫蜂峰锋风疯烽逢冯缝讽奉凤佛否夫敷肤孵扶拂辐幅氟符伏俘服浮涪福袱弗甫抚辅俯釜斧脯腑府腐赴副覆赋复傅付阜父腹负富讣附妇缚咐噶嘎该改概钙盖溉干甘杆柑竿肝赶感秆敢赣冈刚钢缸肛纲岗港杠篙皋高膏羔糕搞镐稿告哥歌搁戈鸽胳疙割革葛格蛤阁隔铬个各给根跟耕更庚羹埂耿梗工攻功恭龚供躬公宫弓巩汞拱贡共钩勾沟苟狗垢构购够辜菇咕箍估沽孤姑鼓古蛊骨谷股故顾固雇刮瓜剐寡挂褂乖拐怪棺关官冠观管馆罐惯灌贯光广逛瑰规圭硅归龟闺轨鬼诡癸桂柜跪贵刽辊滚棍锅郭国果裹过哈骸孩海氦亥害骇酣憨邯韩含涵寒函喊罕翰撼捍旱憾悍焊汗汉夯杭航壕嚎豪毫郝好耗号浩呵喝荷菏核禾和何合盒貉阂河涸赫褐鹤贺嘿黑痕很狠恨哼亨横衡恒轰哄烘虹鸿洪宏弘红喉侯猴吼厚候后呼乎忽瑚壶葫胡蝴狐糊湖弧虎唬护互沪户花哗华猾滑画划化话槐徊怀淮坏欢环桓还缓换患唤痪豢焕涣宦幻荒慌黄磺蝗簧皇凰惶煌晃幌恍谎灰挥辉徽恢蛔回毁悔慧卉惠晦贿秽会烩汇讳诲绘荤昏婚魂浑混豁活伙火获或惑霍货祸击圾基机畸稽积箕肌饥迹激讥鸡姬绩缉吉极棘辑籍集及急疾汲即嫉级挤几脊己蓟技冀季伎祭剂悸济寄寂计记既忌际妓继纪嘉枷夹佳家加荚颊贾甲钾假稼价架驾嫁歼监坚尖笺间煎兼肩艰奸缄茧检柬碱硷拣捡简俭剪减荐槛鉴践贱见键箭件健舰剑饯渐溅涧建僵姜将浆江疆蒋桨奖讲匠酱降蕉椒礁焦胶交郊浇骄娇嚼搅铰矫侥脚狡角饺缴绞剿教酵轿较叫窖揭接皆秸街阶截劫节桔杰捷睫竭洁结解姐戒藉芥界借介疥诫届巾筋斤金今津襟紧锦仅谨进靳晋禁近烬浸尽劲荆兢茎睛晶鲸京惊精粳经井警景颈静境敬镜径痉靖竟竞净炯窘揪究纠玖韭久灸九酒厩救旧臼舅咎就疚鞠拘狙疽居驹菊局咀矩举沮聚拒据巨具距踞锯俱句惧炬剧捐鹃娟倦眷卷绢撅攫抉掘倔爵觉决诀绝均菌钧军君峻俊竣浚郡骏喀咖卡咯开揩楷凯慨刊堪勘坎砍看康慷糠扛抗亢炕考拷烤靠坷苛柯棵磕颗科壳咳可渴克刻客课肯啃垦恳坑吭空恐孔控抠口扣寇枯哭窟苦酷库裤夸垮挎跨胯块筷侩快宽款匡筐狂框矿眶旷况亏盔岿窥葵奎魁傀馈愧溃坤昆捆困括扩廓阔垃拉喇蜡腊辣啦莱来赖蓝婪栏拦篮阑兰澜谰揽览懒缆烂滥琅榔狼廊郎朗浪捞劳牢老佬姥酪烙涝勒乐雷镭蕾磊累儡垒擂肋类泪棱楞冷厘梨犁黎篱狸离漓理李里鲤礼莉荔吏栗丽厉励砾历利傈例俐痢立粒沥隶力璃哩俩联莲连镰廉怜涟帘敛脸链恋炼练粮凉梁粱良两辆量晾亮谅撩聊僚疗燎寥辽潦了撂镣廖料列裂烈劣猎琳林磷霖临邻鳞淋凛赁吝拎玲菱零龄铃伶羚凌灵陵岭领另令溜琉榴硫馏留刘瘤流柳六龙聋咙笼窿隆垄拢陇楼娄搂篓漏陋芦卢颅庐炉掳卤虏鲁麓碌露路赂鹿潞禄录陆戮驴吕铝侣旅履屡缕虑氯律率滤绿峦挛孪滦卵乱掠略抡轮伦仑沦纶论萝螺罗逻锣箩骡裸落洛骆络妈麻玛码蚂马骂嘛吗埋买麦卖迈脉瞒馒蛮满蔓曼慢漫谩芒茫盲氓忙莽猫茅锚毛矛铆卯茂冒帽貌贸么玫枚梅酶霉煤没眉媒镁每美昧寐妹媚门闷们萌蒙檬盟锰猛梦孟眯醚靡糜迷谜弥米秘觅泌蜜密幂棉眠绵冕免勉娩缅面苗描瞄藐秒渺庙妙蔑灭民抿皿敏悯闽明螟鸣铭名命谬摸摹蘑模膜磨摩魔抹末莫墨默沫漠寞陌谋牟某拇牡亩姆母墓暮幕募慕木目睦牧穆拿哪呐钠那娜纳氖乃奶耐奈南男难囊挠脑恼闹淖呢馁内嫩能妮霓倪泥尼拟你匿腻逆溺蔫拈年碾撵捻念娘酿鸟尿捏聂孽啮镊镍涅您柠狞凝宁拧泞牛扭钮纽脓浓农弄奴努怒女暖虐疟挪懦糯诺哦欧鸥殴藕呕偶沤啪趴爬帕怕琶拍排牌徘湃派攀潘盘磐盼畔判叛乓庞旁耪胖抛咆刨炮袍跑泡呸胚培裴赔陪配佩沛喷盆砰抨烹澎彭蓬棚硼篷膨朋鹏捧碰坯砒霹批披劈琵毗啤脾疲皮匹痞僻屁譬篇偏片骗飘漂瓢票撇瞥拼频贫品聘乒坪苹萍平凭瓶评屏坡泼颇婆破魄迫粕剖扑铺仆莆葡菩蒲埔朴圃普浦谱曝瀑期欺栖戚妻七凄漆柒沏其棋奇歧畦崎脐齐旗祈祁骑起岂乞企启契砌器气迄弃汽泣讫掐恰洽牵扦钎铅千迁签仟谦乾黔钱钳前潜遣浅谴堑嵌欠歉枪呛腔羌墙蔷强抢橇锹敲悄桥瞧乔侨巧鞘撬翘峭俏窍切茄且怯窃钦侵亲秦琴勤芹擒禽寝沁青轻氢倾卿清擎晴氰情顷请庆琼穷秋丘邱球求囚酋泅趋区蛆曲躯屈驱渠取娶龋趣去圈颧权醛泉全痊拳犬券劝缺炔瘸却鹊榷确雀裙群然燃冉染瓤壤攘嚷让饶扰绕惹热壬仁人忍韧任认刃妊纫扔仍日戎茸蓉荣融熔溶容绒冗揉柔肉茹蠕儒孺如辱乳汝入褥软阮蕊瑞锐闰润若弱撒洒萨腮鳃塞赛三叁伞散桑嗓丧搔骚扫嫂瑟色涩森僧莎砂杀刹沙纱傻啥煞筛晒珊苫杉山删煽衫闪陕擅赡膳善汕扇缮墒伤商赏晌上尚裳梢捎稍烧芍勺韶少哨邵绍奢赊蛇舌舍赦摄射慑涉社设砷申呻伸身深娠绅神沈审婶甚肾慎渗声生甥牲升绳省盛剩胜圣师失狮施湿诗尸虱十石拾时什食蚀实识史矢使屎驶始式示士世柿事拭誓逝势是嗜噬适仕侍释饰氏市恃室视试收手首守寿授售受瘦兽蔬枢梳殊抒输叔舒淑疏书赎孰熟薯暑曙署蜀黍鼠属术述树束戍竖墅庶数漱恕刷耍摔衰甩帅栓拴霜双爽谁水睡税吮瞬顺舜说硕朔烁斯撕嘶思私司丝死肆寺嗣四伺似饲巳松耸怂颂送宋讼诵搜艘擞嗽苏酥俗素速粟僳塑溯宿诉肃酸蒜算虽隋随绥髓碎岁穗遂隧祟孙损笋蓑梭唆缩琐索锁所塌他它她塔獭挞蹋踏胎苔抬台泰酞太态汰坍摊贪瘫滩坛檀痰潭谭谈坦毯袒碳探叹炭汤塘搪堂棠膛唐糖倘躺淌趟烫掏涛滔绦萄桃逃淘陶讨套特藤腾疼誊梯剔踢锑提题蹄啼体替嚏惕涕剃屉天添填田甜恬舔腆挑条迢眺跳贴铁帖厅听烃汀廷停亭庭挺艇通桐酮瞳同铜彤童桶捅筒统痛偷投头透凸秃突图徒途涂屠土吐兔湍团推颓腿蜕褪退吞屯臀拖托脱鸵陀驮驼椭妥拓唾挖哇蛙洼娃瓦袜歪外豌弯湾玩顽丸烷完碗挽晚皖惋宛婉万腕汪王亡枉网往旺望忘妄威巍微危韦违桅围唯惟为潍维苇萎委伟伪尾纬未蔚味畏胃喂魏位渭谓尉慰卫瘟温蚊文闻纹吻稳紊问嗡翁瓮挝蜗涡窝我斡卧握沃巫呜钨乌污诬屋无芜梧吾吴毋武五捂午舞伍侮坞戊雾晤物勿务悟误昔熙析西硒矽晰嘻吸锡牺稀息希悉膝夕惜熄烯溪汐犀檄袭席习媳喜铣洗系隙戏细瞎虾匣霞辖暇峡侠狭下厦夏吓掀锨先仙鲜纤咸贤衔舷闲涎弦嫌显险现献县腺馅羡宪陷限线相厢镶香箱襄湘乡翔祥详想响享项巷橡像向象萧硝霄削哮嚣销消宵淆晓小孝校肖啸笑效楔些歇蝎鞋协挟携邪斜胁谐写械卸蟹懈泄泻谢屑薪芯锌欣辛新忻心信衅星腥猩惺兴刑型形邢行醒幸杏性姓兄凶胸匈汹雄熊休修羞朽嗅锈秀袖绣墟戌需虚嘘须徐许蓄酗叙旭序畜恤絮婿绪续轩喧宣悬旋玄选癣眩绚靴薛学穴雪血勋熏循旬询寻驯巡殉汛训讯逊迅压押鸦鸭呀丫芽牙蚜崖衙涯雅哑亚讶焉咽阉烟淹盐严研蜒岩延言颜阎炎沿奄掩眼衍演艳堰燕厌砚雁唁彦焰宴谚验殃央鸯秧杨扬佯疡羊洋阳氧仰痒养样漾邀腰妖瑶摇尧遥窑谣姚咬舀药要耀椰噎耶爷野冶也页掖业叶曳腋夜液一壹医揖铱依伊衣颐夷遗移仪胰疑沂宜姨彝椅蚁倚已乙矣以艺抑易邑屹亿役臆逸肄疫亦裔意毅忆义益溢诣议谊译异翼翌绎茵荫因殷音阴姻吟银淫寅饮尹引隐印英樱婴鹰应缨莹萤营荧蝇迎赢盈影颖硬映哟拥佣臃痈庸雍踊蛹咏泳涌永恿勇用幽优悠忧尤由邮铀犹油游酉有友右佑釉诱又幼迂淤于盂榆虞愚舆余俞逾鱼愉渝渔隅予娱雨与屿禹宇语羽玉域芋郁吁遇喻峪御愈欲狱育誉浴寓裕预豫驭鸳渊冤元垣袁原援辕园员圆猿源缘远苑愿怨院曰约越跃钥岳粤月悦阅耘云郧匀陨允运蕴酝晕韵孕匝砸杂栽哉灾宰载再在咱攒暂赞赃脏葬遭糟凿藻枣早澡蚤躁噪造皂灶燥责择则泽贼怎增憎曾赠扎喳渣札轧铡闸眨栅榨咋乍炸诈摘斋宅窄债寨瞻毡詹粘沾盏斩辗崭展蘸栈占战站湛绽樟章彰漳张掌涨杖丈帐账仗胀瘴障招昭找沼赵照罩兆肇召遮折哲蛰辙者锗蔗这浙珍斟真甄砧臻贞针侦枕疹诊震振镇阵蒸挣睁征狰争怔整拯正政帧症郑证芝枝支吱蜘知肢脂汁之织职直植殖执值侄址指止趾只旨纸志挚掷至致置帜峙制智秩稚质炙痔滞治窒中盅忠钟衷终种肿重仲众舟周州洲诌粥轴肘帚咒皱宙昼骤珠株蛛朱猪诸诛逐竹烛煮拄瞩嘱主著柱助蛀贮铸筑住注祝驻抓爪拽专砖转撰赚篆桩庄装妆撞壮状椎锥追赘坠缀谆准捉拙卓桌琢茁酌啄着灼浊兹咨资姿滋淄孜紫仔籽滓子自渍字鬃棕踪宗综总纵邹走奏揍租足卒族祖诅阻组钻纂嘴醉最罪尊遵昨左佐柞做作坐座亍丌兀丐廿卅丕亘丞鬲孬噩丨禺丿匕乇夭爻卮氐囟胤馗毓睾鼗丶亟鼐乜乩亓芈孛啬嘏仄厍厝厣厥厮靥赝匚叵匦匮匾赜卦卣刂刈刎刭刳刿剀剌剞剡剜蒯剽劂劁劐劓冂罔亻仃仉仂仨仡仫仞伛仳伢佤仵伥伧伉伫佞佧攸佚佝佟佗伲伽佶佴侑侉侃侏佾佻侪佼侬侔俦俨俪俅俚俣俜俑俟俸倩偌俳倬倏倮倭俾倜倌倥倨偾偃偕偈偎偬偻傥傧傩傺僖儆僭僬僦僮儇儋仝氽佘佥俎龠汆籴兮巽黉馘冁夔勹匍訇匐凫夙兕亠兖亳衮袤亵脔裒禀嬴蠃羸冫冱冽冼凇冖冢冥讠讦讧讪讴讵讷诂诃诋诏诎诒诓诔诖诘诙诜诟诠诤诨诩诮诰诳诶诹诼诿谀谂谄谇谌谏谑谒谔谕谖谙谛谘谝谟谠谡谥谧谪谫谮谯谲谳谵谶卩卺阝阢阡阱阪阽阼陂陉陔陟陧陬陲陴隈隍隗隰邗邛邝邙邬邡邴邳邶邺邸邰郏郅邾郐郄郇郓郦郢郜郗郛郫郯郾鄄鄢鄞鄣鄱鄯鄹酃酆刍奂劢劬劭劾哿勐勖勰叟燮矍廴凵凼鬯厶弁畚巯坌垩垡塾墼壅壑圩圬圪圳圹圮圯坜圻坂坩垅坫垆坼坻坨坭坶坳垭垤垌垲埏垧垴垓垠埕埘埚埙埒垸埴埯埸埤埝堋堍埽埭堀堞堙塄堠塥塬墁墉墚墀馨鼙懿艹艽艿芏芊芨芄芎芑芗芙芫芸芾芰苈苊苣芘芷芮苋苌苁芩芴芡芪芟苄苎芤苡茉苷苤茏茇苜苴苒苘茌苻苓茑茚茆茔茕苠苕茜荑荛荜茈莒茼茴茱莛荞茯荏荇荃荟荀茗荠茭茺茳荦荥荨茛荩荬荪荭荮莰荸莳莴莠莪莓莜莅荼莶莩荽莸荻莘莞莨莺莼菁萁菥菘堇萘萋菝菽菖萜萸萑萆菔菟萏萃菸菹菪菅菀萦菰菡葜葑葚葙葳蒇蒈葺蒉葸萼葆葩葶蒌蒎萱葭蓁蓍蓐蓦蒽蓓蓊蒿蒺蓠蒡蒹蒴蒗蓥蓣蔌甍蔸蓰蔹蔟蔺蕖蔻蓿蓼蕙蕈蕨蕤蕞蕺瞢蕃蕲蕻薤薨薇薏蕹薮薜薅薹薷薰藓藁藜藿蘧蘅蘩蘖蘼廾弈夼奁耷奕奚奘匏尢尥尬尴扌扪抟抻拊拚拗拮挢拶挹捋捃掭揶捱捺掎掴捭掬掊捩掮掼揲揸揠揿揄揞揎摒揆掾摅摁搋搛搠搌搦搡摞撄摭撖摺撷撸撙撺擀擐擗擤擢攉攥攮弋忒甙弑卟叱叽叩叨叻吒吖吆呋呒呓呔呖呃吡呗呙吣吲咂咔呷呱呤咚咛咄呶呦咝哐咭哂咴哒咧咦哓哔呲咣哕咻咿哌哙哚哜咩咪咤哝哏哞唛哧唠哽唔哳唢唣唏唑唧唪啧喏喵啉啭啁啕唿啐唼唷啖啵啶啷唳唰啜喋嗒喃喱喹喈喁喟啾嗖喑啻嗟喽喾喔喙嗪嗷嗉嘟嗑嗫嗬嗔嗦嗝嗄嗯嗥嗲嗳嗌嗍嗨嗵嗤辔嘞嘈嘌嘁嘤嘣嗾嘀嘧嘭噘嘹噗嘬噍噢噙噜噌噔嚆噤噱噫噻噼嚅嚓嚯囔囗囝囡囵囫囹囿圄圊圉圜帏帙帔帑帱帻帼帷幄幔幛幞幡岌屺岍岐岖岈岘岙岑岚岜岵岢岽岬岫岱岣峁岷峄峒峤峋峥崂崃崧崦崮崤崞崆崛嵘崾崴崽嵬嵛嵯嵝嵫嵋嵊嵩嵴嶂嶙嶝豳嶷巅彳彷徂徇徉後徕徙徜徨徭徵徼衢彡犭犰犴犷犸狃狁狎狍狒狨狯狩狲狴狷猁狳猃狺狻猗猓猡猊猞猝猕猢猹猥猬猸猱獐獍獗獠獬獯獾舛夥飧夤夂饣饧饨饩饪饫饬饴饷饽馀馄馇馊馍馐馑馓馔馕庀庑庋庖庥庠庹庵庾庳赓廒廑廛廨廪膺忄忉忖忏怃忮怄忡忤忾怅怆忪忭忸怙怵怦怛怏怍怩怫怊怿怡恸恹恻恺恂恪恽悖悚悭悝悃悒悌悛惬悻悱惝惘惆惚悴愠愦愕愣惴愀愎愫慊慵憬憔憧憷懔懵忝隳闩闫闱闳闵闶闼闾阃阄阆阈阊阋阌阍阏阒阕阖阗阙阚丬爿戕氵汔汜汊沣沅沐沔沌汨汩汴汶沆沩泐泔沭泷泸泱泗沲泠泖泺泫泮沱泓泯泾洹洧洌浃浈洇洄洙洎洫浍洮洵洚浏浒浔洳涑浯涞涠浞涓涔浜浠浼浣渚淇淅淞渎涿淠渑淦淝淙渖涫渌涮渫湮湎湫溲湟溆湓湔渲渥湄滟溱溘滠漭滢溥溧溽溻溷滗溴滏溏滂溟潢潆潇漤漕滹漯漶潋潴漪漉漩澉澍澌潸潲潼潺濑濉澧澹澶濂濡濮濞濠濯瀚瀣瀛瀹瀵灏灞宀宄宕宓宥宸甯骞搴寤寮褰寰蹇謇辶迓迕迥迮迤迩迦迳迨逅逄逋逦逑逍逖逡逵逶逭逯遄遑遒遐遨遘遢遛暹遴遽邂邈邃邋彐彗彖彘尻咫屐屙孱屣屦羼弪弩弭艴弼鬻屮妁妃妍妩妪妣妗姊妫妞妤姒妲妯姗妾娅娆姝娈姣姘姹娌娉娲娴娑娣娓婀婧婊婕娼婢婵胬媪媛婷婺媾嫫媲嫒嫔媸嫠嫣嫱嫖嫦嫘嫜嬉嬗嬖嬲嬷孀尕尜孚孥孳孑孓孢驵驷驸驺驿驽骀骁骅骈骊骐骒骓骖骘骛骜骝骟骠骢骣骥骧纟纡纣纥纨纩纭纰纾绀绁绂绉绋绌绐绔绗绛绠绡绨绫绮绯绱绲缍绶绺绻绾缁缂缃缇缈缋缌缏缑缒缗缙缜缛缟缡缢缣缤缥缦缧缪缫缬缭缯缰缱缲缳缵幺畿巛甾邕玎玑玮玢玟珏珂珑玷玳珀珉珈珥珙顼琊珩珧珞玺珲琏琪瑛琦琥琨琰琮琬琛琚瑁瑜瑗瑕瑙瑷瑭瑾璜璎璀璁璇璋璞璨璩璐璧瓒璺韪韫韬杌杓杞杈杩枥枇杪杳枘枧杵枨枞枭枋杷杼柰栉柘栊柩枰栌柙枵柚枳柝栀柃枸柢栎柁柽栲栳桠桡桎桢桄桤梃栝桕桦桁桧桀栾桊桉栩梵梏桴桷梓桫棂楮棼椟椠棹椤棰椋椁楗棣椐楱椹楠楂楝榄楫榀榘楸椴槌榇榈槎榉楦楣楹榛榧榻榫榭槔榱槁槊槟榕槠榍槿樯槭樗樘橥槲橄樾檠橐橛樵檎橹樽樨橘橼檑檐檩檗檫猷獒殁殂殇殄殒殓殍殚殛殡殪轫轭轱轲轳轵轶轸轷轹轺轼轾辁辂辄辇辋辍辎辏辘辚軎戋戗戛戟戢戡戥戤戬臧瓯瓴瓿甏甑甓攴旮旯旰昊昙杲昃昕昀炅曷昝昴昱昶昵耆晟晔晁晏晖晡晗晷暄暌暧暝暾曛曜曦曩贲贳贶贻贽赀赅赆赈赉赇赍赕赙觇觊觋觌觎觏觐觑牮犟牝牦牯牾牿犄犋犍犏犒挈挲掰搿擘耄毪毳毽毵毹氅氇氆氍氕氘氙氚氡氩氤氪氲攵敕敫牍牒牖爰虢刖肟肜肓肼朊肽肱肫肭肴肷胧胨胩胪胛胂胄胙胍胗朐胝胫胱胴胭脍脎胲胼朕脒豚脶脞脬脘脲腈腌腓腴腙腚腱腠腩腼腽腭腧塍媵膈膂膑滕膣膪臌朦臊膻臁膦欤欷欹歃歆歙飑飒飓飕飙飚殳彀毂觳斐齑斓於旆旄旃旌旎旒旖炀炜炖炝炻烀炷炫炱烨烊焐焓焖焯焱煳煜煨煅煲煊煸煺熘熳熵熨熠燠燔燧燹爝爨灬焘煦熹戾戽扃扈扉礻祀祆祉祛祜祓祚祢祗祠祯祧祺禅禊禚禧禳忑忐怼恝恚恧恁恙恣悫愆愍慝憩憝懋懑戆肀聿沓泶淼矶矸砀砉砗砘砑斫砭砜砝砹砺砻砟砼砥砬砣砩硎硭硖硗砦硐硇硌硪碛碓碚碇碜碡碣碲碹碥磔磙磉磬磲礅磴礓礤礞礴龛黹黻黼盱眄眍盹眇眈眚眢眙眭眦眵眸睐睑睇睃睚睨睢睥睿瞍睽瞀瞌瞑瞟瞠瞰瞵瞽町畀畎畋畈畛畲畹疃罘罡罟詈罨罴罱罹羁罾盍盥蠲钅钆钇钋钊钌钍钏钐钔钗钕钚钛钜钣钤钫钪钭钬钯钰钲钴钶钷钸钹钺钼钽钿铄铈铉铊铋铌铍铎铐铑铒铕铖铗铙铘铛铞铟铠铢铤铥铧铨铪铩铫铮铯铳铴铵铷铹铼铽铿锃锂锆锇锉锊锍锎锏锒锓锔锕锖锘锛锝锞锟锢锪锫锩锬锱锲锴锶锷锸锼锾锿镂锵镄镅镆镉镌镎镏镒镓镔镖镗镘镙镛镞镟镝镡镢镤镥镦镧镨镩镪镫镬镯镱镲镳锺矧矬雉秕秭秣秫稆嵇稃稂稞稔稹稷穑黏馥穰皈皎皓皙皤瓞瓠甬鸠鸢鸨鸩鸪鸫鸬鸲鸱鸶鸸鸷鸹鸺鸾鹁鹂鹄鹆鹇鹈鹉鹋鹌鹎鹑鹕鹗鹚鹛鹜鹞鹣鹦鹧鹨鹩鹪鹫鹬鹱鹭鹳疒疔疖疠疝疬疣疳疴疸痄疱疰痃痂痖痍痣痨痦痤痫痧瘃痱痼痿瘐瘀瘅瘌瘗瘊瘥瘘瘕瘙瘛瘼瘢瘠癀瘭瘰瘿瘵癃瘾瘳癍癞癔癜癖癫癯翊竦穸穹窀窆窈窕窦窠窬窨窭窳衤衩衲衽衿袂袢裆袷袼裉裢裎裣裥裱褚裼裨裾裰褡褙褓褛褊褴褫褶襁襦襻疋胥皲皴矜耒耔耖耜耠耢耥耦耧耩耨耱耋耵聃聆聍聒聩聱覃顸颀颃颉颌颍颏颔颚颛颞颟颡颢颥颦虍虔虬虮虿虺虼虻蚨蚍蚋蚬蚝蚧蚣蚪蚓蚩蚶蛄蚵蛎蚰蚺蚱蚯蛉蛏蚴蛩蛱蛲蛭蛳蛐蜓蛞蛴蛟蛘蛑蜃蜇蛸蜈蜊蜍蜉蜣蜻蜞蜥蜮蜚蜾蝈蜴蜱蜩蜷蜿螂蜢蝽蝾蝻蝠蝰蝌蝮螋蝓蝣蝼蝤蝙蝥螓螯螨蟒蟆螈螅螭螗螃螫蟥螬螵螳蟋蟓螽蟑蟀蟊蟛蟪蟠蟮蠖蠓蟾蠊蠛蠡蠹蠼缶罂罄罅舐竺竽笈笃笄笕笊笫笏筇笸笪笙笮笱笠笥笤笳笾笞筘筚筅筵筌筝筠筮筻筢筲筱箐箦箧箸箬箝箨箅箪箜箢箫箴篑篁篌篝篚篥篦篪簌篾篼簏簖簋簟簪簦簸籁籀臾舁舂舄臬衄舡舢舣舭舯舨舫舸舻舳舴舾艄艉艋艏艚艟艨衾袅袈裘裟襞羝羟羧羯羰羲籼敉粑粝粜粞粢粲粼粽糁糇糌糍糈糅糗糨艮暨羿翎翕翥翡翦翩翮翳糸絷綦綮繇纛麸麴赳趄趔趑趱赧赭豇豉酊酐酎酏酤酢酡酰酩酯酽酾酲酴酹醌醅醐醍醑醢醣醪醭醮醯醵醴醺豕鹾趸跫踅蹙蹩趵趿趼趺跄跖跗跚跞跎跏跛跆跬跷跸跣跹跻跤踉跽踔踝踟踬踮踣踯踺蹀踹踵踽踱蹉蹁蹂蹑蹒蹊蹰蹶蹼蹯蹴躅躏躔躐躜躞豸貂貊貅貘貔斛觖觞觚觜觥觫觯訾謦靓雩雳雯霆霁霈霏霎霪霭霰霾龀龃龅龆龇龈龉龊龌黾鼋鼍隹隼隽雎雒瞿雠銎銮鋈錾鍪鏊鎏鐾鑫鱿鲂鲅鲆鲇鲈稣鲋鲎鲐鲑鲒鲔鲕鲚鲛鲞鲟鲠鲡鲢鲣鲥鲦鲧鲨鲩鲫鲭鲮鲰鲱鲲鲳鲴鲵鲶鲷鲺鲻鲼鲽鳄鳅鳆鳇鳊鳋鳌鳍鳎鳏鳐鳓鳔鳕鳗鳘鳙鳜鳝鳟鳢靼鞅鞑鞒鞔鞯鞫鞣鞲鞴骱骰骷鹘骶骺骼髁髀髅髂髋髌髑魅魃魇魉魈魍魑飨餍餮饕饔髟髡髦髯髫髻髭髹鬈鬏鬓鬟鬣麽麾縻麂麇麈麋麒鏖麝麟黛黜黝黠黟黢黩黧黥黪黯鼢鼬鼯鼹鼷鼽鼾齄!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~,、。:;?!()《》〈〉「」『』【】—〜…·°“”‘’£€¥¢฿¥ - * - multilingual - - 601 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~°£€¥¢฿çëÇËèïîôûêÈÏÎÔÛÊáéíóúäöÁÉÍÓÚÄÖəğışüƏĞİŞÜ₼ñÑčćđšžČĆĐŠŽàòÀÒ·ŀĿďěňřťůýĎĚŇŘŤŮÝæøåÆØÅõÕĉĝĥĵŝŭĈĜĤĴŜŬ₷âùÂÙƒƑßẞɓɗƙƴƁƊƘƳ₦őűŐŰðþÐÞìÌāēģīķļņūĀĒĢĪĶĻŅŪąęėįųĄĘĖĮŲōŌċġħżĊĠĦŻśźŚŹłńŁŃãÃășțĂȘȚľĺŕĽĹŔ¡¿₱₺ʻʼảạắằẳẵặấầẩẫậẻẽẹếềểễệỏọốồổộỗơớờởợỡủũụưứừửữựỉĩịỳỷỹỵẢẠẮẰẲẴẶẤẦẨẪẬẺẼẸẾỀỂỄỆỎỌỐỒỔỘỖƠỚỜỞỢỠỦŨỤƯỨỪỬỮỰỈĨỊỲỶỸỴ₫ŵŷŴŶṣṢǹǸ§абвгдежзийклмнопрстуфхцчшщьюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЮЯёыэЁЫЭъЪ₽ўіЎІґїєҐЇЄ₴ӘәҖҗҢңӨөҮүҺһҒғҚқҲҳҶҷӢӣӮӯҰұ₸ЃѓЅѕЈјЉљЊњЌќЏџ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙₮ҔҕҤҥЂђЋћαβγδεζηθικλμνξοπρστςυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩάέήίϊΐόύϋΰώΆΈΉΊΪΌΎΫΏ diff --git a/docs/source/modules/io.rst b/docs/source/modules/io.rst deleted file mode 100644 index df556dad03..0000000000 --- a/docs/source/modules/io.rst +++ /dev/null @@ -1,240 +0,0 @@ -doctr.io -======== - - -.. currentmodule:: doctr.io - -The io module enables users to easily access content from documents and export analysis -results to structured formats. - -.. _document_structure: - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -Prediction -^^^^^^^^^^ -A Prediction is a Word with an additional crop orientation field indicating the detected text rotation angle. - -.. autoclass:: Prediction - -Line -^^^^ -A Line is a collection of Words aligned spatially and meant to be read together (on a two-column page, on the same horizontal, we will consider that there are two Lines). - -.. autoclass:: Line - -Artefact -^^^^^^^^ - -An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.). - -.. autoclass:: Artefact - -LayoutElement -^^^^^^^^^^^^^ - -A LayoutElement is a region predicted by a layout detection model (e.g. Title, Text, Table, Page-header, Page-footer). Layout regions are attached to a :class:`Page` when the ``ocr_predictor`` / ``kie_predictor`` is run with ``detect_layout=True``. - -.. autoclass:: LayoutElement - -Block -^^^^^ -A Block is a collection of Lines (e.g. an address written on several lines) and Artefacts (e.g. a graph with its title underneath). - -.. autoclass:: Block - -Page -^^^^ - -A Page is a collection of Blocks that were on the same physical page. - -.. autoclass:: Page - - .. automethod:: show - .. automethod:: items_in_reading_order - .. automethod:: export_as_markdown - .. automethod:: export_as_asciidoc - .. automethod:: export_as_html - .. automethod:: export_as_xml - .. automethod:: export - .. automethod:: render - .. automethod:: export_as - - -KIEPage -^^^^^^^ - -A KIEPage is returned by the :py:meth:`kie_predictor `. It groups predictions by -semantic class rather than by spatial layout. - -.. autoclass:: KIEPage - - .. automethod:: show - .. automethod:: export_as_markdown - .. automethod:: export_as_asciidoc - .. automethod:: export_as_html - .. automethod:: export_as_xml - .. automethod:: export - .. automethod:: render - .. automethod:: export_as - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - .. automethod:: show - .. automethod:: export_as_markdown - .. automethod:: export_as_asciidoc - .. automethod:: export_as_xml - .. automethod:: export_as_html - .. automethod:: export - .. automethod:: render - .. automethod:: export_as - - -KIEDocument -^^^^^^^^^^^ - -A KIEDocument is a collection of :class:`KIEPage` elements, returned by the -:py:meth:`kie_predictor `. - -.. autoclass:: KIEDocument - - .. automethod:: show - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img_as_numpy - -.. autofunction:: read_img_as_tensor - -.. autofunction:: decode_img_as_tensor - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - - -.. _reading_order: - -Reading order -------------- - -The reading-order-aware export of a :class:`Document` / :class:`Page` to Markdown, AsciiDoc, HTML, or XML is available -through the ``export_as_markdown`` / ``export_as_asciidoc`` / ``export_as_html`` / ``export_as_xml`` / ``export_as`` / ``export`` / ``render`` methods documented above, which -delegate to the exporters of :mod:`doctr.io.exporters`. -The underlying ordering primitives live in :mod:`doctr.models.reading_order`. - -Every export path shares the same linearization, so ``render()``, ``export()``, ``export_as_xml()`` and the -Markdown / AsciiDoc / HTML exports all present the content in the same order. The result is memoized on the -page, so exporting one page to several formats orders it only once. - -.. currentmodule:: doctr.io - -.. autoclass:: TextExporter - :members: export_page, export_kie_page, export_document - -.. autoclass:: MarkdownExporter - :members: export_page, export_kie_page, export_document - -.. autoclass:: AsciiDocExporter - :members: export_page, export_kie_page, export_document - -.. autoclass:: HTMLExporter - :members: export_page, export_kie_page, export_document - -.. autoclass:: XMLExporter - :members: export_page, export_kie_page, export_document - -.. autofunction:: doctr.io.exporters.page_reading_order - -.. autofunction:: doctr.io.exporters.predictions_in_reading_order - -.. autofunction:: doctr.io.exporters.to_json_safe - -Figures -------- - -When the predictor runs the layout model (``detect_layout=True`` or ``detect_tables=True``), the detected figures -take part in the reading order, and the Markdown, AsciiDoc and HTML exports render them according to their -``images`` argument, an image mode or a configured :class:`FigureEncoder`: - -* ``'placeholder'`` (default): a comment marks each figure (```` in Markdown and HTML, ``// image`` in - AsciiDoc) -* ``'none'``: the figures are left out -* ``'embedded'``: each figure is cropped and inlined as a base64 data URI -* ``'referenced'``: each crop is written to an image directory and linked by its relative path, which needs a - configured encoder: ``images=FigureEncoder("referenced", image_dir=...)``. Set ``path_prefix`` when the export is - not written to the working directory: it locates the image directory from the export. The file names carry the - position and a hash of the figure (e.g. ``page1_figure2-3fa2b1c9.png``), so several documents can share a - directory. - -.. note:: - Once a figure carries its pixels (``'embedded'`` and ``'referenced'``), the text recognized inside it is left - out of the export, since the image shows it, and its caption becomes the alternative text of the image. A figure - which cannot be cropped (e.g. on a page restored from a JSON export) falls back to a placeholder and keeps its - text. ``page.items_in_reading_order(include_figures=True)`` returns the figures along with the blocks and tables. - -.. code:: python - - from doctr.io import DocumentFile, FigureEncoder - from doctr.models import ocr_predictor - - predictor = ocr_predictor(pretrained=True, detect_layout=True) - doc = predictor(DocumentFile.from_pdf("report.pdf")) - - # A self-contained Markdown file - markdown = doc.export_as_markdown(images="embedded") - # ... or one that links to the crops written to `assets` - markdown = doc.export_as_markdown(images=FigureEncoder("referenced", image_dir="assets")) - -The caption also stays visible: an italic paragraph under the image in Markdown, a ``
`` in HTML and a -block title in AsciiDoc. Plain text never carries figures, and hOCR positions them as ``ocr_photo`` areas. A figure -lying (at least half) in a page header or footer, e.g. a logo, is read with that furniture, and left out with it by -``include_furniture=False``. - -The command line exposes the same options: ``--images`` picks the mode, ``--image_format`` the file format, and in -``referenced`` mode the crops are written to ``--image_dir`` (by default ``_images`` next to the output): - -.. code:: bash - - doctr-cli --input_path report.pdf --detect_layout --output report.md --images referenced - -.. autoclass:: FigureEncoder - :members: resolve, source, enabled, materializes - -.. autofunction:: crop_layout_region - -.. autofunction:: encode_crop - -.. autofunction:: picture_regions - -.. autofunction:: is_picture_region - -.. autofunction:: is_picture_label diff --git a/docs/source/modules/transforms.rst b/docs/source/modules/transforms.rst deleted file mode 100644 index 0a1a74a5d1..0000000000 --- a/docs/source/modules/transforms.rst +++ /dev/null @@ -1,39 +0,0 @@ -doctr.transforms -================ - -.. currentmodule:: doctr.transforms - -Data transformations are part of both training and inference procedure. Drawing inspiration from the design of `torchvision `_, we express transformations as composable modules. - - -Supported transformations -------------------------- -Here are all transformations that are available through docTR: - -.. currentmodule:: doctr.transforms.modules - -.. autoclass:: Resize -.. autoclass:: GaussianNoise -.. autoclass:: ChannelShuffle -.. autoclass:: RandomHorizontalFlip -.. autoclass:: RandomShadow - -.. autoclass:: RandomGlare - -.. autoclass:: RandomLighting - -.. autoclass:: RandomPerspective -.. autoclass:: RandomResize - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: SampleCompose -.. autoclass:: ImageTransform -.. autoclass:: ColorInversion -.. autoclass:: OneOf -.. autoclass:: RandomApply -.. autoclass:: RandomRotate -.. autoclass:: RandomCrop diff --git a/docs/source/modules/utils.rst b/docs/source/modules/utils.rst deleted file mode 100644 index a6f4be3f13..0000000000 --- a/docs/source/modules/utils.rst +++ /dev/null @@ -1,67 +0,0 @@ -doctr.utils -=========== - -This module regroups non-core features that are complementary to the rest of the package. - -.. currentmodule:: doctr.utils - - -Visualization -------------- -Easy-to-use functions to make sense of your model's predictions. - -.. currentmodule:: doctr.utils.visualization - -.. autofunction:: visualize_page - -.. autofunction:: visualize_kie_page - -.. autofunction:: draw_boxes - -Reconstitution ---------------- - -.. currentmodule:: doctr.utils.reconstitution - -.. autofunction:: synthesize_page - -.. autofunction:: synthesize_kie_page - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: TextMatch - - .. automethod:: update - .. automethod:: summary - -.. autoclass:: LocalizationConfusion - - .. automethod:: update - .. automethod:: summary - -.. autoclass:: OCRMetric - - .. automethod:: update - .. automethod:: summary - -.. autoclass:: DetectionMetric - - .. automethod:: update - .. automethod:: summary - -.. autoclass:: ObjectDetectionMetric - - .. automethod:: update - .. automethod:: summary - -.. autoclass:: TableCellMetric - - .. automethod:: update - .. automethod:: summary diff --git a/docs/source/notebooks.rst b/docs/source/notebooks.rst deleted file mode 120000 index 6c3d7bfec2..0000000000 --- a/docs/source/notebooks.rst +++ /dev/null @@ -1 +0,0 @@ -../../notebooks/README.rst \ No newline at end of file diff --git a/docs/source/training/custom_models.md b/docs/source/training/custom_models.md new file mode 100644 index 0000000000..392b34546f --- /dev/null +++ b/docs/source/training/custom_models.md @@ -0,0 +1,175 @@ +# Using your trained models + +Every predictor factory accepts a model **instance** instead of an architecture name. To use your checkpoint, +build the architecture with the same settings as for training, load the weights with `from_pretrained`, and pass +the model to the predictor. Pretrained and custom models can be mixed freely. + +:::{tip} +`from_pretrained` accepts a local path or a URL. The `.json` file written next to the checkpoint +records the settings to rebuild the model with (vocabulary, class names...). +::: + +## Detection and recognition models + +::::{tab-set} + +:::{tab-item} Custom detection +```python +from doctr.models import db_resnet50, ocr_predictor + +det_model = db_resnet50(pretrained=False, pretrained_backbone=False) +det_model.from_pretrained("path/to/detection.pt", map_location="cpu") + +predictor = ocr_predictor(det_arch=det_model, reco_arch="crnn_vgg16_bn", pretrained=True) +``` +::: + +:::{tab-item} Custom recognition +```python +from doctr.models import crnn_vgg16_bn, ocr_predictor + +reco_model = crnn_vgg16_bn(pretrained=False, pretrained_backbone=False) +reco_model.from_pretrained("path/to/recognition.pt", map_location="cpu") + +predictor = ocr_predictor(det_arch="fast_base", reco_arch=reco_model, pretrained=True) +``` +::: + +:::{tab-item} Both +```python +from doctr.models import crnn_vgg16_bn, db_resnet50, ocr_predictor + +det_model = db_resnet50(pretrained=False, pretrained_backbone=False) +det_model.from_pretrained("path/to/detection.pt") +reco_model = crnn_vgg16_bn(pretrained=False, pretrained_backbone=False) +reco_model.from_pretrained("path/to/recognition.pt") + +# pretrained=True still loads the pretrained orientation classifiers when they are needed +predictor = ocr_predictor(det_arch=det_model, reco_arch=reco_model, pretrained=True) +``` +::: +:::: + +### Another vocabulary + +A recognition model trained on another vocabulary than the default (`french`) must be built with that vocabulary, +so that its output layer matches the checkpoint: + +```python +from doctr.datasets import VOCABS +from doctr.models import crnn_vgg16_bn, ocr_predictor + +reco_model = crnn_vgg16_bn(pretrained=False, pretrained_backbone=False, vocab=VOCABS["german"]) +reco_model.from_pretrained("path/to/recognition.pt") + +predictor = ocr_predictor(reco_arch=reco_model, pretrained=True) +``` + +### KIE classes + +A multi-class detection model is built with its class names, and used with the +[KIE predictor](../user_guide/kie.md): + +```python +from doctr.models import db_resnet50, kie_predictor + +det_model = db_resnet50(pretrained=False, pretrained_backbone=False, class_names=["total", "date"]) +det_model.from_pretrained("path/to/detection.pt") + +predictor = kie_predictor(det_arch=det_model, reco_arch="crnn_vgg16_bn", pretrained=True) +``` + +## Layout models + +A layout model trained on your own classes is passed through `layout_arch`. The detected regions carry your class +names: + +```python +from doctr.models import lw_detr_s, ocr_predictor + +layout_model = lw_detr_s(pretrained=False, class_names=["heading", "paragraph", "figure", "table"]) +layout_model.from_pretrained("path/to/layout.pt") + +predictor = ocr_predictor(pretrained=True, detect_layout=True, layout_arch=layout_model) +result = predictor(pages) +print([(region.type, region.confidence) for region in result.pages[0].layout]) +``` + +:::{note} +Table recognition (`detect_tables=True`) crops the regions labelled `Table`: keep this class name if your layout +model should feed the table model. +::: + +## Table structure models + +```python +from doctr.models import ocr_predictor, tablecenternet +from doctr.models.table_structure.zoo import table_predictor + +table_model = tablecenternet(pretrained=False) +table_model.from_pretrained("path/to/table.pt") + +predictor = ocr_predictor(pretrained=True, detect_tables=True) +predictor.table_predictor = table_predictor(table_model) # replace the default table model +``` + +## Orientation classifiers + +The orientation classifiers are used with `assume_straight_pages=False`, `straighten_pages=True` or +`detect_orientation=True`. Only `mobilenet_v3_small` based models are supported. Replace the default ones after +building the predictor: + +```python +from doctr.models import ( + crop_orientation_predictor, + mobilenet_v3_small_crop_orientation, + mobilenet_v3_small_page_orientation, + ocr_predictor, + page_orientation_predictor, +) + +page_model = mobilenet_v3_small_page_orientation(pretrained=False) +page_model.from_pretrained("path/to/page_orientation.pt") +crop_model = mobilenet_v3_small_crop_orientation(pretrained=False) +crop_model.from_pretrained("path/to/crop_orientation.pt") + +predictor = ocr_predictor(pretrained=True, assume_straight_pages=False, detect_orientation=True) +predictor.page_orientation_predictor = page_orientation_predictor(page_model) +predictor.crop_orientation_predictor = crop_orientation_predictor(crop_model) +``` + +## Custom pre-processing + +To change how images are resized and normalized (e.g. a model trained with another input size or other +statistics), assemble the predictors yourself: + +```python +from doctr.models import crnn_vgg16_bn, db_resnet50 +from doctr.models.detection.predictor import DetectionPredictor +from doctr.models.predictor import OCRPredictor +from doctr.models.preprocessor import PreProcessor +from doctr.models.recognition.predictor import RecognitionPredictor + +det_model = db_resnet50(pretrained=False, pretrained_backbone=False) +det_model.from_pretrained("path/to/detection.pt") +reco_model = crnn_vgg16_bn(pretrained=False, pretrained_backbone=False) +reco_model.from_pretrained("path/to/recognition.pt") + +det_predictor = DetectionPredictor( + PreProcessor((1024, 1024), batch_size=1, mean=(0.798, 0.785, 0.772), std=(0.264, 0.2749, 0.287)), + det_model, +) +reco_predictor = RecognitionPredictor( + PreProcessor( + (32, 128), preserve_aspect_ratio=True, batch_size=32, mean=(0.694, 0.695, 0.693), std=(0.299, 0.296, 0.301) + ), + reco_model, +) + +predictor = OCRPredictor(det_predictor, reco_predictor) +``` + +## Models from the Hugging Face Hub + +Models shared on the Hub are loaded with {func}`~doctr.models.factory.from_hub`, which restores the architecture and its +settings automatically. See [](sharing_models.md). diff --git a/docs/source/training/datasets.md b/docs/source/training/datasets.md new file mode 100644 index 0000000000..ead07e4a2a --- /dev/null +++ b/docs/source/training/datasets.md @@ -0,0 +1,137 @@ +# Datasets + +{mod}`doctr.datasets` provides ready-to-use public datasets, synthetic data generators, and loaders for your own +annotations. They all return samples in the format expected by the docTR models and training scripts. + +## Built-in datasets + +Public datasets are downloaded and extracted on first use (`download=True`) into `~/.cache/doctr/datasets`. + +```python +from doctr.datasets import CORD + +train_set = CORD(train=True, download=True) +sample = train_set[0] +image, target = sample.image, sample.target +``` + +The same dataset can be loaded for each task: + +::::{tab-set} + +:::{tab-item} OCR (default) +Full pages, with the boxes **and** the transcriptions of the words: + +```python +train_set = CORD(train=True, download=True) +target = train_set[0].target # {"boxes": array of shape (N, 4), "labels": [str, ...]} +``` +::: + +:::{tab-item} Detection +Full pages, with the boxes of the words only: + +```python +train_set = CORD(train=True, download=True, detection_task=True) +``` +::: + +:::{tab-item} Recognition +Word crops, with their transcription: + +```python +train_set = CORD(train=True, download=True, recognition_task=True) +image, word = train_set[0].image, train_set[0].target +``` +::: +:::: + +Add `use_polygons=True` to get rotated boxes as `(4, 2)` polygons instead of straight boxes (for recognition, +crops are then extracted along the rotated box). + +### Available datasets + +| Dataset | Detection (train / test pages) | Recognition (train / test crops) | Notes | +| --- | ---: | ---: | --- | +| {class}`~doctr.datasets.FUNSD` | 149 / 50 | 21,888 / 8,707 | English forms | +| {class}`~doctr.datasets.SROIE` | 626 / 360 | 33,608 / 19,342 | Receipts, upper-case labels | +| {class}`~doctr.datasets.CORD` | 800 / 100 | 19,370 / 2,186 | Receipts | +| {class}`~doctr.datasets.IIIT5K` | 2,000 / 3,000 | 2,000 / 3,000 | Scene text | +| {class}`~doctr.datasets.SVT` | 100 / 249 | 257 / 647 | Street view, upper-case labels | +| {class}`~doctr.datasets.SVHN` | 33,402 / 13,068 | 73,257 / 26,032 | House numbers, digits only | +| {class}`~doctr.datasets.SynthText` | 772,875 / 85,875 | ~7,100,000 / 707,470 | Synthetic | +| {class}`~doctr.datasets.IC03` | 246 / 249 | 1,156 / 1,107 | Scene text | +| {class}`~doctr.datasets.IC13` | 229 / 233 | 849 / 1,095 | Scene text, manual download | +| {class}`~doctr.datasets.IMGUR5K` | 7,149 / 796 | 207,901 / 22,672 | Handwritten, manual download | +| {class}`~doctr.datasets.MJSynth` | — | 7,581,382 / 1,337,891 | Synthetic words, manual download | +| {class}`~doctr.datasets.IIITHWS` | — | 7,141,797 / 793,533 | Handwritten words, manual download | +| {class}`~doctr.datasets.WILDRECEIPT` | 1,268 / 472 | 49,377 / 19,598 | Receipts, manual download | +| {class}`~doctr.datasets.COCOTEXT` | 13,880 / 3,261 | 59,130 / 13,258 | Natural images, legible text only, manual download | +| {class}`~doctr.datasets.DocArtefacts` | 2,700 / 300 | — | Object detection: QR codes, bar codes, logos, photos | + +Datasets marked *manual download* must be fetched from their authors' website first: pass the paths of the +extracted files to the constructor (see the [API reference](../api/datasets.md)). + +## Synthetic data + +Generators render random characters or words on the fly, which is useful to pre-train a model or to add an +alphabet to it. Both are used by the reference training scripts when no dataset is given. + +```python +from doctr.datasets import VOCABS, CharacterGenerator, WordGenerator + +# Single characters, e.g. to train a character classifier +chars = CharacterGenerator(vocab=VOCABS["german"], num_samples=1000) + +# Random words of 1 to 12 characters +words = WordGenerator(vocab=VOCABS["german"], min_chars=1, max_chars=12, num_samples=10000) +sample = words[0] # sample.image: rendered word, sample.target: its text +``` + +Both accept a `font_family` (one font or a list of fonts) to render with your own fonts. + +## Your own data + +Load annotated data stored in the [training format](train.md) of each task: + +| Class | Task | Expected files | +| --- | --- | --- | +| {class}`~doctr.datasets.DetectionDataset` | Text detection | `images/` + `labels.json` with polygons (optionally per class) | +| {class}`~doctr.datasets.RecognitionDataset` | Text recognition | word images + `labels.json` mapping each image to its text | +| {class}`~doctr.datasets.OCRDataset` | End-to-end OCR | `images/` + `labels.json` with boxes and transcriptions | +| {class}`~doctr.datasets.LayoutDataset` | Layout analysis | `images/` + `labels.json` with polygons and classes | +| {class}`~doctr.datasets.TableStructureDataset` | Table structure | `images/` + `labels.json` with cells and their logical coordinates | +| {class}`~doctr.datasets.OrientationDataset` | Orientation classification | a folder of upright images | + +```python +from doctr.datasets import DetectionDataset, OCRDataset, RecognitionDataset + +det_set = DetectionDataset(img_folder="path/to/images", label_path="path/to/labels.json") +reco_set = RecognitionDataset(img_folder="path/to/images", labels_path="path/to/labels.json") +ocr_set = OCRDataset(img_folder="path/to/images", label_file="path/to/labels.json") +``` + +## Transforms and batching + +Pass transformations with `img_transforms` (applied to the image) and `sample_transforms` (applied to the image +and its target, e.g. rotations). Datasets return images of different sizes, so resize them before batching. Batch +with a PyTorch {class}`~torch.utils.data.DataLoader` and the `collate_fn` of the dataset: + +```python +from torch.utils.data import DataLoader + +from doctr import transforms as T +from doctr.datasets import RecognitionDataset + +train_set = RecognitionDataset( + img_folder="path/to/images", + labels_path="path/to/labels.json", + img_transforms=T.Resize((32, 128), preserve_aspect_ratio=True), +) + +train_loader = DataLoader(train_set, batch_size=32, shuffle=True, collate_fn=train_set.collate_fn) +images, targets = next(iter(train_loader)) # images: (32, 3, 32, 128) tensor, targets: list of 32 strings +``` + +{mod}`doctr.transforms` provides document-specific augmentations (shadows, glare, perspective, rotations...). See +the [API reference](../api/transforms.md) and the reference training scripts for complete pipelines. diff --git a/docs/source/training/index.md b/docs/source/training/index.md new file mode 100644 index 0000000000..7cd3fb8b10 --- /dev/null +++ b/docs/source/training/index.md @@ -0,0 +1,63 @@ +# Training + +The pretrained models work well on common printed documents. When your documents are different (another +language, handwriting, unusual fonts, specific layouts, fields to extract), **fine-tuning** a model on a few hundred +annotated samples usually brings a large improvement. + +## The workflow + +```{raw} html +
    +
  1. Collect and annotate a representative sample of your documents, e.g. with the + docTR Labeler.
  2. +
  3. Format the data as an images/ folder and a labels.json file, + following the format of each task.
  4. +
  5. Train with the reference script of the task, starting from the pretrained weights.
  6. +
  7. Evaluate the checkpoint on held-out data with the evaluation script.
  8. +
  9. Load your checkpoint into a predictor, alongside the pretrained models.
  10. +
  11. Share it on the Hugging Face Hub, if you like.
  12. +
+``` + +## In this section + +::::{grid} 1 2 2 2 +:gutter: 3 + +:::{grid-item-card} {octicon}`database;1.1em` Datasets +:link: datasets +:link-type: doc + +Built-in public datasets, synthetic data generators and loaders for your own data. +::: + +:::{grid-item-card} {octicon}`mortar-board;1.1em` Training a model +:link: train +:link-type: doc + +Data formats, training scripts, multi-GPU and mixed precision, for every task. +::: + +:::{grid-item-card} {octicon}`plug;1.1em` Using your trained models +:link: custom_models +:link-type: doc + +Load your checkpoints into the OCR, KIE and task-specific predictors. +::: + +:::{grid-item-card} {octicon}`share;1.1em` Sharing models +:link: sharing_models +:link-type: doc + +Publish and load models with the Hugging Face Hub, and browse community models. +::: +:::: + +```{toctree} +:hidden: + +datasets +train +custom_models +sharing_models +``` diff --git a/docs/source/training/sharing_models.md b/docs/source/training/sharing_models.md new file mode 100644 index 0000000000..62450d06fe --- /dev/null +++ b/docs/source/training/sharing_models.md @@ -0,0 +1,70 @@ +# Sharing models + +docTR is integrated with the [Hugging Face Hub](https://huggingface.co/models?library=doctr): load models shared by +the community in one line, and publish your own. + +## Load a model from the Hub + +{func}`~doctr.models.factory.from_hub` downloads a model and rebuilds it with its configuration (architecture, vocabulary, +classes...). Plug it into any predictor: + +```python +from doctr.io import DocumentFile +from doctr.models import from_hub, ocr_predictor + +det_model = from_hub("Felix92/doctr-torch-db-mobilenet-v3-large") +reco_model = from_hub("Felix92/doctr-torch-parseq-multilingual-v1") + +predictor = ocr_predictor(det_arch=det_model, reco_arch=reco_model) +result = predictor(DocumentFile.from_images("path/to/page.jpg")) +``` + +## Publish a model + +You need a [Hugging Face account](https://huggingface.co/join) and [Git LFS](https://git-lfs.com) installed. + +```python +from doctr.models import login_to_hub, push_to_hf_hub, recognition + +login_to_hub() # paste an access token with write permissions + +model = recognition.crnn_mobilenet_v3_large(pretrained=True) +push_to_hf_hub( + model, + model_name="doctr-crnn-mobilenet-v3-large-french-v1", + task="recognition", # classification, detection, recognition, layout or table_structure + arch="crnn_mobilenet_v3_large", +) +``` + +Existing repositories are never overwritten. The reference training scripts can also publish the model at the end +of the training: + +```bash +python references/recognition/train.py crnn_mobilenet_v3_large --name doctr-crnn-mobilenet-v3-large --push-to-hub +``` + +### Naming conventions + +| Task | Suggested name | +| --- | --- | +| Classification | `doctr--` | +| Detection | `doctr-` | +| Recognition | `doctr--` | +| Layout | `doctr-` | +| Table structure | `doctr-` | + +## Community models + +Models trained and shared by the community. Open a pull request to add yours! + +| Task | Architecture | Repository | Language | +| --- | --- | --- | --- | +| Detection | `db_resnet50` | [`rania-sr/doctr-Detection-model-v1-arabic`](https://huggingface.co/rania-sr/doctr-Detection-model-v1-arabic) | Arabic | +| Recognition | `parseq` | [`Felix92/doctr-torch-parseq-multilingual-v1`](https://huggingface.co/Felix92/doctr-torch-parseq-multilingual-v1) | Multilingual | +| Recognition | `parseq` | [`rania-sr/doctr-model-v1-arabic`](https://huggingface.co/rania-sr/doctr-model-v1-arabic) | Arabic | +| Recognition | `crnn_vgg16_bn` | [`tilman-rassy/doctr-crnn-vgg16-bn-fascan-v1`](https://huggingface.co/tilman-rassy/doctr-crnn-vgg16-bn-fascan-v1) | French + German + § | + +Dummy models, handy for tests: [`Felix92/doctr-dummy-torch-resnet18`](https://huggingface.co/Felix92/doctr-dummy-torch-resnet18) (classification), +[`Felix92/doctr-dummy-torch-lw-detr-s`](https://huggingface.co/Felix92/doctr-dummy-torch-lw-detr-s) (layout) and +[`Felix92/doctr-dummy-torch-tablecenternet`](https://huggingface.co/Felix92/doctr-dummy-torch-tablecenternet) (table structure). diff --git a/docs/source/training/train.md b/docs/source/training/train.md new file mode 100644 index 0000000000..fa0b22657e --- /dev/null +++ b/docs/source/training/train.md @@ -0,0 +1,233 @@ +# Training a model + +The [`references/`](https://github.com/mindee/doctr/tree/main/references) folder of the repository contains +ready-to-use training, evaluation and latency scripts for every task. This page summarizes how to use them; each +task folder has a README with all the details. + +## Setup + +The scripts live in the repository, not in the package. Clone it and install the extra requirements: + +```bash +git clone https://github.com/mindee/doctr.git +cd doctr +pip install -e . +pip install -r references/requirements.txt +``` + +All scripts share the same interface: the architecture as first argument, `--train_path` and `--val_path` +pointing to your data, and options such as `--epochs`, `--batch_size`, `--lr`, `--pretrained` (start from the +pretrained weights, recommended for fine-tuning) or `--name` (experiment name). Run any script with `--help` to +list them all. + +:::{tip} +Annotating data is the hardest part. The [docTR Labeler](../community/tools.md#doctr-labeler) lets you draw and +correct polygons with automatic suggestions, and exports the formats below. +::: + +## Data formats + +Every task uses the same layout on disk, one folder per split: + +```text +train_set/ +├── images/ +│ ├── sample_img_01.png +│ ├── sample_img_02.png +│ └── ... +└── labels.json +``` + +`labels.json` maps each image file name to its annotation, which depends on the task. Coordinates are **absolute +pixel coordinates**. + +::::{tab-set} + +:::{tab-item} Detection +```{code-block} javascript +:caption: labels.json + +{ + "sample_img_01.png": { + "img_dimensions": [900, 600], + "img_hash": "", + "polygons": [[[x1, y1], [x2, y2], [x3, y3], [x4, y4]], ...] + } +} +``` + +For a multi-class model (e.g. for [KIE](../user_guide/kie.md)), `polygons` is a dictionary with one list per class: + +```javascript +"polygons": { + "date": [[[x1, y1], [x2, y2], [x3, y3], [x4, y4]], ...], + "total": [[[x1, y1], [x2, y2], [x3, y3], [x4, y4]], ...] +} +``` + +Every class must appear in both splits; an image without a box of a class has an empty list for it. +::: + +:::{tab-item} Recognition +`images/` contains **word crops** and `labels.json` their transcription, in UTF-8: + +```{code-block} javascript +:caption: labels.json + +{ + "img_1.jpg": "I", + "img_2.jpg": "am", + "img_3.jpg": "a", + "img_4.jpg": "Jedi" +} +``` + +Labels must not contain spaces, and their characters must be part of the vocabulary you train with. +::: + +:::{tab-item} Layout +```{code-block} javascript +:caption: labels.json + +{ + "sample_img_01.png": { + "img_dimensions": [900, 600], + "img_hash": "", + "polygons": [[[x1, y1], [x2, y2], [x3, y3], [x4, y4]], ...], + "classes": ["Title", "Text", ...] + } +} +``` +::: + +:::{tab-item} Table structure +`images/` contains images of **tables**: + +```{code-block} javascript +:caption: labels.json + +{ + "sample_img_01.png": { + "cells": [[[x1, y1], [x2, y2], [x3, y3], [x4, y4]], ...], + "logic": [[start_col, end_col, start_row, end_row], ...] + } +} +``` + +Cell polygons are ordered top-left, top-right, bottom-right, bottom-left. Logical coordinates are 0-indexed and +inclusive: a cell spanning one row and one column has equal start and end indices. +::: + +:::{tab-item} Orientation +No labels: a folder of **upright** images. The script rotates them to generate the training targets. + +```text +train_set/ +├── sample_img_01.png +├── sample_img_02.png +└── ... +``` +::: +:::: + +## Train + +### Text detection + +```bash +python references/detection/train.py db_resnet50 \ + --train_path path/to/train_set --val_path path/to/val_set \ + --epochs 5 --pretrained +``` + +Add `--rotation` to train on rotated documents. Built-in datasets can replace local folders: +`--train_datasets FUNSD CORD --val_datasets FUNSD`. +[Full guide](https://github.com/mindee/doctr/tree/main/references/detection#readme). + +### Text recognition + +```bash +python references/recognition/train.py crnn_vgg16_bn \ + --train_path path/to/train_set --val_path path/to/val_set \ + --vocab german --epochs 5 --pretrained +``` + +`--vocab` selects one of the [vocabularies](../models/vocabs.md) (`french` by default). Without any dataset, the +script trains on synthetic words generated from the vocabulary, optionally with your own fonts (`--font`). +[Full guide](https://github.com/mindee/doctr/tree/main/references/recognition#readme). + +### Layout analysis + +```bash +python references/layout/train.py lw_detr_s \ + --train_path path/to/train_set --val_path path/to/val_set --epochs 5 +``` + +[Full guide](https://github.com/mindee/doctr/tree/main/references/layout#readme). + +### Table structure + +```bash +python references/table/train.py tablecenternet \ + --train_path path/to/train_set --val_path path/to/val_set --epochs 5 +``` + +[Full guide](https://github.com/mindee/doctr/tree/main/references/table#readme), including a toy dataset to try +the pipeline end to end. + +### Orientation classifiers + +```bash +# --type page for whole pages, --type crop for word crops +python references/classification/train_orientation.py mobilenet_v3_small_page_orientation \ + --type page --train_path path/to/train_set --val_path path/to/val_set --epochs 5 +``` + +[Full guide](https://github.com/mindee/doctr/tree/main/references/classification#readme). + +## Hardware + +::::{tab-set} + +:::{tab-item} Single GPU +Every script accepts `--device`: a CUDA index (`0`), `cuda:N`, `mps` or `cpu`. By default, the script picks CUDA, +then MPS, then the CPU. + +`--amp` enables automatic mixed precision on CUDA; add `--amp-dtype bfloat16` on Ampere GPUs or newer to avoid the +overflows of float16: + +```bash +python references/detection/train.py db_resnet50 --train_path train --val_path val \ + --device 0 --amp --amp-dtype bfloat16 +``` +::: + +:::{tab-item} Multi-GPU +Launch the same scripts with [`torchrun`](https://pytorch.org/docs/stable/elastic/run.html) to train with +DistributedDataParallel: + +```bash +CUDA_VISIBLE_DEVICES=0,2 torchrun --nproc_per_node=2 references/detection/train.py db_resnet50 \ + --train_path train --val_path val --epochs 5 --backend nccl +``` +::: + +:::{tab-item} Apple Silicon +```bash +# Let the few operations MPS lacks fall back to the CPU +PYTORCH_ENABLE_MPS_FALLBACK=1 python references/detection/train.py db_resnet50 \ + --train_path train --val_path val --device mps +``` +::: +:::: + +## Outputs, logging and evaluation + +- **Checkpoints** are saved in `--output_dir` as `.pt`, next to a `.json` file + with the run metadata: architecture and task settings (vocabulary, class names...), dataset hashes, library + versions and all the arguments. +- **Tracking**: `--wb` logs to Weights & Biases and `--clearml` to ClearML. +- **Evaluation**: each task folder has an `evaluate.py` script, and a `latency.py` script to benchmark speed. +- **Sharing**: `--push-to-hub` publishes the final model on the [Hugging Face Hub](sharing_models.md). + +Then [load your checkpoint](custom_models.md) into a predictor. diff --git a/docs/source/user_guide/artefacts.md b/docs/source/user_guide/artefacts.md new file mode 100644 index 0000000000..249d1fe508 --- /dev/null +++ b/docs/source/user_guide/artefacts.md @@ -0,0 +1,56 @@ +# Artefact detection + +The `doctr.contrib` module gathers complementary features that are useful in document pipelines but are not part +of the core OCR. Its first member, {class}`~doctr.contrib.ArtefactDetector`, finds non-textual elements on a page: +**bar codes, QR codes, logos and photos**. + +:::{note} +Contrib modules run on [ONNX Runtime](https://onnxruntime.ai) instead of PyTorch. Install the extra first: + +```bash +pip install "python-doctr[contrib]" +# or, for GPU inference: pip install onnxruntime-gpu +``` +::: + +## Detect artefacts + +```python +from doctr.contrib import ArtefactDetector +from doctr.io import DocumentFile + +pages = DocumentFile.from_images(["path/to/page.jpg"]) + +detector = ArtefactDetector(batch_size=2, conf_threshold=0.5, iou_threshold=0.5) +results = detector(pages) + +for artefact in results[0]: # one list per page + print(artefact["label"], artefact["confidence"], artefact["box"]) +# e.g. qr_code 0.91 [812, 64, 1003, 255] + +detector.show() # requires the `viz` extra +``` + +Each artefact is a dict with a `label`, a `confidence` and a `box` as `[xmin, ymin, xmax, ymax]` in **pixels**. + +| Parameter | Default | Description | +| ----------------- | ------- | -------------------------------------------------------------- | +| `batch_size` | `2` | Number of pages processed at once | +| `conf_threshold` | `0.5` | Minimum confidence of a detection | +| `iou_threshold` | `0.5` | Overlap above which duplicate detections are merged (NMS) | + +## Use your own YOLOv8 model + +The detector is based on [YOLOv8](https://github.com/ultralytics/ultralytics). You can run any YOLOv8 detection +model exported to ONNX, e.g. one trained to find tables, stamps or signatures: + +```python +from doctr.contrib import ArtefactDetector + +detector = ArtefactDetector(model_path="path/to/your/model.onnx", labels=["table", "figure"]) +``` + +:::{important} +The model must be exported to ONNX **with a dynamic batch size**. Oriented bounding box (OBB) models are not +supported. +::: diff --git a/docs/source/user_guide/cli.md b/docs/source/user_guide/cli.md new file mode 100644 index 0000000000..f928c8788e --- /dev/null +++ b/docs/source/user_guide/cli.md @@ -0,0 +1,135 @@ +# Command-line interface + +`doctr-cli` runs the complete OCR pipeline on images and PDFs from your terminal, and writes the result as JSON, +plain text, Markdown, AsciiDoc, HTML or hOCR. It is installed with the package. + +```bash +doctr-cli --input_path invoice.pdf --output invoice.json +``` + +## Common recipes + +::::{tab-set} + +:::{tab-item} Text +```bash +doctr-cli --input_path scan.jpg --output scan.txt +``` +::: + +:::{tab-item} Markdown with tables +```bash +doctr-cli --input_path report.pdf --detect_tables --output report.md +``` +::: + +:::{tab-item} Several files +Every page of every input is appended to a single document: + +```bash +doctr-cli --input_path page1.jpg page2.jpg annex.pdf --output dossier.json +``` +::: + +:::{tab-item} Rotated scans +```bash +doctr-cli --input_path skewed.pdf --straighten_pages --output skewed.txt +``` +::: + +:::{tab-item} GPU & hOCR +On a CUDA device of compute capability 8.0 or newer, the models run in bfloat16: + +```bash +doctr-cli --input_path doc.pdf --device cuda:0 --output doc.xml +``` +::: + +:::{tab-item} Web page +Requires the `html` extra: + +```bash +doctr-cli --input_path https://www.example.com --output page.md +``` +::: +:::: + +The format is **inferred from the extension** of `--output` (`.json`, `.txt`, `.md`, `.adoc`, `.html`, `.xml`) and +falls back to JSON. Use `--format` to set it explicitly. + +## Arguments + +Run `doctr-cli --help` for the full list with default values. + +### Input + +| Argument | Default | Description | +| ----------------- | ------- | ------------------------------------------------------------------------------------------- | +| `--input_path` | — | **Required.** One or more images, PDF files, or `http(s)://` URLs of web pages | +| `--pdf_password` | `None` | Password of encrypted PDF files | +| `--pdf_scale` | `2` | PDF rendering scale (1 corresponds to 72 dpi) | + +### Models + +| Argument | Default | Description | +| --------------- | ----------------- | --------------------------------------------------------------------------------- | +| `--det_arch` | `fast_base` | Detection architecture | +| `--reco_arch` | `crnn_vgg16_bn` | Recognition architecture | +| `--layout_arch` | `lw_detr_s` | Layout architecture | +| `--device` | `auto` | `auto`, `cpu`, `cuda`, `cuda:1`, `mps`... (`auto` picks a GPU when available) | + +### Processing + +| Argument | Default | Description | +| ------------------------------------------------ | --------- | ---------------------------------------------------------------------- | +| `--assume_straight_pages` / `--no-assume_straight_pages` | on | Handle pages as straight (see [](rotated_documents.md)) | +| `--straighten_pages` | off | Straighten rotated pages before reading them | +| `--preserve_aspect_ratio` / `--no-preserve_aspect_ratio` | on | Pad pages instead of stretching them | +| `--symmetric_pad` / `--no-symmetric_pad` | on | Pad on all sides | +| `--det_bs`, `--reco_bs` | `2`, `128`| Batch sizes of the detection and recognition models | +| `--bin_thresh`, `--box_thresh` | model | [Detection thresholds](ocr_predictor.md#tune-the-text-detection), between 0 and 1 | +| `--confidence_aggregation` | model | `mean`, `min`, `max`, `median`, `geometric_mean` or `harmonic_mean` | +| `--detect_orientation` | off | Estimate the orientation of each page | +| `--detect_language` | off | Estimate the language of each page | +| `--detect_layout` | off | Attach the layout regions to each page | +| `--detect_tables` | off | Recognize tables (enables the layout model) | +| `--ignore_regions CLASS [CLASS ...]` | none | Layout classes to mask before OCR, e.g. `Picture Table` | +| `--export_as_straight_boxes` | off | Export straight boxes with `--no-assume_straight_pages` | +| `--preserve_original_coords` | off | With `--straighten_pages`, map the boxes back to the original pages | +| `--disable_page_orientation` | off | Skip the page orientation classifier | +| `--disable_crop_orientation` | off | Skip the crop orientation classifier | + +### Document assembly + +| Argument | Default | Description | +| ----------------------------------------- | ------- | --------------------------------------------------- | +| `--resolve_lines` / `--no-resolve_lines` | on | Group words into lines | +| `--resolve_blocks` | off | Group lines into blocks | +| `--paragraph_break` | `0.035` | Relative space that splits a line | +| `--keep_reading_order` | off | Sort the content of each page in reading order | + +### Output + +| Argument | Default | Description | +| ------------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------- | +| `--output` | `results.json` | Output file | +| `--format` | from extension | `json`/`dict`, `txt`/`text`, `md`/`markdown`, `adoc`/`asciidoc`, `html`, `xml`/`hocr` | +| `--direction` | `auto` | Reading direction: `auto`, `ltr`, `rtl`, `ttb-rtl`, `ttb-ltr` | +| `--reading_order` / `--no-reading_order` | on | Linearize JSON and hOCR exports in reading order | +| `--escape` / `--no-escape` | on | Escape markup characters (Markdown, AsciiDoc) | +| `--include_furniture` / `--no-include_furniture` | on | Include page headers, footers and footnotes | +| `--images` | `placeholder` | Figures in Markdown, AsciiDoc and HTML: `none`, `placeholder`, `embedded`, `referenced` (see [figures](exporting.md#figures)) | +| `--image_dir` | `_images` | Directory of the figures with `--images referenced` | +| `--image_format` | `png` | `png`, `jpg`, `jpeg` or `webp` | +| `--file_title` | `docTR - XML export (hOCR)` | Title of the hOCR files | +| `--indent` | `4` | Indentation of the JSON export | +| `--quiet` | off | Only log errors | +| `--version` | | Print the docTR version and exit | + +## Output files + +- **JSON** contains the full structure: pages (dimensions, orientation, language, layout), blocks, lines and words + with their confidence and geometry, and the tables with `--detect_tables`. +- **Text-like formats** (`txt`, `md`, `adoc`, `html`) are written to a single file. +- **hOCR** describes one page per file: `--output doc.xml` on a three-page document writes `doc_page_1.xml`, + `doc_page_2.xml` and `doc_page_3.xml`. diff --git a/docs/source/user_guide/exporting.md b/docs/source/user_guide/exporting.md new file mode 100644 index 0000000000..b2e87a8ddf --- /dev/null +++ b/docs/source/user_guide/exporting.md @@ -0,0 +1,210 @@ +# Exporting results + +A {class}`~doctr.io.Document` can be exported to six formats. All of them present the content in the same +**reading order**, which follows columns, places headers and footers, and adapts to right-to-left scripts. + +| Format | Method | Output | Best for | +| ------------- | ------------------------------------------------------- | ---------------------------------------- | ------------------------------------------- | +| Plain text | {meth}`~doctr.io.Document.render` | `str` | Search indexing, NLP, LLM prompts | +| JSON | {meth}`~doctr.io.Document.export` | `dict` | Storing every word with its geometry | +| Markdown | {meth}`~doctr.io.Document.export_as_markdown` | `str` | LLMs, RAG pipelines, readable documents | +| AsciiDoc | {meth}`~doctr.io.Document.export_as_asciidoc` | `str` | Technical documentation toolchains | +| HTML | {meth}`~doctr.io.Document.export_as_html` | `str` | Web display | +| hOCR (XML) | {meth}`~doctr.io.Document.export_as_xml` | `list[tuple[bytes, ElementTree]]` | Searchable PDFs, OCR interoperability | + +The same methods exist on a single {class}`~doctr.io.Page`. The {meth}`~doctr.io.Document.export_as` dispatcher +selects the format by name, which is convenient when it comes from a configuration: + +```python +result.export_as("markdown") # or "md" +result.export_as("asciidoc") # or "adoc" +result.export_as("html") +result.export_as("text") # or "txt", same as render() +result.export_as("json") # or "dict", same as export() +result.export_as("xml") # or "hocr", same as export_as_xml() +``` + +## Plain text + +```python +text = result.render() +``` + +Blocks are separated by a blank line and pages by three blank lines. Both separators are configurable, and page +headers and footers can be left out: + +```python +text = result.render(page_break="\f", include_furniture=False) +``` + +## Markdown, AsciiDoc and HTML + +```python +markdown = result.export_as_markdown() +asciidoc = result.export_as_asciidoc() +html = result.export_as_html() +``` + +The structure of these exports depends on what the predictor detected: + +- **Without layout analysis**, blocks become paragraphs. +- **With layout analysis** (`detect_layout=True`), the layout regions are used to render titles and section + headers as headings, list items as lists, and to place the page furniture (headers, footers, footnotes). +- **With table recognition** (`detect_tables=True`), tables are rendered as real tables. + +::::{tab-set} + +:::{tab-item} Markdown +```markdown +## ACME Office Supplies + +42 Harbour Road, Hamburg + +## Bill to: + +Jane Doe + +12 Market Street, Berlin + +INVOICE + +No. 2026-0412 +Date: 26/10/2026 + +| Description | Qty | Unit price | Amount | +| --- | --- | --- | --- | +| Recycled paper A4 | 10 | 8.50 | 85.00 | +| Ballpoint pens (box) | 5 | 12.00 | 60.00 | +| Desk lamp | 2 | 45.00 | 90.00 | +| Office chair | 1 | 1,015.00 | 1,015.00 | + +Total due + +1,250.00 EUR +``` +::: + +:::{tab-item} HTML +```html +

ACME Office Supplies

+

42 Harbour Road, Hamburg

+

Bill to:

+

Jane Doe

+

12 Market Street, Berlin

+

INVOICE

+

No. 2026-0412
+Date: 26/10/2026

+ + + +... +
DescriptionQtyUnit priceAmount
Recycled paper A4108.5085.00
+

Total due

+

1,250.00 EUR

+``` +::: +:::: + +*Output of `ocr_predictor(pretrained=True, detect_tables=True)` on the +{download}`sample invoice <../_static/images/examples/invoice.pdf>`.* + +Useful options: + +`escape` (Markdown, AsciiDoc) +: Escape the characters that carry a meaning in the markup (`*`, `#`, `|`...). Enabled by default. + +`include_furniture` +: Include page headers, page footers and footnotes. Enabled by default. + +`page_break` +: The separator inserted between pages: `---` in Markdown, `<<<` in AsciiDoc, `
` in HTML. + +`images` +: How figures are rendered, see [](#figures) below. + +## hOCR + +[hOCR](https://github.com/kba/hocr-spec) is an open standard to store OCR results in XML. docTR produces one hOCR +document per page, as raw bytes and as an {class}`xml.etree.ElementTree.ElementTree`: + +```python +for page_idx, (xml_bytes, xml_tree) in enumerate(result.export_as_xml()): + with open(f"page_{page_idx + 1}.hocr", "wb") as f: + f.write(xml_bytes) +``` + +hOCR files are the input of tools such as [hocr-tools](https://github.com/ocropus/hocr-tools) or +[OCRmyPDF](https://ocrmypdf.readthedocs.io). The +[Export as PDF/A notebook](https://github.com/mindee/notebooks/blob/main/doctr/export_as_pdfa.ipynb) shows how to +turn docTR results into **searchable PDFs**. + +## Reading order + +Every export path shares the same linearization of the page, computed once and memoized. It: + +- follows the **columns** of multi-column layouts instead of reading straight across the page, +- infers the **reading direction** from the recognized text (right-to-left for Arabic or Hebrew, for instance), +- uses the **layout regions**, when available, to order blocks, headings, tables and figures and to place the + page furniture. + +Force the direction when automatic detection is not what you want: + +```python +result.export_as_markdown(direction="rtl") # 'auto', 'ltr', 'rtl', 'ttb-rtl' or 'ttb-ltr' +``` + +To get the elements of a page (blocks, tables and optionally figures) in reading order, for your own exporter: + +```python +for item in result.pages[0].items_in_reading_order(include_figures=True): + print(type(item).__name__, item.render()[:40]) +``` + +The JSON export follows the reading order by default (`export(reading_order=False)` keeps the detection order). To +sort the `Document` tree itself, build the predictor with `keep_reading_order=True`. + +## Figures + +When the layout model runs (`detect_layout=True` or `detect_tables=True`), the regions it labels as figures +(pictures, charts...) take part in the reading order. The Markdown, AsciiDoc and HTML exports render them according +to their `images` argument: + +| Mode | Rendering | +| ---------------------------- | ----------------------------------------------------------------------------------------- | +| `"placeholder"` *(default)* | A comment marks the figure: `` (Markdown, HTML) or `// image` (AsciiDoc) | +| `"none"` | Figures are left out | +| `"embedded"` | Each figure is cropped and inlined as a base64 data URI: the export is self-contained | +| `"referenced"` | Each crop is written to a directory and linked by its relative path | + +```python +from doctr.io import DocumentFile, FigureEncoder +from doctr.models import ocr_predictor + +predictor = ocr_predictor(pretrained=True, detect_layout=True) +result = predictor(DocumentFile.from_pdf("report.pdf")) + +# A self-contained Markdown file, figures included +markdown = result.export_as_markdown(images="embedded") + +# A Markdown file linking to the crops written to `assets/` +markdown = result.export_as_markdown(images=FigureEncoder("referenced", image_dir="assets")) +``` + +{class}`~doctr.io.FigureEncoder` also sets the image format (`png`, `jpg`, `webp`), the quality of lossy formats, +a padding around each crop, and a `path_prefix` when the export is not written next to the image directory. +Crop file names contain the position and a hash of the figure (e.g. `page1_figure2-3fa2b1c9.png`), so several +documents can share a directory. + +:::{note} +Once a figure carries its pixels (`"embedded"` and `"referenced"`), the text recognized inside it is left out of +the export, since the image shows it, and its caption becomes the alternative text of the image. Plain text never +contains figures, and hOCR positions them as `ocr_photo` areas. +::: + +## From the command line + +The [CLI](cli.md) exposes all these formats; the format is inferred from the extension of the output file: + +```bash +doctr-cli --input_path report.pdf --detect_layout --output report.md --images referenced +``` diff --git a/docs/source/user_guide/faq.md b/docs/source/user_guide/faq.md new file mode 100644 index 0000000000..80e3f9b2df --- /dev/null +++ b/docs/source/user_guide/faq.md @@ -0,0 +1,112 @@ +# Troubleshooting & FAQ + +## Installation and setup + +:::{dropdown} `ModuleNotFoundError: No module named 'matplotlib'` when calling `.show()` +:open: + +Visualization relies on optional dependencies. Install the `viz` extra: + +```bash +pip install "python-doctr[viz]" +``` + +Similarly, `DocumentFile.from_url` needs the `html` extra and the `contrib` module needs the `contrib` extra +(see [](../getting_started/installation.md#optional-extras)). +::: + +:::{dropdown} The pretrained weights cannot be downloaded +Weights are downloaded on first use. Behind a proxy or on a machine without internet access, download them once +on a connected machine and copy the cache directory (`~/.cache/doctr` by default), or point `DOCTR_CACHE_DIR` to +a shared location. See [](../deployment/configuration.md). +::: + +:::{dropdown} Errors in a restricted environment (AWS Lambda, read-only file systems) +docTR writes its model cache to `~/.cache/doctr` and uses multiprocessing for some data operations. Set +`DOCTR_CACHE_DIR` to a writable directory and `DOCTR_MULTIPROCESSING_DISABLE=TRUE` +(see [](../deployment/configuration.md#aws-lambda)). +::: + +## Inputs + +:::{dropdown} `ValueError: incorrect input shape` +Predictors expect a **list** of pages, each a 3-channel array of shape `(H, W, 3)`. Wrap a single page in a list +(`predictor([page])`) and convert grayscale images to 3 channels: + +```python +import numpy as np + +page = np.stack([gray] * 3, axis=-1) +``` +::: + +:::{dropdown} The results are wrong on images loaded with OpenCV +OpenCV loads images in **BGR** order, while docTR expects **RGB**. Convert them first, or use +`DocumentFile.from_images`, which handles it for you: + +```python +import cv2 + +page = cv2.cvtColor(cv2.imread("scan.jpg"), cv2.COLOR_BGR2RGB) +``` +::: + +:::{dropdown} Does docTR use the text layer of digital PDFs? +No: every page is rendered to an image and read by the models, so scanned and digital-born PDFs are processed the +same way. If your PDFs have a reliable text layer and you only need the text, a PDF text extractor is faster. +::: + +## Accuracy + +:::{dropdown} Some words are missing +- Small print: render PDFs at a higher resolution, e.g. `DocumentFile.from_pdf(path, scale=3)`. +- Faint or low-contrast text: lower the detection thresholds (`bin_thresh`, `box_thresh`), see + [](ocr_predictor.md#tune-the-text-detection). +- Very large images: keep `preserve_aspect_ratio=True` (default) so the page is not distorted. +::: + +:::{dropdown} The output contains odd characters (accents, symbols) that are not in my documents +Restrict the recognition model to the characters you expect with a whitelist, see +[](ocr_predictor.md#restrict-the-output-characters). +::: + +:::{dropdown} My documents are in a language with another alphabet +The pretrained recognition models are trained on the `french` vocabulary (Latin script). For other scripts, look for +a [community model](../training/sharing_models.md#community-models) on the Hugging Face Hub, or +[train a model](../training/train.md) on a matching [vocabulary](../models/vocabs.md). +::: + +:::{dropdown} The text is read in the wrong order +Use an export method (`render()`, Markdown, HTML...), which applies the reading-order algorithm, rather than +iterating the blocks. Enable `detect_layout=True` for complex layouts, and set `direction` if the automatic +detection fails. See [](exporting.md#reading-order). +::: + +:::{dropdown} Rotated pages give poor results +The default mode assumes upright pages. Use `straighten_pages=True` or `assume_straight_pages=False` +(see [](rotated_documents.md)). +::: + +## Performance + +:::{dropdown} OCR is slow +- Run on a GPU, and use half precision (see [](../deployment/performance.md)). +- Create the predictor **once** and reuse it. +- Pass several pages per call and increase `det_bs` / `reco_bs` if your hardware allows it. +- Try lighter architectures (`fast_tiny`, `crnn_mobilenet_v3_small`), see the [model zoo](../models/index.md). +- Keep `assume_straight_pages=True` when your pages are upright. +::: + +:::{dropdown} I run out of GPU memory +Lower the batch sizes (`det_bs`, `reco_bs`), process fewer pages per call, or render PDFs at a lower `scale`. +::: + +## Still stuck? + +Search the [GitHub issues](https://github.com/mindee/doctr/issues) and +[discussions](https://github.com/mindee/doctr/discussions), or ask the community (see [](../community/index.md)). +When opening an issue, include the output of: + +```bash +python scripts/collect_env.py +``` diff --git a/docs/source/user_guide/index.md b/docs/source/user_guide/index.md new file mode 100644 index 0000000000..8c09b7a4b7 --- /dev/null +++ b/docs/source/user_guide/index.md @@ -0,0 +1,124 @@ +# User guide + +The user guide explains how to get the most out of docTR on your own documents, one topic at a time. Each page +is self-contained and full of examples you can copy. + +## The essentials + +::::{grid} 1 2 2 2 +:gutter: 3 + +:::{grid-item-card} {octicon}`file;1.1em` Loading documents +:link: loading_documents +:link-type: doc + +Read PDFs, images, web pages and in-memory data into pages. +::: + +:::{grid-item-card} {octicon}`gear;1.1em` Running OCR +:link: ocr_predictor +:link-type: doc + +Choose the models and configure the end-to-end OCR predictor. +::: + +:::{grid-item-card} {octicon}`list-unordered;1.1em` Working with results +:link: results +:link-type: doc + +Navigate the `Document`, convert coordinates, visualize and save predictions. +::: + +:::{grid-item-card} {octicon}`file-code;1.1em` Exporting results +:link: exporting +:link-type: doc + +Text, JSON, Markdown, AsciiDoc, HTML and hOCR, in reading order. +::: +:::: + +## Beyond plain text + +::::{grid} 1 2 2 2 +:gutter: 3 + +:::{grid-item-card} {octicon}`table;1.1em` Layout analysis & tables +:link: layout_and_tables +:link-type: doc + +Detect titles, figures and tables, and extract tables as row/column grids. +::: + +:::{grid-item-card} {octicon}`sync;1.1em` Rotated documents +:link: rotated_documents +:link-type: doc + +Read skewed scans, photos and pages in any orientation. +::: + +:::{grid-item-card} {octicon}`tag;1.1em` Key information extraction +:link: kie +:link-type: doc + +Detect several classes of fields (dates, amounts...) and read them in one pass. +::: + +:::{grid-item-card} {octicon}`package;1.1em` Task-specific predictors +:link: standalone_predictors +:link-type: doc + +Use the detection, recognition, layout, table and orientation models on their own. +::: + +:::{grid-item-card} {octicon}`image;1.1em` Artefact detection +:link: artefacts +:link-type: doc + +Find logos, QR codes, barcodes and photos with the `contrib` module. +::: + +:::{grid-item-card} {octicon}`terminal;1.1em` Command-line interface +:link: cli +:link-type: doc + +Run OCR on files from your terminal with `doctr-cli`. +::: +:::: + +## When something goes wrong + +:::{card} {octicon}`question;1.1em` Troubleshooting & FAQ +:link: faq +:link-type: doc + +Solutions to the most common installation, input and accuracy issues. +::: + +```{toctree} +:hidden: +:caption: The essentials + +loading_documents +ocr_predictor +results +exporting +``` + +```{toctree} +:hidden: +:caption: Beyond plain text + +layout_and_tables +rotated_documents +kie +standalone_predictors +artefacts +cli +``` + +```{toctree} +:hidden: +:caption: Help + +faq +``` diff --git a/docs/source/user_guide/kie.md b/docs/source/user_guide/kie.md new file mode 100644 index 0000000000..5587d1ff7f --- /dev/null +++ b/docs/source/user_guide/kie.md @@ -0,0 +1,73 @@ +# Key information extraction + +The OCR predictor finds *all* the words of a page. Often you only care about a few fields: the date, the total +amount, the invoice number... The **KIE predictor** (Key Information Extraction) uses a detection model trained on +several classes, so that each detected box comes with the *type* of information it contains. + +## How it differs from OCR + +| | {func}`~doctr.models.ocr_predictor` | {func}`~doctr.models.kie_predictor` | +| --- | --- | --- | +| Detection model | One class: `words` | Any number of classes (`date`, `total`, `address`...) | +| Output | {class}`~doctr.io.Document` of pages, blocks, lines and words | {class}`~doctr.io.KIEDocument` of pages with predictions grouped **by class** | +| Typical use | Read everything | Extract specific fields | + +## Run a KIE predictor + +The KIE predictor takes the same arguments as the OCR predictor (architectures, orientation, layout...): + +```python +from doctr.io import DocumentFile +from doctr.models import kie_predictor + +predictor = kie_predictor(det_arch="db_resnet50", reco_arch="crnn_vgg16_bn", pretrained=True) +result = predictor(DocumentFile.from_pdf("invoice.pdf")) + +page = result.pages[0] +for class_name, predictions in page.predictions.items(): + for prediction in predictions: + print(class_name, prediction.value, prediction.confidence, prediction.geometry) +``` + +Each {class}`~doctr.io.Prediction` is a word with an extra `crop_orientation` field. `render()` and the exports +group the predictions by class: + +```python +print(result.render()) +# words: ACME +# +# words: Office +# ... +``` + +:::{important} +The pretrained detection models only know the `words` class, so out of the box the KIE predictor behaves like the +OCR predictor with a flat output. Its value comes with a detection model **trained on your own classes**. +::: + +## Use your own classes + +1. Annotate your documents with one polygon list per class (see the + [multi-class detection format](../training/train.md#text-detection)), e.g. with the + [docTR Labeler](../community/tools.md#doctr-labeler). +2. Train a detection model with these classes using the + [detection training script](../training/train.md#text-detection). +3. Load it with its class names and plug it into the KIE predictor: + +```python +from doctr.models import db_resnet50, kie_predictor + +det_model = db_resnet50(pretrained=False, pretrained_backbone=False, class_names=["date", "total", "invoice_id"]) +det_model.from_pretrained("path/to/your/checkpoint.pt") + +predictor = kie_predictor(det_arch=det_model, reco_arch="crnn_vgg16_bn", pretrained=True) +result = predictor(pages) + +dates = [prediction.value for prediction in result.pages[0].predictions["date"]] +``` + +## Visualize and export + +{class}`~doctr.io.KIEDocument` offers the same tools as `Document`: `show()` draws the boxes with one color per +class, `export()` returns a JSON-ready dict with a `predictions` key per page, and the +[export formats](exporting.md) are available as well. diff --git a/docs/source/user_guide/layout_and_tables.md b/docs/source/user_guide/layout_and_tables.md new file mode 100644 index 0000000000..5ad7ece8ff --- /dev/null +++ b/docs/source/user_guide/layout_and_tables.md @@ -0,0 +1,177 @@ +# Layout analysis & tables + +Text alone loses the structure of a document. docTR can also tell *what* each region of a page is (a title, a +paragraph, a table, a figure...) and rebuild tables as rows and columns. + +## Layout analysis + +A layout model detects and classifies the regions of a page. The pretrained `lw_detr_s` model recognizes +11 classes: + +{bdg-primary-line}`Title` {bdg-primary-line}`Section-header` {bdg-primary-line}`Text` {bdg-primary-line}`List-item` +{bdg-primary-line}`Caption` {bdg-primary-line}`Footnote` {bdg-primary-line}`Formula` {bdg-primary-line}`Table` +{bdg-primary-line}`Picture` {bdg-primary-line}`Page-header` {bdg-primary-line}`Page-footer` + +### In the OCR pipeline + +Pass `detect_layout=True` and the regions are attached to every page as +{class}`~doctr.io.LayoutElement` objects: + +```python +from doctr.io import DocumentFile +from doctr.models import ocr_predictor + +predictor = ocr_predictor(pretrained=True, detect_layout=True) +result = predictor(DocumentFile.from_pdf("invoice.pdf")) + +for region in result.pages[0].layout: + print(f"{region.type:<15} {region.confidence:.2f}") +``` + +```text +Page-footer 0.89 +Section-header 0.75 +Section-header 0.67 +Table 0.95 +Text 0.78 +... +``` + +```{image} ../_static/images/examples/invoice_layout.png +:alt: Words and layout regions overlaid on the invoice +:width: 520px +:align: center +``` + +The layout also improves the exports: headings, lists, tables, figures, headers and footers are rendered with the +right structure and in the right place (see [](exporting.md)). It is part of the JSON export under the `layout` +key, and `page.show()` draws it. + +The layout model shares the input settings of the predictor (`assume_straight_pages`, `preserve_aspect_ratio`...) +and the batch size of the detection model (`det_bs`). Use `layout_arch` to select another architecture, or to pass +[your own fine-tuned model](../training/custom_models.md#layout-models). + +### Ignore some regions + +Pictures, stamps or tables can produce noisy text. `ignore_regions` masks the regions of the given layout classes +**before** text detection and recognition, so their content is simply not read: + +```python +predictor = ocr_predictor(pretrained=True, detect_layout=True, ignore_regions=["Picture", "Page-footer"]) +``` + +:::{important} +`ignore_regions` relies on the layout model: enable it with `detect_layout=True` (or `detect_tables=True`), +otherwise the regions cannot be located and nothing is masked. The CLI enables it automatically. +::: + +## Table structure recognition + +With `detect_tables=True`, each region the layout model labels as a `Table` is cropped and passed to a table +structure model (`tablecenternet`), which predicts its cells and the rows and columns they span. The words falling +inside a cell are regrouped into a {class}`~doctr.io.Table`: + +```python +from doctr.io import DocumentFile +from doctr.models import ocr_predictor + +predictor = ocr_predictor(pretrained=True, detect_tables=True) +result = predictor(DocumentFile.from_pdf("invoice.pdf")) + +table = result.pages[0].tables[0] +print(table.num_rows, table.num_cols) # 5 4 +print(table.render()) +``` + +```text +Description Qty Unit price Amount +Recycled paper A4 10 8.50 85.00 +Ballpoint pens (box) 5 12.00 60.00 +Desk lamp 2 45.00 90.00 +Office chair 1 1,015.00 1,015.00 +``` + +:::{important} +The words of a table are **removed from the regular `blocks`** of the page, so the same text is not returned +twice. Read them through `page.tables`, or use an export: tables are rendered in place by `render()`, Markdown, +AsciiDoc and HTML, and stored under the `tables` key of the JSON export. +::: + +### Load a table into pandas + +{meth}`~doctr.io.Table.to_grid` returns a dense `num_rows` × `num_cols` grid of strings. A cell spanning several +rows or columns has its value at its top-left position: + +```python +import pandas as pd + +grid = table.to_grid() +df = pd.DataFrame(grid[1:], columns=grid[0]) # first row as header +print(df) +``` + +```text + Description Qty Unit price Amount +0 Recycled paper A4 10 8.50 85.00 +1 Ballpoint pens (box) 5 12.00 60.00 +2 Desk lamp 2 45.00 90.00 +3 Office chair 1 1,015.00 1,015.00 +``` + +### Cells, spans and geometry + +For full control, iterate over the {class}`~doctr.io.TableCell` objects: + +```python +for cell in table.cells: + print(cell.value, (cell.row_start, cell.row_end), (cell.col_start, cell.col_end), cell.geometry) +``` + +| Attribute | Description | +| ---------------------------- | ------------------------------------------------------------------- | +| `value` | The text of the words assigned to the cell | +| `row_start`, `row_end` | First and last row spanned by the cell (0-indexed, inclusive) | +| `col_start`, `col_end` | First and last column spanned by the cell (0-indexed, inclusive) | +| `row_span`, `col_span` | Number of rows / columns spanned | +| `geometry` | Position of the cell, relative to the page | +| `confidence` | Mean recognition confidence of the words of the cell | +| `objectness_score` | Score of the cell predicted by the table model | + +## Standalone predictors + +Both models can also run on their own, without text recognition: + +::::{tab-set} + +:::{tab-item} Layout +```python +from doctr.models import layout_predictor + +predictor = layout_predictor("lw_detr_s", pretrained=True) +out = predictor(pages) + +# One dict per page +out[0]["class_names"] # ['Page-footer', 'Section-header', ..., 'Table', 'Text', ...] +out[0]["boxes"] # (N, 4) array of relative (xmin, ymin, xmax, ymax) +out[0]["scores"] # N confidence scores +``` +::: + +:::{tab-item} Table structure +Pass images of tables, e.g. crops of the `Table` regions: + +```python +from doctr.models import table_predictor + +predictor = table_predictor("tablecenternet", pretrained=True) +out = predictor([table_image]) + +out[0]["num_rows"], out[0]["num_cols"] # (5, 4) +out[0]["cells"][0] +# {'geometry': [[x1, y1], ..., [x4, y4]], # relative to the table image +# 'score': 0.42, 'row_start': 0, 'row_end': 0, 'col_start': 0, 'col_end': 0} +``` +::: +:::: + +See [](standalone_predictors.md) for all the task-specific predictors. diff --git a/docs/source/user_guide/loading_documents.md b/docs/source/user_guide/loading_documents.md new file mode 100644 index 0000000000..5798f20eb6 --- /dev/null +++ b/docs/source/user_guide/loading_documents.md @@ -0,0 +1,131 @@ +# Loading documents + +Predictors work on **pages**: a list of NumPy arrays of shape `(H, W, 3)`, in RGB order and `uint8` type. +{class}`~doctr.io.DocumentFile` reads PDFs, images and web pages into that format, so you rarely have to think +about it. + +## From a PDF + +```python +from doctr.io import DocumentFile + +pages = DocumentFile.from_pdf("path/to/report.pdf") +print(len(pages), pages[0].shape) # e.g. 3 (2339, 1653, 3) +``` + +Every page of the PDF is rendered to an image (with [pypdfium2](https://pypdfium2.readthedocs.io)), so +`pages[i]` and `result.pages[i]` always correspond to the *i*-th page of the file. + +::::{tab-set} + +:::{tab-item} Rendering resolution +The `scale` parameter controls the rendering resolution, where `1` corresponds to 72 dpi. The default, `2` +(144 dpi), is a good trade-off for most documents; increase it for small print: + +```python +pages = DocumentFile.from_pdf("path/to/report.pdf", scale=3) # 216 dpi +``` +::: + +:::{tab-item} Encrypted PDF +```python +pages = DocumentFile.from_pdf("path/to/protected.pdf", password="s3cr3t") +``` +::: + +:::{tab-item} From memory +Pass the raw bytes, e.g. a file received by a web API or downloaded from object storage: + +```python +with open("path/to/report.pdf", "rb") as f: + pages = DocumentFile.from_pdf(f.read()) +``` +::: +:::: + +:::{tip} +docTR always runs OCR on the rendered pages, even when the PDF already contains a text layer. This gives +consistent results on scanned and digital-born PDFs alike. +::: + +## From images + +Any format supported by OpenCV works (PNG, JPEG, TIFF, BMP, WebP...). Pass one path, or several to build a +multi-page document: + +```python +from doctr.io import DocumentFile + +# A single image is a one-page document +pages = DocumentFile.from_images("path/to/scan.jpg") + +# Several images are read as consecutive pages of the same document +pages = DocumentFile.from_images(["path/to/page1.png", "path/to/page2.png"]) + +# Raw bytes work too +with open("path/to/scan.jpg", "rb") as f: + pages = DocumentFile.from_images(f.read()) +``` + +:::{note} +A multi-frame TIFF is read as a single image (its first frame). Split it into frames first, e.g. with Pillow, +and pass them as NumPy arrays (see below). +::: + +## From a web page + +Web pages are first printed to PDF with [WeasyPrint](https://weasyprint.org), so this requires the `html` extra +(`pip install "python-doctr[html]"`): + +```python +from doctr.io import DocumentFile + +pages = DocumentFile.from_url("https://www.example.com") +``` + +## From NumPy arrays + +If your images are already in memory, e.g. frames from a camera or pages produced by another library, +pass them directly to the predictor as a list of arrays: + +```python +import numpy as np +from PIL import Image + +from doctr.models import ocr_predictor + +predictor = ocr_predictor(pretrained=True) + +page = np.asarray(Image.open("path/to/scan.png").convert("RGB")) # (H, W, 3), uint8, RGB +result = predictor([page]) +``` + +:::{warning} +Make sure arrays are **RGB** (OpenCV loads images as BGR: convert them with `cv2.cvtColor(img, cv2.COLOR_BGR2RGB)`), +of type `uint8`, and have **3 channels** (convert grayscale images with `np.stack([img] * 3, axis=-1)`). +::: + +## Mixing sources + +`DocumentFile` methods return plain Python lists, so you can concatenate them into a single document: + +```python +pages = DocumentFile.from_pdf("cover_letter.pdf") + DocumentFile.from_images(["id_front.jpg", "id_back.jpg"]) +result = predictor(pages) # one Document with all the pages, in order +``` + +## Low-level readers + +`DocumentFile` is a thin wrapper around these functions, which you can use directly when you need more control: + +| Function | Reads | +| --------------------------------------------- | --------------------------------------------------------- | +| {func}`~doctr.io.read_pdf` | A PDF into a list of arrays (`scale`, `password`, `rgb_mode`) | +| {func}`~doctr.io.read_img_as_numpy` | One image into an array (optionally resized) | +| {func}`~doctr.io.read_img_as_tensor` | One image into a `torch.Tensor` | +| {func}`~doctr.io.decode_img_as_tensor` | Image bytes into a `torch.Tensor` | +| {func}`~doctr.io.read_html` | A web page into PDF bytes | + +## Next steps + +Your pages are ready: head to [](ocr_predictor.md) to configure the predictor that will read them. diff --git a/docs/source/user_guide/ocr_predictor.md b/docs/source/user_guide/ocr_predictor.md new file mode 100644 index 0000000000..f43b950039 --- /dev/null +++ b/docs/source/user_guide/ocr_predictor.md @@ -0,0 +1,323 @@ +# Running OCR + +{func}`~doctr.models.ocr_predictor` builds the end-to-end pipeline: it detects the words of each page, reads them, +and assembles the result into a {class}`~doctr.io.Document`. This page walks through its options, from choosing +the models to fine-tuning the output. + +## Create a predictor + +```python +from doctr.io import DocumentFile +from doctr.models import ocr_predictor + +predictor = ocr_predictor(pretrained=True) + +pages = DocumentFile.from_pdf("path/to/report.pdf") +result = predictor(pages) +``` + +Building a predictor loads the models (and downloads their weights the first time), so **create it once and reuse +it** for all your documents. The predictor runs in inference mode and accepts any number of pages per call. + +## Choose the architectures + +By default, `ocr_predictor` uses `fast_base` for text detection and `crnn_vgg16_bn` for text recognition. Pass the +names of other architectures to trade speed for accuracy: + +```python +predictor = ocr_predictor(det_arch="db_resnet50", reco_arch="parseq", pretrained=True) +``` + +| You want... | Detection (`det_arch`) | Recognition (`reco_arch`) | +| ---------------------------------- | --------------------------------------- | ------------------------------------------------ | +| A good default | `fast_base` | `crnn_vgg16_bn` | +| Maximum speed (CPU, edge devices) | `fast_tiny`, `db_mobilenet_v3_large` | `crnn_mobilenet_v3_small`, `viptr_tiny` | +| Maximum accuracy | `db_resnet50`, `fast_base` | `parseq`, `vitstr_base`, `master` | + +The [model zoo](../models/index.md) lists every architecture with its benchmark. Instead of a name, you can also pass +a model **instance**, which is how you plug in [your own trained models](../training/custom_models.md): + +```python +from doctr.models import ocr_predictor, fast_base, parseq + +predictor = ocr_predictor( + det_arch=fast_base(pretrained=True), + reco_arch=parseq(pretrained=True), +) +``` + +## Options at a glance + +All options are keyword arguments of {func}`~doctr.models.ocr_predictor`. + +::::{tab-set} + +:::{tab-item} Page geometry +| Option | Default | Effect | +| -------------------------- | ------- | -------------------------------------------------------------------------------------------------------- | +| `assume_straight_pages` | `True` | Assume upright pages with horizontal text: fastest, returns straight boxes. Set to `False` for rotated or skewed documents. | +| `straighten_pages` | `False` | Estimate the page rotation and straighten the pages before running the models | +| `export_as_straight_boxes` | `False` | With `assume_straight_pages=False`, still export straight boxes instead of polygons | +| `preserve_aspect_ratio` | `True` | Pad the pages instead of stretching them when resizing them for the models | +| `symmetric_pad` | `True` | With `preserve_aspect_ratio=True`, pad on all sides instead of bottom-right only | + +See [](rotated_documents.md) for a detailed guide. +::: + +:::{tab-item} Extra predictions +| Option | Default | Effect | +| -------------------- | ------------ | --------------------------------------------------------------------------------------------------- | +| `detect_orientation` | `False` | Estimate the general orientation of each page, available as `page.orientation` | +| `detect_language` | `False` | Estimate the language of each page, available as `page.language` | +| `detect_layout` | `False` | Run a layout model and attach its regions to each page (`page.layout`) | +| `layout_arch` | `"lw_detr_s"`| Layout architecture name or model instance | +| `detect_tables` | `False` | Recognize the structure of tables (`page.tables`); enables the layout model | +| `ignore_regions` | `None` | Layout classes to mask out before OCR, e.g. `["Picture"]`; requires the layout model (`detect_layout=True`) | + +See [](layout_and_tables.md). +::: + +:::{tab-item} Output structure +| Option | Default | Effect | +| -------------------- | ------- | --------------------------------------------------------------------------------------- | +| `resolve_lines` | `True` | Group words into lines | +| `resolve_blocks` | `False` | Group lines into blocks (by default, all the lines of a page form a single block) | +| `paragraph_break` | `0.035` | Minimum horizontal gap, relative to the page width, that splits two words into different lines | +| `keep_reading_order` | `False` | Sort the blocks of every page in reading order | +::: + +:::{tab-item} Performance +| Option | Default | Effect | +| -------------------------- | ------- | --------------------------------------------------------------------------------------- | +| `det_bs` | `2` | Batch size of the detection model (also used by the layout and table models) | +| `reco_bs` | `128` | Batch size of the recognition model (number of word crops per batch) | +| `disable_page_orientation` | `False` | Skip the page orientation classifier (see [rotated documents](rotated_documents.md)) | +| `disable_crop_orientation` | `False` | Skip the word crop orientation classifier | + +See [](../deployment/performance.md) for GPU and half-precision inference. +::: +:::: + +## Tune the text detection + +The detection models output a probability map that is binarized, then split into boxes. Two thresholds of the +post-processor control this step: + +`bin_thresh` +: Minimum probability for a pixel to be considered as text. Lower it to catch faint text, raise it to separate words + that are close to each other. + +`box_thresh` +: Minimum mean score of a box to be kept. Lower it to keep more (uncertain) words, raise it to drop noise. + +```python +predictor = ocr_predictor(pretrained=True) + +predictor.det_predictor.model.postprocessor.bin_thresh = 0.3 +predictor.det_predictor.model.postprocessor.box_thresh = 0.1 +``` + +## Confidence scores + +The recognition model predicts a probability for each character of a word. They are aggregated into +`word.confidence` with one of the following methods: + +| Method | Behavior | +| ------------------------------------- | ----------------------------------------------------------------------------------- | +| `"min"` | The least confident character (default for CRNN, VIPTR, SAR and MASTER) | +| `"mean"` | Average probability (default for ViTSTR and PARSeq) | +| `"geometric_mean"`, `"harmonic_mean"` | Means that penalize a single uncertain character more than the arithmetic mean | +| `"median"`, `"max"` | Ignore most uncertain characters: they barely separate misread words from correct ones | +| a callable | Any function taking the 1D array of probabilities and returning a float | + +For the probabilities `[0.99, 0.98, 0.12, 0.99]` (one doubtful character), the mean is `0.77`, the geometric mean +`0.58`, the harmonic mean `0.35` and the minimum `0.12`. Pick a method that makes uncertain words stand out if +you use the confidence to flag words for human review. + +```python +from doctr.models import ocr_predictor, parseq + +# When building the predictor (modifies a model instance passed as `reco_arch`) +predictor = ocr_predictor(pretrained=True, confidence_aggregation="geometric_mean") + +# Or afterwards, on the post-processor of the recognition model +predictor.reco_predictor.model.postprocessor.confidence_aggregation = "mean" + +# Or on a model instance +predictor = ocr_predictor(reco_arch=parseq(pretrained=True, confidence_aggregation="min"), pretrained=True) +``` + +:::{dropdown} Details: which probabilities are aggregated? +Only the probabilities of the predicted characters: the CTC blank frames (CRNN, VIPTR) and the positions from the +end-of-sequence token onwards (SAR, MASTER) are ignored, and a CTC character has the highest probability of its +frames. An empty prediction has a confidence of `0`. + +Very wide crops are split into parts that are read separately; their confidences are combined with the +`split_confidence_aggregation` of the recognition predictor (`"min"` by default): + +```python +predictor.reco_predictor.split_confidence_aggregation = "mean" +``` +::: + +## Restrict the output characters + +If you know which languages to expect, restrict the recognition model to their characters with +{func}`~doctr.models.utils.add_whitelist`. Forbidden characters can no longer be predicted, which removes a whole +class of errors (e.g. `ł` read instead of `l` on an English document). + +```python +from doctr.datasets import VOCABS +from doctr.models import ocr_predictor +from doctr.models.utils import add_whitelist + +predictor = ocr_predictor(pretrained=True) + +# Only German and Polish characters can be predicted from now on +handle = add_whitelist(predictor, [VOCABS["german"], VOCABS["polish"]]) +result = predictor(pages) + +# Restore the original decoding +handle.remove() +``` + +The handle is also a context manager, which removes the whitelist when the block ends: + +```python +with add_whitelist(predictor, VOCABS["german"]): + result = predictor(pages) +``` + +By default, forbidden characters are dropped (`strategy="mask"`) and the model falls back to its best allowed +character. With `strategy="nearest"`, each forbidden character is first folded onto the closest allowed one, +which is handy to normalize accents onto a base alphabet: + +```python +# é -> e, ł -> l, ß -> ss...: an ASCII-only output +handle = add_whitelist(predictor, VOCABS["latin"], strategy="nearest") +``` + +The closest character is found by transliteration by default. Pass `mapping="weights"` to derive it from the +model's own confusions instead, or a `{"forbidden": "allowed"}` dict to override specific characters. + +:::{note} +A whitelist can only *restrict* a model: characters it was not trained on are silently ignored. To read a new +alphabet, use a model trained on a matching [vocabulary](../models/vocabs.md). +::: + +## Hooks + +Hooks let you edit the detected boxes **before they are read**. A hook is a callable that receives the location +predictions right after text detection and returns them, possibly modified. The recognition model then reads the +crops of the boxes the hook returned: + +- **remove** boxes to skip regions you don't need (they are not read, which also saves time); +- **move or resize** boxes, e.g. to pad crops that are too tight; +- **add** boxes, e.g. for fields at a known position on a form. + +The location predictions are a list with one NumPy array per page, in relative coordinates: `(N, 4)` straight boxes +as `xmin, ymin, xmax, ymax`, or `(N, 4, 2)` polygons with `assume_straight_pages=False`. A hook returns the same +structure, with any number of boxes per page. + +::::{tab-set} + +:::{tab-item} Skip headers & footers +```python +import numpy as np + +from doctr.models import ocr_predictor + + +class SkipMargins: + """Ignore the words in the top and bottom bands of the page (headers, footers)""" + + def __init__(self, band: float = 0.05): + self.band = band + + def __call__(self, loc_preds: list[np.ndarray]) -> list[np.ndarray]: + kept = [] + for boxes in loc_preds: # one array per page, relative coordinates + if boxes.ndim == 2: # straight boxes (N, 4): xmin, ymin, xmax, ymax + center_y = (boxes[:, 1] + boxes[:, 3]) / 2 + else: # rotated boxes (N, 4, 2): polygons + center_y = boxes[:, :, 1].mean(axis=1) + keep = (center_y > self.band) & (center_y < 1 - self.band) + kept.append(boxes[keep]) + return kept + + +predictor = ocr_predictor(pretrained=True) +predictor.add_hook(SkipMargins()) +``` + +On the {download}`sample invoice <../_static/images/examples/invoice.pdf>`, the footer line +(`ACME Office Supplies GmbH - VAT ID ...`) is no longer read. +::: + +:::{tab-item} Pad the boxes +```python +import numpy as np + +from doctr.models import ocr_predictor + + +def pad_boxes(loc_preds: list[np.ndarray], pad: float = 0.002) -> list[np.ndarray]: + """Enlarge the straight boxes slightly, so that the crops keep the character edges""" + offsets = np.array([-pad, -pad, pad, pad]) + return [np.clip(boxes + offsets.astype(boxes.dtype), 0, 1) for boxes in loc_preds] + + +predictor = ocr_predictor(pretrained=True) +predictor.add_hook(pad_boxes) +``` +::: +:::: + +Good to know: + +- Hooks run in the order they were added, each one receiving the output of the previous one. +- Each returned box keeps the `objectness_score` of the closest detected box of its page; a box added on a page + without any detection gets a score of `1`. +- With the [KIE predictor](kie.md), hooks receive and return a dict with one such list per class. +- An output in the wrong format (not one array per page, or boxes of another shape) raises a `ValueError`. + +## Process many documents + +- **Reuse the predictor**: building it is the expensive part. +- **Batch your pages**: one call on a list of pages is faster than one call per page, as the models process + several pages and word crops at once (see `det_bs` and `reco_bs` above). +- **Free memory between documents**: the predictor returns plain Python objects, so drop the results you no + longer need. + +```python +from pathlib import Path + +from doctr.io import DocumentFile +from doctr.models import ocr_predictor + +predictor = ocr_predictor(pretrained=True) + +for path in sorted(Path("invoices").glob("*.pdf")): + result = predictor(DocumentFile.from_pdf(path)) + path.with_suffix(".txt").write_text(result.render()) +``` + +## Next steps + +::::{grid} 1 2 2 2 +:gutter: 3 + +:::{grid-item-card} {octicon}`list-unordered;1.1em` Working with results +:link: results +:link-type: doc + +Navigate the `Document`, read words and boxes, and save the predictions. +::: + +:::{grid-item-card} {octicon}`rocket;1.1em` Performance +:link: ../deployment/performance +:link-type: doc + +Run on GPU, in half precision, and compile the models. +::: +:::: diff --git a/docs/source/user_guide/results.md b/docs/source/user_guide/results.md new file mode 100644 index 0000000000..93978ccac4 --- /dev/null +++ b/docs/source/user_guide/results.md @@ -0,0 +1,234 @@ +# Working with results + +The OCR predictor returns a {class}`~doctr.io.Document`. This page shows how to navigate it, turn its geometries +into pixels, visualize the predictions, and save them for later. + +:::{seealso} +[](../getting_started/concepts.md#the-document-tree) introduces the elements of the tree and +[](exporting.md) covers the text-oriented export formats (Markdown, HTML, hOCR...). +::: + +## Navigate the tree + +```python +from doctr.io import DocumentFile +from doctr.models import ocr_predictor + +predictor = ocr_predictor(pretrained=True) +result = predictor(DocumentFile.from_pdf("invoice.pdf")) + +page = result.pages[0] +page.dimensions # (height, width) of the page image, in pixels: (1684, 1191) +page.page # the page image itself, as a NumPy array +page.blocks # list of Block +page.orientation # {"value": ..., "confidence": ...} with detect_orientation=True +page.language # {"value": ..., "confidence": ...} with detect_language=True +page.layout # list of LayoutElement with detect_layout=True +page.tables # list of Table with detect_tables=True +``` + +Most of the time, you want a flat list of words. A small generator does the job: + +```python +def iter_words(document): + for page_idx, page in enumerate(document.pages): + for block in page.blocks: + for line in block.lines: + for word in line.words: + yield page_idx, word + + +words = [word.value for _, word in iter_words(result)] +``` + +Every element also has a `render()` method that returns its text: `line.render()` joins the words of a line with +spaces, `page.render()` returns the whole page. + +## Filter by confidence + +`word.confidence` tells how sure the recognition model is. Use it to send doubtful words to a human, or to drop +noise: + +```python +doubtful = [(page_idx, word.value, word.confidence) for page_idx, word in iter_words(result) if word.confidence < 0.5] +``` + +:::{tip} +The way character probabilities are combined into `word.confidence` is configurable: see +[confidence scores](ocr_predictor.md#confidence-scores). +::: + +## Convert coordinates to pixels + +Geometries are relative to the page size, with `(0, 0)` at the top-left corner. Multiply them by the page +dimensions to get pixels: + +::::{tab-set} + +:::{tab-item} Straight boxes +```python +import numpy as np + +height, width = page.dimensions +(xmin, ymin), (xmax, ymax) = word.geometry + +x0, y0 = int(xmin * width), int(ymin * height) +x1, y1 = int(xmax * width), int(ymax * height) + +word_image = page.page[y0:y1, x0:x1] # crop the word from the page +``` +::: + +:::{tab-item} Rotated boxes +With `assume_straight_pages=False`, geometries are `(4, 2)` arrays of points: + +```python +import numpy as np + +height, width = page.dimensions +polygon = np.asarray(word.geometry) * np.array([width, height]) # (4, 2) pixel coordinates +``` +::: +:::: + +## Find a value next to a keyword + +Combining positions and text lets you build simple extraction rules. For instance, to read the amount written to +the right of *Total*: + +```python +def value_right_of(page, keyword): + words = [w for block in page.blocks for line in block.lines for w in line.words] + anchor = next(w for w in words if w.value.lower().startswith(keyword.lower())) + (_, ay0), (ax1, ay1) = anchor.geometry + center_y = (ay0 + ay1) / 2 + # Words on the same horizontal band, to the right of the keyword, sorted left to right + candidates = [w for w in words if w.geometry[0][1] < center_y < w.geometry[1][1] and w.geometry[0][0] > ax1] + return " ".join(w.value for w in sorted(candidates, key=lambda w: w.geometry[0][0])) + + +print(value_right_of(result.pages[0], "Total")) # due 1,250.00 EUR +``` + +For forms and invoices with many fields, a [KIE predictor](kie.md) trained on your fields is more robust than +hand-written rules. + +## Visualize the predictions + +::::{tab-set} + +:::{tab-item} Interactive window +Requires the `viz` extra. Hover a box to see the word and its confidence: + +```python +result.show() # all pages; or result.pages[0].show() +``` + +When the layout model ran, its regions are drawn as well (`display_layout=False` hides them). +::: + +:::{tab-item} Image file +{func}`~doctr.utils.visualization.visualize_page` returns a Matplotlib figure that you can save, e.g. on a +server without display: + +```python +import matplotlib + +matplotlib.use("Agg") # no display needed +from doctr.utils.visualization import visualize_page + +page = result.pages[0] +fig = visualize_page(page.export(), page.page, interactive=False, add_labels=False) +fig.savefig("predictions.png", bbox_inches="tight") +``` +::: +:::: + +```{image} ../_static/images/examples/invoice_words.png +:alt: Detected words overlaid on the invoice +:width: 520px +:align: center +``` + +## Rebuild the page from its predictions + +{meth}`~doctr.io.Page.synthesize` draws the recognized words at their position on a blank page. It is a quick +way to eyeball what was read, and where: + +```python +import matplotlib.pyplot as plt + +synthetic_pages = result.synthesize() # one image per page +plt.imshow(synthetic_pages[0]) +plt.axis("off") +plt.show() +``` + +```{image} ../_static/images/examples/invoice_synthesized.png +:alt: The invoice rebuilt from the predictions +:width: 520px +:align: center +``` + +## Save and reload + +{meth}`~doctr.io.Document.export` converts the result into a nested dictionary of plain Python types, ready for +JSON. {meth}`~doctr.io.Document.from_dict` rebuilds the `Document` from it: + +```python +import json + +from doctr.io import Document + +# Save +with open("invoice.json", "w") as f: + json.dump(result.export(), f) + +# ... later: reload +with open("invoice.json") as f: + restored = Document.from_dict(json.load(f)) + +print(restored.render()) +``` + +The page images are not part of the export. Pass them back with `Document.from_dict(data, pages=pages)` if you +need them, e.g. to call `show()`. + +:::{dropdown} What does the JSON export look like? +```json +{ + "pages": [ + { + "page_idx": 0, + "dimensions": [1684, 1191], + "orientation": {"value": null, "confidence": null}, + "language": {"value": null, "confidence": null}, + "blocks": [ + { + "geometry": [[0.0706, 0.0537], [0.3992, 0.0732]], + "objectness_score": 0.83, + "lines": [ + { + "geometry": [[0.0706, 0.0537], [0.3992, 0.0732]], + "objectness_score": 0.83, + "words": [ + { + "value": "ACME", + "confidence": 0.99995, + "geometry": [[0.0706, 0.0537], [0.1603, 0.0713]], + "objectness_score": 0.80, + "crop_orientation": {"value": 0, "confidence": null} + } + ] + } + ], + "artefacts": [] + } + ], + "layout": [], + "tables": [] + } + ] +} +``` +::: diff --git a/docs/source/user_guide/rotated_documents.md b/docs/source/user_guide/rotated_documents.md new file mode 100644 index 0000000000..abfd886469 --- /dev/null +++ b/docs/source/user_guide/rotated_documents.md @@ -0,0 +1,94 @@ +# Rotated documents + +Scans are rarely perfectly aligned: pages are skewed, photographed at an angle, fed upside down into the scanner, +or contain vertical text. docTR handles all of these cases, at a small cost in speed. This page helps you choose +the right options. + +## Which setting do I need? + +| Your documents | Recommended options | +| ------------------------------------------------------------------- | ------------------------------------------------------------------ | +| Upright pages, horizontal text (digital PDFs, clean scans) | defaults (`assume_straight_pages=True`): fastest | +| Slightly skewed or rotated pages, and you want **straight boxes** | `straighten_pages=True` | +| Pages in any orientation, text in several directions | `assume_straight_pages=False` | +| As above, but your application only understands rectangles | `assume_straight_pages=False, export_as_straight_boxes=True` | +| You only want to **know** how each page is rotated | `detect_orientation=True` | + +## Straight pages (default) + +With `assume_straight_pages=True`, docTR fits axis-aligned boxes to the text and reads the crops as they are. +This is the fastest mode and the right one for most digital documents. Geometries are +`((xmin, ymin), (xmax, ymax))` pairs. + +## Straighten the pages first + +`straighten_pages=True` estimates the rotation of each page, rotates the page back, and only then runs text +detection and recognition. The output looks as if the page had been scanned straight: straight boxes, and a +reading order that follows the text. + +```python +from doctr.models import ocr_predictor + +predictor = ocr_predictor(pretrained=True, straighten_pages=True) +``` + +The coordinates refer to the **straightened** page, which may have different dimensions than the input (check +`page.dimensions`). To get coordinates on the original page instead, add `preserve_original_coords=True`. + +## Rotated boxes + +`assume_straight_pages=False` makes the detection model predict **rotated boxes**, and enables two small +classifiers: + +- a **page orientation** classifier, which estimates the general orientation of each page; +- a **crop orientation** classifier, which rotates each word crop upright (0°, 90°, 180° or 270°) before it is read. + +```python +predictor = ocr_predictor(pretrained=True, assume_straight_pages=False) +result = predictor(pages) + +word = result.pages[0].blocks[0].lines[0].words[0] +word.geometry # four (x, y) points: ((x1, y1), (x2, y2), (x3, y3), (x4, y4)) +word.crop_orientation # {'value': 0, 'confidence': 1.0} +``` + +Add `export_as_straight_boxes=True` to get the enclosing axis-aligned boxes instead of polygons. + +## Detect the page orientation + +`detect_orientation=True` adds the estimated rotation of each page, in degrees, to the results. It works with every +other option: + +```python +predictor = ocr_predictor(pretrained=True, assume_straight_pages=False, detect_orientation=True) +result = predictor(pages) + +print(result.pages[0].orientation) # {'value': -10, 'confidence': None} for a page skewed by 10° +``` + +## Speed up the orientation models + +The orientation classifiers are light, but you can skip them when you know your documents well: + +`disable_page_orientation=True` +: Skip the page orientation classifier, e.g. for pages that are only slightly skewed (between about -45° and 45°). + Only relevant with `assume_straight_pages=False`, `straighten_pages=True` or `detect_orientation=True`. + +`disable_crop_orientation=True` +: Skip the word crop classifier when all the text is horizontal (never upside down or vertical). Only relevant + with `assume_straight_pages=False` or `straighten_pages=True`. + +```python +predictor = ocr_predictor( + pretrained=True, + assume_straight_pages=False, + disable_page_orientation=True, + disable_crop_orientation=True, +) +``` + +## Learn more + +- The [Dealing with rotated documents](https://colab.research.google.com/github/mindee/notebooks/blob/main/doctr/dealing_with_rotations.ipynb) + notebook compares all these modes on real examples. +- The orientation classifiers can be [retrained on your data](../training/custom_models.md#orientation-classifiers). diff --git a/docs/source/user_guide/standalone_predictors.md b/docs/source/user_guide/standalone_predictors.md new file mode 100644 index 0000000000..27d1ebfdaf --- /dev/null +++ b/docs/source/user_guide/standalone_predictors.md @@ -0,0 +1,96 @@ +# Task-specific predictors + +The OCR predictor chains several models. Each of them is also available on its own, wrapped in a predictor that +handles pre-processing, batching and post-processing. Use them when you only need one step: locating text, +reading pre-cropped words, classifying orientations... + +| Predictor | Input | Output per input image | +| ------------------------------------------------------ | ------------------------- | ------------------------------------------------------ | +| {func}`~doctr.models.detection.detection_predictor` | Pages | `{class_name: array of boxes + scores}` | +| {func}`~doctr.models.recognition.recognition_predictor` | Word crops | `(text, confidence)` | +| {func}`~doctr.models.layout.layout_predictor` | Pages | `{"class_names", "boxes", "scores"}` | +| {func}`~doctr.models.table_structure.table_predictor` | Table images | `{"cells", "num_rows", "num_cols"}` | +| {func}`~doctr.models.classification.page_orientation_predictor` | Pages | Orientation class, angle and probability | +| {func}`~doctr.models.classification.crop_orientation_predictor` | Word crops | Orientation class, angle and probability | + +All of them take a **list of images** (NumPy arrays of shape `(H, W, 3)`, `uint8`, RGB) and return one result per +image. As with the OCR predictor, pass `pretrained=True` to load trained weights. + +## Text detection + +```python +from doctr.io import DocumentFile +from doctr.models import detection_predictor + +predictor = detection_predictor("fast_base", pretrained=True) +pages = DocumentFile.from_pdf("invoice.pdf") +out = predictor(pages) + +boxes = out[0]["words"] # (N, 5): xmin, ymin, xmax, ymax, score (relative coordinates) +``` + +With `assume_straight_pages=False`, each box is a rotated polygon: the array has shape `(N, 5, 2)`, with the +four corners followed by the score. Multi-class detection models (see [](kie.md)) return one entry per class. + +The predictor accepts the same input options as the OCR predictor: `assume_straight_pages`, +`preserve_aspect_ratio`, `symmetric_pad` and `batch_size`. + +## Text recognition + +The recognition predictor reads **one word per image**. Crops are resized to the input size of the model (32 px +high), so they should be tightly cropped around the word: + +```python +from doctr.models import recognition_predictor + +predictor = recognition_predictor("crnn_vgg16_bn", pretrained=True) +out = predictor([word_crop]) # e.g. the crop of "ACME" from the sample invoice + +print(out) # [('ACME', 0.9996)] +``` + +:::{note} +The pretrained vocabularies have no space character: feed the predictor single words, not whole lines. To read +lines or pages, use the OCR predictor, which detects the words first. +::: + +Very wide crops are split into overlapping parts that are read separately and merged. `batch_size` (default +`128`) sets how many crops are processed at once. + +## Layout analysis & table structure + +See [](layout_and_tables.md#standalone-predictors). + +## Orientation classifiers + +Two lightweight classifiers predict the orientation of a page or a word crop among 0°, 90°, 180° and 270°: + +```python +from doctr.models import crop_orientation_predictor, page_orientation_predictor + +page_classifier = page_orientation_predictor(pretrained=True) +class_idxs, angles, probs = page_classifier([upright_page, rotated_page]) +print(angles) # [0, -90] + +crop_classifier = crop_orientation_predictor(pretrained=True) +class_idxs, angles, probs = crop_classifier([word_crop, upside_down_crop]) +print(angles) # [0, 180] +``` + +They are used by the OCR predictor for [rotated documents](rotated_documents.md), and can be +[retrained on your data](../training/custom_models.md#orientation-classifiers). + +## Accessing the models + +Every predictor exposes its model as `.model`, a regular PyTorch module: + +```python +predictor = detection_predictor("db_resnet50", pretrained=True) + +predictor.model # the DBNet module +predictor.model.cfg # its configuration (input shape, mean/std, url...) +predictor.model.postprocessor.bin_thresh # post-processing parameters +``` + +The [Using standalone predictors](https://colab.research.google.com/github/mindee/notebooks/blob/main/doctr/using_standalone_predictors.ipynb) +notebook shows these predictors in action. diff --git a/docs/source/using_doctr/custom_models_training.rst b/docs/source/using_doctr/custom_models_training.rst deleted file mode 100644 index bcb0fc0556..0000000000 --- a/docs/source/using_doctr/custom_models_training.rst +++ /dev/null @@ -1,221 +0,0 @@ -.. _custom_models_training: - -Train your own model -==================== - -If the pretrained models don't meet your specific needs, you have the option to train your own model using the docTR library. -For details on the training process and the necessary data and data format, refer to the following links: - -- `detection `_ -- `recognition `_ -- `layout `_ -- `table structure `_ - -If you’re looking for a lightweight yet efficient tool to annotate small amounts of data, especially tailored for docTR, -check out the `docTR Labeling Tool `_. -This tool makes it easy to create your own dataset for fine-tuning and optimizing your OCR models. - -Loading your custom trained model ---------------------------------- - -This section shows how you can easily load a custom trained model in docTR. - -.. code:: python3 - - import torch - from doctr.models import ocr_predictor, db_resnet50, crnn_vgg16_bn - - # Load custom detection model - det_model = db_resnet50(pretrained=False, pretrained_backbone=False) - det_model.from_pretrained('', map_location="cpu") - predictor = ocr_predictor(det_arch=det_model, reco_arch="vitstr_small", pretrained=True) - - # Load custom recognition model - reco_model = crnn_vgg16_bn(pretrained=False, pretrained_backbone=False) - reco_model.from_pretrained('', map_location="cpu") - predictor = ocr_predictor(det_arch="linknet_resnet18", reco_arch=reco_model, pretrained=True) - - # Load custom detection and recognition model - det_model = db_resnet50(pretrained=False, pretrained_backbone=False) - det_model.from_pretrained('', map_location="cpu") - reco_model = crnn_vgg16_bn(pretrained=False, pretrained_backbone=False) - reco_model.from_pretrained('', map_location="cpu") - predictor = ocr_predictor(det_arch=det_model, reco_arch=reco_model, pretrained=False) - - -Load a custom recognition model trained on another vocabulary as the default one (French): - -.. code:: python3 - - import torch - from doctr.models import ocr_predictor, crnn_vgg16_bn - from doctr.datasets import VOCABS - - reco_model = crnn_vgg16_bn(pretrained=False, pretrained_backbone=False, vocab=VOCABS["german"]) - reco_model.from_pretrained('') - - predictor = ocr_predictor(det_arch='linknet_resnet18', reco_arch=reco_model, pretrained=True) - - -Plug a custom layout analysis model (trained on another set of classes) directly into the OCR pipeline so the detected regions are attached to every page: - -.. code:: python3 - - import torch - from doctr.models import ocr_predictor, lw_detr_s - - # Custom layout model with your own class names - layout_model = lw_detr_s(pretrained=False, class_names=["heading", "paragraph", "figure", "table"]) - layout_model.from_pretrained('') - - # Pass it through `layout_arch`, exactly as for the detection / recognition models - predictor = ocr_predictor(pretrained=True, detect_layout=True, layout_arch=layout_model) - - result = predictor(doc) - # The regions (with your custom class names) are available on each page - print([(region.type, region.confidence) for region in result.pages[0].layout]) - - - -Plug a custom layout analysis model (trained on another set of classes) directly into the OCR pipeline so the detected regions are attached to every page: - -.. code:: python3 - - import torch - from doctr.models import ocr_predictor, lw_detr_s - - # Custom layout model with your own class names - layout_model = lw_detr_s(pretrained=False, class_names=["heading", "paragraph", "figure", "table"]) - layout_model.from_pretrained('') - - # Pass it through `layout_arch`, exactly as for the detection / recognition models - predictor = ocr_predictor(pretrained=True, detect_layout=True, layout_arch=layout_model) - - result = predictor(doc) - # The regions (with your custom class names) are available on each page - print([(region.type, region.confidence) for region in result.pages[0].layout]) - - -Load a custom trained KIE detection model: - -.. code:: python3 - - import torch - from doctr.models import kie_predictor, db_resnet50 - - det_model = db_resnet50(pretrained=False, pretrained_backbone=False, class_names=['total', 'date']) - det_model.from_pretrained('') - kie_predictor(det_arch=det_model, reco_arch='crnn_vgg16_bn', pretrained=True) - -Load a model with customized Preprocessor: - -.. code:: python3 - - import torch - from doctr.models.predictor import OCRPredictor - from doctr.models.detection.predictor import DetectionPredictor - from doctr.models.recognition.predictor import RecognitionPredictor - from doctr.models.preprocessor import PreProcessor - from doctr.models import db_resnet50, crnn_vgg16_bn - - det_model = db_resnet50(pretrained=False, pretrained_backbone=False) - det_model.from_pretrained('') - reco_model = crnn_vgg16_bn(pretrained=False, pretrained_backbone=False) - reco_model.from_pretrained('') - - det_predictor = DetectionPredictor( - PreProcessor( - (1024, 1024), - batch_size=1, - mean=(0.798, 0.785, 0.772), - std=(0.264, 0.2749, 0.287) - ), - det_model - ) - - reco_predictor = RecognitionPredictor( - PreProcessor( - (32, 128), - preserve_aspect_ratio=True, - batch_size=32, - mean=(0.694, 0.695, 0.693), - std=(0.299, 0.296, 0.301) - ), - reco_model - ) - - predictor = OCRPredictor(det_predictor, reco_predictor) - -Custom orientation classification models ----------------------------------------- - -If you work with rotated documents and make use of the orientation classification feature by passing one of the following arguments: - -* `assume_straight_pages=False` -* `detect_orientation=True` -* `straigten_pages=True` - -You can train your own orientation classification model using the docTR library. For details on the training process and the necessary data and data format, refer to the following link: - -- `orientation `_ - -**NOTE**: Currently we support only `mobilenet_v3_small` models for crop and page orientation classification. - -Loading your custom trained orientation classification model -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code:: python3 - - import torch - from doctr.io import DocumentFile - from doctr.models import ocr_predictor, mobilenet_v3_small_page_orientation, mobilenet_v3_small_crop_orientation - from doctr.models.classification.zoo import crop_orientation_predictor, page_orientation_predictor - - custom_page_orientation_model = mobilenet_v3_small_page_orientation(pretrained=False) - custom_page_orientation_model.from_pretrained('') - custom_crop_orientation_model = mobilenet_v3_small_crop_orientation(pretrained=False) - custom_crop_orientation_model.from_pretrained('') - - predictor = ocr_predictor( - pretrained=True, - assume_straight_pages=False, - straighten_pages=True, - detect_orientation=True, - ) - - # Overwrite the default orientation models - predictor.crop_orientation_predictor = crop_orientation_predictor(custom_crop_orientation_model) - predictor.page_orientation_predictor = page_orientation_predictor(custom_page_orientation_model) - -Custom table structure recognition models ------------------------------------------ - -If you work with documents containing tables and make use of the table structure recognition feature by passing the following arguments: - -* `detect_tables=True` - -You can train your own table structure recognition model using the docTR library. For details on the training process and the necessary data and data format, refer to the following link: - -- `table structure recognition `_ - -Loading your custom trained table structure recognition model -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code:: python3 - - import torch - from doctr.io import DocumentFile - from doctr.models import ocr_predictor, tablecenternet - from doctr.models.table_structure.zoo import table_predictor - - custom_table_structure_model = tablecenternet(pretrained=False) - custom_table_structure_model.from_pretrained('') - - predictor = ocr_predictor( - pretrained=True, - detect_layout=True, - detect_tables=True, - ) - - # Overwrite the default table structure model - predictor.table_predictor = table_predictor(custom_table_structure_model) diff --git a/docs/source/using_doctr/running_on_aws.rst b/docs/source/using_doctr/running_on_aws.rst deleted file mode 100644 index 8a5e1a4cc4..0000000000 --- a/docs/source/using_doctr/running_on_aws.rst +++ /dev/null @@ -1,10 +0,0 @@ -AWS Lambda -========== - -The security policy of `AWS Lambda `_ restricts writing outside the ``/tmp`` directory. - -To make docTR work on Lambda, you need to perform the following two steps: - -1. Disable the usage of the ``multiprocessing`` package by setting the ``DOCTR_MULTIPROCESSING_DISABLE`` environment variable to ``TRUE``. This step is necessary because the package uses the ``/dev/shm`` directory for shared memory. - -2. Change the caching directory used by docTR for models. By default, it is set to ``~/.cache/doctr``, which is outside the ``/tmp`` directory on AWS Lambda. You can modify this by setting the ``DOCTR_CACHE_DIR`` environment variable. diff --git a/docs/source/using_doctr/sharing_models.rst b/docs/source/using_doctr/sharing_models.rst deleted file mode 100644 index 37f73ef12b..0000000000 --- a/docs/source/using_doctr/sharing_models.rst +++ /dev/null @@ -1,127 +0,0 @@ -Share your model with the community -=================================== - -docTR's focus is on open source, and if you feel the same way, we appreciate you sharing your trained model with the community. -To make it easy for you, we have integrated an interface to the Hugging Face Hub. - -.. currentmodule:: doctr.models.factory - - -Loading from Hugging Face Hub -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This section shows how you can easily load a pretrained model from the Hugging Face Hub. - -.. code:: python3 - - from doctr.io import DocumentFile - from doctr.models import ocr_predictor, from_hub - image = DocumentFile.from_images(['data/example.jpg']) - # Load a custom detection model from the Hugging Face Hub - det_model = from_hub('Felix92/doctr-torch-db-mobilenet-v3-large') - # Load a custom recognition model from the Hugging Face Hub - reco_model = from_hub('Felix92/doctr-torch-crnn-mobilenet-v3-large-french') - # You can easily plug these models in to the OCR predictor - predictor = ocr_predictor(det_arch=det_model, reco_arch=reco_model) - result = predictor(image) - - -Pushing to the Hugging Face Hub -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -You can also push your trained model to the Hugging Face Hub. -You need only to provide the task type (classification, detection, recognition or obj_detection), a name for your trained model (NOTE: -existing repositories will not be overwritten) and the model name itself. - -- Prerequisites: - - Hugging Face account (you can easily create one at https://huggingface.co/) - - installed Git LFS (check installation at: https://git-lfs.github.com/) in the repository - -.. code:: python3 - - from doctr.models import recognition, login_to_hub, push_to_hf_hub - login_to_hub() - my_awesome_model = recognition.crnn_mobilenet_v3_large(pretrained=True) - push_to_hf_hub(my_awesome_model, model_name='doctr-crnn-mobilenet-v3-large-french-v1', task='recognition', arch='crnn_mobilenet_v3_large') - -It is also possible to push your model directly after training. - -.. code:: bash - - python3 ~/doctr/references/recognition/train.py crnn_mobilenet_v3_large --name doctr-crnn-mobilenet-v3-large --push-to-hub - - -Pretrained community models -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This section is to provide some tables for pretrained community models. -Feel free to open a pull request or issue to add your model to this list. - -Naming conventions ------------------- - -We suggest using the following naming conventions for your models: - -**Classification:** ``doctr--`` - -**Detection:** ``doctr-`` - -**Recognition:** ``doctr--`` - -**Layout:** ``doctr-`` - -**Table structure:** ``doctr-`` - - -Classification --------------- - -+---------------------------------+-------------------------------------+-----------------------+ -| **Architecture** | **Repo_ID** | **Vocabulary** | -+=================================+=====================================+=======================+ -| resnet18 (dummy) | Felix92/doctr-dummy-torch-resnet18 | french | -+---------------------------------+-------------------------------------+-----------------------+ - - -Detection ---------- - -+---------------------------------+-------------------------------------------------+------------------------+ -| **Architecture** | **Repo_ID** | **Framework** | -+=================================+=================================================+========================+ -| db_resnet50 | rania-sr/doctr-Detection-model-v1-arabic | PyTorch | -+---------------------------------+-------------------------------------------------+------------------------+ - - -Recognition ------------ - -+---------------------------------+---------------------------------------------------+---------------------+------------------------+ -| **Architecture** | **Repo_ID** | **Language** | **Framework** | -+=================================+===================================================+=====================+========================+ -| crnn_vgg16_bn | tilman-rassy/doctr-crnn-vgg16-bn-fascan-v1 | french + german + § | PyTorch | -+---------------------------------+---------------------------------------------------+---------------------+------------------------+ -| parseq | Felix92/doctr-torch-parseq-multilingual-v1 | multilingual | PyTorch | -+---------------------------------+---------------------------------------------------+---------------------+------------------------+ -| parseq | rania-sr/doctr-model-v1-arabic | arabic | PyTorch | -+---------------------------------+---------------------------------------------------+---------------------+------------------------+ - - -Layout ------- - -+---------------------------------+---------------------------------------------------+------------------------+ -| **Architecture** | **Repo_ID** | **Framework** | -+=================================+===================================================+========================+ -| lw_detr_s (dummy) | Felix92/doctr-dummy-torch-lw-detr-s | PyTorch | -+---------------------------------+---------------------------------------------------+------------------------+ - - -Table structure ---------------- - -+---------------------------------+---------------------------------------------------+------------------------+ -| **Architecture** | **Repo_ID** | **Framework** | -+=================================+===================================================+========================+ -| tablecenternet (dummy) | Felix92/doctr-dummy-torch-tablecenternet | PyTorch | -+---------------------------------+---------------------------------------------------+------------------------+ diff --git a/docs/source/using_doctr/using_cli.rst b/docs/source/using_doctr/using_cli.rst deleted file mode 100644 index c1f4b37c16..0000000000 --- a/docs/source/using_doctr/using_cli.rst +++ /dev/null @@ -1,124 +0,0 @@ -Using the CLI for Optical Character Recognition -=============================================== - -The full Optical Character Recognition (OCR) task can be executed by using the Command Line Interface (CLI) implemented in docTR. This tool allows you to process both images and PDF files without writing a single line of Python code, and to export the results as JSON, plain text, Markdown, AsciiDoc, HTML or hOCR. - -Basic Usage ------------ - -To run the OCR engine on a file, use the following command structure: - -.. code-block:: bash - - doctr-cli --input_path path/to/your/document.pdf --output results.json - -Arguments ---------- - -The CLI supports a variety of arguments to fine-tune the detection, recognition and export process: - -**Mandatory Arguments:** - -* ``--input_path``: Path to one or more input images / PDF files. Every page is appended to a single document, so several files can be processed in one go. Values starting with ``http://`` or ``https://`` are interpreted as web pages and rendered as PDF (this requires ``weasyprint``, cf. :meth:`doctr.io.DocumentFile.from_url`). - -**General Options:** - -* ``--version``: Prints the installed docTR version and exits. - -**Input Options:** - -* ``--pdf_password``: Password used to unlock encrypted PDF files. *Default: None* -* ``--pdf_scale``: PDF rendering scale, where 1 corresponds to 72dpi. *Default: 2* - -**Architecture Selection:** - -* ``--det_arch``: The detection architecture to use (e.g., ``fast_base``). *Default: fast_base* -* ``--reco_arch``: The recognition architecture to use (e.g., ``crnn_vgg16_bn``). *Default: crnn_vgg16_bn* -* ``--layout_arch``: The layout architecture to use. *Default: lw_detr_s* -* ``--device``: Device the models are loaded on (``auto``, ``cpu``, ``cuda:0``, ...). On a CUDA device of compute capability 8.0 or above, the models are cast to bfloat16. *Default: auto* - -**Processing Options:** - -* ``--assume_straight_pages``, ``--no-assume_straight_pages``: Determine whether pages should be handled as straight or skewed pages. *Default: True* -* ``--straighten_pages``: If flagged, the tool will attempt to straighten skewed pages before analysis. *Default: False* -* ``--preserve_aspect_ratio``, ``--no-preserve_aspect_ratio``: Ensures that the aspect ratio is maintained during resizing. *Default: True* -* ``--symmetric_pad``, ``--no-symmetric_pad``: Applies symmetric padding to the input images. *Default: True* -* ``--det_bs``: Batch size used for the detection model. *Default: 2* -* ``--reco_bs``: Batch size used for the recognition model. *Default: 128* -* ``--bin_thresh``: Binarization threshold of the detection post-processing. *Default: the value of the architecture* -* ``--box_thresh``: Minimum confidence of a detected box. *Default: the value of the architecture* -* ``--confidence_aggregation``: Aggregation method of the character probabilities into the word confidence, one of ``mean``, ``min``, ``max``, ``median``, ``geometric_mean``, ``harmonic_mean``. *Default: the method of the recognition architecture* -* ``--detect_orientation``: Enables automatic detection of page orientation. *Default: False* -* ``--detect_language``: Enables language detection for the extracted text. *Default: False* -* ``--detect_layout``: Attaches the detected layout regions to each page. *Default: False* -* ``--detect_tables``: Regroups the words of the detected tables into structured tables. This enables the layout model. *Default: False* -* ``--ignore_regions``: Layout class names to mask out before detection & recognition (e.g. ``--ignore_regions Picture Table``). This enables the layout model. *Default: None* -* ``--export_as_straight_boxes``: Exports (potentially rotated) predictions as straight boxes. *Default: False* -* ``--preserve_original_coords``: Maps the boxes back to the original page coordinates when ``--straighten_pages`` is used. *Default: False* -* ``--disable_page_orientation``: Disables the page orientation classifier. *Default: False* -* ``--disable_crop_orientation``: Disables the crop orientation classifier. *Default: False* - -**Document Assembling Options:** - -* ``--resolve_lines``, ``--no-resolve_lines``: Groups the words into lines. *Default: True* -* ``--resolve_blocks``: Groups the lines into blocks. *Default: False* -* ``--paragraph_break``: Relative length of the minimum space separating two paragraphs. *Default: 0.035* -* ``--keep_reading_order``: Arranges the content of every page in reading order. *Default: False* - -**Output Options:** - -* ``--output``: The destination path where the results will be saved. *Default: results.json* -* ``--format``: The export format, one of ``json``/``dict``, ``txt``/``text``, ``md``/``markdown``, ``adoc``/``asciidoc``, ``html``, ``xml``/``hocr``. When it is not set, the format is inferred from the extension of ``--output``, and falls back to JSON. *Default: None* -* ``--direction``: Reading direction of the document, one of ``auto``, ``ltr``, ``rtl``, ``ttb-rtl``, ``ttb-ltr``. *Default: auto* -* ``--reading_order``, ``--no-reading_order``: Linearizes the content in reading order (JSON & hOCR exports). *Default: True* -* ``--escape``, ``--no-escape``: Escapes the characters carrying a structural meaning (Markdown & AsciiDoc exports). *Default: True* -* ``--include_furniture``, ``--no-include_furniture``: Includes page headers, page footers and footnotes (text-like exports). *Default: True* -* ``--file_title``: Title of the exported hOCR files. *Default: docTR - XML export (hOCR)* -* ``--indent``: Indentation of the JSON export. *Default: 4* -* ``--quiet``: Only logs errors. *Default: False* - -Examples --------- - -**Running OCR on an image:** - -.. code-block:: bash - - doctr-cli --input_path image.jpg --output ocr_res.json - -**Running OCR on several files at once:** - -.. code-block:: bash - - doctr-cli --input_path page1.jpg page2.jpg scan.pdf --output ocr_res.json - -**Using a specific detection architecture and straightening pages:** - -.. code-block:: bash - - doctr-cli --input_path doc.pdf --det_arch db_mobilenet_v3_large --straighten_pages - -**Exporting the text as Markdown, with tables and layout:** - -.. code-block:: bash - - doctr-cli --input_path doc.pdf --detect_tables --output doc.md - -**Exporting as hOCR on the GPU, ignoring the pictures of the document:** - -.. code-block:: bash - - doctr-cli --input_path doc.pdf --device cuda:0 --ignore_regions Picture --output doc.xml - -Output Format -------------- - -By default, the results are exported in a structured JSON format containing: - -* **Pages**: Dimensions, orientation, language and layout regions. -* **Blocks**: Grouping of lines. -* **Lines**: Grouping of words. -* **Words**: The actual text content with confidence scores and bounding box coordinates. -* **Tables**: The structured tables, when ``--detect_tables`` is used. - -The text-like formats (``txt``, ``md``, ``adoc``, ``html``) are written to a single file. Since hOCR describes a single page per file, the ``xml`` export writes one file per page: an ``--output`` of ``doc.xml`` on a 3-page document produces ``doc_page_1.xml``, ``doc_page_2.xml`` and ``doc_page_3.xml``. diff --git a/docs/source/using_doctr/using_contrib_modules.rst b/docs/source/using_doctr/using_contrib_modules.rst deleted file mode 100644 index 30b86b0846..0000000000 --- a/docs/source/using_doctr/using_contrib_modules.rst +++ /dev/null @@ -1,51 +0,0 @@ -Integrate contributions into your pipeline -========================================== - -The `contrib` module provides a collection of additional features which could be relevant for your document analysis pipeline. -The following sections will give you an overview of the available modules and features. - -.. currentmodule:: doctr.contrib - - -Available contribution modules ------------------------------- - -**NOTE:** To use the contrib module, you need to install the `onnxruntime` package. You can install it using the following command: - -.. code:: bash - - pip install python-doctr[contrib] - # Or - pip install onnxruntime # pip install onnxruntime-gpu - -Here are all contribution modules that are available through docTR: - -ArtefactDetector -^^^^^^^^^^^^^^^^^ - -The ArtefactDetector module provides a set of functions to detect artefacts in the document images, such as logos, QR codes, bar codes, etc. -It is based on the YOLOv8 architecture, which is a state-of-the-art object detection model. - -.. code:: python3 - - from doctr.io import DocumentFile - from doctr.contrib.artefacts import ArtefactDetector - - # Load the document - doc = DocumentFile.from_images(["path/to/your/image"]) - detector = ArtefactDetector(batch_size=2, conf_threshold=0.5, iou_threshold=0.5) - artefacts = detector(doc) - - # Visualize the detected artefacts - detector.show() - -You can also use your custom trained YOLOv8 model to detect artefacts or anything else you need. -Reference: `YOLOv8 `_ - -**NOTE:** The YOLOv8 model (no Oriented Bounding Box (OBB) inference supported yet) needs to be provided as onnx exported model with a dynamic batch size. - -.. code:: python3 - - from doctr.contrib import ArtefactDetector - - detector = ArtefactDetector(model_path="path/to/your/model.onnx", labels=["table", "figure"]) diff --git a/docs/source/using_doctr/using_datasets.rst b/docs/source/using_doctr/using_datasets.rst deleted file mode 100644 index 8a9f1a0259..0000000000 --- a/docs/source/using_doctr/using_datasets.rst +++ /dev/null @@ -1,188 +0,0 @@ -Choose a ready to use dataset -============================= - -Whether it is for training or for evaluation, having predefined objects to access datasets in your prefered framework -can be a significant save of time. - -.. currentmodule:: doctr.datasets - - -Available Datasets ------------------- -In the package reference you will also find some samples for each dataset. - -Here are all datasets that are available through docTR: - -Detection -^^^^^^^^^ - -This datasets contains the information to train or validate a text detection model. - -+-----------------------------+---------------------------------+---------------------------------+---------------------------------------+ -| **Dataset** | **Train Samples** | **Test Samples** | **Information** | -+=============================+=================================+=================================+=======================================+ -| FUNSD | 149 | 50 | | -+-----------------------------+---------------------------------+---------------------------------+---------------------------------------+ -| SROIE | 626 | 360 | | -+-----------------------------+---------------------------------+---------------------------------+---------------------------------------+ -| CORD | 800 | 100 | | -+-----------------------------+---------------------------------+---------------------------------+---------------------------------------+ -| IIIT5K | 2000 | 3000 | | -+-----------------------------+---------------------------------+---------------------------------+---------------------------------------+ -| SVT | 100 | 249 | | -+-----------------------------+---------------------------------+---------------------------------+---------------------------------------+ -| SVHN | 33402 | 13068 | Character Localization | -+-----------------------------+---------------------------------+---------------------------------+---------------------------------------+ -| SynthText | 772875 | 85875 | | -+-----------------------------+---------------------------------+---------------------------------+---------------------------------------+ -| IC03 | 246 | 249 | | -+-----------------------------+---------------------------------+---------------------------------+---------------------------------------+ -| IC13 | 229 | 233 | external resources | -+-----------------------------+---------------------------------+---------------------------------+---------------------------------------+ -| IMGUR5K | 7149 | 796 | Handwritten / external resources | -+-----------------------------+---------------------------------+---------------------------------+---------------------------------------+ -| WILDRECEIPT | 1268 | 472 | external resources | -+-----------------------------+---------------------------------+---------------------------------+---------------------------------------+ -| COCOTEXT | 13880 | 3261 | external resources / legible filtered | -+-----------------------------+---------------------------------+---------------------------------+---------------------------------------+ - -.. code:: python3 - - from doctr.datasets import CORD - # Load straight boxes - train_set = CORD(train=True, download=True, detection_task=True) - # Load rotated boxes - train_set = CORD(train=True, download=True, use_polygons=True, detection_task=True) - img, target = train_set[0] - - -Recognition -^^^^^^^^^^^ - -This datasets contains the information to train or validate a text recognition model. - -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| **Dataset** | **Train Samples** | **Test Samples** | **Information** | -+=============================+=================================+=================================+==================================================+ -| FUNSD | 21888 | 8707 | english | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| SROIE | 33608 | 19342 | english / only uppercase labels | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| CORD | 19370 | 2186 | english | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| IIIT5K | 2000 | 3000 | english | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| SVT | 257 | 647 | english / only uppercase labels | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| SVHN | 73257 | 26032 | digits | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| SynthText | ~7100000 | 707470 | english | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| IC03 | 1156 | 1107 | english | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| IC13 | 849 | 1095 | english / external resources | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| IMGUR5K | 207901 | 22672 | english / handwritten / external resources | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| MJSynth | 7581382 | 1337891 | english / external resources | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| IIITHWS | 7141797 | 793533 | english / handwritten / external resources | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| WILDRECEIPT | 49377 | 19598 | english / external resources | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ -| COCOTEXT | 59130 | 13258 | english / external resources / legible filtered | -+-----------------------------+---------------------------------+---------------------------------+--------------------------------------------------+ - -.. code:: python3 - - from doctr.datasets import CORD - # Crop boxes as is (can contain irregular) - train_set = CORD(train=True, download=True, recognition_task=True) - # Crop rotated boxes (always regular) - train_set = CORD(train=True, download=True, use_polygons=True, recognition_task=True) - img, target = train_set[0] - - -OCR -^^^ - -The same dataset table as for detection, but with information about the bounding boxes and labels. - -.. code:: python3 - - from doctr.datasets import CORD - # Load straight boxes - train_set = CORD(train=True, download=True) - # Load rotated boxes - train_set = CORD(train=True, download=True, use_polygons=True) - img, target = train_set[0] - - -Object Detection -^^^^^^^^^^^^^^^^ - -This datasets contains the information to train or validate a object detection model. - -+-----------------------------+---------------------------------+---------------------------------+-------------------------------------------------------+ -| **Dataset** | **Train Samples** | **Test Samples** | **Information** | -+=============================+=================================+=================================+=======================================================+ -| DocArtefacts | 2700 | 300 |["background", "qr_code", "bar_code", "logo", "photo"] | -+-----------------------------+---------------------------------+---------------------------------+-------------------------------------------------------+ - -.. code:: python3 - - from doctr.datasets import DocArtefacts - train_set = DocArtefacts(train=True, download=True) - img, target = train_set[0] - - -Synthetic dataset generator -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -docTR provides also some generator objects, which can be used to generate synthetic datasets. -Both are also integrated in the training scripts to train a classification or recognition model. - -.. code:: python3 - - from doctr.datasets import CharacterGenerator - ds = CharacterGenerator(vocab='abdef', num_samples=100) - img, target = ds[0] - -.. code:: python3 - - from doctr.datasets import WordGenerator - ds = WordGenerator(vocab='abdef', min_chars=1, max_chars=32, num_samples=100) - img, target = ds[0] - - -Use your own datasets ---------------------- - -Since many documents include sensitive / personal information, we are not able to share all the data that has been used for this project. -However, we provide some guidance on how to format your own dataset into the same format so that you can use all docTR tools more easily. -You can find further information about the format in references. - -.. code:: python3 - - from doctr.datasets import DetectionDataset - # Load a detection dataset - train_set = DetectionDataset(img_folder="/path/to/images", label_path="/path/to/labels.json") - # Load a recognition Dataset - train_set = RecognitionDataset(img_folder="/path/to/images", labels_path="/path/to/labels.json") - # Load a OCR dataset which contains annotations for the boxes and labels - train_set = OCRDataset(img_folder="/path/to/images", label_file="/path/to/labels.json") - img, target = train_set[0] - - -Data Loading ------------- - -Each dataset has its specific way to load a sample, but handling batch aggregation and the underlying iterator is a task deferred to another object in docTR. - -.. code:: python3 - - from doctr.datasets import CORD, DataLoader - train_set = CORD(train=True, download=True) - train_loader = DataLoader(train_set, batch_size=32) - train_iter = iter(train_loader) - images, targets = next(train_iter) diff --git a/docs/source/using_doctr/using_model_export.rst b/docs/source/using_doctr/using_model_export.rst deleted file mode 100644 index 4aafce335f..0000000000 --- a/docs/source/using_doctr/using_model_export.rst +++ /dev/null @@ -1,151 +0,0 @@ -Preparing your model for inference -================================== - -A well-trained model is a good achievement but you might want to tune a few things to make it production-ready! - -.. currentmodule:: doctr.models.utils - - -Model optimization ------------------- - -This section is meant to help you perform inference with optimized versions of your model. - -.. _half-precision: - - -Half-precision -^^^^^^^^^^^^^^ - -**NOTE:** We support half-precision inference for PyTorch models only on **GPU devices**. - -Half-precision formats occupy 16 bits in computer memory instead of the 32 bits used by -single-precision (FP32). Two formats are supported: - -- **BF16** (``bfloat16``): keeps the same exponent range as FP32 with a reduced mantissa. -- **FP16** (``float16``): higher precision than BF16, but a much narrower dynamic range. - -Advantages: - -- Faster inference -- Less memory usage - -We recommend **BF16 over FP16**. Because it retains the full FP32 exponent range, BF16 is far -less prone to overflow and underflow. BF16 requires an Ampere-generation GPU or newer -(compute capability 8.0+); on older hardware, use FP16 instead. - -.. code:: python3 - - import torch - predictor = ocr_predictor( - reco_arch="crnn_mobilenet_v3_small", - det_arch="linknet_resnet34", - pretrained=True - ).cuda().bfloat16() # or .half() for FP16 - res = predictor(doc) - - -Compiling your models (PyTorch only) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**NOTE:** - -- This feature is only available if you use PyTorch as backend. -- The recognition architecture `master` is not supported for model compilation yet. -- We provide only official support for the default (`inductor`) backend, but you can try other backends, configurations depending on your hardware and requirements as well. - -Compiling your PyTorch models with `torch.compile` optimizes the model by converting it to a graph representation and applying backends that can improve performance. -This process can make inference faster and reduce memory overhead during execution. - -Further information can be found in the `PyTorch documentation `_. - -.. code:: - - import torch - from doctr.models import ( - ocr_predictor, - vitstr_small, - fast_base, - mobilenet_v3_small_crop_orientation, - mobilenet_v3_small_page_orientation, - crop_orientation_predictor, - page_orientation_predictor - ) - - # Compile the models - detection_model = torch.compile( - fast_base(pretrained=True).eval() - ) - recognition_model = torch.compile( - vitstr_small(pretrained=True).eval() - ) - crop_orientation_model = torch.compile( - mobilenet_v3_small_crop_orientation(pretrained=True).eval() - ) - page_orientation_model = torch.compile( - mobilenet_v3_small_page_orientation(pretrained=True).eval() - ) - - predictor = ocr_predictor( - detection_model, recognition_model, assume_straight_pages=False - ) - # NOTE: Only required for non-straight pages (`assume_straight_pages=False`) and non-disabled orientation classification - # Set the orientation predictors - predictor.crop_orientation_predictor = crop_orientation_predictor(crop_orientation_model) - predictor.page_orientation_predictor = page_orientation_predictor(page_orientation_model) - - compiled_out = predictor(doc) - -Export to ONNX -^^^^^^^^^^^^^^ - -ONNX (Open Neural Network Exchange) is an open and interoperable format for representing and exchanging machine learning models. -It defines a common format for representing models, including the network structure, layer types, parameters, and metadata. - -.. code:: python3 - - import torch - from doctr.models import vitstr_small - from doctr.models.utils import export_model_to_onnx - - batch_size = 1 - input_shape = (3, 32, 128) - model = vitstr_small(pretrained=True, exportable=True) - dummy_input = torch.rand((batch_size, *input_shape), dtype=torch.float32) - model_path = export_model_to_onnx( - model, - model_name="vitstr.onnx", - dummy_input=dummy_input - ) - - -Using your ONNX exported model -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -To use your exported model, we have build a dedicated lightweight package called `OnnxTR `_. -The package doesn't require PyTorch or TensorFlow to be installed - build on top of ONNXRuntime. -It is simple and easy-to-use (with the same interface you know already from docTR), that allows you to perform inference with your exported model. - -- `Installation `_ -- `Loading custom exported model `_ - -.. code:: shell - - pip install onnxtr[cpu] - -.. code:: python3 - - from onnxtr.io import DocumentFile - from onnxtr.models import ocr_predictor, parseq, linknet_resnet18 - # Load your documents - single_img_doc = DocumentFile.from_images("path/to/your/img.jpg") - - # Load your exported model/s - reco_model = parseq("path_to_custom_model.onnx", vocab="ABC") - det_model = linknet_resnet18("path_to_custom_model.onnx") - predictor = ocr_predictor(det_arch=det_model, reco_arch=reco_model) - # Or use any of the pre-trained models - predictor = ocr_predictor(det_arch="linknet_resnet18", reco_arch="parseq") - - # Get your results - res = predictor(single_img_doc) diff --git a/docs/source/using_doctr/using_models.rst b/docs/source/using_doctr/using_models.rst deleted file mode 100644 index af4012d076..0000000000 --- a/docs/source/using_doctr/using_models.rst +++ /dev/null @@ -1,836 +0,0 @@ -.. _using_models: - -Choosing the right model -======================== - -The full Optical Character Recognition task can be seen as two consecutive tasks: text detection and text recognition. -Either performed at once or separately, to each task corresponds a type of deep learning architecture. - -For a given task, docTR provides a Predictor, which is composed of 2 components: - -* PreProcessor: a module in charge of making inputs directly usable by the deep learning model. -* Model: a deep learning model, implemented with all supported deep learning backends (PyTorch) along with its specific post-processor to make outputs structured and reusable. - - -Which predictor should I use? ------------------------------- - -.. list-table:: - :widths: 60 40 - :header-rows: 1 - - * - I want to… - - Use - * - Extract all text (words, lines, layout hierarchy) from a document - - :py:meth:`ocr_predictor ` - * - Detect document regions by type (tables, figures, headers, …) - - :py:meth:`layout_predictor ` - * - Get word bounding-boxes only, without recognition - - :py:meth:`detection_predictor ` - * - Transcribe pre-cropped word images to strings - - :py:meth:`recognition_predictor ` - * - Detect the structure of a table (cell bounding-boxes and logical coordinates) - - :py:meth:`table_predictor ` - -For :doc:`custom model loading ` or sharing models, see the dedicated pages. - - -Text Detection --------------- - -The task consists of localizing textual elements in a given image. -While those text elements can represent many things, in docTR, we will consider uninterrupted character sequences (words). Additionally, the localization can take several forms: from straight bounding boxes (delimited by the 2D coordinates of the top-left and bottom-right corner), to polygons, or binary segmentation (flagging which pixels belong to this element, and which don't). -Our latest detection models works with rotated and skewed documents! - -Available detection architectures -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The following architectures are currently supported: - -* :py:meth:`linknet_resnet34 ` -* :py:meth:`linknet_resnet50 ` -* :py:meth:`db_resnet50 ` -* :py:meth:`db_mobilenet_v3_large ` -* :py:meth:`fast_tiny ` -* :py:meth:`fast_small ` -* :py:meth:`fast_base ` - -For a comprehensive comparison, we have compiled a detailed benchmark on publicly available datasets: - - -+------------------------------------------------------------------------------------+----------------------------+----------------------------+--------------------+ -| | FUNSD | CORD | | -+==================================================+=================+===============+============+===============+============+===============+====================+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **sec/it (B: 1)** | -+--------------------------------------------------+-----------------+---------------+------------+---------------+------------+---------------+--------------------+ -| db_resnet34 | (1024, 1024, 3) | 22.4 M | 82.76 | 76.75 | 89.20 | 71.74 | 0.8 | -+--------------------------------------------------+-----------------+---------------+------------+---------------+------------+---------------+--------------------+ -| db_resnet50 | (1024, 1024, 3) | 25.4 M | 83.56 | 86.68 | 92.61 | 86.39 | 1.1 | -+--------------------------------------------------+-----------------+---------------+------------+---------------+------------+---------------+--------------------+ -| db_mobilenet_v3_large | (1024, 1024, 3) | 4.2 M | 82.69 | 84.63 | 94.51 | 70.28 | 0.5 | -+--------------------------------------------------+-----------------+---------------+------------+---------------+------------+---------------+--------------------+ -| linknet_resnet18 | (1024, 1024, 3) | 11.5 M | 81.64 | 85.52 | 88.92 | 82.74 | 0.6 | -+--------------------------------------------------+-----------------+---------------+------------+---------------+------------+---------------+--------------------+ -| linknet_resnet34 | (1024, 1024, 3) | 21.6 M | 81.62 | 82.95 | 86.26 | 81.06 | 0.7 | -+--------------------------------------------------+-----------------+---------------+------------+---------------+------------+---------------+--------------------+ -| linknet_resnet50 | (1024, 1024, 3) | 28.8 M | 81.78 | 82.47 | 87.29 | 85.54 | 1.0 | -+--------------------------------------------------+-----------------+---------------+------------+---------------+------------+---------------+--------------------+ -| fast_tiny | (1024, 1024, 3) | 13.5 M (8.5M) | 84.90 | 85.04 | 93.73 | 76.26 | 0.7 (0.4) | -+--------------------------------------------------+-----------------+---------------+------------+---------------+------------+---------------+--------------------+ -| fast_small | (1024, 1024, 3) | 14.7 M (9.7M) | 85.36 | 86.68 | 94.09 | 78.53 | 0.7 (0.5) | -+--------------------------------------------------+-----------------+---------------+------------+---------------+------------+---------------+--------------------+ -| fast_base | (1024, 1024, 3) | 16.3 M (10.6M)| 84.95 | 86.73 | 94.39 | 85.36 | 0.8 (0.5) | -+--------------------------------------------------+-----------------+---------------+------------+---------------+------------+---------------+--------------------+ - - -All text detection models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. :ref:`datasets`). -Explanations about the metrics being used are available in :ref:`metrics`. - -*Disclaimer: both FUNSD subsets combined have 199 pages which might not be representative enough of the model capabilities* - -Seconds per iteration (with a batch size of 1) is computed after a warmup phase of 100 tensors, by measuring the average number of processed tensors per second over 1000 samples. Those results were obtained on a `11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz`. - - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ - -:py:meth:`detection_predictor ` wraps your detection model to make it easily usable with your favorite deep learning framework seamlessly. - -.. code:: python3 - - import numpy as np - from doctr.models import detection_predictor - model = detection_predictor('db_resnet50') - dummy_img = (255 * np.random.rand(800, 600, 3)).astype(np.uint8) - out = model([dummy_img]) - -You can pass specific boolean arguments to the predictor: - -* ``pretrained``: if you want to use a model that has been pretrained on a specific dataset, setting ``pretrained=True`` will load the corresponding weights. If ``pretrained=False`` (the default), the model is randomly initialized and will produce no useful results. -* ``assume_straight_pages``: if you work with straight documents only, it will fit straight bounding boxes to the text areas. -* ``preserve_aspect_ratio``: if you want to preserve the aspect ratio of your documents while resizing before sending them to the model. -* ``symmetric_pad``: if you choose to preserve the aspect ratio, it will pad the image symmetrically and not from the bottom-right. - -.. code:: python3 - - from doctr.models import detection_predictor - predictor = detection_predictor('db_resnet50', pretrained=True, assume_straight_pages=False, preserve_aspect_ratio=True) - - -Text Recognition ----------------- - -The task consists of transcribing the character sequence in a given image. - - -Available architectures -^^^^^^^^^^^^^^^^^^^^^^^ - -The following architectures are currently supported: - -* :py:meth:`crnn_vgg16_bn ` -* :py:meth:`crnn_mobilenet_v3_small ` -* :py:meth:`crnn_mobilenet_v3_large ` -* :py:meth:`sar_resnet31 ` -* :py:meth:`master ` -* :py:meth:`vitstr_small ` -* :py:meth:`vitstr_base ` -* :py:meth:`parseq ` -* :py:meth:`viptr_tiny ` - - -For a comprehensive comparison, we have compiled a detailed benchmark on publicly available datasets: - - -+-----------------------------------------------------------------------------------+----------------------------+----------------------------+--------------------+ -| | FUNSD | CORD | | -+==================================================+=================+==============+============+===============+============+===============+====================+ -| **Architecture** | **Input shape** | **# params** | **Exact** | **Partial** | **Exact** | **Partial** | **sec/it (B: 64)** | -+--------------------------------------------------+-----------------+--------------+------------+---------------+------------+---------------+--------------------+ -| crnn_vgg16_bn | (32, 128, 3) | 15.8 M | 88.21 | 88.95 | 95.47 | 95.91 | 0.6 | -+--------------------------------------------------+-----------------+--------------+------------+---------------+------------+---------------+--------------------+ -| crnn_mobilenet_v3_small | (32, 128, 3) | 2.1 M | 87.25 | 87.99 | 93.91 | 94.34 | 0.05 | -+--------------------------------------------------+-----------------+--------------+------------+---------------+------------+---------------+--------------------+ -| crnn_mobilenet_v3_large | (32, 128, 3) | 4.5 M | 87.38 | 88.09 | 94.46 | 94.92 | 0.08 | -+--------------------------------------------------+-----------------+--------------+------------+---------------+------------+---------------+--------------------+ -| master | (32, 128, 3) | 58.7 M | 88.57 | 89.39 | 95.73 | 96.21 | 17.6 | -+--------------------------------------------------+-----------------+--------------+------------+---------------+------------+---------------+--------------------+ -| sar_resnet31 | (32, 128, 3) | 55.4 M | 88.10 | 88.88 | 94.83 | 95.29 | 4.9 | -+--------------------------------------------------+-----------------+--------------+------------+---------------+------------+---------------+--------------------+ -| vitstr_small | (32, 128, 3) | 21.4 M | 88.00 | 88.82 | 95.40 | 95.78 | 1.5 | -+--------------------------------------------------+-----------------+--------------+------------+---------------+------------+---------------+--------------------+ -| vitstr_base | (32, 128, 3) | 85.2 M | 88.33 | 89.09 | 95.32 | 95.71 | 4.1 | -+--------------------------------------------------+-----------------+--------------+------------+---------------+------------+---------------+--------------------+ -| parseq | (32, 128, 3) | 23.8 M | 88.53 | 89.24 | 95.56 | 95.91 | 2.2 | -+--------------------------------------------------+-----------------+--------------+------------+---------------+------------+---------------+--------------------+ -| viptr_tiny | (32, 128, 3) | 3.2 M | 86.03 | 86.71 | 93.08 | 93.47 | 0.08 | -+--------------------------------------------------+-----------------+--------------+------------+---------------+------------+---------------+--------------------+ - - -All text recognition models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. :ref:`datasets`). -Explanations about the metric being used (exact match) are available in :ref:`metrics`. - -While most of our recognition models were trained on our french vocab (cf. :ref:`vocabs`), you can easily access the vocab of any model as follows: - -.. code:: python3 - - from doctr.models import recognition_predictor - predictor = recognition_predictor('crnn_vgg16_bn') - print(predictor.model.cfg['vocab']) - - -*Disclaimer: both FUNSD subsets combined have 30595 word-level crops which might not be representative enough of the model capabilities* - -Seconds per iteration (with a batch size of 64) is computed after a warmup phase of 100 tensors, by measuring the average number of processed tensors per second over 1000 samples. Those results were obtained on a `11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz`. - - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -:py:meth:`recognition_predictor ` wraps your recognition model to make it easily usable with your favorite deep learning framework seamlessly. - -.. code:: python3 - - import numpy as np - from doctr.models import recognition_predictor - model = recognition_predictor('crnn_vgg16_bn') - dummy_img = (255 * np.random.rand(50, 150, 3)).astype(np.uint8) - out = model([dummy_img]) - - -Layout Analysis ---------------- - -The task consists of localizing and classifying visual elements in a given image. -This is a more general task than text detection, as it can be used to detect and classify any type of visual element in a document, such as tables, figures, headers, footers, etc. -Our latest layout models works with rotated and skewed documents! - -Available layout architectures -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The following architectures are currently supported: - -* :py:meth:`lw_detr_s ` -* :py:meth:`lw_detr_m ` - -For a comprehensive comparison, we have compiled a detailed benchmark: - -+--------------------------------------------------+-----------------+---------------+------------------+-------------+--------------+--------------------+ -| | | | | | | | -+==================================================+=================+===============+==================+=============+==============+====================+ -| **Architecture** | **Input shape** | **# params** | **mAP@[.5:.95]** | **AP@[.5]** | **AP@[.75]** | **sec/it (B: 1)** | -+--------------------------------------------------+-----------------+---------------+------------------+-------------+--------------+--------------------+ -| lw_detr_s | (1024, 1024, 3) | 15.1 M | 66.89 | 82.83 | 73.75 | 0.5 | -+--------------------------------------------------+-----------------+---------------+------------------+-------------+--------------+--------------------+ -| lw_detr_m | (1024, 1024, 3) | 29.5 M | soon | soon | soon | 0.7 | -+--------------------------------------------------+-----------------+---------------+------------------+-------------+--------------+--------------------+ - - -Explanations about the metrics being used are available in :ref:`metrics`. - -Seconds per iteration (with a batch size of 1) is computed after a warmup phase of 100 tensors, by measuring the average number of processed tensors per second over 1000 samples. Those results were obtained on a `11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz`. - - -Layout predictors -^^^^^^^^^^^^^^^^^ - -:py:meth:`layout_predictor ` wraps your layout model to make it easily usable with your favorite deep learning framework seamlessly. - -.. code:: python3 - - import numpy as np - from doctr.models import layout_predictor - model = layout_predictor('lw_detr_s') - dummy_img = (255 * np.random.rand(800, 600, 3)).astype(np.uint8) - out = model([dummy_img]) - -You can pass specific boolean arguments to the predictor: - -* ``pretrained``: if you want to use a model that has been pretrained on a specific dataset, setting ``pretrained=True`` will load the corresponding weights. If ``pretrained=False`` (the default), the model is randomly initialized and will produce no useful results. -* ``assume_straight_pages``: if you work with straight documents only, it will fit straight bounding boxes to the text areas. -* ``preserve_aspect_ratio``: if you want to preserve the aspect ratio of your documents while resizing before sending them to the model. -* ``symmetric_pad``: if you choose to preserve the aspect ratio, it will pad the image symmetrically and not from the bottom-right. - -For instance, this snippet instantiates a layout predictor able to detect text on rotated documents while preserving the aspect ratio: - -.. code:: python3 - - from doctr.models import layout_predictor - predictor = layout_predictor('lw_detr_s', pretrained=True, assume_straight_pages=False, preserve_aspect_ratio=True) - - -Table Structure Recognition ---------------------------- - -The task consists of parsing the structure of a table into a machine-understandable representation: localizing every -cell (its spatial structure) and recovering the row and column it spans (its logical structure). - -Available table architectures -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The following architectures are currently supported: - -* :py:meth:`tablecenternet ` - -For a comprehensive comparison, we have compiled a detailed benchmark on a publicly available dataset: - -+--------------------------------------------------+-----------------+---------------+--------------+---------------+------------+-------------------+--------------------+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **F1** | **Structure acc** | **sec/it (B: 1)** | -+==================================================+=================+===============+==============+===============+============+===================+====================+ -| tablecenternet | (1024, 1024, 3) | 7.1 M | 82.31 | 96.01 | 88.64 | 77.53 | 0.5 | -+--------------------------------------------------+-----------------+---------------+--------------+---------------+------------+-------------------+--------------------+ - -.. note:: - - The reported metrics are produced by ``references/table/evaluate.py`` using the - :py:class:`TableCellMetric `: cell-detection **Recall**, **Precision** and - **F1** (cells matched above an IoU threshold of 0.5), and **Structure acc**, the share of matched cells whose - logical (row/column) coordinates are correctly predicted. - -Table structure predictors -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:py:meth:`table_predictor ` wraps your table model so it can be used directly on -document images. For each page it returns the list of detected cells, each with its geometry, its confidence score and its logical coordinates, together with the inferred number of rows -and columns. - -.. code:: python3 - - import numpy as np - from doctr.models import table_predictor - model = table_predictor('tablecenternet', pretrained=True) - table_crop = (255 * np.random.rand(800, 600, 3)).astype(np.uint8) - out = model([table_crop]) - # out[0] -> {"cells": [{"geometry": ..., "score": ..., "row_start": 0, "row_end": 0, - # "col_start": 0, "col_end": 0}, ...], "num_rows": ..., "num_cols": ...} - -Tables in the OCR pipeline -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Passing ``detect_tables=True`` to :py:meth:`ocr_predictor ` runs table structure -recognition. It relies on the layout model: each region the layout model labels as -a table is cropped and passed to the table model, so a page yields one structured table per detected table region -(``detect_tables=True`` therefore also enables the layout model, whose regions are attached to the page). The words -whose center falls inside a detected cell are regrouped into a structured table, attached to the page as -``page.tables``, and **removed from the regular** ``blocks`` **output** so the same text is not returned twice. Each -table exposes a dense row/column grid that loads directly into pandas. - -.. code:: python3 - - from doctr.io import DocumentFile - from doctr.models import ocr_predictor - - model = ocr_predictor(pretrained=True, detect_tables=True) - doc = DocumentFile.from_images("invoice_with_table.png") - result = model(doc) - - page = result.pages[0] - # Structured tables (one or more per page), kept out of the regular text blocks - for i, table in enumerate(page.tables): - df = table.to_grid() # for a plain list of lists - print(f"Table {i} ({table.num_rows}x{table.num_cols}):") - print(df) - - # The remaining (non-table) text is still available as usual - print(page.render()) - -Tables are included in :meth:`Page.export` under the ``tables`` key, so they are preserved in the JSON export as well. - - -End-to-End OCR --------------- - -The task consists of both localizing and transcribing textual elements in a given image. - -Available OCR architectures -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -You can use any combination of detection and recognition models supported by docTR. - -For a comprehensive comparison, we have compiled a detailed benchmark on publicly available datasets: - -+---------------------------------------------------------------------------+----------------------------+----------------------------+ -| | FUNSD | CORD | -+===========================================================================+============================+============+===============+ -| **Architecture** | **Recall** | **Precision** | **Recall** | **Precision** | -+---------------------------------------------------------------------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_vgg16_bn | 73.37 | 76.11 | 84.80 | 79.09 | -+---------------------------------------------------------------------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_mobilenet_v3_small | 73.06 | 75.79 | 84.64 | 78.94 | -+---------------------------------------------------------------------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_mobilenet_v3_large | 73.17 | 75.90 | 84.96 | 79.25 | -+---------------------------------------------------------------------------+------------+---------------+------------+---------------+ -| db_resnet50 + master | 73.90 | 76.66 | 85.84 | 80.07 | -+---------------------------------------------------------------------------+------------+---------------+------------+---------------+ -| db_resnet50 + sar_resnet31 | 73.58 | 76.33 | 85.64 | 79.88 | -+---------------------------------------------------------------------------+------------+---------------+------------+---------------+ -| db_resnet50 + vitstr_small | 73.06 | 75.79 | 85.95 | 80.17 | -+---------------------------------------------------------------------------+------------+---------------+------------+---------------+ -| db_resnet50 + vitstr_base | 73.70 | 76.46 | 85.76 | 79.99 | -+---------------------------------------------------------------------------+------------+---------------+------------+---------------+ -| db_resnet50 + parseq | 73.52 | 76.27 | 85.91 | 80.13 | -+---------------------------------------------------------------------------+------------+---------------+------------+---------------+ -| Gvision text detection | 59.50 | 62.50 | 75.30 | 59.03 | -+---------------------------------------------------------------------------+------------+---------------+------------+---------------+ -| Gvision doc. text detection | 64.00 | 53.30 | 68.90 | 61.10 | -+---------------------------------------------------------------------------+------------+---------------+------------+---------------+ -| AWS textract | 78.10 | 83.00 | 87.50 | 66.00 | -+---------------------------------------------------------------------------+------------+---------------+------------+---------------+ -| Azure Form Recognizer (v3.2) | 79.42 | 85.89 | 89.62 | 88.93 | -+---------------------------------------------------------------------------+------------+---------------+------------+---------------+ - - -All OCR models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. :ref:`datasets`). -Explanations about the metrics being used are available in :ref:`metrics`. - -*Disclaimer: both FUNSD subsets combined have 199 pages which might not be representative enough of the model capabilities* - - -Two-stage approaches -^^^^^^^^^^^^^^^^^^^^ -Those architectures involve one stage of text detection, and one stage of text recognition. The text detection will be used to produces cropped images that will be passed into the text recognition block. Everything is wrapped up with :py:meth:`ocr_predictor `. - -.. code:: python3 - - import numpy as np - from doctr.models import ocr_predictor - model = ocr_predictor('db_resnet50', 'crnn_vgg16_bn', pretrained=True) - input_page = (255 * np.random.rand(800, 600, 3)).astype(np.uint8) - out = model([input_page]) - - -You can pass specific boolean arguments to the predictor: - -* `assume_straight_pages`: if you work with straight documents only, it will fit straight bounding boxes to the text areas. -* `preserve_aspect_ratio`: if you want to preserve the aspect ratio of your documents while resizing before sending them to the model. -* `symmetric_pad`: if you choose to preserve the aspect ratio, it will pad the image symmetrically and not from the bottom-right. - -Those 3 are going straight to the detection predictor, as mentioned above (in the detection part). - -Additional arguments which can be passed to the `ocr_predictor` are: - -* `export_as_straight_boxes`: If you work with rotated and skewed documents but you still want to export straight bounding boxes and not polygons, set it to True. -* `straighten_pages`: If you want to straighten the pages before sending them to the detection model, set it to True. -* `detect_orientation`: If you want to estimate the general page orientation and add it to each page, set it to True. -* `detect_language`: If you want to predict the language of the text on each page, set it to True. -* `detect_layout`: If you want to run a layout detection model on each page and attach the detected regions to each page, set it to True (default: False). -* `layout_arch`: The layout architecture name (e.g. ``'lw_detr_s'``, ``'lw_detr_m'``) or your own (fine-tuned) layout model instance to use when ``detect_layout=True``. - -For instance, this snippet instantiates an end-to-end ocr_predictor working with rotated documents, which preserves the aspect ratio of the documents, and returns polygons: - -.. code:: python3 - - from doctr.models import ocr_predictor - model = ocr_predictor('linknet_resnet18', pretrained=True, assume_straight_pages=False, preserve_aspect_ratio=True) - - -Additionally, you can change the batch size of the underlying detection and recognition predictors to optimize the performance depending on your hardware: - -* `det_bs`: batch size for the detection model (default: 2) - will also be used for the layout model if ``detect_layout=True`` -* `reco_bs`: batch size for the recognition model (default: 128) - -.. code:: python3 - - from doctr.models import ocr_predictor - model = ocr_predictor(pretrained=True, det_bs=4, reco_bs=1024) - -To modify the output structure you can pass the following arguments to the predictor which will be handled by the underlying `DocumentBuilder`: - -* `resolve_lines`: whether words should be automatically grouped into lines (default: True) -* `resolve_blocks`: whether lines should be automatically grouped into blocks (default: False) -* `paragraph_break`: relative length of the minimum space separating paragraphs (default: 0.035) - -For example to disable the automatic grouping of lines into blocks: - -.. code:: python3 - - from doctr.models import ocr_predictor - model = ocr_predictor(pretrained=True, resolve_blocks=False) - - -Detecting the document layout -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In addition to running the :py:meth:`layout_predictor ` standalone, you can plug a layout detection model directly into the end-to-end pipeline by setting ``detect_layout=True``. The detected regions (e.g. Title, Text, Table, Page-header, Page-footer) are attached to every :class:`Page ` and can be accessed through ``page.layout``, exported alongside the rest of the page, and rendered with :py:meth:`show `. - -.. code:: python3 - - from doctr.io import DocumentFile - from doctr.models import ocr_predictor - - model = ocr_predictor(pretrained=True, detect_layout=True) - doc = DocumentFile.from_images("path/to/your/doc.jpg") - result = model(doc) - - # Access the detected layout regions of the first page - for region in result.pages[0].layout: - print(region.type, region.confidence, region.geometry) - - # The layout is part of the exported representation - export = result.pages[0].export() - print(export["layout"]) - - # Overlay both text and layout regions (use display_layout=False to hide the regions) - result.pages[0].show() - -The same ``detect_layout`` / ``layout_arch`` arguments are available for the :py:meth:`kie_predictor `. - -The figures found by the layout model also take part in the reading order, and the Markdown / AsciiDoc / HTML exports can render them, inlined as base64 data URIs or written to an image directory (see :ref:`Figures`): - -.. code:: python3 - - # A self-contained Markdown file, figures included - markdown = result.export_as_markdown(images="embedded") - - # ... or one referencing the crops written to an `assets` directory - from doctr.io import FigureEncoder - - markdown = result.export_as_markdown(images=FigureEncoder("referenced", image_dir="assets")) - -By default (``images="placeholder"``), a comment marks each figure, and ``images="none"`` leaves them out. In the ``"embedded"`` and ``"referenced"`` modes, the text recognized inside a figure is left out of the export, since the image shows it. - - -Running the predictors on GPU -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -You can run the predictors on GPU by specifying the appropriate device. - -Here's how to do it for both **NVIDIA** and **Apple Silicon (MPS)** GPUs: - -.. code:: python3 - - import torch - from doctr.models import ocr_predictor - - # For NVIDIA GPU - device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') - predictor = ocr_predictor(pretrained=True).to(device) - # Alternatively: predictor = ocr_predictor(pretrained=True).cuda() - - # For Apple Silicon (MPS) - device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu') - predictor = ocr_predictor(pretrained=True).to(device) - - -The same approach applies to all standalone predictors: - -* `recognition_predictor` -* `detection_predictor` -* `crop_orientation_predictor` -* `page_orientation_predictor` -* `layout_predictor` - -Just create the predictor instance and move it to the appropriate device. -To enable **half-precision inference**, append `.bfloat16()` after moving the predictor to the -device -- or `.half()` for FP16, though BF16 is preferred (see :ref:`half-precision` for details) - - -What should I do with the output? -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ocr_predictor returns a `Document` object with a nested structure (with `Page`, `Block`, `Line`, `Word`, `Artefact`). -When ``detect_layout=True`` was passed, each `Page` additionally carries a list of `LayoutElement` regions under ``page.layout``. -To get a better understanding of our document model, check our :ref:`document_structure` section - -Here is a typical `Document` layout:: - - Document( - (pages): [Page( - dimensions=(340, 600) - (blocks): [Block( - (lines): [Line( - (words): [ - Word(value='No.', confidence=0.91), - Word(value='RECEIPT', confidence=0.99), - Word(value='DATE', confidence=0.96), - ] - )] - (artefacts): [] - )] - )] - ) - -To get only the text content of the `Document`, you can use the `render` method:: - - text_output = result.render() - -For reference, here is the output for the `Document` above:: - - No. RECEIPT DATE - -You can also export them as a nested dict, more appropriate for JSON format:: - - json_output = result.export() - -For reference, here is the export for the same `Document` as above:: - - { - 'pages': [ - { - 'page_idx': 0, - 'dimensions': (340, 600), - 'orientation': {'value': None, 'confidence': None}, - 'language': {'value': None, 'confidence': None}, - 'blocks': [ - { - 'geometry': ((0.1357421875, 0.0361328125), (0.8564453125, 0.8603515625)), - 'lines': [ - { - 'geometry': ((0.1357421875, 0.0361328125), (0.8564453125, 0.8603515625)), - 'words': [ - { - 'value': 'No.', - 'confidence': 0.914085328578949, - 'geometry': ((0.5478515625, 0.06640625), (0.5810546875, 0.0966796875)), - 'objectness_score': 0.96, - 'crop_orientation': {'value': 0, 'confidence': None}, - }, - { - 'value': 'RECEIPT', - 'confidence': 0.9949972033500671, - 'geometry': ((0.1357421875, 0.0361328125), (0.51171875, 0.1630859375)), - 'objectness_score': 0.99, - 'crop_orientation': {'value': 0, 'confidence': None}, - }, - { - 'value': 'DATE', - 'confidence': 0.9578408598899841, - 'geometry': ((0.1396484375, 0.3232421875), (0.185546875, 0.3515625)), - 'objectness_score': 0.99, - 'crop_orientation': {'value': 0, 'confidence': None}, - } - ] - } - ], - 'artefacts': [] - } - ] - } - ] - } - -To export the output as XML (hocr-format) you can use the `export_as_xml` method: - -.. code-block:: python - - xml_output = result.export_as_xml() - for output in xml_output: - xml_bytes_string = output[0] - xml_element = output[1] - -For reference, here is a sample XML byte string output: - -.. code-block:: xml - - - - - docTR - hOCR - - - - - -
-
-

- - Hello - XML - World - -

-
-
- - - -To export the output in reading order as Markdown, AsciiDoc, HTML or XML, you can use the `export_as_markdown`, -`export_as_asciidoc`, `export_as_html` and `export_as_xml` methods (also available on a single :class:`Page`). -Reading order is always applied by these exporters: the content is linearized column by column, the reading direction is inferred from the -recognized text (e.g. right-to-left for Arabic or Hebrew documents), and - when the predictor is run with -layout detection (``detect_layout=True``) - the layout regions are used to render headings, list items and -recognized tables, and to place the page furniture (headers, footers, footnotes): - -.. code-block:: python - - markdown_output = result.export_as_markdown() - asciidoc_output = result.export_as_asciidoc() - html_output = result.export_as_html() - xml_output = result.export_as_xml() - raw_text_output = result.render() # same as result.export_as("text") - dict_output = result.export() # same as result.export_as("json") - -The `export_as` method is a convenience dispatcher over all the formats above:: - - result.export_as("markdown") # or "md" - result.export_as("asciidoc") # or "adoc" - result.export_as("html") - result.export_as("text") # same as render() - result.export_as("json") # same as export() - result.export_as("xml") # same as export_as_xml() - -The document structure itself can also be produced in reading order by building the predictor with -``keep_reading_order=True``, which sorts the blocks of every page in reading order:: - - predictor = ocr_predictor(pretrained=True, keep_reading_order=True) - -Advanced options -^^^^^^^^^^^^^^^^ -We provide a few advanced options to customize the behavior of the predictor to your needs: - -* Modify the binarization threshold for the detection model. -* Modify the box threshold for the detection model. - -This is useful to detect (possible less) text regions more accurately with a higher threshold, or to detect more text regions with a lower threshold. - - -.. code:: python3 - - import numpy as np - from doctr.models import ocr_predictor - predictor = ocr_predictor('db_resnet50', 'crnn_vgg16_bn', pretrained=True) - - # Modify the binarization threshold and the box threshold - predictor.det_predictor.model.postprocessor.bin_thresh = 0.5 - predictor.det_predictor.model.postprocessor.box_thresh = 0.2 - - input_page = (255 * np.random.rand(800, 600, 3)).astype(np.uint8) - out = predictor([input_page]) - - -* Modify how the word confidences are aggregated. - -The character probabilities of the recognition model are aggregated into the word confidence with `"mean"`, `"min"`, -`"max"`, `"median"`, `"geometric_mean"`, `"harmonic_mean"` or a Python callable taking the -1D array of probabilities and returning a float. The default is `"min"` for the CRNN, VIPTR, SAR and MASTER models and -`"mean"` for the ViTSTR and PARSeq models. -A mean can hide a single uncertain character: for `[0.99, 0.98, 0.12, 0.99]`, the mean is `0.77`, the geometric mean -`0.58`, the harmonic mean `0.35` and the minimum `0.12`. `"max"` and `"median"` ignore most uncertain characters, so -they hardly separate the misread words from the correct ones. - -Only the probabilities of the predicted characters are aggregated: the CTC blank frames (CRNN, VIPTR) and the -positions from the end-of-sequence token onwards (SAR, MASTER) are ignored, a CTC character has the highest -probability of its frames, and an empty prediction has a confidence of `0`. The parts of a split wide crop are -aggregated with the `split_confidence_aggregation` of the recognition predictor (`"min"` by default). - -Pass `confidence_aggregation` to `ocr_predictor`, `kie_predictor` or `recognition_predictor` (a model instance passed -to them is modified), or change it afterwards on the postprocessor of the recognition model. - -.. code:: python3 - - from doctr.models import ocr_predictor, parseq - - # Aggregate the character probabilities into the word confidence with the mean - predictor = ocr_predictor(pretrained=True, confidence_aggregation="mean") - - # Or modify the aggregation method of the recognition model afterwards - predictor = ocr_predictor('db_resnet50', 'crnn_vgg16_bn', pretrained=True) - predictor.reco_predictor.model.postprocessor.confidence_aggregation = "geometric_mean" - - # Modify the aggregation of the confidences of the parts of a split wide crop - predictor.reco_predictor.split_confidence_aggregation = "mean" - - # Or set it when instantiating a model - predictor = ocr_predictor('db_resnet50', parseq(pretrained=True, confidence_aggregation="min"), pretrained=True) - - -* Disable page orientation classification - -If you deal with documents which contains only small rotations (~ -45 to 45 degrees), you can disable the page orientation classification to speed up the inference. - -This will only have an effect with `assume_straight_pages=False` and/or `straighten_pages=True` and/or `detect_orientation=True`. - -.. code:: python3 - - from doctr.models import ocr_predictor - model = ocr_predictor(pretrained=True, assume_straight_pages=False, disable_page_orientation=True) - - -* Disable crop orientation classification - -If you deal with documents which contains only horizontal text, you can disable the crop orientation classification to speed up the inference. - -This will only have an effect with `assume_straight_pages=False` and/or `straighten_pages=True`. - -.. code:: python3 - - from doctr.models import ocr_predictor - model = ocr_predictor(pretrained=True, assume_straight_pages=False, disable_crop_orientation=True) - - -* Add a hook to the `ocr_predictor` to manipulate the location predictions before the crops are passed to the recognition model. - -.. code:: python3 - - from doctr.models import ocr_predictor - - class CustomHook: - def __call__(self, loc_preds): - # Manipulate the location predictions here - # 1. The output structure needs to be the same as the input location predictions - # 2. Be aware that the coordinates are relative and needs to be between 0 and 1 - return loc_preds - - my_hook = CustomHook() - - predictor = ocr_predictor(pretrained=True) - # Add a hook in the middle of the pipeline - predictor.add_hook(my_hook) - # You can also add multiple hooks which will be executed sequentially - for hook in [my_hook, my_hook, my_hook]: - predictor.add_hook(hook) - - -* Restrict the recognition model to a subset of its vocabulary. - -If you only expect text from one or more known languages, you can whitelist the corresponding vocabs so the -recognition model can no longer predict any character outside of them. This works with every recognition -architecture and with any predictor wrapping one (`ocr_predictor`, `kie_predictor`, `recognition_predictor`). -A whitelist can only restrict a model to characters it already knows: characters that are not part of the -model's own vocabulary are silently ignored, so make sure the model was trained on a vocab that covers the -languages you need (e.g. a multilingual model). - -.. code:: python3 - - from doctr.datasets import VOCABS - from doctr.io import DocumentFile - from doctr.models import ocr_predictor - from doctr.models.utils import add_whitelist - - predictor = ocr_predictor(pretrained=True) - - # The recognition model can now only predict Polish/German characters - handle = add_whitelist(predictor, [VOCABS["polish"], VOCABS["german"]]) - - input_page = DocumentFile.from_images("path/to/your/image.png") - out = predictor(input_page) - - # Restore the original, unconstrained decoding - handle.remove() - -The returned handle can also be used as a context manager, in which case the whitelist is removed on exit: - -.. code:: python3 - - with add_whitelist(predictor, VOCABS["german"]): - out = predictor(input_page) # only German characters can be predicted here - # the whitelist is automatically removed outside of the ``with`` block - -By default forbidden characters are dropped (``strategy="mask"``), so decoding falls back to the highest-scoring -allowed character. Alternatively, ``strategy="nearest"`` folds each forbidden character onto the closest allowed -one (e.g. ``ä`` -> ``a``, ``ł`` -> ``l``), which is useful to normalize accents/diacritics onto a base alphabet. -The mapping is built by transliteration by default; pass ``mapping="weights"`` to derive it from the model's own -learned confusions, or a ``{forbidden_char: allowed_char}`` dict to override specific characters. - -.. code:: python3 - - from doctr.datasets import VOCABS - from doctr.models import ocr_predictor - from doctr.models.utils import add_whitelist - - predictor = ocr_predictor(pretrained=True) - - # Fold any non-ASCII character onto its closest ASCII letter (e.g. é -> e, ł -> l) - handle = add_whitelist(predictor, VOCABS["latin"], strategy="nearest") - out = predictor(input_page) - handle.remove() diff --git a/doctr/utils/metrics.py b/doctr/utils/metrics.py index 4d20bc7078..909074a858 100644 --- a/doctr/utils/metrics.py +++ b/doctr/utils/metrics.py @@ -661,8 +661,7 @@ class ObjectDetectionMetric: - :math:`\mathcal{C}` is the set of possible class indices, - :math:`S` are confidence scores associated to predictions, - :math:`\mathcal{T} = \{0.5, 0.55, \dots, 0.95\}` is the set of IoU thresholds, - - :math:`AP_t(c)` is the Average Precision for class :math:`c` - at IoU threshold :math:`t`. + - :math:`AP_t(c)` is the Average Precision for class :math:`c` at IoU threshold :math:`t`. For a given class and IoU threshold, predictions from all images are aggregated and sorted globally by decreasing confidence score. diff --git a/pyproject.toml b/pyproject.toml index b50f62111c..2c726eaa21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,14 +81,16 @@ quality = [ "pre-commit>=2.17.0", ] docs = [ - "sphinx>=3.0.0,!=3.5.0", - "sphinxemoji>=0.1.8", - "sphinx-copybutton>=0.3.1", + "sphinx>=8.0.0", "docutils<0.23", - "recommonmark>=0.7.1", - "sphinx-markdown-tables>=0.0.15", + "pydata-sphinx-theme>=0.23.0", + "myst-parser>=4.0.0", + "sphinx-design>=0.6.0", + "sphinx-copybutton>=0.5.2", + "sphinx-reredirects>=0.1.5", + # Only used to render the documentation of previous releases (cf. docs/build.sh) + "sphinxemoji>=0.1.8", "sphinx-tabs>=3.3.0", - "furo>=2022.3.4", ] dev = [ # PyTorch @@ -110,14 +112,16 @@ dev = [ "mypy>=1.0", "pre-commit>=3.0.0", # Docs - "sphinx>=3.0.0,!=3.5.0", - "sphinxemoji>=0.1.8", - "sphinx-copybutton>=0.3.1", + "sphinx>=8.0.0", "docutils<0.23", - "recommonmark>=0.7.1", - "sphinx-markdown-tables>=0.0.15", + "pydata-sphinx-theme>=0.23.0", + "myst-parser>=4.0.0", + "sphinx-design>=0.6.0", + "sphinx-copybutton>=0.5.2", + "sphinx-reredirects>=0.1.5", + # Only used to render the documentation of previous releases (cf. docs/build.sh) + "sphinxemoji>=0.1.8", "sphinx-tabs>=3.3.0", - "furo>=2022.3.4", ] [project.urls]